mirror of
https://github.com/docker/cli.git
synced 2026-08-27 18:14:20 -05:00
Compare commits
164
Commits
26.1
...
v27.0.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+3
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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:
|
||||
@@ -77,20 +77,20 @@ jobs:
|
||||
platformPair=${platform//\//-}
|
||||
tar -cvzf "/tmp/out/docker-${platformPair}.tar.gz" .
|
||||
if [ -z "${{ matrix.use_glibc }}" ]; then
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}" >> $GITHUB_ENV
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-glibc" >> $GITHUB_ENV
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}-glibc" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
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:
|
||||
-
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
- 26.1 # latest
|
||||
- 25.0 # 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
|
||||
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
ctn:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.9
|
||||
go-version: 1.21.11
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
# 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:
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+6
-4
@@ -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
|
||||
|
||||
+7
-7
@@ -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
-6
@@ -1,15 +1,15 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG ALPINE_VERSION=3.20
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.21.9
|
||||
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.14.1
|
||||
ARG COMPOSE_VERSION=v2.27.1
|
||||
|
||||
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/*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,11 +1,13 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli-plugins/hooks"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -27,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
|
||||
@@ -67,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,
|
||||
@@ -92,11 +101,35 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
nextSteps = append(nextSteps, processedHook...)
|
||||
|
||||
var appended bool
|
||||
nextSteps, appended = appendNextSteps(nextSteps, processedHook)
|
||||
if !appended {
|
||||
logrus.Debugf("Plugin %s responded with an empty hook message %q. Ignoring.", pluginName, string(hookReturn))
|
||||
}
|
||||
}
|
||||
return nextSteps
|
||||
}
|
||||
|
||||
// appendNextSteps appends the processed hook output to the nextSteps slice.
|
||||
// If the processed hook output is empty, it is not appended.
|
||||
// Empty lines are not stripped if there's at least one non-empty line.
|
||||
func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
|
||||
empty := true
|
||||
for _, l := range processed {
|
||||
if strings.TrimSpace(l) != "" {
|
||||
empty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if empty {
|
||||
return nextSteps, false
|
||||
}
|
||||
|
||||
return append(nextSteps, processed...), true
|
||||
}
|
||||
|
||||
// pluginMatch takes a plugin configuration and a string representing the
|
||||
// command being executed (such as 'image ls' – the root 'docker' is omitted)
|
||||
// and, if the configuration includes a hook for the invoked command, returns
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestGetNaiveFlags(t *testing.T) {
|
||||
@@ -108,3 +109,35 @@ func TestPluginMatch(t *testing.T) {
|
||||
assert.Equal(t, match, tc.expectedMatch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendNextSteps(t *testing.T) {
|
||||
testCases := []struct {
|
||||
processed []string
|
||||
expectedOut []string
|
||||
}{
|
||||
{
|
||||
processed: []string{},
|
||||
expectedOut: []string{},
|
||||
},
|
||||
{
|
||||
processed: []string{"", ""},
|
||||
expectedOut: []string{},
|
||||
},
|
||||
{
|
||||
processed: []string{"Some hint", "", "Some other hint"},
|
||||
expectedOut: []string{"Some hint", "", "Some other hint"},
|
||||
},
|
||||
{
|
||||
processed: []string{"Hint 1", "Hint 2"},
|
||||
expectedOut: []string{"Hint 1", "Hint 2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got, appended := appendNextSteps([]string{}, tc.processed)
|
||||
assert.Check(t, is.DeepEqual(got, tc.expectedOut))
|
||||
assert.Check(t, is.Equal(appended, len(got) > 0))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +240,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||
cmd.Env = append(cmd.Environ(), ReexecEnvvar+"="+os.Args[0])
|
||||
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
|
||||
|
||||
return cmd, nil
|
||||
|
||||
@@ -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)
|
||||
@@ -52,6 +46,24 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
||||
opts = append(opts, withPluginClientConn(plugin.Name()))
|
||||
}
|
||||
err = tcmd.Initialize(opts...)
|
||||
ogRunE := cmd.RunE
|
||||
if ogRunE == nil {
|
||||
ogRun := cmd.Run
|
||||
// necessary because error will always be nil here
|
||||
// see: https://github.com/golangci/golangci-lint/issues/1379
|
||||
//nolint:unparam
|
||||
ogRunE = func(cmd *cobra.Command, args []string) error {
|
||||
ogRun(cmd, args)
|
||||
return nil
|
||||
}
|
||||
cmd.Run = nil
|
||||
}
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
stopInstrumentation := dockerCli.StartInstrumentation(cmd)
|
||||
err := ogRunE(cmd, args)
|
||||
stopInstrumentation(err)
|
||||
return err
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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{
|
||||
|
||||
+10
-5
@@ -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
|
||||
@@ -124,7 +124,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
|
||||
}
|
||||
|
||||
@@ -273,6 +273,11 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
return ResolveDefaultContext(cli.options, cli.contextStoreConfig)
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(krissetto): pass ctx to the funcs instead of using this
|
||||
cli.createGlobalMeterProvider(cli.baseCtx)
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -310,7 +315,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 {
|
||||
@@ -541,7 +546,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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -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 {
|
||||
@@ -239,7 +239,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
||||
p, err := platforms.Parse(options.platform)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing specified platform")
|
||||
return "", errors.Wrap(errdefs.InvalidParameter(err), "error parsing specified platform")
|
||||
}
|
||||
platform = &p
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -206,7 +207,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 +236,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,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",
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -186,7 +191,11 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
|
||||
defer closeFn()
|
||||
}
|
||||
|
||||
statusChan := waitExitOrRemoved(ctx, apiClient, containerID, copts.autoRemove)
|
||||
// New context here because we don't to cancel waiting on container exit/remove
|
||||
// when we cancel attach, etc.
|
||||
statusCtx, cancelStatusCtx := context.WithCancel(context.WithoutCancel(ctx))
|
||||
defer cancelStatusCtx()
|
||||
statusChan := waitExitOrRemoved(statusCtx, apiClient, containerID, copts.autoRemove)
|
||||
|
||||
// start the container
|
||||
if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -36,6 +35,7 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
|
||||
|
||||
statusC := make(chan int)
|
||||
go func() {
|
||||
defer close(statusC)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
@@ -67,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
|
||||
|
||||
@@ -24,6 +24,10 @@ type CreateOptions struct {
|
||||
Description string
|
||||
Docker map[string]string
|
||||
From string
|
||||
|
||||
// Additional Metadata to store in the context. This option is not
|
||||
// currently exposed to the user.
|
||||
metaData map[string]any
|
||||
}
|
||||
|
||||
func longCreateDescription() string {
|
||||
@@ -94,7 +98,8 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
docker.DockerEndpoint: dockerEP,
|
||||
},
|
||||
Metadata: command.DockerContext{
|
||||
Description: o.Description,
|
||||
Description: o.Description,
|
||||
AdditionalFields: o.metaData,
|
||||
},
|
||||
Name: o.Name,
|
||||
}
|
||||
|
||||
@@ -8,14 +8,18 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestExportImportWithFile(t *testing.T) {
|
||||
contextFile := filepath.Join(t.TempDir(), "exported")
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "test")
|
||||
createTestContext(t, cli, "test", map[string]any{
|
||||
"MyCustomMetadata": t.Name(),
|
||||
})
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
||||
ContextName: "test",
|
||||
@@ -29,18 +33,26 @@ func TestExportImportWithFile(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, context1.Endpoints, context2.Endpoints)
|
||||
assert.DeepEqual(t, context1.Metadata, context2.Metadata)
|
||||
assert.Equal(t, "test", context1.Name)
|
||||
assert.Equal(t, "test2", context2.Name)
|
||||
|
||||
assert.Equal(t, "test2\n", cli.OutBuffer().String())
|
||||
assert.Equal(t, "Successfully imported context \"test2\"\n", cli.ErrBuffer().String())
|
||||
assert.Check(t, is.DeepEqual(context1.Metadata, command.DockerContext{
|
||||
Description: "description of test",
|
||||
AdditionalFields: map[string]any{"MyCustomMetadata": t.Name()},
|
||||
}))
|
||||
|
||||
assert.Check(t, is.DeepEqual(context1.Endpoints, context2.Endpoints))
|
||||
assert.Check(t, is.DeepEqual(context1.Metadata, context2.Metadata))
|
||||
assert.Check(t, is.Equal("test", context1.Name))
|
||||
assert.Check(t, is.Equal("test2", context2.Name))
|
||||
|
||||
assert.Check(t, is.Equal("test2\n", cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal("Successfully imported context \"test2\"\n", cli.ErrBuffer().String()))
|
||||
}
|
||||
|
||||
func TestExportImportPipe(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "test")
|
||||
createTestContext(t, cli, "test", map[string]any{
|
||||
"MyCustomMetadata": t.Name(),
|
||||
})
|
||||
cli.ErrBuffer().Reset()
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
||||
@@ -56,13 +68,19 @@ func TestExportImportPipe(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, context1.Endpoints, context2.Endpoints)
|
||||
assert.DeepEqual(t, context1.Metadata, context2.Metadata)
|
||||
assert.Equal(t, "test", context1.Name)
|
||||
assert.Equal(t, "test2", context2.Name)
|
||||
|
||||
assert.Equal(t, "test2\n", cli.OutBuffer().String())
|
||||
assert.Equal(t, "Successfully imported context \"test2\"\n", cli.ErrBuffer().String())
|
||||
assert.Check(t, is.DeepEqual(context1.Metadata, command.DockerContext{
|
||||
Description: "description of test",
|
||||
AdditionalFields: map[string]any{"MyCustomMetadata": t.Name()},
|
||||
}))
|
||||
|
||||
assert.Check(t, is.DeepEqual(context1.Endpoints, context2.Endpoints))
|
||||
assert.Check(t, is.DeepEqual(context1.Metadata, context2.Metadata))
|
||||
assert.Check(t, is.Equal("test", context1.Name))
|
||||
assert.Check(t, is.Equal("test2", context2.Name))
|
||||
|
||||
assert.Check(t, is.Equal("test2\n", cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal("Successfully imported context \"test2\"\n", cli.ErrBuffer().String()))
|
||||
}
|
||||
|
||||
func TestExportExistingFile(t *testing.T) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
|
||||
func TestInspect(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "current", map[string]any{
|
||||
"MyCustomMetadata": "MyCustomMetadataValue",
|
||||
})
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runInspect(cli, inspectOptions{
|
||||
refs: []string{"current"},
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
@@ -66,6 +69,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
||||
Name: rawMeta.Name,
|
||||
Current: isCurrent,
|
||||
Error: err.Error(),
|
||||
|
||||
ContextType: getContextType(nil, opts.format),
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -80,6 +85,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
||||
Description: meta.Description,
|
||||
DockerEndpoint: dockerEndpoint.Host,
|
||||
Error: errMsg,
|
||||
|
||||
ContextType: getContextType(meta.AdditionalFields, opts.format),
|
||||
}
|
||||
contexts = append(contexts, &desc)
|
||||
}
|
||||
@@ -96,6 +103,8 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
||||
Name: curContext,
|
||||
Current: true,
|
||||
Error: errMsg,
|
||||
|
||||
ContextType: getContextType(nil, opts.format),
|
||||
})
|
||||
}
|
||||
sort.Slice(contexts, func(i, j int) bool {
|
||||
@@ -111,6 +120,30 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getContextType sets the LegacyContextType field for compatibility with
|
||||
// Visual Studio, which depends on this field from the "cloud integration"
|
||||
// wrapper.
|
||||
//
|
||||
// https://github.com/docker/compose-cli/blob/c156ce6da4c2b317174d42daf1b019efa87e9f92/api/context/store/contextmetadata.go#L28-L34
|
||||
// https://github.com/docker/compose-cli/blob/c156ce6da4c2b317174d42daf1b019efa87e9f92/api/context/store/store.go#L34-L51
|
||||
//
|
||||
// TODO(thaJeztah): remove this and [ClientContext.ContextType] once Visual Studio is updated to no longer depend on this.
|
||||
func getContextType(meta map[string]any, format string) string {
|
||||
if format != formatter.JSONFormat && format != formatter.JSONFormatKey {
|
||||
// We only need the ContextType field when formatting as JSON,
|
||||
// which is the format-string used by Visual Studio to detect the
|
||||
// context-type.
|
||||
return ""
|
||||
}
|
||||
if ct, ok := meta["Type"]; ok {
|
||||
// If the context on-disk has a context-type (ecs, aci), return it.
|
||||
return ct.(string)
|
||||
}
|
||||
|
||||
// Use the default context-type.
|
||||
return "moby"
|
||||
}
|
||||
|
||||
func format(dockerCli command.Cli, opts *listOptions, contexts []*formatter.ClientContext) error {
|
||||
contextCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
|
||||
@@ -4,36 +4,70 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
func createTestContext(t *testing.T, cli command.Cli, name string) {
|
||||
func createTestContexts(t *testing.T, cli command.Cli, name ...string) {
|
||||
t.Helper()
|
||||
for _, n := range name {
|
||||
createTestContext(t, cli, n, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestContext(t *testing.T, cli command.Cli, name string, metaData map[string]any) {
|
||||
t.Helper()
|
||||
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: name,
|
||||
Description: "description of " + name,
|
||||
Docker: map[string]string{keyHost: "https://someswarmserver.example.com"},
|
||||
|
||||
metaData: metaData,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContext(t, cli, "unset")
|
||||
createTestContexts(t, cli, "current", "other", "unset")
|
||||
cli.SetCurrentContext("current")
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{}))
|
||||
golden.Assert(t, cli.OutBuffer().String(), "list.golden")
|
||||
}
|
||||
|
||||
func TestListJSON(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current", nil)
|
||||
createTestContext(t, cli, "context1", map[string]any{"Type": "aci"})
|
||||
createTestContext(t, cli, "context2", map[string]any{"Type": "ecs"})
|
||||
createTestContext(t, cli, "context3", map[string]any{"Type": "moby"})
|
||||
cli.SetCurrentContext("current")
|
||||
|
||||
t.Run("format={{json .}}", func(t *testing.T) {
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{format: formatter.JSONFormat}))
|
||||
golden.Assert(t, cli.OutBuffer().String(), "list-json.golden")
|
||||
})
|
||||
|
||||
t.Run("format=json", func(t *testing.T) {
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{format: formatter.JSONFormatKey}))
|
||||
golden.Assert(t, cli.OutBuffer().String(), "list-json.golden")
|
||||
})
|
||||
|
||||
t.Run("format={{ json .Name }}", func(t *testing.T) {
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{format: `{{ json .Name }}`}))
|
||||
golden.Assert(t, cli.OutBuffer().String(), "list-json-name.golden")
|
||||
})
|
||||
}
|
||||
|
||||
func TestListQuiet(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
cli.SetCurrentContext("current")
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, runList(cli, &listOptions{quiet: true}))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,8 +13,7 @@ import (
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
assert.NilError(t, RunRemove(cli, RemoveOptions{}, []string{"other"}))
|
||||
_, err := cli.ContextStore().GetMetadata("current")
|
||||
assert.NilError(t, err)
|
||||
@@ -24,8 +23,7 @@ func TestRemove(t *testing.T) {
|
||||
|
||||
func TestRemoveNotAContext(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"not-a-context"})
|
||||
assert.ErrorContains(t, err, `context "not-a-context" does not exist`)
|
||||
|
||||
@@ -35,8 +33,7 @@ func TestRemoveNotAContext(t *testing.T) {
|
||||
|
||||
func TestRemoveCurrent(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
cli.SetCurrentContext("current")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"current"})
|
||||
assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`)
|
||||
@@ -50,8 +47,7 @@ func TestRemoveCurrentForce(t *testing.T) {
|
||||
assert.NilError(t, testCfg.Save())
|
||||
|
||||
cli := makeFakeCli(t, withCliConfig(testCfg))
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
cli.SetCurrentContext("current")
|
||||
assert.NilError(t, RunRemove(cli, RemoveOptions{Force: true}, []string{"current"}))
|
||||
reloadedConfig, err := config.Load(configDir)
|
||||
@@ -61,7 +57,7 @@ func TestRemoveCurrentForce(t *testing.T) {
|
||||
|
||||
func TestRemoveDefault(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "other")
|
||||
createTestContext(t, cli, "other", nil)
|
||||
cli.SetCurrentContext("current")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"default"})
|
||||
assert.ErrorContains(t, err, `default: context "default" cannot be removed`)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func TestShow(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "current")
|
||||
createTestContext(t, cli, "current", nil)
|
||||
cli.SetCurrentContext("current")
|
||||
|
||||
cli.OutBuffer().Reset()
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
{
|
||||
"Name": "current",
|
||||
"Metadata": {
|
||||
"Description": "description of current"
|
||||
"Description": "description of current",
|
||||
"MyCustomMetadata": "MyCustomMetadataValue"
|
||||
},
|
||||
"Endpoints": {
|
||||
"docker": {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"context1"
|
||||
"context2"
|
||||
"context3"
|
||||
"current"
|
||||
"default"
|
||||
@@ -0,0 +1,5 @@
|
||||
{"Name":"context1","Description":"description of context1","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"aci"}
|
||||
{"Name":"context2","Description":"description of context2","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"ecs"}
|
||||
{"Name":"context3","Description":"description of context3","DockerEndpoint":"https://someswarmserver.example.com","Current":false,"Error":"","ContextType":"moby"}
|
||||
{"Name":"current","Description":"description of current","DockerEndpoint":"https://someswarmserver.example.com","Current":true,"Error":"","ContextType":"moby"}
|
||||
{"Name":"default","Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","Current":false,"Error":"","ContextType":"moby"}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/assert/cmp"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestUpdateDescriptionOnly(t *testing.T) {
|
||||
@@ -34,7 +34,7 @@ func TestUpdateDescriptionOnly(t *testing.T) {
|
||||
|
||||
func TestUpdateDockerOnly(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "test")
|
||||
createTestContext(t, cli, "test", nil)
|
||||
assert.NilError(t, RunUpdate(cli, &UpdateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{
|
||||
@@ -46,7 +46,7 @@ func TestUpdateDockerOnly(t *testing.T) {
|
||||
dc, err := command.GetDockerContext(c)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, dc.Description, "description of test")
|
||||
assert.Check(t, cmp.Contains(c.Endpoints, docker.DockerEndpoint))
|
||||
assert.Check(t, is.Contains(c.Endpoints, docker.DockerEndpoint))
|
||||
assert.Equal(t, c.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta).Host, "tcp://some-host")
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 @@
|
||||
package formatter
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
// ClientContextTableFormat is the default client context format.
|
||||
ClientContextTableFormat = "table {{.Name}}{{if .Current}} *{{end}}\t{{.Description}}\t{{.DockerEndpoint}}\t{{.Error}}"
|
||||
@@ -28,6 +30,13 @@ type ClientContext struct {
|
||||
DockerEndpoint string
|
||||
Current bool
|
||||
Error string
|
||||
|
||||
// ContextType is a temporary field for compatibility with
|
||||
// Visual Studio, which depends on this from the "cloud integration"
|
||||
// wrapper.
|
||||
//
|
||||
// Deprecated: this type is only for backward-compatibility. Do not use.
|
||||
ContextType string `json:"ContextType,omitempty"`
|
||||
}
|
||||
|
||||
// ClientContextWrite writes formatted contexts using the Context
|
||||
@@ -60,6 +69,13 @@ func newClientContextContext() *clientContextContext {
|
||||
}
|
||||
|
||||
func (c *clientContextContext) MarshalJSON() ([]byte, error) {
|
||||
if c.c.ContextType != "" {
|
||||
// We only have ContextType set for plain "json" or "{{json .}}" formatting,
|
||||
// so we should be able to just use the default json.Marshal with no
|
||||
// special handling.
|
||||
return json.Marshal(c.c)
|
||||
}
|
||||
// FIXME(thaJeztah): why do we need a special marshal function here?
|
||||
return MarshalJSON(c)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ---------->|
|
||||
// | |
|
||||
|
||||
@@ -458,7 +458,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
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,18 +2,24 @@ 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 +29,7 @@ type pushOptions struct {
|
||||
remote string
|
||||
untrusted bool
|
||||
quiet bool
|
||||
platform string
|
||||
}
|
||||
|
||||
// NewPushCommand creates a new `docker push` command
|
||||
@@ -48,12 +55,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 +112,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 +120,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 +134,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")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -29,36 +29,36 @@ func TestNetworkContext(t *testing.T) {
|
||||
call func() string
|
||||
}{
|
||||
{networkContext{
|
||||
n: types.NetworkResource{ID: networkID},
|
||||
n: network.Summary{ID: networkID},
|
||||
trunc: false,
|
||||
}, networkID, ctx.ID},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{ID: networkID},
|
||||
n: network.Summary{ID: networkID},
|
||||
trunc: true,
|
||||
}, stringid.TruncateID(networkID), ctx.ID},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{Name: "network_name"},
|
||||
n: network.Summary{Name: "network_name"},
|
||||
}, "network_name", ctx.Name},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{Driver: "driver_name"},
|
||||
n: network.Summary{Driver: "driver_name"},
|
||||
}, "driver_name", ctx.Driver},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{EnableIPv6: true},
|
||||
n: network.Summary{EnableIPv6: true},
|
||||
}, "true", ctx.IPv6},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{EnableIPv6: false},
|
||||
n: network.Summary{EnableIPv6: false},
|
||||
}, "false", ctx.IPv6},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{Internal: true},
|
||||
n: network.Summary{Internal: true},
|
||||
}, "true", ctx.Internal},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{Internal: false},
|
||||
n: network.Summary{Internal: false},
|
||||
}, "false", ctx.Internal},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{},
|
||||
n: network.Summary{},
|
||||
}, "", ctx.Labels},
|
||||
{networkContext{
|
||||
n: types.NetworkResource{Labels: map[string]string{"label1": "value1", "label2": "value2"}},
|
||||
n: network.Summary{Labels: map[string]string{"label1": "value1", "label2": "value2"}},
|
||||
}, "label1=value1,label2=value2", ctx.Labels},
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC
|
||||
timestamp1, _ := time.Parse("2006-01-02", "2016-01-01")
|
||||
timestamp2, _ := time.Parse("2006-01-02", "2017-01-01")
|
||||
|
||||
networks := []types.NetworkResource{
|
||||
networks := []network.Summary{
|
||||
{ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local", Created: timestamp1},
|
||||
{ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local", Created: timestamp2},
|
||||
}
|
||||
@@ -176,7 +176,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func TestNetworkContextWriteJSON(t *testing.T) {
|
||||
networks := []types.NetworkResource{
|
||||
networks := []network.Summary{
|
||||
{ID: "networkID1", Name: "foobar_baz"},
|
||||
{ID: "networkID2", Name: "foobar_bar"},
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func TestNetworkContextWriteJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNetworkContextWriteJSONField(t *testing.T) {
|
||||
networks := []types.NetworkResource{
|
||||
networks := []network.Summary{
|
||||
{ID: "networkID1", Name: "foobar_baz"},
|
||||
{ID: "networkID2", Name: "foobar_bar"},
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
||||
client := dockerCli.Client()
|
||||
|
||||
getNetFunc := func(name string) (any, []byte, error) {
|
||||
return client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{Verbose: opts.verbose})
|
||||
return client.NetworkInspectWithRaw(ctx, name, network.InspectOptions{Verbose: opts.verbose})
|
||||
}
|
||||
|
||||
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -47,8 +47,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
func runList(ctx context.Context, dockerCli command.Cli, options listOptions) error {
|
||||
client := dockerCli.Client()
|
||||
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
|
||||
networkResources, err := client.NetworkList(ctx, listOptions)
|
||||
networkResources, err := client.NetworkList(ctx, network.ListOptions{Filters: options.filter.Value()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
|
||||
func TestNetworkListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
return []types.NetworkResource{}, errors.Errorf("error creating network")
|
||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
||||
return []network.Summary{}, errors.Errorf("error creating network")
|
||||
},
|
||||
expectedError: "error creating network",
|
||||
},
|
||||
@@ -43,7 +43,7 @@ func TestNetworkListErrors(t *testing.T) {
|
||||
func TestNetworkList(t *testing.T) {
|
||||
testCases := []struct {
|
||||
doc string
|
||||
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
||||
flags map[string]string
|
||||
golden string
|
||||
}{
|
||||
@@ -53,13 +53,13 @@ func TestNetworkList(t *testing.T) {
|
||||
"filter": "image.name=ubuntu",
|
||||
},
|
||||
golden: "network-list.golden",
|
||||
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
expectedOpts := types.NetworkListOptions{
|
||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
||||
expectedOpts := network.ListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("image.name", "ubuntu")),
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options, cmp.AllowUnexported(filters.Args{})))
|
||||
|
||||
return []types.NetworkResource{*builders.NetworkResource(builders.NetworkResourceID("123454321"),
|
||||
return []network.Summary{*builders.NetworkResource(builders.NetworkResourceID("123454321"),
|
||||
builders.NetworkResourceName("network_1"),
|
||||
builders.NetworkResourceDriver("09.7.01"),
|
||||
builders.NetworkResourceScope("global"))}, nil
|
||||
@@ -71,8 +71,8 @@ func TestNetworkList(t *testing.T) {
|
||||
"format": "{{ .Name }}",
|
||||
},
|
||||
golden: "network-list-sort.golden",
|
||||
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
return []types.NetworkResource{
|
||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
||||
return []network.Summary{
|
||||
*builders.NetworkResource(builders.NetworkResourceName("network-2-foo")),
|
||||
*builders.NetworkResource(builders.NetworkResourceName("network-1-foo")),
|
||||
*builders.NetworkResource(builders.NetworkResourceName("network-10-foo")),
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"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/network"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ func TestNetworkPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) {
|
||||
return types.NetworksPruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
|
||||
return network.PruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func runRemove(ctx context.Context, dockerCli command.Cli, networks []string, op
|
||||
status := 0
|
||||
|
||||
for _, name := range networks {
|
||||
nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{})
|
||||
nw, _, err := client.NetworkInspectWithRaw(ctx, name, network.InspectOptions{})
|
||||
if err == nil && nw.Ingress {
|
||||
r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), ingressWarning)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -104,8 +104,8 @@ func TestNetworkRemovePromptTermination(t *testing.T) {
|
||||
networkRemoveFunc: func(ctx context.Context, networkID string) error {
|
||||
return errors.New("fakeClient networkRemoveFunc should not be called")
|
||||
},
|
||||
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
|
||||
return types.NetworkResource{
|
||||
networkInspectFunc: func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) {
|
||||
return network.Inspect{
|
||||
ID: "existing-network",
|
||||
Name: "existing-network",
|
||||
Ingress: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -38,9 +38,9 @@ func NewNodeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Reference returns the reference of a node. The special value "self" for a node
|
||||
// reference is mapped to the current node, hence the node ID is retrieved using
|
||||
// the `/info` endpoint.
|
||||
func Reference(ctx context.Context, client apiclient.APIClient, ref string) (string, error) {
|
||||
func Reference(ctx context.Context, apiClient client.APIClient, ref string) (string, error) {
|
||||
if ref == "self" {
|
||||
info, err := client.Info(ctx)
|
||||
info, err := apiClient.Info(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func Reference(ctx context.Context, client apiclient.APIClient, ref string) (str
|
||||
// If there's no node ID in /info, the node probably
|
||||
// isn't a manager. Call a swarm-specific endpoint to
|
||||
// get a more specific error message.
|
||||
_, err = client.NodeList(ctx, types.NodeListOptions{})
|
||||
_, err = apiClient.NodeList(ctx, types.NodeListOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -91,16 +91,14 @@ func TestCreateErrorFromDaemon(t *testing.T) {
|
||||
fs.WithFile("config.json", `{ "Name": "plugin-foo" }`))
|
||||
defer tmpDir.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
cmd := newCreateCommand(test.NewFakeCli(&fakeClient{
|
||||
pluginCreateFunc: func(createContext io.Reader, createOptions types.PluginCreateOptions) error {
|
||||
return fmt.Errorf("Error creating plugin")
|
||||
return errors.New("error creating plugin")
|
||||
},
|
||||
})
|
||||
|
||||
cmd := newCreateCommand(cli)
|
||||
}))
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()})
|
||||
cmd.SetOut(io.Discard)
|
||||
assert.ErrorContains(t, cmd.Execute(), "Error creating plugin")
|
||||
assert.ErrorContains(t, cmd.Execute(), "error creating plugin")
|
||||
}
|
||||
|
||||
func TestCreatePlugin(t *testing.T) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@@ -27,9 +27,9 @@ func TestPluginDisableErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
expectedError: "Error disabling plugin",
|
||||
expectedError: "error disabling plugin",
|
||||
pluginDisableFunc: func(name string, disableOptions types.PluginDisableOptions) error {
|
||||
return fmt.Errorf("Error disabling plugin")
|
||||
return errors.New("error disabling plugin")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestPluginEnableErrors(t *testing.T) {
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
pluginEnableFunc: func(name string, options types.PluginEnableOptions) error {
|
||||
return fmt.Errorf("failed to enable plugin")
|
||||
return errors.New("failed to enable plugin")
|
||||
},
|
||||
expectedError: "failed to enable plugin",
|
||||
},
|
||||
@@ -43,10 +43,9 @@ func TestPluginEnableErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmd := newEnableCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
pluginEnableFunc: tc.pluginEnableFunc,
|
||||
}))
|
||||
cmd := newEnableCommand(test.NewFakeCli(&fakeClient{
|
||||
pluginEnableFunc: tc.pluginEnableFunc,
|
||||
}))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
@@ -52,7 +52,7 @@ func TestInspectErrors(t *testing.T) {
|
||||
args: []string{"foo"},
|
||||
expectedError: "error inspecting plugin",
|
||||
inspectFunc: func(name string) (*types.Plugin, []byte, error) {
|
||||
return nil, nil, fmt.Errorf("error inspecting plugin")
|
||||
return nil, nil, errors.New("error inspecting plugin")
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -136,13 +136,12 @@ func runInstall(ctx context.Context, dockerCli command.Cli, opts pluginOptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func acceptPrivileges(dockerCli command.Cli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
||||
return func(privileges types.PluginPrivileges) (bool, error) {
|
||||
func acceptPrivileges(dockerCli command.Cli, name string) func(ctx context.Context, privileges types.PluginPrivileges) (bool, error) {
|
||||
return func(ctx context.Context, privileges types.PluginPrivileges) (bool, error) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
|
||||
for _, privilege := range privileges {
|
||||
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
|
||||
}
|
||||
ctx := context.TODO()
|
||||
return command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -38,9 +38,9 @@ func TestInstallErrors(t *testing.T) {
|
||||
{
|
||||
description: "installation error",
|
||||
args: []string{"foo"},
|
||||
expectedError: "Error installing plugin",
|
||||
expectedError: "error installing plugin",
|
||||
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("Error installing plugin")
|
||||
return nil, errors.New("error installing plugin")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -48,7 +48,7 @@ func TestInstallErrors(t *testing.T) {
|
||||
args: []string{"foo"},
|
||||
expectedError: "docker image pull",
|
||||
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("(image) when fetching")
|
||||
return nil, errors.New("(image) when fetching")
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestInstallContentTrustErrors(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("should not try to install plugin")
|
||||
return nil, errors.New("should not try to install plugin")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestListErrors(t *testing.T) {
|
||||
args: []string{},
|
||||
expectedError: "error listing plugins",
|
||||
listFunc: func(filter filters.Args) (types.PluginsListResponse, error) {
|
||||
return types.PluginsListResponse{}, fmt.Errorf("error listing plugins")
|
||||
return types.PluginsListResponse{}, errors.New("error listing plugins")
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@@ -24,9 +24,9 @@ func TestRemoveErrors(t *testing.T) {
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
pluginRemoveFunc: func(name string, options types.PluginRemoveOptions) error {
|
||||
return fmt.Errorf("Error removing plugin")
|
||||
return errors.New("error removing plugin")
|
||||
},
|
||||
expectedError: "Error removing plugin",
|
||||
expectedError: "error removing plugin",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -27,14 +28,21 @@ const patSuggest = "You can log in with your password or a Personal Access " +
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command.
|
||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
||||
return func() (string, error) {
|
||||
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
|
||||
return func(ctx context.Context) (string, error) {
|
||||
fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||
indexServer := registry.GetAuthConfigKey(index)
|
||||
isDefaultRegistry := indexServer == registry.IndexServer
|
||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
err = ConfigureAuth(cli, "", "", &authConfig, isDefaultRegistry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const unencryptedWarning = `WARNING! Your password will be stored unencrypted in %s.
|
||||
Configure a credential helper to remove this warning. See
|
||||
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
|
||||
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
|
||||
`
|
||||
|
||||
type loginOptions struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user