Compare commits
117 Commits
v28.4.0-rc
...
28.x
| Author | SHA1 | Date | |
|---|---|---|---|
| 30df2d0b3a | |||
| ecc694264d | |||
| c475c696cf | |||
| 7494d2cee4 | |||
| 5306df36fa | |||
| 5dbaa52325 | |||
| dd832b6d97 | |||
| a99e91cc89 | |||
| 579b72aa06 | |||
| 6fc5891770 | |||
| 9af6cbc489 | |||
| ba53322412 | |||
| 61d2e8aaad | |||
| 6abcd4a2a1 | |||
| 00c129b974 | |||
| fbf5c8a86d | |||
| 4c7d52534f | |||
| 70123c523c | |||
| 4942b2747f | |||
| d8bab71747 | |||
| 36d9523e31 | |||
| 7063a0ef07 | |||
| 22487ad6f6 | |||
| 4737ed4906 | |||
| 8cff0087da | |||
| e8b22ce03c | |||
| e180ab8ab8 | |||
| 0d799c556f | |||
| 887030fbe8 | |||
| 9c6a0e0ba9 | |||
| f784471104 | |||
| d7afcf9b98 | |||
| 9d9adf6346 | |||
| d4b7734f18 | |||
| a1061611fd | |||
| 5e42f826b4 | |||
| 645c23bd13 | |||
| e491078fc6 | |||
| a3ffb8a148 | |||
| a4ae5f2f7a | |||
| 394991e2ab | |||
| e5bce5cd2d | |||
| d5c181abf4 | |||
| 0351ece9e5 | |||
| ec00b85794 | |||
| a69c591c5a | |||
| f9d2820a20 | |||
| c28ec0e4ce | |||
| d1c8336395 | |||
| 61b9fd4068 | |||
| 2ef1b4eabe | |||
| bea31fccbb | |||
| e9c189e1c2 | |||
| 118548d02b | |||
| 026ef0df2d | |||
| 4665091715 | |||
| 261d8bcf8d | |||
| 3755161455 | |||
| db5a0ae673 | |||
| 843153da37 | |||
| a2d7989230 | |||
| 985cee2de0 | |||
| e1dd0e1501 | |||
| 82ff4b5634 | |||
| 4de56bc72f | |||
| 2ed0d99acc | |||
| cbeddb1390 | |||
| 1fb1577626 | |||
| 67885d0dcc | |||
| 2ed42a8ade | |||
| 4bac500fb2 | |||
| 9d9f632527 | |||
| a2e17eb9d5 | |||
| 5d201ca436 | |||
| d1122a2293 | |||
| d52de77ef4 | |||
| 01f8949484 | |||
| b9e3346b1e | |||
| 25421ace0b | |||
| ede7019b14 | |||
| d48256b26d | |||
| 09fcf8e3dd | |||
| 56f7bd0759 | |||
| 3bacc99580 | |||
| f77defa891 | |||
| 4a677b86a6 | |||
| 1cf78c49ab | |||
| 2fb1298416 | |||
| a0ebf3e35c | |||
| 428518a84a | |||
| c1914f73f5 | |||
| cab5014d57 | |||
| 7c3ec3ec02 | |||
| aa976192cb | |||
| 2de1ea9769 | |||
| 0ac676b171 | |||
| b627f18262 | |||
| d8eb465f86 | |||
| a83e3df40b | |||
| ff5ea757b4 | |||
| c1d14aef3c | |||
| 18adfd54a9 | |||
| 6e20b9d93c | |||
| 3cadbd82c5 | |||
| b3df92053d | |||
| b89b069082 | |||
| 9deb5e9cd3 | |||
| e8be20bc9c | |||
| f2cd1979c0 | |||
| 991d942cc3 | |||
| 8e49313c0c | |||
| 6cd42da462 | |||
| 2b9489d827 | |||
| fbeae8516b | |||
| d593e61275 | |||
| 139968dff2 | |||
| 925db59377 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
||||
fi
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
|
||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@ -61,19 +61,19 @@ jobs:
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
go-version: "1.24.9"
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
3
.github/workflows/e2e.yml
vendored
3
.github/workflows/e2e.yml
vendored
@ -39,8 +39,7 @@ jobs:
|
||||
engine-version:
|
||||
- 28 # latest
|
||||
- 27 # latest - 1
|
||||
- 26 # github actions default
|
||||
- 23 # mirantis lts
|
||||
- 25 # mirantis lts
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@ -53,8 +53,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-13 # macOS 13 on Intel
|
||||
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
|
||||
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
|
||||
- macos-15-intel # macOS 15 on Intel
|
||||
- macos-15 # macOS 15 on arm64 (Apple Silicon M1)
|
||||
# - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history
|
||||
steps:
|
||||
-
|
||||
@ -64,9 +65,9 @@ jobs:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
go-version: "1.24.9"
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.24.6"
|
||||
go: "1.24.9"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
@ -8,13 +8,13 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.24.6
|
||||
ARG GO_VERSION=1.24.9
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||
|
||||
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
|
||||
# It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository.
|
||||
ARG GOTESTSUM_VERSION=v1.12.3
|
||||
ARG GOTESTSUM_VERSION=v1.13.0
|
||||
|
||||
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
||||
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||
|
||||
@ -32,14 +32,12 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
|
||||
func TestValidateCandidate(t *testing.T) {
|
||||
const (
|
||||
goodPluginName = metadata.NamePrefix + "goodplugin"
|
||||
builtinName = metadata.NamePrefix + "builtin"
|
||||
builtinAlias = metadata.NamePrefix + "alias"
|
||||
|
||||
builtinName = metadata.NamePrefix + "builtin"
|
||||
builtinAlias = metadata.NamePrefix + "alias"
|
||||
|
||||
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
|
||||
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
|
||||
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
|
||||
metaExperimental = `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`
|
||||
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
|
||||
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
|
||||
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
|
||||
)
|
||||
|
||||
fakeroot := &cobra.Command{Use: "docker"}
|
||||
@ -51,31 +49,103 @@ func TestValidateCandidate(t *testing.T) {
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
c *fakeCandidate
|
||||
name string
|
||||
plugin *fakeCandidate
|
||||
|
||||
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
|
||||
err string
|
||||
invalid string
|
||||
expVer string
|
||||
}{
|
||||
/* Each failing one of the tests */
|
||||
{name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"},
|
||||
{name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)},
|
||||
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
|
||||
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
|
||||
{name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`},
|
||||
{name: "fetch failure", c: &fakeCandidate{path: goodPluginPath, exec: false}, invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath)},
|
||||
{name: "metadata not json", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, invalid: "invalid character"},
|
||||
{name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`},
|
||||
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
|
||||
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
|
||||
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
|
||||
// This one should work
|
||||
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
|
||||
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
|
||||
// Invalid cases.
|
||||
{
|
||||
name: "empty path",
|
||||
plugin: &fakeCandidate{path: ""},
|
||||
err: "plugin candidate path cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "bad prefix",
|
||||
plugin: &fakeCandidate{path: badPrefixPath},
|
||||
err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix),
|
||||
},
|
||||
{
|
||||
name: "bad path",
|
||||
plugin: &fakeCandidate{path: badNamePath},
|
||||
invalid: "did not match",
|
||||
},
|
||||
{
|
||||
name: "builtin command",
|
||||
plugin: &fakeCandidate{path: builtinName},
|
||||
invalid: `plugin "builtin" duplicates builtin command`,
|
||||
},
|
||||
{
|
||||
name: "builtin alias",
|
||||
plugin: &fakeCandidate{path: builtinAlias},
|
||||
invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`,
|
||||
},
|
||||
{
|
||||
name: "fetch failure",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: false},
|
||||
invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath),
|
||||
},
|
||||
{
|
||||
name: "metadata not json",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`},
|
||||
invalid: "invalid character",
|
||||
},
|
||||
{
|
||||
name: "empty schemaversion",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`},
|
||||
invalid: `plugin SchemaVersion version cannot be empty`,
|
||||
},
|
||||
{
|
||||
name: "invalid schemaversion",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
|
||||
invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be <major>.<minor>.<patch>`,
|
||||
},
|
||||
{
|
||||
name: "invalid schemaversion major",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`},
|
||||
invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`,
|
||||
},
|
||||
{
|
||||
name: "no vendor",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`},
|
||||
invalid: "plugin metadata does not define a vendor",
|
||||
},
|
||||
{
|
||||
name: "empty vendor",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`},
|
||||
invalid: "plugin metadata does not define a vendor",
|
||||
},
|
||||
|
||||
// Valid cases.
|
||||
{
|
||||
name: "valid",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "0.1.0",
|
||||
},
|
||||
{
|
||||
// Including the deprecated "experimental" field should not break processing.
|
||||
name: "with legacy experimental",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`},
|
||||
expVer: "0.1.0",
|
||||
},
|
||||
{
|
||||
// note that this may not be supported by older CLIs
|
||||
name: "new minor schema version",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "0.2.0",
|
||||
},
|
||||
{
|
||||
// note that this may not be supported by older CLIs
|
||||
name: "new major schema version",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "1.0.0",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p, err := newPlugin(tc.c, fakeroot.Commands())
|
||||
p, err := newPlugin(tc.plugin, fakeroot.Commands())
|
||||
switch {
|
||||
case tc.err != "":
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
@ -86,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
|
||||
default:
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
|
||||
assert.Equal(t, p.SchemaVersion, "0.1.0")
|
||||
assert.Equal(t, p.SchemaVersion, tc.expVer)
|
||||
assert.Equal(t, p.Vendor, "e2e-testing")
|
||||
}
|
||||
})
|
||||
|
||||
@ -38,6 +38,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: p.Name,
|
||||
Short: p.ShortDescription,
|
||||
Hidden: p.Hidden,
|
||||
Run: func(_ *cobra.Command, _ []string) {},
|
||||
Annotations: annotations,
|
||||
DisableFlagParsing: true,
|
||||
|
||||
@ -2,6 +2,7 @@ package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@ -23,6 +25,8 @@ const (
|
||||
// used to originally invoke the docker CLI when executing a
|
||||
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
||||
// the plugin to re-execute the original CLI.
|
||||
//
|
||||
// Deprecated: use [metadata.ReexecEnvvar]. This alias will be removed in the next release.
|
||||
ReexecEnvvar = metadata.ReexecEnvvar
|
||||
)
|
||||
|
||||
@ -72,9 +76,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
|
||||
return
|
||||
}
|
||||
for _, dentry := range dentries {
|
||||
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||
case 0, os.ModeSymlink:
|
||||
// Regular file or symlink, keep going
|
||||
switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||
case os.ModeSymlink:
|
||||
if !debug.IsEnabled() {
|
||||
// Skip broken symlinks unless debug is enabled. With debug
|
||||
// enabled, this will print a warning in "docker info".
|
||||
if _, err := os.Stat(filepath.Join(d, dentry.Name())); errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
case 0:
|
||||
// Regular file, keep going
|
||||
default:
|
||||
// Something else, ignore.
|
||||
continue
|
||||
|
||||
@ -38,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) {
|
||||
"plugins3-target", // Will be referenced as a symlink from below
|
||||
fs.WithFile("docker-plugin1", ""),
|
||||
fs.WithDir("ignored3"),
|
||||
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later)
|
||||
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is ignored
|
||||
fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
|
||||
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
|
||||
),
|
||||
@ -72,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) {
|
||||
"hardlink2": {
|
||||
dir.Join("plugins2", "docker-hardlink2"),
|
||||
},
|
||||
"brokensymlink": {
|
||||
dir.Join("plugins3", "docker-brokensymlink"),
|
||||
},
|
||||
"symlinked": {
|
||||
dir.Join("plugins3", "docker-symlinked"),
|
||||
},
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
@ -118,8 +119,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
|
||||
p.Err = wrapAsPluginError(err, "invalid metadata")
|
||||
return p, nil
|
||||
}
|
||||
if p.Metadata.SchemaVersion != "0.1.0" {
|
||||
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
||||
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
|
||||
p.Err = &pluginError{cause: err}
|
||||
return p, nil
|
||||
}
|
||||
if p.Metadata.Vendor == "" {
|
||||
@ -129,6 +130,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// validateSchemaVersion validates if the plugin's schemaVersion is supported.
|
||||
//
|
||||
// The current schema-version is "0.1.0", but we don't want to break compatibility
|
||||
// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0.
|
||||
//
|
||||
// Note that CLI versions before 28.4.1 may not support these versions as they were
|
||||
// hard-coded to only accept "0.1.0".
|
||||
func validateSchemaVersion(version string) error {
|
||||
if version == "0.1.0" {
|
||||
return nil
|
||||
}
|
||||
if version == "" {
|
||||
return errors.New("plugin SchemaVersion version cannot be empty")
|
||||
}
|
||||
major, _, ok := strings.Cut(version, ".")
|
||||
majorVersion, err := strconv.Atoi(major)
|
||||
if !ok || err != nil {
|
||||
return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be <major>.<minor>.<patch>", version)
|
||||
}
|
||||
if majorVersion > 1 {
|
||||
return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunHook executes the plugin's hooks command
|
||||
// and returns its unprocessed output.
|
||||
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
||||
|
||||
@ -33,4 +33,6 @@ type Metadata struct {
|
||||
ShortDescription string `json:",omitempty"`
|
||||
// URL is a pointer to the plugin's homepage.
|
||||
URL string `json:",omitempty"`
|
||||
// Hidden hides the plugin in completion and help message output.
|
||||
Hidden bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
@ -80,19 +80,23 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
|
||||
return cmd.Execute()
|
||||
}
|
||||
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
|
||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should
|
||||
// be called from the plugin's "main()" function. It initializes a new
|
||||
// [command.DockerCli] instance with the given options before calling
|
||||
// makeCmd to construct the plugin command, then invokes the plugin command
|
||||
// using [RunPlugin].
|
||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata, ops ...command.CLIOption) {
|
||||
otel.SetErrorHandler(debug.OTELErrorHandler)
|
||||
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
dockerCLI, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
plugin := makeCmd(dockerCli)
|
||||
plugin := makeCmd(dockerCLI)
|
||||
|
||||
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
|
||||
if err := RunPlugin(dockerCLI, plugin, meta); err != nil {
|
||||
var stErr cli.StatusError
|
||||
if errors.As(err, &stErr) {
|
||||
// StatusError should only be used for errors, and all errors should
|
||||
@ -100,10 +104,10 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
|
||||
if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere.
|
||||
stErr.StatusCode = 1
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
|
||||
os.Exit(stErr.StatusCode)
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), err)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,13 +376,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
cmds := []*cobra.Command{}
|
||||
for _, sub := range cmd.Commands() {
|
||||
if isPlugin(sub) {
|
||||
if invalidPluginReason(sub) == "" {
|
||||
cmds = append(cmds, sub)
|
||||
}
|
||||
if invalidPluginReason(sub) != "" {
|
||||
continue
|
||||
}
|
||||
if sub.IsAvailableCommand() && sub.HasSubCommands() {
|
||||
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
|
||||
cmds = append(cmds, sub)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) {
|
||||
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
|
||||
}
|
||||
|
||||
func TestHiddenPlugin(t *testing.T) {
|
||||
root := &cobra.Command{Use: "root"}
|
||||
sub1 := &cobra.Command{
|
||||
Use: "sub1",
|
||||
Hidden: true,
|
||||
Annotations: map[string]string{
|
||||
metadata.CommandAnnotationPlugin: "true",
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
|
||||
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
|
||||
sub2 := &cobra.Command{
|
||||
Use: "sub2",
|
||||
Annotations: map[string]string{
|
||||
metadata.CommandAnnotationPlugin: "true",
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
root.AddCommand(sub1, sub2)
|
||||
sub1.AddCommand(sub1sub1, sub1sub2)
|
||||
|
||||
assert.DeepEqual(t, allManagementSubCommands(root), []*cobra.Command{sub2}, cmpopts.IgnoreFields(cobra.Command{}, "Run"), cmpopts.IgnoreUnexported(cobra.Command{}))
|
||||
}
|
||||
|
||||
func TestCommandAliases(t *testing.T) {
|
||||
root := &cobra.Command{Use: "root"}
|
||||
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
@ -50,7 +49,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.39"},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -86,9 +85,12 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
|
||||
All: options.all,
|
||||
KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720
|
||||
Filters: pruneFilters,
|
||||
All: options.all,
|
||||
// TODO(austinvazquez): remove when updated to use github.com/moby/moby/client@v0.1.0
|
||||
// See https://github.com/moby/moby/pull/50772 for more details.
|
||||
KeepStorage: options.keepStorage.Value(),
|
||||
ReservedSpace: options.keepStorage.Value(),
|
||||
Filters: pruneFilters,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -30,7 +29,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts.checkpoint = args[1]
|
||||
return runCreate(cmd.Context(), dockerCli, opts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -48,9 +48,7 @@ type Cli interface {
|
||||
Apply(ops ...CLIOption) error
|
||||
config.Provider
|
||||
ServerInfo() ServerInfo
|
||||
DefaultVersion() string
|
||||
CurrentVersion() string
|
||||
ContentTrustEnabled() bool
|
||||
BuildKitEnabled() (bool, error)
|
||||
ContextStore() store.Store
|
||||
CurrentContext() string
|
||||
@ -78,6 +76,7 @@ type DockerCli struct {
|
||||
dockerEndpoint docker.Endpoint
|
||||
contextStoreConfig *store.Config
|
||||
initTimeout time.Duration
|
||||
userAgent string
|
||||
res telemetryResource
|
||||
|
||||
// baseCtx is the base context used for internal operations. In the future
|
||||
@ -89,6 +88,8 @@ type DockerCli struct {
|
||||
}
|
||||
|
||||
// DefaultVersion returns [api.DefaultVersion].
|
||||
//
|
||||
// Deprecated: this function is no longer used and will be removed in the next release.
|
||||
func (*DockerCli) DefaultVersion() string {
|
||||
return api.DefaultVersion
|
||||
}
|
||||
@ -159,6 +160,8 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
|
||||
|
||||
// ContentTrustEnabled returns whether content trust has been enabled by an
|
||||
// environment variable.
|
||||
//
|
||||
// Deprecated: check the value of the DOCKER_CONTENT_TRUST environment variable to detect whether content-trust is enabled.
|
||||
func (cli *DockerCli) ContentTrustEnabled() bool {
|
||||
return cli.contentTrust
|
||||
}
|
||||
@ -269,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
cli.contextStore = &ContextStoreWithDefault{
|
||||
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||
},
|
||||
}
|
||||
|
||||
@ -306,17 +309,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
||||
contextStore := &ContextStoreWithDefault{
|
||||
Store: store.New(config.ContextStoreDir(), storeConfig),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return ResolveDefaultContext(opts, storeConfig)
|
||||
return resolveDefaultContext(opts, storeConfig)
|
||||
},
|
||||
}
|
||||
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
|
||||
}
|
||||
return newAPIClientFromEndpoint(endpoint, configFile)
|
||||
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
|
||||
}
|
||||
|
||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
|
||||
opts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -324,7 +327,14 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
||||
if len(configFile.HTTPHeaders) > 0 {
|
||||
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
||||
}
|
||||
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
|
||||
withCustomHeaders, err := withCustomHeadersFromEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if withCustomHeaders != nil {
|
||||
opts = append(opts, withCustomHeaders)
|
||||
}
|
||||
opts = append(opts, extraOpts...)
|
||||
return client.NewClientWithOpts(opts...)
|
||||
}
|
||||
|
||||
@ -545,7 +555,8 @@ func (cli *DockerCli) initialize() error {
|
||||
return
|
||||
}
|
||||
if cli.client == nil {
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
|
||||
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -558,6 +569,8 @@ func (cli *DockerCli) initialize() error {
|
||||
}
|
||||
|
||||
// Apply all the operation on the cli
|
||||
//
|
||||
// Deprecated: this method is no longer used and will be removed in the next release if there are no remaining users.
|
||||
func (cli *DockerCli) Apply(ops ...CLIOption) error {
|
||||
for _, op := range ops {
|
||||
if err := op(cli); err != nil {
|
||||
@ -589,15 +602,18 @@ type ServerInfo struct {
|
||||
// environment.
|
||||
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
||||
defaultOps := []CLIOption{
|
||||
WithContentTrustFromEnv(),
|
||||
withContentTrustFromEnv(),
|
||||
WithDefaultContextStoreConfig(),
|
||||
WithStandardStreams(),
|
||||
WithUserAgent(UserAgent()),
|
||||
}
|
||||
ops = append(defaultOps, ops...)
|
||||
|
||||
cli := &DockerCli{baseCtx: context.Background()}
|
||||
if err := cli.Apply(ops...); err != nil {
|
||||
return nil, err
|
||||
for _, op := range ops {
|
||||
if err := op(cli); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
@ -613,7 +629,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// UserAgent returns the user agent string used for making API requests
|
||||
// UserAgent returns the default user agent string used for making API requests.
|
||||
func UserAgent() string {
|
||||
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
||||
}
|
||||
|
||||
@ -75,8 +75,8 @@ func WithErrorStream(err io.Writer) CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
func WithContentTrustFromEnv() CLIOption {
|
||||
// withContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
func withContentTrustFromEnv() CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contentTrust = false
|
||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
||||
@ -89,7 +89,16 @@ func WithContentTrustFromEnv() CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
//
|
||||
// Deprecated: this option is no longer used, and will be removed in the next release.
|
||||
func WithContentTrustFromEnv() CLIOption {
|
||||
return withContentTrustFromEnv()
|
||||
}
|
||||
|
||||
// WithContentTrust enables content trust on a cli.
|
||||
//
|
||||
// Deprecated: this option is no longer used, and will be removed in the next release.
|
||||
func WithContentTrust(enabled bool) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contentTrust = enabled
|
||||
@ -180,61 +189,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
|
||||
// override headers with the same name).
|
||||
//
|
||||
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
|
||||
func withCustomHeadersFromEnv() client.Opt {
|
||||
return func(apiClient *client.Client) error {
|
||||
value := os.Getenv(envOverrideHTTPHeaders)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return invalidParameter(errors.Errorf(
|
||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||
envOverrideHTTPHeaders,
|
||||
func withCustomHeadersFromEnv() (client.Opt, error) {
|
||||
value := os.Getenv(envOverrideHTTPHeaders)
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||
envOverrideHTTPHeaders,
|
||||
))
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
for _, kv := range fields {
|
||||
k, v, hasValue := strings.Cut(kv, "=")
|
||||
|
||||
// Only strip whitespace in keys; preserve whitespace in values.
|
||||
k = strings.TrimSpace(k)
|
||||
|
||||
if k == "" {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
|
||||
// We don't currently allow empty key=value pairs, and produce an error.
|
||||
// This is something we could allow in future (e.g. to read value
|
||||
// from an environment variable with the same name). In the meantime,
|
||||
// produce an error to prevent users from depending on this.
|
||||
if !hasValue {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
for _, kv := range fields {
|
||||
k, v, hasValue := strings.Cut(kv, "=")
|
||||
env[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
|
||||
// Only strip whitespace in keys; preserve whitespace in values.
|
||||
k = strings.TrimSpace(k)
|
||||
if len(env) == 0 {
|
||||
// We should probably not hit this case, as we don't skip values
|
||||
// (only return errors), but we don't want to discard existing
|
||||
// headers with an empty set.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if k == "" {
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||
return client.WithHTTPHeaders(env), nil
|
||||
}
|
||||
|
||||
// We don't currently allow empty key=value pairs, and produce an error.
|
||||
// This is something we could allow in future (e.g. to read value
|
||||
// from an environment variable with the same name). In the meantime,
|
||||
// produce an error to prevent users from depending on this.
|
||||
if !hasValue {
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
|
||||
env[http.CanonicalHeaderKey(k)] = v
|
||||
// WithUserAgent configures the User-Agent string for cli HTTP requests.
|
||||
func WithUserAgent(userAgent string) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
if userAgent == "" {
|
||||
return errors.New("user agent cannot be blank")
|
||||
}
|
||||
|
||||
if len(env) == 0 {
|
||||
// We should probably not hit this case, as we don't skip values
|
||||
// (only return errors), but we don't want to discard existing
|
||||
// headers with an empty set.
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||
return client.WithHTTPHeaders(env)(apiClient)
|
||||
cli.userAgent = userAgent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
func contentTrustEnabled(t *testing.T) bool {
|
||||
t.Helper()
|
||||
var cli DockerCli
|
||||
assert.NilError(t, WithContentTrustFromEnv()(&cli))
|
||||
assert.NilError(t, withContentTrustFromEnv()(&cli))
|
||||
return cli.contentTrust
|
||||
}
|
||||
|
||||
|
||||
@ -374,3 +374,26 @@ func TestSetGoDebug(t *testing.T) {
|
||||
assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
|
||||
var received string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
received = r.UserAgent()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
|
||||
opts := &flags.ClientOptions{Hosts: []string{host}}
|
||||
|
||||
cli, err := NewDockerCli(
|
||||
WithUserAgent("fake-agent/0.0.1"),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
cli.currentContext = DefaultContextName
|
||||
cli.options = opts
|
||||
cli.configFile = &configfile.ConfigFile{}
|
||||
|
||||
_, err = cli.Client().Ping(context.Background())
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, received, "fake-agent/0.0.1")
|
||||
}
|
||||
|
||||
@ -141,7 +141,9 @@ func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCom
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
// NoComplete is used for commands where there's no relevant completion
|
||||
// NoComplete is used for commands where there's no relevant completion.
|
||||
//
|
||||
// Deprecated: use [cobra.NoFileCompletions]. This function will be removed in the next release.
|
||||
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@ -288,12 +288,6 @@ func TestCompleteNetworkNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteNoComplete(t *testing.T) {
|
||||
values, directives := NoComplete(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.Len(values, 0))
|
||||
}
|
||||
|
||||
func TestCompletePlatforms(t *testing.T) {
|
||||
values, directives := Platforms(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/moby/sys/sequential"
|
||||
@ -47,7 +46,7 @@ func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
createOpts.file = args[1]
|
||||
return runCreate(cmd.Context(), dockerCLI, createOpts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -42,7 +41,7 @@ func newConfigListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCLI, listOpts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -96,7 +96,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
addPlatformFlag(flags, &options.platform)
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
copts = addFlags(flags)
|
||||
|
||||
addCompletions(cmd, dockerCLI)
|
||||
@ -105,7 +105,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
|
||||
return cmd
|
||||
|
||||
@ -249,6 +249,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
@ -258,7 +259,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newCreateCommand(fakeCLI)
|
||||
cmd.SetOut(io.Discard)
|
||||
|
||||
@ -167,6 +167,7 @@ func (c *statsContext) Container() string {
|
||||
}
|
||||
|
||||
func (c *statsContext) Name() string {
|
||||
// TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char.
|
||||
if len(c.s.Name) > 1 {
|
||||
return c.s.Name[1:]
|
||||
}
|
||||
|
||||
@ -5,45 +5,181 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestContainerStatsContext(t *testing.T) {
|
||||
containerID := test.RandomID()
|
||||
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
|
||||
|
||||
var ctx statsContext
|
||||
tt := []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
stats StatsEntry
|
||||
osType string
|
||||
expValue string
|
||||
expHeader string
|
||||
call func() string
|
||||
}{
|
||||
{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
|
||||
{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
|
||||
{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
|
||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
|
||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
|
||||
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
|
||||
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
|
||||
{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
|
||||
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
|
||||
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
|
||||
{
|
||||
name: "Container id",
|
||||
stats: StatsEntry{ID: actorID, Container: actorID},
|
||||
expValue: actorID,
|
||||
expHeader: containerHeader,
|
||||
call: ctx.Container,
|
||||
},
|
||||
{
|
||||
name: "Container name",
|
||||
stats: StatsEntry{ID: actorID, Container: "a-long-container-name"},
|
||||
expValue: "a-long-container-name",
|
||||
expHeader: containerHeader,
|
||||
call: ctx.Container,
|
||||
},
|
||||
{
|
||||
name: "ID",
|
||||
stats: StatsEntry{ID: actorID},
|
||||
expValue: actorID,
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
stats: StatsEntry{Name: "/container-name"},
|
||||
expValue: "container-name",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "Name empty",
|
||||
stats: StatsEntry{Name: ""},
|
||||
expValue: "--",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "Name prefix only",
|
||||
stats: StatsEntry{Name: "/"},
|
||||
expValue: "--",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "CPUPerc",
|
||||
stats: StatsEntry{CPUPercentage: 5.5},
|
||||
expValue: "5.50%",
|
||||
expHeader: cpuPercHeader,
|
||||
call: ctx.CPUPerc,
|
||||
},
|
||||
{
|
||||
name: "CPUPerc invalid",
|
||||
stats: StatsEntry{CPUPercentage: 5.5, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: cpuPercHeader,
|
||||
call: ctx.CPUPerc,
|
||||
},
|
||||
{
|
||||
name: "NetIO",
|
||||
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3},
|
||||
expValue: "0.31B / 12.3B",
|
||||
expHeader: netIOHeader,
|
||||
call: ctx.NetIO,
|
||||
},
|
||||
{
|
||||
name: "NetIO invalid",
|
||||
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: netIOHeader,
|
||||
call: ctx.NetIO,
|
||||
},
|
||||
{
|
||||
name: "BlockIO",
|
||||
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3},
|
||||
expValue: "0.1B / 2.3B",
|
||||
expHeader: blockIOHeader,
|
||||
call: ctx.BlockIO,
|
||||
},
|
||||
{
|
||||
name: "BlockIO invalid",
|
||||
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: blockIOHeader,
|
||||
call: ctx.BlockIO,
|
||||
},
|
||||
{
|
||||
name: "MemPerc",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2},
|
||||
expValue: "10.20%",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemPerc invalid",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemPerc windows",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2},
|
||||
osType: "windows",
|
||||
expValue: "--",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemUsage",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
|
||||
expValue: "24B / 30B",
|
||||
expHeader: memUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "MemUsage invalid",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true},
|
||||
expValue: "-- / --",
|
||||
expHeader: memUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "MemUsage windows",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
|
||||
osType: "windows",
|
||||
expValue: "24B",
|
||||
expHeader: winMemUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "PIDs",
|
||||
stats: StatsEntry{PidsCurrent: 10},
|
||||
expValue: "10",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
{
|
||||
name: "PIDs invalid",
|
||||
stats: StatsEntry{PidsCurrent: 10, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
{
|
||||
name: "PIDs windows",
|
||||
stats: StatsEntry{PidsCurrent: 10},
|
||||
osType: "windows",
|
||||
expValue: "--",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tt {
|
||||
ctx = statsContext{s: te.stats, os: te.osType}
|
||||
if v := te.call(); v != te.expValue {
|
||||
t.Fatalf("Expected %q, got %q", te.expValue, v)
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx = statsContext{s: tc.stats, os: tc.osType}
|
||||
if v := tc.call(); v != tc.expValue {
|
||||
t.Fatalf("Expected %q, got %q", tc.expValue, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -50,7 +49,7 @@ func newPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
"category-top": "3",
|
||||
"aliases": "docker container ls, docker container list, docker container ps, docker ps",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -88,7 +87,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
|
||||
|
||||
// always validate template when `--format` is used, for consistency
|
||||
if len(options.format) > 0 {
|
||||
tmpl, err := templates.NewParse("", options.format)
|
||||
tmpl, err := templates.Parse(options.format)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse template")
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@ -350,7 +351,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
|
||||
// Validate the input mac address
|
||||
if copts.macAddress != "" {
|
||||
if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
|
||||
if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil {
|
||||
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
|
||||
}
|
||||
}
|
||||
@ -883,7 +884,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
||||
}
|
||||
}
|
||||
if ep.MacAddress != "" {
|
||||
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
|
||||
if _, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress)); err != nil {
|
||||
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
|
||||
}
|
||||
epConfig.MacAddress = ep.MacAddress
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
@ -45,7 +44,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/sys/signal"
|
||||
@ -74,7 +75,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
|
||||
addPlatformFlag(flags, &options.platform)
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
copts = addFlags(flags)
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
||||
@ -84,7 +85,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
|
||||
return cmd
|
||||
|
||||
@ -323,6 +323,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
@ -332,7 +333,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newRunCommand(fakeCLI)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
@ -139,7 +139,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
eh := newEventHandler()
|
||||
if options.All {
|
||||
eh.setHandler(events.ActionCreate, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
s := NewStats(e.Actor.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -148,7 +148,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
}
|
||||
|
||||
eh.setHandler(events.ActionStart, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
s := NewStats(e.Actor.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -157,7 +157,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
|
||||
if !options.All {
|
||||
eh.setHandler(events.ActionDie, func(e events.Message) {
|
||||
cStats.remove(e.Actor.ID[:12])
|
||||
cStats.remove(e.Actor.ID)
|
||||
})
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
return err
|
||||
}
|
||||
for _, ctr := range cs {
|
||||
s := NewStats(ctr.ID[:12])
|
||||
s := NewStats(ctr.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -363,7 +363,12 @@ func (eh *eventHandler) watch(c <-chan events.Message) {
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("event handler: received event: %v", e)
|
||||
if e.Actor.ID == "" {
|
||||
logrus.WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.WithField("event", e).Debugf("event handler: received %s event for: %s", e.Action, e.Actor.ID)
|
||||
go h(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter/tabwriter"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
@ -19,6 +18,8 @@ import (
|
||||
)
|
||||
|
||||
// CreateOptions are the options used for creating a context
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type CreateOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
@ -30,6 +31,18 @@ type CreateOptions struct {
|
||||
metaData map[string]any
|
||||
}
|
||||
|
||||
// createOptions are the options used for creating a context
|
||||
type createOptions struct {
|
||||
name string
|
||||
description string
|
||||
endpoint map[string]string
|
||||
from string
|
||||
|
||||
// Additional Metadata to store in the context. This option is not
|
||||
// currently exposed to the user.
|
||||
metaData map[string]any
|
||||
}
|
||||
|
||||
func longCreateDescription() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString("Create a context\n\nDocker endpoint config:\n\n")
|
||||
@ -44,52 +57,68 @@ func longCreateDescription() string {
|
||||
}
|
||||
|
||||
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
opts := &CreateOptions{}
|
||||
opts := createOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] CONTEXT",
|
||||
Short: "Create a context",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Name = args[0]
|
||||
return RunCreate(dockerCLI, opts)
|
||||
opts.name = args[0]
|
||||
return runCreate(dockerCLI, &opts)
|
||||
},
|
||||
Long: longCreateDescription(),
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
|
||||
flags.StringVar(&opts.From, "from", "", "create context from a named context")
|
||||
flags.StringVar(&opts.description, "description", "", "Description of the context")
|
||||
flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint")
|
||||
flags.StringVar(&opts.from, "from", "", "create context from a named context")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunCreate creates a Docker context
|
||||
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunCreate(dockerCLI command.Cli, o *CreateOptions) error {
|
||||
if o == nil {
|
||||
o = &CreateOptions{}
|
||||
}
|
||||
|
||||
return runCreate(dockerCLI, &createOptions{
|
||||
name: o.Name,
|
||||
description: o.Description,
|
||||
endpoint: o.Docker,
|
||||
metaData: o.metaData,
|
||||
})
|
||||
}
|
||||
|
||||
// runCreate creates a Docker context
|
||||
func runCreate(dockerCLI command.Cli, opts *createOptions) error {
|
||||
s := dockerCLI.ContextStore()
|
||||
err := checkContextNameForCreation(s, o.Name)
|
||||
err := checkContextNameForCreation(s, opts.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case o.From == "" && o.Docker == nil:
|
||||
err = createFromExistingContext(s, dockerCLI.CurrentContext(), o)
|
||||
case o.From != "":
|
||||
err = createFromExistingContext(s, o.From, o)
|
||||
case opts.from == "" && opts.endpoint == nil:
|
||||
err = createFromExistingContext(s, dockerCLI.CurrentContext(), opts)
|
||||
case opts.from != "":
|
||||
err = createFromExistingContext(s, opts.from, opts)
|
||||
default:
|
||||
err = createNewContext(s, o)
|
||||
err = createNewContext(s, opts)
|
||||
}
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), o.Name)
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.name)
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", opts.name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
if o.Docker == nil {
|
||||
func createNewContext(contextStore store.ReaderWriter, opts *createOptions) error {
|
||||
if opts.endpoint == nil {
|
||||
return errors.New("docker endpoint configuration is required")
|
||||
}
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker)
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, opts.endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create docker endpoint config: %w", err)
|
||||
}
|
||||
@ -98,10 +127,10 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
docker.DockerEndpoint: dockerEP,
|
||||
},
|
||||
Metadata: command.DockerContext{
|
||||
Description: o.Description,
|
||||
AdditionalFields: o.metaData,
|
||||
Description: opts.description,
|
||||
AdditionalFields: opts.metaData,
|
||||
},
|
||||
Name: o.Name,
|
||||
Name: opts.name,
|
||||
}
|
||||
contextTLSData := store.ContextTLSData{}
|
||||
if dockerTLS != nil {
|
||||
@ -115,7 +144,7 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
return contextStore.ResetTLSMaterial(o.Name, &contextTLSData)
|
||||
return contextStore.ResetTLSMaterial(opts.name, &contextTLSData)
|
||||
}
|
||||
|
||||
func checkContextNameForCreation(s store.Reader, name string) error {
|
||||
@ -131,16 +160,16 @@ func checkContextNameForCreation(s store.Reader, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFromExistingContext(s store.ReaderWriter, fromContextName string, o *CreateOptions) error {
|
||||
if len(o.Docker) != 0 {
|
||||
func createFromExistingContext(s store.ReaderWriter, fromContextName string, opts *createOptions) error {
|
||||
if len(opts.endpoint) != 0 {
|
||||
return errors.New("cannot use --docker flag when --from is set")
|
||||
}
|
||||
reader := store.Export(fromContextName, &descriptionDecorator{
|
||||
Reader: s,
|
||||
description: o.Description,
|
||||
description: opts.description,
|
||||
})
|
||||
defer reader.Close()
|
||||
return store.Import(o.Name, s, reader)
|
||||
return store.Import(opts.name, s, reader)
|
||||
}
|
||||
|
||||
type descriptionDecorator struct {
|
||||
|
||||
@ -60,7 +60,7 @@ func TestCreate(t *testing.T) {
|
||||
assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"}))
|
||||
tests := []struct {
|
||||
doc string
|
||||
options CreateOptions
|
||||
options createOptions
|
||||
expecterErr string
|
||||
}{
|
||||
{
|
||||
@ -69,30 +69,30 @@ func TestCreate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "reserved name",
|
||||
options: CreateOptions{
|
||||
Name: "default",
|
||||
options: createOptions{
|
||||
name: "default",
|
||||
},
|
||||
expecterErr: `"default" is a reserved context name`,
|
||||
},
|
||||
{
|
||||
doc: "whitespace-only name",
|
||||
options: CreateOptions{
|
||||
Name: " ",
|
||||
options: createOptions{
|
||||
name: " ",
|
||||
},
|
||||
expecterErr: `context name " " is invalid`,
|
||||
},
|
||||
{
|
||||
doc: "existing context",
|
||||
options: CreateOptions{
|
||||
Name: "existing-context",
|
||||
options: createOptions{
|
||||
name: "existing-context",
|
||||
},
|
||||
expecterErr: `context "existing-context" already exists`,
|
||||
},
|
||||
{
|
||||
doc: "invalid docker host",
|
||||
options: CreateOptions{
|
||||
Name: "invalid-docker-host",
|
||||
Docker: map[string]string{
|
||||
options: createOptions{
|
||||
name: "invalid-docker-host",
|
||||
endpoint: map[string]string{
|
||||
"host": "some///invalid/host",
|
||||
},
|
||||
},
|
||||
@ -100,27 +100,27 @@ func TestCreate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "ssh host with skip-tls-verify=false",
|
||||
options: CreateOptions{
|
||||
Name: "skip-tls-verify-false",
|
||||
Docker: map[string]string{
|
||||
options: createOptions{
|
||||
name: "skip-tls-verify-false",
|
||||
endpoint: map[string]string{
|
||||
"host": "ssh://example.com,skip-tls-verify=false",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "ssh host with skip-tls-verify=true",
|
||||
options: CreateOptions{
|
||||
Name: "skip-tls-verify-true",
|
||||
Docker: map[string]string{
|
||||
options: createOptions{
|
||||
name: "skip-tls-verify-true",
|
||||
endpoint: map[string]string{
|
||||
"host": "ssh://example.com,skip-tls-verify=true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "ssh host with skip-tls-verify=INVALID",
|
||||
options: CreateOptions{
|
||||
Name: "skip-tls-verify-invalid",
|
||||
Docker: map[string]string{
|
||||
options: createOptions{
|
||||
name: "skip-tls-verify-invalid",
|
||||
endpoint: map[string]string{
|
||||
"host": "ssh://example.com",
|
||||
"skip-tls-verify": "INVALID",
|
||||
},
|
||||
@ -129,9 +129,9 @@ func TestCreate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "unknown option",
|
||||
options: CreateOptions{
|
||||
Name: "unknown-option",
|
||||
Docker: map[string]string{
|
||||
options: createOptions{
|
||||
name: "unknown-option",
|
||||
endpoint: map[string]string{
|
||||
"UNKNOWN": "value",
|
||||
},
|
||||
},
|
||||
@ -140,7 +140,7 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
err := RunCreate(cli, &tc.options)
|
||||
err := runCreate(cli, &tc.options)
|
||||
if tc.expecterErr == "" {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
@ -159,9 +159,9 @@ func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) {
|
||||
func TestCreateOrchestratorEmpty(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assertContextCreateLogging(t, cli, "test")
|
||||
@ -187,20 +187,20 @@ func TestCreateFromContext(t *testing.T) {
|
||||
|
||||
cli := makeFakeCli(t)
|
||||
cli.ResetOutputBuffers()
|
||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
||||
Name: "original",
|
||||
Description: "original description",
|
||||
Docker: map[string]string{
|
||||
assert.NilError(t, runCreate(cli, &createOptions{
|
||||
name: "original",
|
||||
description: "original description",
|
||||
endpoint: map[string]string{
|
||||
keyHost: "tcp://42.42.42.42:2375",
|
||||
},
|
||||
}))
|
||||
assertContextCreateLogging(t, cli, "original")
|
||||
|
||||
cli.ResetOutputBuffers()
|
||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
||||
Name: "dummy",
|
||||
Description: "dummy description",
|
||||
Docker: map[string]string{
|
||||
assert.NilError(t, runCreate(cli, &createOptions{
|
||||
name: "dummy",
|
||||
description: "dummy description",
|
||||
endpoint: map[string]string{
|
||||
keyHost: "tcp://24.24.24.24:2375",
|
||||
},
|
||||
}))
|
||||
@ -211,11 +211,11 @@ func TestCreateFromContext(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli.ResetOutputBuffers()
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
From: "original",
|
||||
Name: tc.name,
|
||||
Description: tc.description,
|
||||
Docker: tc.docker,
|
||||
err := runCreate(cli, &createOptions{
|
||||
from: "original",
|
||||
name: tc.name,
|
||||
description: tc.description,
|
||||
endpoint: tc.docker,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assertContextCreateLogging(t, cli, tc.name)
|
||||
@ -251,10 +251,10 @@ func TestCreateFromCurrent(t *testing.T) {
|
||||
|
||||
cli := makeFakeCli(t)
|
||||
cli.ResetOutputBuffers()
|
||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
||||
Name: "original",
|
||||
Description: "original description",
|
||||
Docker: map[string]string{
|
||||
assert.NilError(t, runCreate(cli, &createOptions{
|
||||
name: "original",
|
||||
description: "original description",
|
||||
endpoint: map[string]string{
|
||||
keyHost: "tcp://42.42.42.42:2375",
|
||||
},
|
||||
}))
|
||||
@ -265,9 +265,9 @@ func TestCreateFromCurrent(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli.ResetOutputBuffers()
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: tc.name,
|
||||
Description: tc.description,
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: tc.name,
|
||||
description: tc.description,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assertContextCreateLogging(t, cli, tc.name)
|
||||
|
||||
@ -21,14 +21,11 @@ func TestExportImportWithFile(t *testing.T) {
|
||||
"MyCustomMetadata": t.Name(),
|
||||
})
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
||||
ContextName: "test",
|
||||
Dest: contextFile,
|
||||
}))
|
||||
assert.NilError(t, runExport(cli, "test", contextFile))
|
||||
assert.Equal(t, cli.ErrBuffer().String(), fmt.Sprintf("Written file %q\n", contextFile))
|
||||
cli.OutBuffer().Reset()
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, RunImport(cli, "test2", contextFile))
|
||||
assert.NilError(t, runImport(cli, "test2", contextFile))
|
||||
context1, err := cli.ContextStore().GetMetadata("test")
|
||||
assert.NilError(t, err)
|
||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||
@ -55,15 +52,12 @@ func TestExportImportPipe(t *testing.T) {
|
||||
})
|
||||
cli.ErrBuffer().Reset()
|
||||
cli.OutBuffer().Reset()
|
||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
||||
ContextName: "test",
|
||||
Dest: "-",
|
||||
}))
|
||||
assert.NilError(t, runExport(cli, "test", "-"))
|
||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||
cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBuffer(cli.OutBuffer().Bytes()))))
|
||||
cli.OutBuffer().Reset()
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, RunImport(cli, "test2", "-"))
|
||||
assert.NilError(t, runImport(cli, "test2", "-"))
|
||||
context1, err := cli.ContextStore().GetMetadata("test")
|
||||
assert.NilError(t, err)
|
||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||
@ -88,6 +82,6 @@ func TestExportExistingFile(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, os.WriteFile(contextFile, []byte{}, 0o644))
|
||||
err := RunExport(cli, &ExportOptions{ContextName: "test", Dest: contextFile})
|
||||
err := runExport(cli, "test", contextFile)
|
||||
assert.Assert(t, os.IsExist(err))
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
// ExportOptions are the options used for exporting a context
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type ExportOptions struct {
|
||||
ContextName string
|
||||
Dest string
|
||||
@ -24,15 +26,14 @@ func newExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
|
||||
Args: cli.RequiresRangeArgs(1, 2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts := &ExportOptions{
|
||||
ContextName: args[0],
|
||||
}
|
||||
contextName := args[0]
|
||||
var dest string
|
||||
if len(args) == 2 {
|
||||
opts.Dest = args[1]
|
||||
dest = args[1]
|
||||
} else {
|
||||
opts.Dest = opts.ContextName + ".dockercontext"
|
||||
dest = contextName + ".dockercontext"
|
||||
}
|
||||
return RunExport(dockerCLI, opts)
|
||||
return runExport(dockerCLI, contextName, dest)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, true),
|
||||
}
|
||||
@ -65,11 +66,21 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {
|
||||
}
|
||||
|
||||
// RunExport exports a Docker context
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunExport(dockerCli command.Cli, opts *ExportOptions) error {
|
||||
if err := store.ValidateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName {
|
||||
if opts == nil {
|
||||
opts = &ExportOptions{}
|
||||
}
|
||||
return runExport(dockerCli, opts.ContextName, opts.Dest)
|
||||
}
|
||||
|
||||
// runExport exports a Docker context.
|
||||
func runExport(dockerCLI command.Cli, contextName string, dest string) error {
|
||||
if err := store.ValidateContextName(contextName); err != nil && contextName != command.DefaultContextName {
|
||||
return err
|
||||
}
|
||||
reader := store.Export(opts.ContextName, dockerCli.ContextStore())
|
||||
reader := store.Export(contextName, dockerCLI.ContextStore())
|
||||
defer reader.Close()
|
||||
return writeTo(dockerCli, reader, opts.Dest)
|
||||
return writeTo(dockerCLI, reader, dest)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Import a context from a tar or zip file",
|
||||
Args: cli.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunImport(dockerCli, args[0], args[1])
|
||||
return runImport(dockerCli, args[0], args[1])
|
||||
},
|
||||
// TODO(thaJeztah): this should also include "-"
|
||||
ValidArgsFunction: completion.FileNames,
|
||||
@ -27,14 +27,21 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// RunImport imports a Docker context
|
||||
func RunImport(dockerCli command.Cli, name string, source string) error {
|
||||
if err := checkContextNameForCreation(dockerCli.ContextStore(), name); err != nil {
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunImport(dockerCLI command.Cli, name string, source string) error {
|
||||
return runImport(dockerCLI, name, source)
|
||||
}
|
||||
|
||||
// runImport imports a Docker context.
|
||||
func runImport(dockerCLI command.Cli, name string, source string) error {
|
||||
if err := checkContextNameForCreation(dockerCLI.ContextStore(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
if source == "-" {
|
||||
reader = dockerCli.In()
|
||||
reader = dockerCLI.In()
|
||||
} else {
|
||||
f, err := os.Open(source)
|
||||
if err != nil {
|
||||
@ -44,11 +51,11 @@ func RunImport(dockerCli command.Cli, name string, source string) error {
|
||||
reader = f
|
||||
}
|
||||
|
||||
if err := store.Import(name, dockerCli.ContextStore(), reader); err != nil {
|
||||
if err := store.Import(name, dockerCLI.ContextStore(), reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), name)
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Successfully imported context %q\n", name)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully imported context %q\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(dockerCli, opts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -19,10 +19,10 @@ func createTestContexts(t *testing.T, cli command.Cli, name ...string) {
|
||||
func createTestContext(t *testing.T, cli command.Cli, name string, metaData map[string]any) {
|
||||
t.Helper()
|
||||
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: name,
|
||||
Description: "description of " + name,
|
||||
Docker: map[string]string{keyHost: "https://someswarmserver.example.com"},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: name,
|
||||
description: "description of " + name,
|
||||
endpoint: map[string]string{keyHost: "https://someswarmserver.example.com"},
|
||||
|
||||
metaData: metaData,
|
||||
})
|
||||
|
||||
@ -11,34 +11,48 @@ import (
|
||||
)
|
||||
|
||||
// RemoveOptions are the options used to remove contexts
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type RemoveOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
// removeOptions are the options used to remove contexts.
|
||||
type removeOptions struct {
|
||||
force bool
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
var opts RemoveOptions
|
||||
var opts removeOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm CONTEXT [CONTEXT...]",
|
||||
Aliases: []string{"remove"},
|
||||
Short: "Remove one or more contexts",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunRemove(dockerCLI, opts, args)
|
||||
return runRemove(dockerCLI, opts, args)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, -1, false),
|
||||
}
|
||||
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force the removal of a context in use")
|
||||
cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Force the removal of a context in use")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunRemove removes one or more contexts
|
||||
func RunRemove(dockerCLI command.Cli, opts RemoveOptions, names []string) error {
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunRemove(dockerCLI command.Cli, opts removeOptions, names []string) error {
|
||||
return runRemove(dockerCLI, opts, names)
|
||||
}
|
||||
|
||||
// runRemove removes one or more contexts.
|
||||
func runRemove(dockerCLI command.Cli, opts removeOptions, names []string) error {
|
||||
var errs []error
|
||||
currentCtx := dockerCLI.CurrentContext()
|
||||
for _, name := range names {
|
||||
if name == "default" {
|
||||
errs = append(errs, errors.New(`context "default" cannot be removed`))
|
||||
} else if err := doRemove(dockerCLI, name, name == currentCtx, opts.Force); err != nil {
|
||||
} else if err := doRemove(dockerCLI, name, name == currentCtx, opts.force); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
func TestRemove(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
assert.NilError(t, RunRemove(cli, RemoveOptions{}, []string{"other"}))
|
||||
assert.NilError(t, runRemove(cli, removeOptions{}, []string{"other"}))
|
||||
_, err := cli.ContextStore().GetMetadata("current")
|
||||
assert.NilError(t, err)
|
||||
_, err = cli.ContextStore().GetMetadata("other")
|
||||
@ -24,10 +24,10 @@ func TestRemove(t *testing.T) {
|
||||
func TestRemoveNotAContext(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"not-a-context"})
|
||||
err := runRemove(cli, removeOptions{}, []string{"not-a-context"})
|
||||
assert.ErrorContains(t, err, `context "not-a-context" does not exist`)
|
||||
|
||||
err = RunRemove(cli, RemoveOptions{Force: true}, []string{"not-a-context"})
|
||||
err = runRemove(cli, removeOptions{force: true}, []string{"not-a-context"})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ func TestRemoveCurrent(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
cli.SetCurrentContext("current")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"current"})
|
||||
err := runRemove(cli, removeOptions{}, []string{"current"})
|
||||
assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`)
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ func TestRemoveCurrentForce(t *testing.T) {
|
||||
cli := makeFakeCli(t, withCliConfig(testCfg))
|
||||
createTestContexts(t, cli, "current", "other")
|
||||
cli.SetCurrentContext("current")
|
||||
assert.NilError(t, RunRemove(cli, RemoveOptions{Force: true}, []string{"current"}))
|
||||
assert.NilError(t, runRemove(cli, removeOptions{force: true}, []string{"current"}))
|
||||
reloadedConfig, err := config.Load(configDir)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, "", reloadedConfig.CurrentContext)
|
||||
@ -59,6 +59,6 @@ func TestRemoveDefault(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "other", nil)
|
||||
cli.SetCurrentContext("current")
|
||||
err := RunRemove(cli, RemoveOptions{}, []string{"default"})
|
||||
err := runRemove(cli, removeOptions{}, []string{"default"})
|
||||
assert.ErrorContains(t, err, `context "default" cannot be removed`)
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -19,7 +18,7 @@ func newShowCommand(dockerCli command.Cli) *cobra.Command {
|
||||
runShow(dockerCli)
|
||||
return nil
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -13,12 +13,21 @@ import (
|
||||
)
|
||||
|
||||
// UpdateOptions are the options used to update a context
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type UpdateOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
Docker map[string]string
|
||||
}
|
||||
|
||||
// updateOptions are the options used to update a context.
|
||||
type updateOptions struct {
|
||||
name string
|
||||
description string
|
||||
endpoint map[string]string
|
||||
}
|
||||
|
||||
func longUpdateDescription() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString("Update a context\n\nDocker endpoint config:\n\n")
|
||||
@ -33,31 +42,45 @@ func longUpdateDescription() string {
|
||||
}
|
||||
|
||||
func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
opts := &UpdateOptions{}
|
||||
opts := updateOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "update [OPTIONS] CONTEXT",
|
||||
Short: "Update a context",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Name = args[0]
|
||||
return RunUpdate(dockerCLI, opts)
|
||||
opts.name = args[0]
|
||||
return runUpdate(dockerCLI, &opts)
|
||||
},
|
||||
Long: longUpdateDescription(),
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
|
||||
flags.StringVar(&opts.description, "description", "", "Description of the context")
|
||||
flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunUpdate updates a Docker context
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
if err := store.ValidateContextName(o.Name); err != nil {
|
||||
if o == nil {
|
||||
o = &UpdateOptions{}
|
||||
}
|
||||
return runUpdate(dockerCLI, &updateOptions{
|
||||
name: o.Name,
|
||||
description: o.Description,
|
||||
endpoint: o.Docker,
|
||||
})
|
||||
}
|
||||
|
||||
// runUpdate updates a Docker context.
|
||||
func runUpdate(dockerCLI command.Cli, opts *updateOptions) error {
|
||||
if err := store.ValidateContextName(opts.name); err != nil {
|
||||
return err
|
||||
}
|
||||
s := dockerCLI.ContextStore()
|
||||
c, err := s.GetMetadata(o.Name)
|
||||
c, err := s.GetMetadata(opts.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -65,16 +88,16 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Description != "" {
|
||||
dockerContext.Description = o.Description
|
||||
if opts.description != "" {
|
||||
dockerContext.Description = opts.description
|
||||
}
|
||||
|
||||
c.Metadata = dockerContext
|
||||
|
||||
tlsDataToReset := make(map[string]*store.EndpointTLSData)
|
||||
|
||||
if o.Docker != nil {
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, o.Docker)
|
||||
if opts.endpoint != nil {
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, opts.endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create docker endpoint config: %w", err)
|
||||
}
|
||||
@ -88,13 +111,13 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
return err
|
||||
}
|
||||
for ep, tlsData := range tlsDataToReset {
|
||||
if err := s.ResetEndpointTLSMaterial(o.Name, ep, tlsData); err != nil {
|
||||
if err := s.ResetEndpointTLSMaterial(opts.name, ep, tlsData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), o.Name)
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", o.Name)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.name)
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", opts.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -11,16 +11,16 @@ import (
|
||||
|
||||
func TestUpdateDescriptionOnly(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
cli.OutBuffer().Reset()
|
||||
cli.ErrBuffer().Reset()
|
||||
assert.NilError(t, RunUpdate(cli, &UpdateOptions{
|
||||
Name: "test",
|
||||
Description: "description",
|
||||
assert.NilError(t, runUpdate(cli, &updateOptions{
|
||||
name: "test",
|
||||
description: "description",
|
||||
}))
|
||||
c, err := cli.ContextStore().GetMetadata("test")
|
||||
assert.NilError(t, err)
|
||||
@ -35,9 +35,9 @@ func TestUpdateDescriptionOnly(t *testing.T) {
|
||||
func TestUpdateDockerOnly(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
createTestContext(t, cli, "test", nil)
|
||||
assert.NilError(t, RunUpdate(cli, &UpdateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{
|
||||
assert.NilError(t, runUpdate(cli, &updateOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{
|
||||
keyHost: "tcp://some-host",
|
||||
},
|
||||
}))
|
||||
@ -52,14 +52,14 @@ func TestUpdateDockerOnly(t *testing.T) {
|
||||
|
||||
func TestUpdateInvalidDockerHost(t *testing.T) {
|
||||
cli := makeFakeCli(t)
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
err = RunUpdate(cli, &UpdateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{
|
||||
err = runUpdate(cli, &updateOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{
|
||||
keyHost: "some///invalid/host",
|
||||
},
|
||||
})
|
||||
|
||||
@ -17,7 +17,7 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
return RunUse(dockerCLI, name)
|
||||
return runUse(dockerCLI, name)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
|
||||
}
|
||||
@ -25,7 +25,14 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// RunUse set the current Docker context
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func RunUse(dockerCLI command.Cli, name string) error {
|
||||
return runUse(dockerCLI, name)
|
||||
}
|
||||
|
||||
// runUse set the current Docker context
|
||||
func runUse(dockerCLI command.Cli, name string) error {
|
||||
// configValue uses an empty string for "default"
|
||||
var configValue string
|
||||
if name != command.DefaultContextName {
|
||||
|
||||
@ -23,9 +23,9 @@ func TestUse(t *testing.T) {
|
||||
configFilePath := filepath.Join(configDir, "config.json")
|
||||
testCfg := configfile.New(configFilePath)
|
||||
cli := makeFakeCli(t, withCliConfig(testCfg))
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.NilError(t, newUseCommand(cli).RunE(nil, []string{"test"}))
|
||||
@ -89,9 +89,9 @@ func TestUseHostOverride(t *testing.T) {
|
||||
configFilePath := filepath.Join(configDir, "config.json")
|
||||
testCfg := configfile.New(configFilePath)
|
||||
cli := makeFakeCli(t, withCliConfig(testCfg))
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
@ -136,9 +136,9 @@ func TestUseHostOverrideEmpty(t *testing.T) {
|
||||
assert.NilError(t, cli.Initialize(flags.NewClientOptions()))
|
||||
}
|
||||
loadCli()
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: "test",
|
||||
Docker: map[string]string{"host": socketPath},
|
||||
err := runCreate(cli, &createOptions{
|
||||
name: "test",
|
||||
endpoint: map[string]string{"host": socketPath},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
||||
@ -52,7 +52,14 @@ type EndpointDefaultResolver interface {
|
||||
}
|
||||
|
||||
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||
//
|
||||
// Deprecated: this function is exported for testing and meant for internal use. It will be removed in the next release.
|
||||
func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
||||
return resolveDefaultContext(opts, config)
|
||||
}
|
||||
|
||||
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
||||
contextTLSData := store.ContextTLSData{
|
||||
Endpoints: make(map[string]store.EndpointTLSData),
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func TestDefaultContextInitializer(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
t.Setenv("DOCKER_HOST", "ssh://someswarmserver")
|
||||
cli.configFile = &configfile.ConfigFile{}
|
||||
ctx, err := ResolveDefaultContext(&cliflags.ClientOptions{
|
||||
ctx, err := resolveDefaultContext(&cliflags.ClientOptions{
|
||||
TLS: true,
|
||||
TLSOptions: &tlsconfig.Options{
|
||||
CAFile: "./testdata/ca.pem",
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
// based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan
|
||||
|
||||
//nolint:gocyclo,nakedret,unused // ignore linting errors, so that we can stick close to upstream
|
||||
//nolint:gocyclo,gofumpt,nakedret,unused // ignore linting errors, so that we can stick close to upstream
|
||||
package tabwriter
|
||||
|
||||
import (
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
@ -152,7 +151,7 @@ func newBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"})
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
|
||||
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||
flags.SetAnnotation("platform", "version", []string{"1.38"})
|
||||
@ -185,7 +184,6 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
||||
//nolint:gocyclo
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) error {
|
||||
var (
|
||||
err error
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
contextDir string
|
||||
@ -267,7 +265,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
}
|
||||
|
||||
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||
return errors.Wrap(err, "error checking context")
|
||||
return errors.Wrap(err, "checking context")
|
||||
}
|
||||
|
||||
// And canonicalize dockerfile name to a platform-independent one
|
||||
@ -341,11 +339,19 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
configFile := dockerCli.ConfigFile()
|
||||
creds, _ := configFile.GetAllCredentials()
|
||||
authConfigs := make(map[string]registrytypes.AuthConfig, len(creds))
|
||||
for k, auth := range creds {
|
||||
authConfigs[k] = registrytypes.AuthConfig(auth)
|
||||
for k, authConfig := range creds {
|
||||
authConfigs[k] = registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}
|
||||
}
|
||||
buildOpts := imageBuildOptions(dockerCli, options)
|
||||
buildOpts.Version = buildtypes.BuilderV1
|
||||
buildOpts.Dockerfile = relDockerfile
|
||||
buildOpts.AuthConfigs = authConfigs
|
||||
buildOpts.RemoteContext = remote
|
||||
@ -355,7 +361,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
if options.quiet {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
|
||||
}
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
@ -372,7 +377,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
|
||||
err = jsonstream.Display(ctx, response.Body, streams.NewOut(buildBuff), jsonstream.WithAuxCallback(aux))
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonstream.JSONError); ok {
|
||||
var jerr *jsonstream.JSONError
|
||||
if errors.As(err, &jerr) {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
@ -385,16 +391,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
// Windows: show error message about modified file permissions if the
|
||||
// daemon isn't running Windows.
|
||||
if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
|
||||
"image from Windows against a non-Windows Docker host. All files and "+
|
||||
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
||||
"It is recommended to double check and reset permissions for sensitive "+
|
||||
"files and directories.")
|
||||
}
|
||||
|
||||
// Everything worked so if -q was provided the output from the daemon
|
||||
// should be just the image ID and we'll print that to stdout.
|
||||
if options.quiet {
|
||||
@ -546,6 +542,7 @@ func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.Rea
|
||||
func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions {
|
||||
configFile := dockerCli.ConfigFile()
|
||||
return buildtypes.ImageBuildOptions{
|
||||
Version: buildtypes.BuilderV1,
|
||||
Memory: options.memory.Value(),
|
||||
MemorySwap: options.memorySwap.Value(),
|
||||
Tags: options.tags.GetSlice(),
|
||||
|
||||
@ -25,9 +25,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
//
|
||||
// Deprecated: this const is no longer used and will be removed in the next release.
|
||||
const DefaultDockerfileName string = "Dockerfile"
|
||||
|
||||
const (
|
||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
DefaultDockerfileName string = "Dockerfile"
|
||||
// defaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
defaultDockerfileName string = "Dockerfile"
|
||||
// archiveHeaderSize is the number of bytes in an archive header
|
||||
archiveHeaderSize = 512
|
||||
)
|
||||
@ -80,7 +85,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
|
||||
if err != nil && os.IsPermission(err) {
|
||||
return errors.Errorf("no permission to read from '%s'", filePath)
|
||||
}
|
||||
currentFile.Close()
|
||||
_ = currentFile.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -97,10 +102,21 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool,
|
||||
|
||||
// DetectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, isArchive is set to true, and to false
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
|
||||
return detectArchiveReader(input)
|
||||
}
|
||||
|
||||
// detectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
|
||||
buf := bufio.NewReader(input)
|
||||
|
||||
magic, err := buf.Peek(archiveHeaderSize * 2)
|
||||
@ -108,13 +124,22 @@ func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool,
|
||||
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
|
||||
}
|
||||
|
||||
return newReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
|
||||
return newReadCloserWrapper(buf, func() error { return input.Close() }), isArchive(magic), nil
|
||||
}
|
||||
|
||||
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by DefaultDockerfileName and returns the path to the
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
return writeTempDockerfile(rc)
|
||||
}
|
||||
|
||||
// writeTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
func writeTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
// err is a named return value, due to the defer call below.
|
||||
dockerfileDir, err = os.MkdirTemp("", "docker-build-tempdockerfile-")
|
||||
if err != nil {
|
||||
@ -126,7 +151,7 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
|
||||
f, err := os.Create(filepath.Join(dockerfileDir, defaultDockerfileName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -141,12 +166,12 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
// Dockerfile or tar archive. Returns a tar archive used as a context and a
|
||||
// path to the Dockerfile inside the tar.
|
||||
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
|
||||
rc, isArchive, err := DetectArchiveReader(rc)
|
||||
rc, ok, err := detectArchiveReader(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if isArchive {
|
||||
if ok {
|
||||
return rc, dockerfileName, nil
|
||||
}
|
||||
|
||||
@ -159,7 +184,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
|
||||
return nil, "", errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
|
||||
}
|
||||
|
||||
dockerfileDir, err := WriteTempDockerfile(rc)
|
||||
dockerfileDir, err := writeTempDockerfile(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -171,14 +196,22 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
|
||||
|
||||
return newReadCloserWrapper(tarArchive, func() error {
|
||||
err := tarArchive.Close()
|
||||
os.RemoveAll(dockerfileDir)
|
||||
_ = os.RemoveAll(dockerfileDir)
|
||||
return err
|
||||
}), DefaultDockerfileName, nil
|
||||
}), defaultDockerfileName, nil
|
||||
}
|
||||
|
||||
// IsArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next release.
|
||||
func IsArchive(header []byte) bool {
|
||||
return isArchive(header)
|
||||
}
|
||||
|
||||
// isArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
func isArchive(header []byte) bool {
|
||||
if compression.Detect(header) != compression.None {
|
||||
return true
|
||||
}
|
||||
@ -201,7 +234,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
|
||||
return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory")
|
||||
}
|
||||
|
||||
absContextDir, err = ResolveAndValidateContextPath(absContextDir)
|
||||
absContextDir, err = resolveAndValidateContextPath(absContextDir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -242,7 +275,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
|
||||
}
|
||||
msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%s: error reading body", msg)
|
||||
}
|
||||
@ -254,7 +287,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
|
||||
// the relative path of the dockerfile in that context directory, and a non-nil
|
||||
// error on success.
|
||||
func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) {
|
||||
localDir, err := ResolveAndValidateContextPath(localDir)
|
||||
localDir, err := resolveAndValidateContextPath(localDir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -274,7 +307,18 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, er
|
||||
|
||||
// ResolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next
|
||||
// release. Use [DetectContextType] to detect the context-type, and use
|
||||
// [GetContextFromLocalDir], [GetContextFromLocalDir], [GetContextFromGitURL],
|
||||
// or [GetContextFromURL] instead.
|
||||
func ResolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
return resolveAndValidateContextPath(givenContextDir)
|
||||
}
|
||||
|
||||
// resolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
func resolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
absContextDir, err := filepath.Abs(givenContextDir)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
|
||||
@ -318,12 +362,12 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error)
|
||||
if absDockerfile == "" {
|
||||
// No -f/--file was specified so use the default relative to the
|
||||
// context directory.
|
||||
absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
|
||||
absDockerfile = filepath.Join(absContextDir, defaultDockerfileName)
|
||||
|
||||
// Just to be nice ;-) look for 'dockerfile' too but only
|
||||
// use it if we found it, otherwise ignore this check
|
||||
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
|
||||
altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
|
||||
altPath := filepath.Join(absContextDir, strings.ToLower(defaultDockerfileName))
|
||||
if _, err = os.Lstat(altPath); err == nil {
|
||||
absDockerfile = altPath
|
||||
}
|
||||
@ -374,7 +418,7 @@ func isUNC(path string) bool {
|
||||
// the relative path to the dockerfile in the context.
|
||||
func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) {
|
||||
file, err := io.ReadAll(dockerfileCtx)
|
||||
dockerfileCtx.Close()
|
||||
_ = dockerfileCtx.Close()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -438,17 +482,19 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
|
||||
go func() {
|
||||
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
|
||||
if err != nil {
|
||||
pipeWriter.CloseWithError(err)
|
||||
_ = pipeWriter.CloseWithError(err)
|
||||
}
|
||||
defer buildCtx.Close()
|
||||
defer func() {
|
||||
_ = buildCtx.Close()
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(compressWriter, buildCtx); err != nil {
|
||||
pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
|
||||
compressWriter.Close()
|
||||
_ = pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
|
||||
_ = compressWriter.Close()
|
||||
return
|
||||
}
|
||||
compressWriter.Close()
|
||||
pipeWriter.Close()
|
||||
_ = compressWriter.Close()
|
||||
_ = pipeWriter.Close()
|
||||
}()
|
||||
|
||||
return pipeReader, nil
|
||||
|
||||
@ -31,7 +31,7 @@ func prepareNoFiles(t *testing.T) string {
|
||||
func prepareOneFile(t *testing.T) string {
|
||||
t.Helper()
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
return contextDir
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
|
||||
|
||||
func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
chdir(t, contextDir)
|
||||
|
||||
@ -74,23 +74,23 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromLocalDirLocalFile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
testFilename := createTestTempFile(t, contextDir, "tmpTest", "test")
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "")
|
||||
@ -112,13 +112,13 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
chdir(t, contextDir)
|
||||
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, defaultDockerfileName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromReaderString(t *testing.T) {
|
||||
@ -135,7 +135,7 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
buff.ReadFrom(tarReader)
|
||||
_, _ = buff.ReadFrom(tarReader)
|
||||
contents := buff.String()
|
||||
|
||||
_, err = tarReader.Next()
|
||||
@ -150,8 +150,8 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
|
||||
}
|
||||
|
||||
if relDockerfile != DefaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
|
||||
if relDockerfile != defaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,12 +164,12 @@ func TestGetContextFromReaderStringConflict(t *testing.T) {
|
||||
|
||||
func TestGetContextFromReaderTar(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
tarStream, err := archive.Tar(contextDir, compression.None)
|
||||
assert.NilError(t, err)
|
||||
|
||||
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
|
||||
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, defaultDockerfileName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
tarReader := tar.NewReader(tarArchive)
|
||||
@ -177,12 +177,12 @@ func TestGetContextFromReaderTar(t *testing.T) {
|
||||
header, err := tarReader.Next()
|
||||
assert.NilError(t, err)
|
||||
|
||||
if header.Name != DefaultDockerfileName {
|
||||
t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
|
||||
if header.Name != defaultDockerfileName {
|
||||
t.Fatalf("Dockerfile name should be: %s, got: %s", defaultDockerfileName, header.Name)
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
buff.ReadFrom(tarReader)
|
||||
_, _ = buff.ReadFrom(tarReader)
|
||||
contents := buff.String()
|
||||
|
||||
_, err = tarReader.Next()
|
||||
@ -197,8 +197,8 @@ func TestGetContextFromReaderTar(t *testing.T) {
|
||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
|
||||
}
|
||||
|
||||
if relDockerfile != DefaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
|
||||
if relDockerfile != defaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ func TestValidateContextDirectoryWithOneFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) {
|
||||
testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName})
|
||||
testValidateContextDirectory(t, prepareOneFile, []string{defaultDockerfileName})
|
||||
}
|
||||
|
||||
// createTestTempDir creates a temporary directory for testing. It returns the
|
||||
@ -263,7 +263,7 @@ func chdir(t *testing.T, dir string) {
|
||||
}
|
||||
|
||||
func TestIsArchive(t *testing.T) {
|
||||
testcases := []struct {
|
||||
tests := []struct {
|
||||
doc string
|
||||
header []byte
|
||||
expected bool
|
||||
@ -289,13 +289,15 @@ func TestIsArchive(t *testing.T) {
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Check(t, is.Equal(testcase.expected, IsArchive(testcase.header)), testcase.doc)
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
assert.Check(t, is.Equal(tc.expected, isArchive(tc.header)), tc.doc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectArchiveReader(t *testing.T) {
|
||||
testcases := []struct {
|
||||
tests := []struct {
|
||||
file string
|
||||
desc string
|
||||
expected bool
|
||||
@ -316,14 +318,18 @@ func TestDetectArchiveReader(t *testing.T) {
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
content, err := os.Open(testcase.file)
|
||||
assert.NilError(t, err)
|
||||
defer content.Close()
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
content, err := os.Open(tc.file)
|
||||
assert.NilError(t, err)
|
||||
defer func() {
|
||||
_ = content.Close()
|
||||
}()
|
||||
|
||||
_, isArchive, err := DetectArchiveReader(content)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(testcase.expected, isArchive), testcase.file)
|
||||
_, isArchive, err := detectArchiveReader(content)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(tc.expected, isArchive), tc.file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,9 @@ func ReadDockerignore(contextDir string) ([]string, error) {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
patterns, err := ignorefile.ReadAll(f)
|
||||
if err != nil {
|
||||
|
||||
@ -42,7 +42,7 @@ func newLoadCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Annotations: map[string]string{
|
||||
"aliases": "docker image load, docker load",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
@ -49,7 +48,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -2,15 +2,14 @@ package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -46,7 +45,9 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
"category-top": "5",
|
||||
"aliases": "docker image pull, docker pull",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
// Complete with local images to help pulling the latest version
|
||||
// of images that are in the image cache.
|
||||
ValidArgsFunction: completion.ImageNames(dockerCLI, 1),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -55,7 +56,7 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
|
||||
addPlatformFlag(flags, &opts.platform)
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
|
||||
@ -85,15 +86,13 @@ func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error
|
||||
// Check if reference has a digest
|
||||
_, isCanonical := distributionRef.(reference.Canonical)
|
||||
if !opts.untrusted && !isCanonical {
|
||||
err = trustedPull(ctx, dockerCLI, imgRefAndAuth, opts)
|
||||
} else {
|
||||
err = imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "when fetching 'plugin'") {
|
||||
return errors.New(err.Error() + " - Use `docker plugin install`")
|
||||
if err := trustedPull(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), imgRefAndAuth.Reference().String())
|
||||
return nil
|
||||
|
||||
@ -118,11 +118,12 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newPullCommand(cli)
|
||||
cmd.SetOut(io.Discard)
|
||||
|
||||
@ -15,12 +15,11 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/docker/docker/api/types/auxprogress"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/morikuni/aec"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@ -64,7 +63,7 @@ func newPushCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image signing")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
|
||||
|
||||
// Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to
|
||||
// pushing the image as-is. This also avoids forcing the platform selection
|
||||
@ -99,9 +98,11 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.all && !reference.IsNameOnly(ref):
|
||||
return errors.New("tag can't be used with --all-tags/-a")
|
||||
case !opts.all && reference.IsNameOnly(ref):
|
||||
@ -111,43 +112,37 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
indexInfo := registry.NewIndexInfo(ref)
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo)
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), ref.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := image.PushOptions{
|
||||
|
||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), image.PushOptions{
|
||||
All: opts.all,
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: nil,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = responseBody.Close()
|
||||
for _, note := range notes {
|
||||
out.PrintNote(note)
|
||||
}
|
||||
}()
|
||||
|
||||
defer responseBody.Close()
|
||||
if !opts.untrusted {
|
||||
// TODO pushTrustedReference currently doesn't respect `--quiet`
|
||||
return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody)
|
||||
return pushTrustedReference(ctx, dockerCli, ref, responseBody)
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux()))
|
||||
if err == nil {
|
||||
fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@ -42,12 +43,18 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (
|
||||
}
|
||||
|
||||
// pushTrustedReference pushes a canonical reference to the trust server.
|
||||
func pushTrustedReference(ctx context.Context, ioStreams command.Streams, indexInfo *registrytypes.IndexInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
|
||||
func pushTrustedReference(ctx context.Context, dockerCLI command.Cli, ref reference.Named, responseBody io.Reader) error {
|
||||
// Resolve the Repository name from fqn to RepositoryInfo, and create an
|
||||
// IndexInfo. Docker Content Trust uses the IndexInfo.Official field to
|
||||
// select the right domain for Docker Hub's Notary server;
|
||||
// https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79
|
||||
indexInfo := registry.NewIndexInfo(ref)
|
||||
repoInfo := &trust.RepositoryInfo{
|
||||
Name: reference.TrimNamed(ref),
|
||||
Index: indexInfo,
|
||||
}
|
||||
return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent())
|
||||
authConfig := command.ResolveAuthConfig(dockerCLI.ConfigFile(), indexInfo)
|
||||
return trust.PushTrustedReference(ctx, dockerCLI, repoInfo, ref, authConfig, responseBody, command.UserAgent())
|
||||
}
|
||||
|
||||
// trustedPull handles content trust pulling of an image
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
@ -54,6 +55,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
|
||||
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
|
||||
return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index)
|
||||
}
|
||||
// FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI.
|
||||
return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure)
|
||||
}
|
||||
|
||||
@ -96,7 +98,7 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
|
||||
manifestStore := newManifestStore(dockerCLI)
|
||||
imageManifest, err := manifestStore.Get(targetRef, imgRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/manifest/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -44,7 +44,7 @@ func createManifestList(ctx context.Context, dockerCLI command.Cli, args []strin
|
||||
manifestStore := newManifestStore(dockerCLI)
|
||||
_, err = manifestStore.GetList(targetRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
// New manifest list
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
@ -3,9 +3,9 @@ package manifest
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/manifest/store"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
)
|
||||
|
||||
@ -72,7 +72,7 @@ func normalizeReference(ref string) (reference.Named, error) {
|
||||
func getManifest(ctx context.Context, dockerCLI command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
|
||||
data, err := newManifestStore(dockerCLI).Get(listRef, namedRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
return newRegistryClient(dockerCLI, insecure).GetManifest(ctx, namedRef)
|
||||
case err != nil:
|
||||
return types.ImageManifest{}, err
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
@ -69,7 +68,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
return runCreate(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -33,7 +32,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||
@ -45,7 +44,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -54,7 +53,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/moby/go-archive"
|
||||
"github.com/moby/go-archive/compression"
|
||||
@ -76,7 +75,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options.context = args[1]
|
||||
return runCreate(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -3,12 +3,12 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
@ -29,9 +29,9 @@ type pluginOptions struct {
|
||||
untrusted bool
|
||||
}
|
||||
|
||||
func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) {
|
||||
func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
}
|
||||
|
||||
func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
||||
@ -50,7 +50,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
loadPullFlags(dockerCli, &options, flags)
|
||||
loadPullFlags(&options, flags)
|
||||
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
|
||||
flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
|
||||
return cmd
|
||||
@ -120,9 +120,6 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
|
||||
}
|
||||
responseBody, err := dockerCLI.Client().PluginInstall(ctx, localName, options)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "(image) when fetching") {
|
||||
return errors.New(err.Error() + " - Use \"docker image pull\"")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@ -43,14 +43,6 @@ func TestInstallErrors(t *testing.T) {
|
||||
return nil, errors.New("error installing plugin")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "installation error due to missing image",
|
||||
args: []string{"foo"},
|
||||
expectedError: "docker image pull",
|
||||
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.New("(image) when fetching")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -94,11 +86,12 @@ func TestInstallContentTrustErrors(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.New("should not try to install plugin")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newInstallCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -32,7 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -33,7 +33,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
@ -31,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
loadPullFlags(dockerCli, &options, flags)
|
||||
loadPullFlags(&options, flags)
|
||||
flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image")
|
||||
return cmd
|
||||
}
|
||||
@ -80,9 +79,6 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
|
||||
|
||||
responseBody, err := dockerCLI.Client().PluginUpgrade(ctx, opts.localName, options)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "target is image") {
|
||||
return errors.New(err.Error() + " - Use `docker image pull`")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@ -77,7 +77,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf
|
||||
}
|
||||
|
||||
a, _ := cfg.GetAuthConfig(configKey)
|
||||
return registrytypes.AuthConfig(a)
|
||||
return registrytypes.AuthConfig{
|
||||
Username: a.Username,
|
||||
Password: a.Password,
|
||||
ServerAddress: a.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: a.Auth,
|
||||
IdentityToken: a.IdentityToken,
|
||||
RegistryToken: a.RegistryToken,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultAuthConfig gets the default auth config given a serverAddress
|
||||
@ -86,19 +95,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
||||
if !isDefaultRegistry {
|
||||
serverAddress = credentials.ConvertToHostname(serverAddress)
|
||||
}
|
||||
authconfig := configtypes.AuthConfig{}
|
||||
authCfg := configtypes.AuthConfig{}
|
||||
var err error
|
||||
if checkCredStore {
|
||||
authconfig, err = cfg.GetAuthConfig(serverAddress)
|
||||
authCfg, err = cfg.GetAuthConfig(serverAddress)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{
|
||||
ServerAddress: serverAddress,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
authconfig.ServerAddress = serverAddress
|
||||
authconfig.IdentityToken = ""
|
||||
return registrytypes.AuthConfig(authconfig), nil
|
||||
|
||||
return registrytypes.AuthConfig{
|
||||
Username: authCfg.Username,
|
||||
Password: authCfg.Password,
|
||||
ServerAddress: serverAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authCfg.Auth,
|
||||
IdentityToken: "",
|
||||
RegistryToken: authCfg.RegistryToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PromptUserForCredentials handles the CLI prompt for the user to input
|
||||
@ -213,7 +230,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
return "", err
|
||||
}
|
||||
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
configtypes "github.com/docker/cli/cli/config/types"
|
||||
"github.com/docker/cli/internal/oauth/manager"
|
||||
@ -59,7 +58,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Annotations: map[string]string{
|
||||
"category-top": "8",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -185,10 +184,15 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth
|
||||
return response.Status, err
|
||||
}
|
||||
|
||||
// OauthLoginEscapeHatchEnvVar disables the browser-based OAuth login workflow.
|
||||
//
|
||||
// Deprecated: this const was only used internally and will be removed in the next release.
|
||||
const OauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN"
|
||||
|
||||
const oauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN"
|
||||
|
||||
func isOauthLoginDisabled() bool {
|
||||
if v := os.Getenv(OauthLoginEscapeHatchEnvVar); v != "" {
|
||||
if v := os.Getenv(oauthLoginEscapeHatchEnvVar); v != "" {
|
||||
enabled, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false
|
||||
@ -255,12 +259,30 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
|
||||
return "", err
|
||||
}
|
||||
|
||||
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig(*authConfig))
|
||||
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil {
|
||||
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -269,7 +291,16 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
|
||||
|
||||
func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthConfig) error {
|
||||
creds := cfg.GetCredentialsStore(authConfig.ServerAddress)
|
||||
if err := creds.Store(configtypes.AuthConfig(authConfig)); err != nil {
|
||||
if err := creds.Store(configtypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}); err != nil {
|
||||
return errors.Errorf("Error saving credentials: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@ -533,7 +533,7 @@ func TestIsOauthLoginDisabled(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Setenv(OauthLoginEscapeHatchEnvVar, tc.envVar)
|
||||
t.Setenv(oauthLoginEscapeHatchEnvVar, tc.envVar)
|
||||
|
||||
disabled := isOauthLoginDisabled()
|
||||
|
||||
|
||||
@ -102,7 +102,16 @@ func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err e
|
||||
// "no credentials found"). We'll get an error when search failed,
|
||||
// so fine to ignore in most situations.
|
||||
authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey)
|
||||
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
||||
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
}
|
||||
|
||||
// splitReposSearchTerm breaks a search term into an index name and remote name
|
||||
|
||||
@ -58,8 +58,17 @@ func TestGetDefaultAuthConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
cfg := configfile.New("filename")
|
||||
for _, authconfig := range testAuthConfigs {
|
||||
assert.Check(t, cfg.GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig)))
|
||||
for _, authConfig := range testAuthConfigs {
|
||||
assert.Check(t, cfg.GetCredentialsStore(authConfig.ServerAddress).Store(configtypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}))
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
serverAddress := tc.inputServerAddress
|
||||
|
||||
@ -29,7 +29,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
}
|
||||
return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), opts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.mode, flagMode, "replicated", `Service mode ("replicated", "global", "replicated-job", "global-job")`)
|
||||
@ -94,7 +94,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
@ -52,7 +51,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -33,7 +32,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCLI, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -45,7 +44,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/internal/logdetails"
|
||||
@ -73,7 +72,7 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/cli/command/node"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
@ -52,7 +51,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/spf13/cobra"
|
||||
@ -35,7 +34,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error {
|
||||
if !dockerCli.ContentTrustEnabled() {
|
||||
if !trust.Enabled() {
|
||||
// When not using content trust, digest resolution happens later when
|
||||
// contacting the registry to retrieve image information.
|
||||
return nil
|
||||
|
||||
@ -137,7 +137,7 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
|
||||
})
|
||||
|
||||
return cmd
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/stack/loader"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
composeLoader "github.com/docker/cli/cli/compose/loader"
|
||||
@ -36,7 +35,7 @@ func newConfigCommand(dockerCli command.Cli) *cobra.Command {
|
||||
_, err = fmt.Fprintf(dockerCli.Out(), "%s", cfg)
|
||||
return err
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/stack/formatter"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/command/stack/swarm"
|
||||
@ -29,7 +28,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCli, opts)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/swarm/progress"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@ -40,7 +39,7 @@ func newCACommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.30",
|
||||
"swarm": "manager",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -44,7 +43,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.24",
|
||||
"swarm": "", // swarm init does not require swarm to be active, and is always available on API 1.24 and up
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -28,7 +27,7 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.24",
|
||||
"swarm": "active",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
@ -29,7 +28,7 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.24",
|
||||
"swarm": "manager",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -32,7 +31,7 @@ func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.24",
|
||||
"swarm": "manager",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -33,7 +32,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
"version": "1.24",
|
||||
"swarm": "manager",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.autolock, flagAutolock, false, "Change manager autolocking setting (true|false)")
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -29,7 +28,7 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return runDiskUsage(cmd.Context(), dockerCli, opts)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -23,7 +22,7 @@ func newDialStdioCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDialStdio(cmd.Context(), dockerCli)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
@ -48,7 +47,7 @@ func newEventsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Annotations: map[string]string{
|
||||
"aliases": "docker system events, docker events",
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user