mirror of https://github.com/docker/cli.git
Compare commits
1 Commits
266a0027ed
...
95500effc3
Author | SHA1 | Date |
---|---|---|
Nate | 95500effc3 |
|
@ -62,7 +62,7 @@ jobs:
|
||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.2
|
go-version: 1.22.8
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
|
|
|
@ -68,7 +68,7 @@ jobs:
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.2
|
go-version: 1.22.8
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- copyloopvar # Detects places where loop variables are copied.
|
|
||||||
- depguard
|
- depguard
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupword # Detects duplicate words.
|
- dupword # Detects duplicate words.
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
|
- exportloopref # Detects pointers to enclosing loop variables.
|
||||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gofumpt # Detects whether code was gofumpt-ed.
|
- gofumpt # Detects whether code was gofumpt-ed.
|
||||||
|
@ -41,9 +41,6 @@ linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
|
||||||
run:
|
run:
|
||||||
# prevent golangci-lint from deducting the go version to lint for through go.mod,
|
|
||||||
# which causes it to fallback to go1.17 semantics.
|
|
||||||
go: "1.23.2"
|
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
@ -55,13 +52,6 @@ linters-settings:
|
||||||
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 16
|
min-complexity: 16
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
|
|
||||||
- G113 # G113: Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772); (only affects go < 1.16.14. and go < 1.17.7)
|
|
||||||
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/docker/cli/issues/5584)
|
|
||||||
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
|
|
||||||
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
|
|
||||||
govet:
|
govet:
|
||||||
enable:
|
enable:
|
||||||
- shadow
|
- shadow
|
||||||
|
@ -97,10 +87,6 @@ issues:
|
||||||
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
|
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
|
||||||
# This option has been defined when Go modules was not existed and when the
|
|
||||||
# golangci-lint core was different, this is not something we still recommend.
|
|
||||||
exclude-dirs-use-default: false
|
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
- parameter .* always receives
|
- parameter .* always receives
|
||||||
|
|
||||||
|
@ -118,9 +104,6 @@ issues:
|
||||||
#
|
#
|
||||||
# These exclusion patterns are copied from the default excluses at:
|
# These exclusion patterns are copied from the default excluses at:
|
||||||
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
|
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
|
||||||
#
|
|
||||||
# The default list of exclusions can be found at:
|
|
||||||
# https://golangci-lint.run/usage/false-positives/#default-exclusions
|
|
||||||
|
|
||||||
# EXC0001
|
# EXC0001
|
||||||
- text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
|
- text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
|
||||||
|
@ -138,6 +121,11 @@ issues:
|
||||||
- text: "Subprocess launch(ed with variable|ing should be audited)"
|
- text: "Subprocess launch(ed with variable|ing should be audited)"
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
|
# EXC0008
|
||||||
|
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
|
||||||
|
- text: "G307"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
# EXC0009
|
# EXC0009
|
||||||
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
|
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
|
||||||
linters:
|
linters:
|
||||||
|
@ -147,6 +135,26 @@ issues:
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
|
|
||||||
|
# G113 Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772)
|
||||||
|
# only affects gp < 1.16.14. and go < 1.17.7
|
||||||
|
- text: "G113"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# TODO: G104: Errors unhandled. (gosec)
|
||||||
|
- text: "G104"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# Looks like the match in "EXC0007" above doesn't catch this one
|
||||||
|
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||||
|
- text: "G204: Subprocess launched with a potential tainted input or cmd arguments"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# Looks like the match in "EXC0009" above doesn't catch this one
|
||||||
|
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||||
|
- text: "G306: Expect WriteFile permissions to be 0600 or less"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
|
||||||
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
|
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
|
||||||
- text: "package-comments: should have a package comment"
|
- text: "package-comments: should have a package comment"
|
||||||
linters:
|
linters:
|
||||||
|
|
|
@ -66,7 +66,7 @@ anybody starts working on it.
|
||||||
We are always thrilled to receive pull requests. We do our best to process them
|
We are always thrilled to receive pull requests. We do our best to process them
|
||||||
quickly. If your pull request is not accepted on the first try,
|
quickly. If your pull request is not accepted on the first try,
|
||||||
don't get discouraged! Our contributor's guide explains [the review process we
|
don't get discouraged! Our contributor's guide explains [the review process we
|
||||||
use for simple changes](https://github.com/docker/docker/blob/master/project/REVIEWING.md).
|
use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
|
||||||
|
|
||||||
### Talking to other Docker users and contributors
|
### Talking to other Docker users and contributors
|
||||||
|
|
||||||
|
@ -124,8 +124,8 @@ submitting a pull request.
|
||||||
Update the documentation when creating or modifying features. Test your
|
Update the documentation when creating or modifying features. Test your
|
||||||
documentation changes for clarity, concision, and correctness, as well as a
|
documentation changes for clarity, concision, and correctness, as well as a
|
||||||
clean documentation build. See our contributors guide for [our style
|
clean documentation build. See our contributors guide for [our style
|
||||||
guide](https://docs.docker.com/contribute/style/grammar/) and instructions on [building
|
guide](https://docs.docker.com/opensource/doc-style) and instructions on [building
|
||||||
the documentation](https://docs.docker.com/contribute/).
|
the documentation](https://docs.docker.com/opensource/project/test-and-docs/#build-and-test-the-documentation).
|
||||||
|
|
||||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||||
|
|
|
@ -4,7 +4,7 @@ ARG BASE_VARIANT=alpine
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.22.8
|
||||||
ARG XX_VERSION=1.5.0
|
ARG XX_VERSION=1.5.0
|
||||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||||
ARG GOTESTSUM_VERSION=v1.10.0
|
ARG GOTESTSUM_VERSION=v1.10.0
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# Docker CLI
|
# Docker CLI
|
||||||
|
|
||||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/docker/cli)](https://pkg.go.dev/github.com/docker/cli)
|
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/cli)
|
||||||
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/cli/build.yml?branch=master&label=build&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Abuild)
|
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/cli/build.yml?branch=master&label=build&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Abuild)
|
||||||
[![Test Status](https://img.shields.io/github/actions/workflow/status/docker/cli/test.yml?branch=master&label=test&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Atest)
|
[![Test Status](https://img.shields.io/github/actions/workflow/status/docker/cli/test.yml?branch=master&label=test&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Atest)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/cli)](https://goreportcard.com/report/github.com/docker/cli)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/cli)](https://goreportcard.com/report/github.com/docker/cli)
|
||||||
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/docker/cli/badge)](https://scorecard.dev/viewer/?uri=github.com/docker/cli)
|
|
||||||
[![Codecov](https://img.shields.io/codecov/c/github/docker/cli?logo=codecov)](https://codecov.io/gh/docker/cli)
|
[![Codecov](https://img.shields.io/codecov/c/github/docker/cli?logo=codecov)](https://codecov.io/gh/docker/cli)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
|
@ -17,5 +17,5 @@ func (c *candidate) Path() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *candidate) Metadata() ([]byte, error) {
|
func (c *candidate) Metadata() ([]byte, error) {
|
||||||
return exec.Command(c.path, MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
return exec.Command(c.path, MetadataSubcommandName).Output()
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
|
p := p
|
||||||
vendor := p.Vendor
|
vendor := p.Vendor
|
||||||
if vendor == "" {
|
if vendor == "" {
|
||||||
vendor = "unknown"
|
vendor = "unknown"
|
||||||
|
|
|
@ -240,8 +240,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
|
||||||
// TODO: why are we not returning plugin.Err?
|
// TODO: why are we not returning plugin.Err?
|
||||||
return nil, errPluginNotFound(name)
|
return nil, errPluginNotFound(name)
|
||||||
}
|
}
|
||||||
cmd := exec.Command(plugin.Path, args...) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
cmd := exec.Command(plugin.Path, args...)
|
||||||
|
|
||||||
// Using dockerCli.{In,Out,Err}() here results in a hang until something is input.
|
// Using dockerCli.{In,Out,Err}() here results in a hang until something is input.
|
||||||
// See: - https://github.com/golang/go/issues/10338
|
// See: - https://github.com/golang/go/issues/10338
|
||||||
// - https://github.com/golang/go/commit/d000e8742a173aa0659584aa01b7ba2834ba28ab
|
// - https://github.com/golang/go/commit/d000e8742a173aa0659584aa01b7ba2834ba28ab
|
||||||
|
|
|
@ -112,7 +112,7 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
|
||||||
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
||||||
}
|
}
|
||||||
|
|
||||||
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes))
|
||||||
pCmd.Env = os.Environ()
|
pCmd.Env = os.Environ()
|
||||||
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
|
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||||
hookCmdOutput, err := pCmd.Output()
|
hookCmdOutput, err := pCmd.Output()
|
||||||
|
|
|
@ -187,18 +187,19 @@ func TestInitializeFromClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, testcase := range testcases {
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
testcase := testcase
|
||||||
|
t.Run(testcase.doc, func(t *testing.T) {
|
||||||
apiclient := &fakeClient{
|
apiclient := &fakeClient{
|
||||||
pingFunc: tc.pingFunc,
|
pingFunc: testcase.pingFunc,
|
||||||
version: defaultVersion,
|
version: defaultVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := &DockerCli{client: apiclient}
|
cli := &DockerCli{client: apiclient}
|
||||||
err := cli.Initialize(flags.NewClientOptions())
|
err := cli.Initialize(flags.NewClientOptions())
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
|
assert.DeepEqual(t, cli.ServerInfo(), testcase.expectedServer)
|
||||||
assert.Equal(t, apiclient.negotiated, tc.negotiated)
|
assert.Equal(t, apiclient.negotiated, testcase.negotiated)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,9 +277,10 @@ func TestExperimentalCLI(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, testcase := range testcases {
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
testcase := testcase
|
||||||
dir := fs.NewDir(t, tc.doc, fs.WithFile("config.json", tc.configfile))
|
t.Run(testcase.doc, func(t *testing.T) {
|
||||||
|
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||||
defer dir.Remove()
|
defer dir.Remove()
|
||||||
apiclient := &fakeClient{
|
apiclient := &fakeClient{
|
||||||
version: defaultVersion,
|
version: defaultVersion,
|
||||||
|
|
|
@ -59,7 +59,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
|
||||||
for _, ctr := range list {
|
for _, ctr := range list {
|
||||||
skip := false
|
skip := false
|
||||||
for _, fn := range filters {
|
for _, fn := range filters {
|
||||||
if fn != nil && !fn(ctr) {
|
if !fn(ctr) {
|
||||||
skip = true
|
skip = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,346 +1,15 @@
|
||||||
package completion
|
package completion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeCLI struct {
|
|
||||||
*fakeClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client implements [APIClientProvider].
|
|
||||||
func (c fakeCLI) Client() client.APIClient {
|
|
||||||
return c.fakeClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeClient struct {
|
|
||||||
client.Client
|
|
||||||
containerListFunc func(options container.ListOptions) ([]container.Summary, error)
|
|
||||||
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
|
|
||||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
|
||||||
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]container.Summary, error) {
|
|
||||||
if c.containerListFunc != nil {
|
|
||||||
return c.containerListFunc(options)
|
|
||||||
}
|
|
||||||
return []container.Summary{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
|
|
||||||
if c.imageListFunc != nil {
|
|
||||||
return c.imageListFunc(options)
|
|
||||||
}
|
|
||||||
return []image.Summary{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
|
||||||
if c.networkListFunc != nil {
|
|
||||||
return c.networkListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return []network.Inspect{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
||||||
if c.volumeListFunc != nil {
|
|
||||||
return c.volumeListFunc(options.Filters)
|
|
||||||
}
|
|
||||||
return volume.ListResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteContainerNames(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
doc string
|
|
||||||
showAll, showIDs bool
|
|
||||||
filters []func(container.Summary) bool
|
|
||||||
containers []container.Summary
|
|
||||||
expOut []string
|
|
||||||
expOpts container.ListOptions
|
|
||||||
expDirective cobra.ShellCompDirective
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
doc: "no results",
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "all containers",
|
|
||||||
showAll: true,
|
|
||||||
containers: []container.Summary{
|
|
||||||
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
||||||
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
||||||
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
|
|
||||||
expOpts: container.ListOptions{All: true},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "all containers with ids",
|
|
||||||
showAll: true,
|
|
||||||
showIDs: true,
|
|
||||||
containers: []container.Summary{
|
|
||||||
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
||||||
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
||||||
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
|
|
||||||
expOpts: container.ListOptions{All: true},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "only running containers",
|
|
||||||
showAll: false,
|
|
||||||
containers: []container.Summary{
|
|
||||||
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"container-c", "container-c/link-b"},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with filter",
|
|
||||||
showAll: true,
|
|
||||||
filters: []func(container.Summary) bool{
|
|
||||||
func(container container.Summary) bool { return container.State == "created" },
|
|
||||||
},
|
|
||||||
containers: []container.Summary{
|
|
||||||
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
||||||
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
||||||
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"container-b"},
|
|
||||||
expOpts: container.ListOptions{All: true},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "multiple filters",
|
|
||||||
showAll: true,
|
|
||||||
filters: []func(container.Summary) bool{
|
|
||||||
func(container container.Summary) bool { return container.ID == "id-a" },
|
|
||||||
func(container container.Summary) bool { return container.State == "created" },
|
|
||||||
},
|
|
||||||
containers: []container.Summary{
|
|
||||||
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
||||||
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
||||||
{ID: "id-a", State: "created", Names: []string{"/container-a"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"container-a"},
|
|
||||||
expOpts: container.ListOptions{All: true},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with error",
|
|
||||||
expDirective: cobra.ShellCompDirectiveError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
|
||||||
if tc.showIDs {
|
|
||||||
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
|
|
||||||
}
|
|
||||||
comp := ContainerNames(fakeCLI{&fakeClient{
|
|
||||||
containerListFunc: func(opts container.ListOptions) ([]container.Summary, error) {
|
|
||||||
assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{})))
|
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
||||||
return nil, errors.New("some error occurred")
|
|
||||||
}
|
|
||||||
return tc.containers, nil
|
|
||||||
},
|
|
||||||
}}, tc.showAll, tc.filters...)
|
|
||||||
|
|
||||||
containers, directives := comp(&cobra.Command{}, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
||||||
assert.Check(t, is.DeepEqual(containers, tc.expOut))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteEnvVarNames(t *testing.T) {
|
|
||||||
env.PatchAll(t, map[string]string{
|
|
||||||
"ENV_A": "hello-a",
|
|
||||||
"ENV_B": "hello-b",
|
|
||||||
})
|
|
||||||
values, directives := EnvVarNames(nil, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
|
||||||
|
|
||||||
sort.Strings(values)
|
|
||||||
expected := []string{"ENV_A", "ENV_B"}
|
|
||||||
assert.Check(t, is.DeepEqual(values, expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteFileNames(t *testing.T) {
|
|
||||||
values, directives := FileNames(nil, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault))
|
|
||||||
assert.Check(t, is.Len(values, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteFromList(t *testing.T) {
|
|
||||||
expected := []string{"one", "two", "three"}
|
|
||||||
|
|
||||||
values, directives := FromList(expected...)(nil, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
|
||||||
assert.Check(t, is.DeepEqual(values, expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteImageNames(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
doc string
|
|
||||||
images []image.Summary
|
|
||||||
expOut []string
|
|
||||||
expDirective cobra.ShellCompDirective
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
doc: "no results",
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with results",
|
|
||||||
images: []image.Summary{
|
|
||||||
{RepoTags: []string{"image-c:latest", "image-c:other"}},
|
|
||||||
{RepoTags: []string{"image-b:latest", "image-b:other"}},
|
|
||||||
{RepoTags: []string{"image-a:latest", "image-a:other"}},
|
|
||||||
},
|
|
||||||
expOut: []string{"image-c:latest", "image-c:other", "image-b:latest", "image-b:other", "image-a:latest", "image-a:other"},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with error",
|
|
||||||
expDirective: cobra.ShellCompDirectiveError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
|
||||||
comp := ImageNames(fakeCLI{&fakeClient{
|
|
||||||
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
|
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
||||||
return nil, errors.New("some error occurred")
|
|
||||||
}
|
|
||||||
return tc.images, nil
|
|
||||||
},
|
|
||||||
}})
|
|
||||||
|
|
||||||
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
||||||
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteNetworkNames(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
doc string
|
|
||||||
networks []network.Summary
|
|
||||||
expOut []string
|
|
||||||
expDirective cobra.ShellCompDirective
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
doc: "no results",
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with results",
|
|
||||||
networks: []network.Summary{
|
|
||||||
{ID: "nw-c", Name: "network-c"},
|
|
||||||
{ID: "nw-b", Name: "network-b"},
|
|
||||||
{ID: "nw-a", Name: "network-a"},
|
|
||||||
},
|
|
||||||
expOut: []string{"network-c", "network-b", "network-a"},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with error",
|
|
||||||
expDirective: cobra.ShellCompDirectiveError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
|
||||||
comp := NetworkNames(fakeCLI{&fakeClient{
|
|
||||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
||||||
return nil, errors.New("some error occurred")
|
|
||||||
}
|
|
||||||
return tc.networks, nil
|
|
||||||
},
|
|
||||||
}})
|
|
||||||
|
|
||||||
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
||||||
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompleteNoComplete(t *testing.T) {
|
|
||||||
values, directives := NoComplete(nil, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
|
||||||
assert.Check(t, is.Len(values, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompletePlatforms(t *testing.T) {
|
func TestCompletePlatforms(t *testing.T) {
|
||||||
values, directives := Platforms(nil, nil, "")
|
values, directives := Platforms(nil, nil, "")
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||||
assert.Check(t, is.DeepEqual(values, commonPlatforms))
|
assert.Check(t, is.DeepEqual(values, commonPlatforms))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompleteVolumeNames(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
doc string
|
|
||||||
volumes []*volume.Volume
|
|
||||||
expOut []string
|
|
||||||
expDirective cobra.ShellCompDirective
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
doc: "no results",
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with results",
|
|
||||||
volumes: []*volume.Volume{
|
|
||||||
{Name: "volume-c"},
|
|
||||||
{Name: "volume-b"},
|
|
||||||
{Name: "volume-a"},
|
|
||||||
},
|
|
||||||
expOut: []string{"volume-c", "volume-b", "volume-a"},
|
|
||||||
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc: "with error",
|
|
||||||
expDirective: cobra.ShellCompDirectiveError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
|
||||||
comp := VolumeNames(fakeCLI{&fakeClient{
|
|
||||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
||||||
return volume.ListResponse{}, errors.New("some error occurred")
|
|
||||||
}
|
|
||||||
return volume.ListResponse{Volumes: tc.volumes}, nil
|
|
||||||
},
|
|
||||||
}})
|
|
||||||
|
|
||||||
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
||||||
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
||||||
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ func TestConfigCreateErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.expectedError, func(t *testing.T) {
|
t.Run(tc.expectedError, func(t *testing.T) {
|
||||||
cmd := newConfigCreateCommand(
|
cmd := newConfigCreateCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
|
|
@ -61,6 +61,7 @@ id_rsa
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -73,6 +73,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
|
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
|
|
@ -178,6 +178,7 @@ func TestSplitCpArg(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
if tc.os == "windows" && runtime.GOOS != "windows" {
|
if tc.os == "windows" && runtime.GOOS != "windows" {
|
||||||
t.Skip("skipping windows test on non-windows platform")
|
t.Skip("skipping windows test on non-windows platform")
|
||||||
|
|
|
@ -113,6 +113,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||||
pullCounter := 0
|
pullCounter := 0
|
||||||
|
|
||||||
|
@ -175,6 +176,7 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||||
dockerCli := test.NewFakeCli(&fakeClient{})
|
dockerCli := test.NewFakeCli(&fakeClient{})
|
||||||
err := runCreate(
|
err := runCreate(
|
||||||
|
@ -205,6 +207,7 @@ func TestCreateContainerValidateFlags(t *testing.T) {
|
||||||
expectedErr: `invalid argument "STDINFO" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
|
expectedErr: `invalid argument "STDINFO" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
|
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
@ -248,6 +251,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||||
createContainerFunc: func(config *container.Config,
|
createContainerFunc: func(config *container.Config,
|
||||||
hostConfig *container.HostConfig,
|
hostConfig *container.HostConfig,
|
||||||
|
@ -308,6 +312,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||||
createContainerFunc: func(config *container.Config,
|
createContainerFunc: func(config *container.Config,
|
||||||
|
|
|
@ -47,6 +47,7 @@ D: /usr/app/old_app.js
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
tc.context.Output = out
|
tc.context.Output = out
|
||||||
|
|
|
@ -178,6 +178,7 @@ container2 -- --
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
@ -222,6 +223,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
|
err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
@ -263,6 +265,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
|
err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -127,6 +127,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
|
||||||
|
|
||||||
func TestContainerListErrors(t *testing.T) {
|
func TestContainerListErrors(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
args []string
|
||||||
flags map[string]string
|
flags map[string]string
|
||||||
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
||||||
expectedError string
|
expectedError string
|
||||||
|
@ -156,10 +157,10 @@ func TestContainerListErrors(t *testing.T) {
|
||||||
containerListFunc: tc.containerListFunc,
|
containerListFunc: tc.containerListFunc,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
for key, value := range tc.flags {
|
for key, value := range tc.flags {
|
||||||
assert.Check(t, cmd.Flags().Set(key, value))
|
assert.Check(t, cmd.Flags().Set(key, value))
|
||||||
}
|
}
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
@ -179,9 +180,6 @@ func TestContainerListWithoutFormat(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format.golden")
|
||||||
}
|
}
|
||||||
|
@ -196,9 +194,6 @@ func TestContainerListNoTrunc(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("no-trunc", "true"))
|
assert.Check(t, cmd.Flags().Set("no-trunc", "true"))
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden")
|
||||||
|
@ -215,9 +210,6 @@ func TestContainerListNamesMultipleTime(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}"))
|
assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}"))
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden")
|
||||||
|
@ -234,9 +226,6 @@ func TestContainerListFormatTemplateWithArg(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`))
|
assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`))
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden")
|
||||||
|
@ -277,6 +266,7 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
containerListFunc: func(options container.ListOptions) ([]container.Summary, error) {
|
containerListFunc: func(options container.ListOptions) ([]container.Summary, error) {
|
||||||
|
@ -285,9 +275,6 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("format", tc.format))
|
assert.Check(t, cmd.Flags().Set("format", tc.format))
|
||||||
if tc.sizeFlag != "" {
|
if tc.sizeFlag != "" {
|
||||||
assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag))
|
assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag))
|
||||||
|
@ -310,9 +297,6 @@ func TestContainerListWithConfigFormat(t *testing.T) {
|
||||||
PsFormat: "{{ .Names }} {{ .Image }} {{ .Labels }} {{ .Size}}",
|
PsFormat: "{{ .Names }} {{ .Image }} {{ .Labels }} {{ .Size}}",
|
||||||
})
|
})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-with-config-format.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-with-config-format.golden")
|
||||||
}
|
}
|
||||||
|
@ -330,9 +314,6 @@ func TestContainerListWithFormat(t *testing.T) {
|
||||||
t.Run("with format", func(t *testing.T) {
|
t.Run("with format", func(t *testing.T) {
|
||||||
cli.OutBuffer().Reset()
|
cli.OutBuffer().Reset()
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}"))
|
assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}"))
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-with-format.golden")
|
golden.Assert(t, cli.OutBuffer().String(), "container-list-with-format.golden")
|
||||||
|
@ -341,9 +322,6 @@ func TestContainerListWithFormat(t *testing.T) {
|
||||||
t.Run("with format and quiet", func(t *testing.T) {
|
t.Run("with format and quiet", func(t *testing.T) {
|
||||||
cli.OutBuffer().Reset()
|
cli.OutBuffer().Reset()
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
|
||||||
cmd.SetErr(io.Discard)
|
|
||||||
assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}"))
|
assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}"))
|
||||||
assert.Check(t, cmd.Flags().Set("quiet", "true"))
|
assert.Check(t, cmd.Flags().Set("quiet", "true"))
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||||
)
|
)
|
||||||
|
@ -363,6 +364,10 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mounts := copts.mounts.Value()
|
||||||
|
if len(mounts) > 0 && copts.volumeDriver != "" {
|
||||||
|
logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
|
||||||
|
}
|
||||||
var binds []string
|
var binds []string
|
||||||
volumes := copts.volumes.GetMap()
|
volumes := copts.volumes.GetMap()
|
||||||
// add any bind targets to the list of container volumes
|
// add any bind targets to the list of container volumes
|
||||||
|
@ -692,7 +697,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
Tmpfs: tmpfs,
|
Tmpfs: tmpfs,
|
||||||
Sysctls: copts.sysctls.GetAll(),
|
Sysctls: copts.sysctls.GetAll(),
|
||||||
Runtime: copts.runtime,
|
Runtime: copts.runtime,
|
||||||
Mounts: copts.mounts.Value(),
|
Mounts: mounts,
|
||||||
MaskedPaths: maskedPaths,
|
MaskedPaths: maskedPaths,
|
||||||
ReadonlyPaths: readonlyPaths,
|
ReadonlyPaths: readonlyPaths,
|
||||||
Annotations: copts.annotations.GetAll(),
|
Annotations: copts.annotations.GetAll(),
|
||||||
|
@ -762,6 +767,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, n := range copts.netMode.Value() {
|
for i, n := range copts.netMode.Value() {
|
||||||
|
n := n
|
||||||
if container.NetworkMode(n.Target).IsUserDefined() {
|
if container.NetworkMode(n.Target).IsUserDefined() {
|
||||||
hasUserDefined = true
|
hasUserDefined = true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -126,6 +126,7 @@ func TestParseRunAttach(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.input, func(t *testing.T) {
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
config, _, _ := mustParse(t, tc.input)
|
config, _, _ := mustParse(t, tc.input)
|
||||||
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
|
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
|
||||||
|
@ -801,6 +802,7 @@ func TestParseRestartPolicy(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.input, func(t *testing.T) {
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
|
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
|
|
|
@ -43,6 +43,7 @@ func TestNewPortCommandOutput(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
inspectFunc: func(string) (container.InspectResponse, error) {
|
inspectFunc: func(string) (container.InspectResponse, error) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ func TestContainerPrunePromptTermination(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewPruneCommand(cli)
|
cmd := NewPruneCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
test.TerminatePrompt(ctx, t, cmd, cli)
|
test.TerminatePrompt(ctx, t, cmd, cli)
|
||||||
|
|
|
@ -58,6 +58,7 @@ func TestRestart(t *testing.T) {
|
||||||
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var restarted []string
|
var restarted []string
|
||||||
mutex := new(sync.Mutex)
|
mutex := new(sync.Mutex)
|
||||||
|
|
|
@ -38,9 +38,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container rm, docker container remove, docker rm",
|
"aliases": "docker container rm, docker container remove, docker rm",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(ctr container.Summary) bool {
|
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
||||||
return opts.force || ctr.State == "exited" || ctr.State == "created"
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -23,6 +23,7 @@ func TestRemoveForce(t *testing.T) {
|
||||||
{name: "without force", args: []string{"nosuchcontainer", "mycontainer"}, expectedErr: "no such container"},
|
{name: "without force", args: []string{"nosuchcontainer", "mycontainer"}, expectedErr: "no such container"},
|
||||||
{name: "with force", args: []string{"--force", "nosuchcontainer", "mycontainer"}, expectedErr: ""},
|
{name: "with force", args: []string{"--force", "nosuchcontainer", "mycontainer"}, expectedErr: ""},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var removed []string
|
var removed []string
|
||||||
mutex := new(sync.Mutex)
|
mutex := new(sync.Mutex)
|
||||||
|
|
|
@ -35,6 +35,7 @@ func TestRunValidateFlags(t *testing.T) {
|
||||||
expectedErr: "conflicting options: cannot specify both --attach and --detach",
|
expectedErr: "conflicting options: cannot specify both --attach and --detach",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewRunCommand(test.NewFakeCli(&fakeClient{}))
|
cmd := NewRunCommand(test.NewFakeCli(&fakeClient{}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
@ -244,6 +245,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||||
createContainerFunc: func(config *container.Config,
|
createContainerFunc: func(config *container.Config,
|
||||||
|
@ -284,6 +286,7 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||||
dockerCli := test.NewFakeCli(&fakeClient{})
|
dockerCli := test.NewFakeCli(&fakeClient{})
|
||||||
err := runRun(
|
err := runRun(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -265,40 +264,31 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||||
// so we unlikely hit this code in practice.
|
// so we unlikely hit this code in practice.
|
||||||
daemonOSType = dockerCLI.ServerInfo().OSType
|
daemonOSType = dockerCLI.ServerInfo().OSType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer to store formatted stats text.
|
|
||||||
// Once formatted, it will be printed in one write to avoid screen flickering.
|
|
||||||
var statsTextBuffer bytes.Buffer
|
|
||||||
|
|
||||||
statsCtx := formatter.Context{
|
statsCtx := formatter.Context{
|
||||||
Output: &statsTextBuffer,
|
Output: dockerCLI.Out(),
|
||||||
Format: NewStatsFormat(format, daemonOSType),
|
Format: NewStatsFormat(format, daemonOSType),
|
||||||
}
|
}
|
||||||
|
cleanScreen := func() {
|
||||||
|
if !options.NoStream {
|
||||||
|
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[2J")
|
||||||
|
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[H")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ticker := time.NewTicker(500 * time.Millisecond)
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
cleanScreen()
|
||||||
var ccStats []StatsEntry
|
var ccStats []StatsEntry
|
||||||
cStats.mu.RLock()
|
cStats.mu.RLock()
|
||||||
for _, c := range cStats.cs {
|
for _, c := range cStats.cs {
|
||||||
ccStats = append(ccStats, c.GetStatistics())
|
ccStats = append(ccStats, c.GetStatistics())
|
||||||
}
|
}
|
||||||
cStats.mu.RUnlock()
|
cStats.mu.RUnlock()
|
||||||
|
|
||||||
if !options.NoStream {
|
|
||||||
// Start by clearing the screen and moving the cursor to the top-left
|
|
||||||
_, _ = fmt.Fprint(&statsTextBuffer, "\033[2J\033[H")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
|
if err = statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprint(dockerCLI.Out(), statsTextBuffer.String())
|
|
||||||
|
|
||||||
statsTextBuffer.Reset()
|
|
||||||
|
|
||||||
if len(cStats.cs) == 0 && !showAll {
|
if len(cStats.cs) == 0 && !showAll {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ func TestStop(t *testing.T) {
|
||||||
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var stopped []string
|
var stopped []string
|
||||||
mutex := new(sync.Mutex)
|
mutex := new(sync.Mutex)
|
||||||
|
|
|
@ -38,7 +38,7 @@ func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitExitOrRemoved(t *testing.T) {
|
func TestWaitExitOrRemoved(t *testing.T) {
|
||||||
tests := []struct {
|
testcases := []struct {
|
||||||
cid string
|
cid string
|
||||||
exitCode int
|
exitCode int
|
||||||
}{
|
}{
|
||||||
|
@ -61,11 +61,9 @@ func TestWaitExitOrRemoved(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &fakeClient{waitFunc: waitFn, Version: api.DefaultVersion}
|
client := &fakeClient{waitFunc: waitFn, Version: api.DefaultVersion}
|
||||||
for _, tc := range tests {
|
for _, testcase := range testcases {
|
||||||
t.Run(tc.cid, func(t *testing.T) {
|
statusC := waitExitOrRemoved(context.Background(), client, testcase.cid, true)
|
||||||
statusC := waitExitOrRemoved(context.Background(), client, tc.cid, true)
|
|
||||||
exitCode := <-statusC
|
exitCode := <-statusC
|
||||||
assert.Check(t, is.Equal(tc.exitCode, exitCode))
|
assert.Check(t, is.Equal(testcase.exitCode, exitCode))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ func TestCreate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.options.Name, func(t *testing.T) {
|
t.Run(tc.options.Name, func(t *testing.T) {
|
||||||
err := RunCreate(cli, &tc.options)
|
err := RunCreate(cli, &tc.options)
|
||||||
if tc.expecterErr == "" {
|
if tc.expecterErr == "" {
|
||||||
|
@ -163,24 +164,25 @@ func TestCreateFromContext(t *testing.T) {
|
||||||
|
|
||||||
cli.SetCurrentContext("dummy")
|
cli.SetCurrentContext("dummy")
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, c := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
c := c
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
err := RunCreate(cli, &CreateOptions{
|
err := RunCreate(cli, &CreateOptions{
|
||||||
From: "original",
|
From: "original",
|
||||||
Name: tc.name,
|
Name: c.name,
|
||||||
Description: tc.description,
|
Description: c.description,
|
||||||
Docker: tc.docker,
|
Docker: c.docker,
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assertContextCreateLogging(t, cli, tc.name)
|
assertContextCreateLogging(t, cli, c.name)
|
||||||
newContext, err := cli.ContextStore().GetMetadata(tc.name)
|
newContext, err := cli.ContextStore().GetMetadata(c.name)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
newContextTyped, err := command.GetDockerContext(newContext)
|
newContextTyped, err := command.GetDockerContext(newContext)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, newContextTyped.Description, tc.expectedDescription)
|
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
|
||||||
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -217,22 +219,23 @@ func TestCreateFromCurrent(t *testing.T) {
|
||||||
|
|
||||||
cli.SetCurrentContext("original")
|
cli.SetCurrentContext("original")
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, c := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
c := c
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
err := RunCreate(cli, &CreateOptions{
|
err := RunCreate(cli, &CreateOptions{
|
||||||
Name: tc.name,
|
Name: c.name,
|
||||||
Description: tc.description,
|
Description: c.description,
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assertContextCreateLogging(t, cli, tc.name)
|
assertContextCreateLogging(t, cli, c.name)
|
||||||
newContext, err := cli.ContextStore().GetMetadata(tc.name)
|
newContext, err := cli.ContextStore().GetMetadata(c.name)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
newContextTyped, err := command.GetDockerContext(newContext)
|
newContextTyped, err := command.GetDockerContext(newContext)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, newContextTyped.Description, tc.expectedDescription)
|
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
|
||||||
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,7 @@ size: 0B
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
@ -410,6 +411,7 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
err := ContainerWrite(tc.context, containers)
|
err := ContainerWrite(tc.context, containers)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -106,6 +106,7 @@ Build Cache 0 0 0B 0B
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -309,6 +309,7 @@ image_id: imageID3
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
@ -369,6 +370,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
err := ImageWrite(tc.context, images)
|
err := ImageWrite(tc.context, images)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -131,6 +131,7 @@ foobar_bar
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -255,6 +255,7 @@ imageID6 17 years ago /bin/bash echo 183MB
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
err := HistoryWrite(tc.context, true, histories)
|
err := HistoryWrite(tc.context, true, histories)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -3,20 +3,17 @@ package image
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/containerd/platforms"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
flagsHelper "github.com/docker/cli/cli/flags"
|
flagsHelper "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type historyOptions struct {
|
type historyOptions struct {
|
||||||
image string
|
image string
|
||||||
platform string
|
|
||||||
|
|
||||||
human bool
|
human bool
|
||||||
quiet bool
|
quiet bool
|
||||||
|
@ -48,24 +45,12 @@ func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show image IDs")
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show image IDs")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.StringVar(&opts.format, "format", "", flagsHelper.FormatHelp)
|
flags.StringVar(&opts.format, "format", "", flagsHelper.FormatHelp)
|
||||||
flags.StringVar(&opts.platform, "platform", "", `Show history for the given platform. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
|
|
||||||
_ = flags.SetAnnotation("platform", "version", []string{"1.48"})
|
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) error {
|
func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) error {
|
||||||
var options image.HistoryOptions
|
history, err := dockerCli.Client().ImageHistory(ctx, opts.image, image.HistoryOptions{})
|
||||||
if opts.platform != "" {
|
|
||||||
p, err := platforms.Parse(opts.platform)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "invalid platform")
|
|
||||||
}
|
|
||||||
options.Platform = &p
|
|
||||||
}
|
|
||||||
|
|
||||||
history, err := dockerCli.Client().ImageHistory(ctx, opts.image, options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,13 +33,9 @@ func TestNewHistoryCommandErrors(t *testing.T) {
|
||||||
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
|
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "invalid platform",
|
|
||||||
args: []string{"--platform", "<invalid>", "arg1"},
|
|
||||||
expectedError: `invalid platform`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
|
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
@ -95,19 +89,9 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "platform",
|
|
||||||
args: []string{"--platform", "linux/amd64", "image:tag"},
|
|
||||||
imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) {
|
|
||||||
assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform))
|
|
||||||
return []image.HistoryResponseItem{{
|
|
||||||
ID: "1234567890123456789",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
}}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Set to UTC timezone as timestamps in output are
|
// Set to UTC timezone as timestamps in output are
|
||||||
// printed in the current timezone
|
// printed in the current timezone
|
||||||
|
|
|
@ -98,6 +98,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}))
|
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
|
|
@ -25,6 +25,7 @@ func TestNewInspectCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}))
|
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
@ -78,6 +79,7 @@ func TestNewInspectCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
imageInspectInvocationCount = 0
|
imageInspectInvocationCount = 0
|
||||||
cli := test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc})
|
cli := test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc})
|
||||||
|
|
|
@ -35,6 +35,7 @@ func TestNewImagesCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
|
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
@ -82,6 +83,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
|
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
|
||||||
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
|
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/containerd/platforms"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
|
@ -18,7 +17,6 @@ import (
|
||||||
type loadOptions struct {
|
type loadOptions struct {
|
||||||
input string
|
input string
|
||||||
quiet bool
|
quiet bool
|
||||||
platform string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLoadCommand creates a new `docker load` command
|
// NewLoadCommand creates a new `docker load` command
|
||||||
|
@ -42,10 +40,7 @@ func NewLoadCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN")
|
flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN")
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output")
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output")
|
||||||
flags.StringVar(&opts.platform, "platform", "", `Load only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
|
|
||||||
_ = flags.SetAnnotation("platform", "version", []string{"1.48"})
|
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,20 +63,12 @@ func runLoad(ctx context.Context, dockerCli command.Cli, opts loadOptions) error
|
||||||
return errors.Errorf("requested load from stdin, but stdin is empty")
|
return errors.Errorf("requested load from stdin, but stdin is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
var options image.LoadOptions
|
var loadOpts image.LoadOptions
|
||||||
if opts.quiet || !dockerCli.Out().IsTerminal() {
|
if opts.quiet || !dockerCli.Out().IsTerminal() {
|
||||||
options.Quiet = true
|
loadOpts.Quiet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.platform != "" {
|
response, err := dockerCli.Client().ImageLoad(ctx, input, loadOpts)
|
||||||
p, err := platforms.Parse(opts.platform)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "invalid platform")
|
|
||||||
}
|
|
||||||
options.Platform = &p
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := dockerCli.Client().ImageLoad(ctx, input, options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,28 +28,19 @@ func TestNewLoadCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "input-to-terminal",
|
name: "input-to-terminal",
|
||||||
args: []string{},
|
|
||||||
isTerminalIn: true,
|
isTerminalIn: true,
|
||||||
expectedError: "requested load from stdin, but stdin is empty",
|
expectedError: "requested load from stdin, but stdin is empty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pull-error",
|
name: "pull-error",
|
||||||
args: []string{},
|
|
||||||
expectedError: "something went wrong",
|
expectedError: "something went wrong",
|
||||||
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
||||||
return image.LoadResponse{}, errors.Errorf("something went wrong")
|
return image.LoadResponse{}, errors.Errorf("something went wrong")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "invalid platform",
|
|
||||||
args: []string{"--platform", "<invalid>"},
|
|
||||||
expectedError: `invalid platform`,
|
|
||||||
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
|
||||||
return image.LoadResponse{}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
||||||
cli.In().SetIsTerminal(tc.isTerminalIn)
|
cli.In().SetIsTerminal(tc.isTerminalIn)
|
||||||
|
@ -82,14 +71,12 @@ func TestNewLoadCommandSuccess(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple",
|
name: "simple",
|
||||||
args: []string{},
|
|
||||||
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
||||||
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "json",
|
name: "json",
|
||||||
args: []string{},
|
|
||||||
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
||||||
json := "{\"ID\": \"1\"}"
|
json := "{\"ID\": \"1\"}"
|
||||||
return image.LoadResponse{
|
return image.LoadResponse{
|
||||||
|
@ -105,16 +92,9 @@ func TestNewLoadCommandSuccess(t *testing.T) {
|
||||||
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "with platform",
|
|
||||||
args: []string{"--platform", "linux/amd64"},
|
|
||||||
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
|
|
||||||
assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform))
|
|
||||||
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
||||||
cmd := NewLoadCommand(cli)
|
cmd := NewLoadCommand(cli)
|
||||||
|
|
|
@ -39,6 +39,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
|
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
|
||||||
imagesPruneFunc: tc.imagesPruneFunc,
|
imagesPruneFunc: tc.imagesPruneFunc,
|
||||||
|
@ -97,6 +98,7 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imagesPruneFunc: tc.imagesPruneFunc})
|
cli := test.NewFakeCli(&fakeClient{imagesPruneFunc: tc.imagesPruneFunc})
|
||||||
// when prompted, answer "Y" to confirm the prune.
|
// when prompted, answer "Y" to confirm the prune.
|
||||||
|
|
|
@ -38,6 +38,7 @@ func TestNewPullCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{})
|
cli := test.NewFakeCli(&fakeClient{})
|
||||||
cmd := NewPullCommand(cli)
|
cmd := NewPullCommand(cli)
|
||||||
|
@ -72,6 +73,7 @@ func TestNewPullCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||||
|
@ -117,6 +119,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ func TestNewPushCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
|
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
|
||||||
cmd := NewPushCommand(cli)
|
cmd := NewPushCommand(cli)
|
||||||
|
@ -67,6 +68,7 @@ func TestNewPushCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
imagePushFunc: func(ref string, options image.PushOptions) (io.ReadCloser, error) {
|
imagePushFunc: func(ref string, options image.PushOptions) (io.ReadCloser, error) {
|
||||||
|
|
|
@ -62,6 +62,7 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||||
imageRemoveFunc: tc.imageRemoveFunc,
|
imageRemoveFunc: tc.imageRemoveFunc,
|
||||||
|
@ -120,6 +121,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||||
cmd := NewRemoveCommand(cli)
|
cmd := NewRemoveCommand(cli)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/containerd/platforms"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
|
@ -16,7 +15,6 @@ import (
|
||||||
type saveOptions struct {
|
type saveOptions struct {
|
||||||
images []string
|
images []string
|
||||||
output string
|
output string
|
||||||
platform string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSaveCommand creates a new `docker save` command
|
// NewSaveCommand creates a new `docker save` command
|
||||||
|
@ -40,10 +38,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
||||||
flags.StringVar(&opts.platform, "platform", "", `Save only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
|
|
||||||
_ = flags.SetAnnotation("platform", "version", []string{"1.48"})
|
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,16 +52,7 @@ func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error
|
||||||
return errors.Wrap(err, "failed to save image")
|
return errors.Wrap(err, "failed to save image")
|
||||||
}
|
}
|
||||||
|
|
||||||
var options image.SaveOptions
|
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, image.SaveOptions{})
|
||||||
if opts.platform != "" {
|
|
||||||
p, err := platforms.Parse(opts.platform)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "invalid platform")
|
|
||||||
}
|
|
||||||
options.Platform = &p
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
@ -52,13 +51,9 @@ func TestNewSaveCommandErrors(t *testing.T) {
|
||||||
args: []string{"-o", "/dev/null", "arg1"},
|
args: []string{"-o", "/dev/null", "arg1"},
|
||||||
expectedError: "failed to save image: invalid output path: \"/dev/null\" must be a directory or a regular file",
|
expectedError: "failed to save image: invalid output path: \"/dev/null\" must be a directory or a regular file",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "invalid platform",
|
|
||||||
args: []string{"--platform", "<invalid>", "arg1"},
|
|
||||||
expectedError: `invalid platform`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
|
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
|
||||||
cli.Out().SetIsTerminal(tc.isTerminal)
|
cli.Out().SetIsTerminal(tc.isTerminal)
|
||||||
|
@ -75,13 +70,13 @@ func TestNewSaveCommandSuccess(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
args []string
|
args []string
|
||||||
isTerminal bool
|
isTerminal bool
|
||||||
imageSaveFunc func(images []string, options image.SaveOptions) (io.ReadCloser, error)
|
imageSaveFunc func(images []string) (io.ReadCloser, error)
|
||||||
deferredFunc func()
|
deferredFunc func()
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
args: []string{"-o", "save_tmp_file", "arg1"},
|
args: []string{"-o", "save_tmp_file", "arg1"},
|
||||||
isTerminal: true,
|
isTerminal: true,
|
||||||
imageSaveFunc: func(images []string, _ image.SaveOptions) (io.ReadCloser, error) {
|
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
|
||||||
assert.Assert(t, is.Len(images, 1))
|
assert.Assert(t, is.Len(images, 1))
|
||||||
assert.Check(t, is.Equal("arg1", images[0]))
|
assert.Check(t, is.Equal("arg1", images[0]))
|
||||||
return io.NopCloser(strings.NewReader("")), nil
|
return io.NopCloser(strings.NewReader("")), nil
|
||||||
|
@ -93,28 +88,21 @@ func TestNewSaveCommandSuccess(t *testing.T) {
|
||||||
{
|
{
|
||||||
args: []string{"arg1", "arg2"},
|
args: []string{"arg1", "arg2"},
|
||||||
isTerminal: false,
|
isTerminal: false,
|
||||||
imageSaveFunc: func(images []string, _ image.SaveOptions) (io.ReadCloser, error) {
|
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
|
||||||
assert.Assert(t, is.Len(images, 2))
|
assert.Assert(t, is.Len(images, 2))
|
||||||
assert.Check(t, is.Equal("arg1", images[0]))
|
assert.Check(t, is.Equal("arg1", images[0]))
|
||||||
assert.Check(t, is.Equal("arg2", images[1]))
|
assert.Check(t, is.Equal("arg2", images[1]))
|
||||||
return io.NopCloser(strings.NewReader("")), nil
|
return io.NopCloser(strings.NewReader("")), nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
args: []string{"--platform", "linux/amd64", "arg1"},
|
|
||||||
isTerminal: false,
|
|
||||||
imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) {
|
|
||||||
assert.Assert(t, is.Len(images, 1))
|
|
||||||
assert.Check(t, is.Equal("arg1", images[0]))
|
|
||||||
assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform))
|
|
||||||
return io.NopCloser(strings.NewReader("")), nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
|
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
|
||||||
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
|
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
|
||||||
imageSaveFunc: tc.imageSaveFunc,
|
imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) {
|
||||||
|
return io.NopCloser(strings.NewReader("")), nil
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
IMAGE CREATED CREATED BY SIZE COMMENT
|
|
||||||
123456789012 Less than a second ago 0B
|
|
|
@ -1 +0,0 @@
|
||||||
Success
|
|
|
@ -56,6 +56,7 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
im := im
|
||||||
sub := subImage{
|
sub := subImage{
|
||||||
Platform: platforms.Format(im.ImageData.Platform),
|
Platform: platforms.Format(im.ImageData.Platform),
|
||||||
Available: im.Available,
|
Available: im.Available,
|
||||||
|
|
|
@ -31,6 +31,7 @@ func TestManifestCreateErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.expectedError, func(t *testing.T) {
|
t.Run(tc.expectedError, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(nil)
|
cli := test.NewFakeCli(nil)
|
||||||
cmd := newCreateListCommand(cli)
|
cmd := newCreateListCommand(cli)
|
||||||
|
|
|
@ -218,6 +218,7 @@ func TestNetworkCreateIPv6(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {
|
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {
|
||||||
|
|
|
@ -161,6 +161,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -83,6 +83,7 @@ func TestNetworkList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc})
|
cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
|
|
|
@ -63,6 +63,7 @@ func TestNetworkRemoveForce(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
fakeCli := test.NewFakeCli(&fakeClient{
|
fakeCli := test.NewFakeCli(&fakeClient{
|
||||||
networkRemoveFunc: func(ctx context.Context, networkID string) error {
|
networkRemoveFunc: func(ctx context.Context, networkID string) error {
|
||||||
|
|
|
@ -202,6 +202,7 @@ foobar_boo Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -106,6 +106,7 @@ func TestNodeInspectPretty(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
nodeInspectFunc: tc.nodeInspectFunc,
|
nodeInspectFunc: tc.nodeInspectFunc,
|
||||||
|
|
|
@ -134,6 +134,7 @@ func TestNodePs(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
infoFunc: tc.infoFunc,
|
infoFunc: tc.infoFunc,
|
||||||
|
|
|
@ -131,6 +131,7 @@ foobar_bar
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -66,6 +66,7 @@ func TestInspectErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
||||||
cmd := newInspectCommand(cli)
|
cmd := newInspectCommand(cli)
|
||||||
|
@ -137,6 +138,7 @@ func TestInspect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
||||||
cmd := newInspectCommand(cli)
|
cmd := newInspectCommand(cli)
|
||||||
|
|
|
@ -54,6 +54,7 @@ func TestInstallErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
||||||
cmd := newInstallCommand(cli)
|
cmd := newInstallCommand(cli)
|
||||||
|
@ -93,6 +94,7 @@ func TestInstallContentTrustErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||||
|
@ -136,6 +138,7 @@ func TestInstall(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
||||||
cmd := newInstallCommand(cli)
|
cmd := newInstallCommand(cli)
|
||||||
|
|
|
@ -46,6 +46,7 @@ func TestListErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
|
@ -165,6 +166,7 @@ func TestList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
||||||
cmd := newListCommand(cli)
|
cmd := newListCommand(cli)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
|
@ -37,13 +36,17 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRemove(ctx context.Context, dockerCli command.Cli, opts *rmOptions) error {
|
func runRemove(ctx context.Context, dockerCli command.Cli, opts *rmOptions) error {
|
||||||
var errs error
|
var errs cli.Errors
|
||||||
for _, name := range opts.plugins {
|
for _, name := range opts.plugins {
|
||||||
if err := dockerCli.Client().PluginRemove(ctx, name, types.PluginRemoveOptions{Force: opts.force}); err != nil {
|
if err := dockerCli.Client().PluginRemove(ctx, name, types.PluginRemoveOptions{Force: opts.force}); err != nil {
|
||||||
errs = errors.Join(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(dockerCli.Out(), name)
|
fmt.Fprintln(dockerCli.Out(), name)
|
||||||
}
|
}
|
||||||
|
// Do not simplify to `return errs` because even if errs == nil, it is not a nil-error interface value.
|
||||||
|
if errs != nil {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,24 +18,20 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const patSuggest = "You can log in with your password or a Personal Access " +
|
||||||
registerSuggest = "Log in with your Docker ID or email address to push and pull images from Docker Hub. " +
|
|
||||||
"If you don't have a Docker ID, head over to https://hub.docker.com/ to create one."
|
|
||||||
patSuggest = "You can log in with your password or a Personal Access " +
|
|
||||||
"Token (PAT). Using a limited-scope PAT grants better security and is required " +
|
"Token (PAT). Using a limited-scope PAT grants better security and is required " +
|
||||||
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
||||||
)
|
|
||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||||
// for the given command.
|
// for the given command.
|
||||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||||
return func(ctx context.Context) (string, error) {
|
return func(ctx context.Context) (string, error) {
|
||||||
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||||
indexServer := registry.GetAuthConfigKey(index)
|
indexServer := registry.GetAuthConfigKey(index)
|
||||||
isDefaultRegistry := indexServer == registry.IndexServer
|
isDefaultRegistry := indexServer == registry.IndexServer
|
||||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -90,8 +86,7 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureAuth handles prompting of user's username and password if needed.
|
// ConfigureAuth handles prompting of user's username and password if needed.
|
||||||
//
|
// Deprecated: use PromptUserForCredentials instead.
|
||||||
// Deprecated: use [PromptUserForCredentials] instead.
|
|
||||||
func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error {
|
func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error {
|
||||||
defaultUsername := authConfig.Username
|
defaultUsername := authConfig.Username
|
||||||
serverAddress := authConfig.ServerAddress
|
serverAddress := authConfig.ServerAddress
|
||||||
|
@ -115,7 +110,7 @@ func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, auth
|
||||||
// If defaultUsername is not empty, the username prompt includes that username
|
// If defaultUsername is not empty, the username prompt includes that username
|
||||||
// and the user can hit enter without inputting a username to use that default
|
// and the user can hit enter without inputting a username to use that default
|
||||||
// username.
|
// username.
|
||||||
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (registrytypes.AuthConfig, error) {
|
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (authConfig registrytypes.AuthConfig, err error) {
|
||||||
// On Windows, force the use of the regular OS stdin stream.
|
// On Windows, force the use of the regular OS stdin stream.
|
||||||
//
|
//
|
||||||
// See:
|
// See:
|
||||||
|
@ -128,71 +123,57 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||||
cli.SetIn(streams.NewIn(os.Stdin))
|
cli.SetIn(streams.NewIn(os.Stdin))
|
||||||
}
|
}
|
||||||
|
|
||||||
argUser = strings.TrimSpace(argUser)
|
isDefaultRegistry := serverAddress == registry.IndexServer
|
||||||
if argUser == "" {
|
defaultUsername = strings.TrimSpace(defaultUsername)
|
||||||
if serverAddress == registry.IndexServer {
|
|
||||||
// When signing in to the default (Docker Hub) registry, we display
|
if argUser = strings.TrimSpace(argUser); argUser == "" {
|
||||||
// hints for creating an account, and (if hints are enabled), using
|
if isDefaultRegistry {
|
||||||
// a token instead of a password.
|
// if this is a default registry (docker hub), then display the following message.
|
||||||
_, _ = fmt.Fprintln(cli.Out(), registerSuggest)
|
fmt.Fprintln(cli.Out(), "Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.")
|
||||||
if hints.Enabled() {
|
if hints.Enabled() {
|
||||||
_, _ = fmt.Fprintln(cli.Out(), patSuggest)
|
fmt.Fprintln(cli.Out(), patSuggest)
|
||||||
_, _ = fmt.Fprintln(cli.Out())
|
fmt.Fprintln(cli.Out())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var prompt string
|
var prompt string
|
||||||
defaultUsername = strings.TrimSpace(defaultUsername)
|
|
||||||
if defaultUsername == "" {
|
if defaultUsername == "" {
|
||||||
prompt = "Username: "
|
prompt = "Username: "
|
||||||
} else {
|
} else {
|
||||||
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
|
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
|
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.AuthConfig{}, err
|
return authConfig, err
|
||||||
}
|
}
|
||||||
if argUser == "" {
|
if argUser == "" {
|
||||||
argUser = defaultUsername
|
argUser = defaultUsername
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if argUser == "" {
|
if argUser == "" {
|
||||||
return registrytypes.AuthConfig{}, errors.Errorf("Error: Non-null Username Required")
|
return authConfig, errors.Errorf("Error: Non-null Username Required")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
argPassword = strings.TrimSpace(argPassword)
|
|
||||||
if argPassword == "" {
|
if argPassword == "" {
|
||||||
restoreInput, err := DisableInputEcho(cli.In())
|
restoreInput, err := DisableInputEcho(cli.In())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.AuthConfig{}, err
|
return authConfig, err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer restoreInput()
|
||||||
if err := restoreInput(); err != nil {
|
|
||||||
// TODO(thaJeztah): we should consider printing instructions how
|
|
||||||
// to restore this manually (other than restarting the shell).
|
|
||||||
// e.g., 'run stty echo' when in a Linux or macOS shell, but
|
|
||||||
// PowerShell and CMD.exe may need different instructions.
|
|
||||||
_, _ = fmt.Fprintln(cli.Err(), "Error: failed to restore terminal state to echo input:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.AuthConfig{}, err
|
return authConfig, err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(cli.Out())
|
fmt.Fprint(cli.Out(), "\n")
|
||||||
if argPassword == "" {
|
if argPassword == "" {
|
||||||
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
|
return authConfig, errors.Errorf("Error: Password Required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return registrytypes.AuthConfig{
|
authConfig.Username = argUser
|
||||||
Username: argUser,
|
authConfig.Password = argPassword
|
||||||
Password: argPassword,
|
authConfig.ServerAddress = serverAddress
|
||||||
ServerAddress: serverAddress,
|
return authConfig, nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
||||||
|
|
|
@ -203,6 +203,7 @@ result2 5
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
err := SearchWrite(formatter.Context{Format: tc.format, Output: &out}, results)
|
err := SearchWrite(formatter.Context{Format: tc.format, Output: &out}, results)
|
||||||
|
|
|
@ -58,9 +58,9 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
||||||
if opts.password != "" {
|
if opts.password != "" {
|
||||||
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
||||||
if opts.passwordStdin {
|
if opts.passwordStdin {
|
||||||
return errors.New("--password and --password-stdin are mutually exclusive")
|
return errors.New("--password and --password-stdin are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) error {
|
func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) error {
|
||||||
if err := verifyLoginOptions(dockerCli, &opts); err != nil {
|
if err := verifyloginOptions(dockerCli, &opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
@ -174,7 +174,7 @@ func loginUser(ctx context.Context, dockerCli command.Cli, opts loginOptions, de
|
||||||
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprint(dockerCli.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
|
fmt.Fprint(dockerCli.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress)
|
return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress)
|
||||||
|
|
|
@ -61,6 +61,7 @@ id_rsa
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -93,6 +93,7 @@ func TestSecretInspectWithoutFormat(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
secretInspectFunc: tc.secretInspectFunc,
|
secretInspectFunc: tc.secretInspectFunc,
|
||||||
|
@ -131,6 +132,7 @@ func TestSecretInspectWithFormat(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
secretInspectFunc: tc.secretInspectFunc,
|
secretInspectFunc: tc.secretInspectFunc,
|
||||||
|
|
|
@ -223,6 +223,7 @@ zarp2
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -168,6 +168,7 @@ func TestServiceListServiceStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range matrix {
|
for _, tc := range matrix {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
if tc.cluster == nil {
|
if tc.cluster == nil {
|
||||||
tc.cluster = generateCluster(t, tc.opts)
|
tc.cluster = generateCluster(t, tc.opts)
|
||||||
|
|
|
@ -50,6 +50,7 @@ func TestCredentialSpecOpt(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var cs credentialSpecOpt
|
var cs credentialSpecOpt
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ func TestRollbackWithErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newRollbackCommand(
|
cmd := newRollbackCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
|
|
@ -1058,6 +1058,7 @@ func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
||||||
|
|
||||||
// Build the current list of portConfig
|
// Build the current list of portConfig
|
||||||
for _, entry := range *portConfig {
|
for _, entry := range *portConfig {
|
||||||
|
entry := entry
|
||||||
if _, ok := portSet[portConfigToString(&entry)]; !ok {
|
if _, ok := portSet[portConfigToString(&entry)]; !ok {
|
||||||
portSet[portConfigToString(&entry)] = entry
|
portSet[portConfigToString(&entry)] = entry
|
||||||
}
|
}
|
||||||
|
@ -1085,6 +1086,7 @@ portLoop:
|
||||||
ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
|
ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
|
||||||
|
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
|
port := port
|
||||||
if _, ok := portSet[portConfigToString(&port)]; ok {
|
if _, ok := portSet[portConfigToString(&port)]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1690,6 +1690,7 @@ func TestUpdateUlimits(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
svc := swarm.ServiceSpec{
|
svc := swarm.ServiceSpec{
|
||||||
TaskTemplate: swarm.TaskSpec{
|
TaskTemplate: swarm.TaskSpec{
|
||||||
|
|
|
@ -51,6 +51,7 @@ bar
|
||||||
{Name: "bar", Services: 1},
|
{Name: "bar", Services: 1},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -48,6 +48,7 @@ func TestListErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.expectedError, func(t *testing.T) {
|
t.Run(tc.expectedError, func(t *testing.T) {
|
||||||
cmd := newListCommand(test.NewFakeCli(&fakeClient{
|
cmd := newListCommand(test.NewFakeCli(&fakeClient{
|
||||||
serviceListFunc: tc.serviceListFunc,
|
serviceListFunc: tc.serviceListFunc,
|
||||||
|
@ -103,6 +104,7 @@ func TestStackList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
var services []swarm.Service
|
var services []swarm.Service
|
||||||
for _, name := range tc.serviceNames {
|
for _, name := range tc.serviceNames {
|
||||||
|
|
|
@ -40,6 +40,7 @@ func TestStackPsErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.expectedError, func(t *testing.T) {
|
t.Run(tc.expectedError, func(t *testing.T) {
|
||||||
cmd := newPsCommand(test.NewFakeCli(&fakeClient{
|
cmd := newPsCommand(test.NewFakeCli(&fakeClient{
|
||||||
taskListFunc: tc.taskListFunc,
|
taskListFunc: tc.taskListFunc,
|
||||||
|
@ -159,6 +160,7 @@ func TestStackPs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
taskListFunc: tc.taskListFunc,
|
taskListFunc: tc.taskListFunc,
|
||||||
|
|
|
@ -67,6 +67,7 @@ func TestStackServicesErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.expectedError, func(t *testing.T) {
|
t.Run(tc.expectedError, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
serviceListFunc: tc.serviceListFunc,
|
serviceListFunc: tc.serviceListFunc,
|
||||||
|
|
|
@ -88,6 +88,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.image, func(t *testing.T) {
|
t.Run(tc.image, func(t *testing.T) {
|
||||||
spec := map[string]swarm.ServiceSpec{
|
spec := map[string]swarm.ServiceSpec{
|
||||||
"myservice": {
|
"myservice": {
|
||||||
|
|
|
@ -63,6 +63,7 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newInitCommand(
|
cmd := newInitCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
|
|
@ -48,6 +48,7 @@ func TestSwarmJoinErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newJoinCommand(
|
cmd := newJoinCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
@ -92,6 +93,7 @@ func TestSwarmJoin(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
infoFunc: tc.infoFunc,
|
infoFunc: tc.infoFunc,
|
||||||
|
|
|
@ -87,6 +87,7 @@ func TestSwarmJoinTokenErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
swarmInspectFunc: tc.swarmInspectFunc,
|
swarmInspectFunc: tc.swarmInspectFunc,
|
||||||
|
@ -197,6 +198,7 @@ func TestSwarmJoinToken(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
swarmInspectFunc: tc.swarmInspectFunc,
|
swarmInspectFunc: tc.swarmInspectFunc,
|
||||||
|
|
|
@ -32,6 +32,7 @@ func TestSwarmLeaveErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newLeaveCommand(
|
cmd := newLeaveCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
|
|
@ -80,6 +80,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newUnlockKeyCommand(
|
cmd := newUnlockKeyCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
@ -157,6 +158,7 @@ func TestSwarmUnlockKey(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
swarmInspectFunc: tc.swarmInspectFunc,
|
swarmInspectFunc: tc.swarmInspectFunc,
|
||||||
|
|
|
@ -64,6 +64,7 @@ func TestSwarmUnlockErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newUnlockCommand(
|
cmd := newUnlockCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
|
|
@ -65,6 +65,7 @@ func TestSwarmUpdateErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := newUpdateCommand(
|
cmd := newUpdateCommand(
|
||||||
test.NewFakeCli(&fakeClient{
|
test.NewFakeCli(&fakeClient{
|
||||||
|
@ -168,6 +169,7 @@ func TestSwarmUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
swarmInspectFunc: tc.swarmInspectFunc,
|
swarmInspectFunc: tc.swarmInspectFunc,
|
||||||
|
|
|
@ -7,11 +7,7 @@ import (
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/api/types/system"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,27 +15,22 @@ type fakeClient struct {
|
||||||
client.Client
|
client.Client
|
||||||
|
|
||||||
version string
|
version string
|
||||||
containerListFunc func(context.Context, container.ListOptions) ([]container.Summary, error)
|
|
||||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
|
||||||
eventsFn func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)
|
|
||||||
imageListFunc func(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
|
|
||||||
infoFunc func(ctx context.Context) (system.Info, error)
|
|
||||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
|
||||||
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
|
|
||||||
nodeListFunc func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
|
||||||
serverVersion func(ctx context.Context) (types.Version, error)
|
serverVersion func(ctx context.Context) (types.Version, error)
|
||||||
volumeListFunc func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
|
eventsFn func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)
|
||||||
|
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
||||||
|
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||||
|
return cli.serverVersion(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *fakeClient) ClientVersion() string {
|
func (cli *fakeClient) ClientVersion() string {
|
||||||
return cli.version
|
return cli.version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *fakeClient) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) {
|
func (cli *fakeClient) Events(ctx context.Context, opts events.ListOptions) (<-chan events.Message, <-chan error) {
|
||||||
if cli.containerListFunc != nil {
|
return cli.eventsFn(ctx, opts)
|
||||||
return cli.containerListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return []container.Summary{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||||
|
@ -49,52 +40,9 @@ func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters
|
||||||
return container.PruneReport{}, nil
|
return container.PruneReport{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *fakeClient) Events(ctx context.Context, opts events.ListOptions) (<-chan events.Message, <-chan error) {
|
|
||||||
return cli.eventsFn(ctx, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) {
|
|
||||||
if cli.imageListFunc != nil {
|
|
||||||
return cli.imageListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return []image.Summary{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) Info(ctx context.Context) (system.Info, error) {
|
|
||||||
if cli.infoFunc != nil {
|
|
||||||
return cli.infoFunc(ctx)
|
|
||||||
}
|
|
||||||
return system.Info{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
|
||||||
if cli.networkListFunc != nil {
|
|
||||||
return cli.networkListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return []network.Summary{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
|
func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
|
||||||
if cli.networkPruneFunc != nil {
|
if cli.networkPruneFunc != nil {
|
||||||
return cli.networkPruneFunc(ctx, pruneFilter)
|
return cli.networkPruneFunc(ctx, pruneFilter)
|
||||||
}
|
}
|
||||||
return network.PruneReport{}, nil
|
return network.PruneReport{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
|
||||||
if cli.nodeListFunc != nil {
|
|
||||||
return cli.nodeListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return []swarm.Node{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
|
||||||
return cli.serverVersion(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *fakeClient) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
||||||
if cli.volumeListFunc != nil {
|
|
||||||
return cli.volumeListFunc(ctx, options)
|
|
||||||
}
|
|
||||||
return volume.ListResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/completion"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/events"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
eventFilters = []string{"container", "daemon", "event", "image", "label", "network", "node", "scope", "type", "volume"}
|
|
||||||
|
|
||||||
// eventTypes is a list of all event types.
|
|
||||||
// This should be moved to the moby codebase once its usage is consolidated here.
|
|
||||||
eventTypes = []events.Type{
|
|
||||||
events.BuilderEventType,
|
|
||||||
events.ConfigEventType,
|
|
||||||
events.ContainerEventType,
|
|
||||||
events.DaemonEventType,
|
|
||||||
events.ImageEventType,
|
|
||||||
events.NetworkEventType,
|
|
||||||
events.NodeEventType,
|
|
||||||
events.PluginEventType,
|
|
||||||
events.SecretEventType,
|
|
||||||
events.ServiceEventType,
|
|
||||||
events.VolumeEventType,
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventActions is a list of all event actions.
|
|
||||||
// This should be moved to the moby codebase once its usage is consolidated here.
|
|
||||||
eventActions = []events.Action{
|
|
||||||
events.ActionCreate,
|
|
||||||
events.ActionStart,
|
|
||||||
events.ActionRestart,
|
|
||||||
events.ActionStop,
|
|
||||||
events.ActionCheckpoint,
|
|
||||||
events.ActionPause,
|
|
||||||
events.ActionUnPause,
|
|
||||||
events.ActionAttach,
|
|
||||||
events.ActionDetach,
|
|
||||||
events.ActionResize,
|
|
||||||
events.ActionUpdate,
|
|
||||||
events.ActionRename,
|
|
||||||
events.ActionKill,
|
|
||||||
events.ActionDie,
|
|
||||||
events.ActionOOM,
|
|
||||||
events.ActionDestroy,
|
|
||||||
events.ActionRemove,
|
|
||||||
events.ActionCommit,
|
|
||||||
events.ActionTop,
|
|
||||||
events.ActionCopy,
|
|
||||||
events.ActionArchivePath,
|
|
||||||
events.ActionExtractToDir,
|
|
||||||
events.ActionExport,
|
|
||||||
events.ActionImport,
|
|
||||||
events.ActionSave,
|
|
||||||
events.ActionLoad,
|
|
||||||
events.ActionTag,
|
|
||||||
events.ActionUnTag,
|
|
||||||
events.ActionPush,
|
|
||||||
events.ActionPull,
|
|
||||||
events.ActionPrune,
|
|
||||||
events.ActionDelete,
|
|
||||||
events.ActionEnable,
|
|
||||||
events.ActionDisable,
|
|
||||||
events.ActionConnect,
|
|
||||||
events.ActionDisconnect,
|
|
||||||
events.ActionReload,
|
|
||||||
events.ActionMount,
|
|
||||||
events.ActionUnmount,
|
|
||||||
events.ActionExecCreate,
|
|
||||||
events.ActionExecStart,
|
|
||||||
events.ActionExecDie,
|
|
||||||
events.ActionExecDetach,
|
|
||||||
events.ActionHealthStatus,
|
|
||||||
events.ActionHealthStatusRunning,
|
|
||||||
events.ActionHealthStatusHealthy,
|
|
||||||
events.ActionHealthStatusUnhealthy,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// completeEventFilters provides completion for the filters that can be used with `--filter`.
|
|
||||||
func completeEventFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
|
||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
key, _, ok := strings.Cut(toComplete, "=")
|
|
||||||
if !ok {
|
|
||||||
return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "container":
|
|
||||||
return prefixWith("container=", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "daemon":
|
|
||||||
return prefixWith("daemon=", daemonNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "event":
|
|
||||||
return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "image":
|
|
||||||
return prefixWith("image=", imageNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "label":
|
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "network":
|
|
||||||
return prefixWith("network=", networkNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "node":
|
|
||||||
return prefixWith("node=", nodeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "scope":
|
|
||||||
return prefixWith("scope=", []string{"local", "swarm"}), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "type":
|
|
||||||
return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
case "volume":
|
|
||||||
return prefixWith("volume=", volumeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
|
||||||
default:
|
|
||||||
return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefixWith prefixes every element in the slice with the given prefix.
|
|
||||||
func prefixWith(prefix string, values []string) []string {
|
|
||||||
result := make([]string, len(values))
|
|
||||||
for i, v := range values {
|
|
||||||
result[i] = prefix + v
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// postfixWith appends postfix to every element in the slice.
|
|
||||||
func postfixWith(postfix string, values []string) []string {
|
|
||||||
result := make([]string, len(values))
|
|
||||||
for i, v := range values {
|
|
||||||
result[i] = v + postfix
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventTypeNames provides a list of all event types.
|
|
||||||
// The list is derived from eventTypes.
|
|
||||||
func eventTypeNames() []string {
|
|
||||||
names := make([]string, len(eventTypes))
|
|
||||||
for i, eventType := range eventTypes {
|
|
||||||
names[i] = string(eventType)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// validEventNames provides a list of all event actions.
|
|
||||||
// The list is derived from eventActions.
|
|
||||||
// Actions that are not suitable for usage in completions are removed.
|
|
||||||
func validEventNames() []string {
|
|
||||||
names := make([]string, 0, len(eventActions))
|
|
||||||
for _, eventAction := range eventActions {
|
|
||||||
if strings.Contains(string(eventAction), " ") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, string(eventAction))
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// containerNames contacts the API to get names and optionally IDs of containers.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command, args []string, toComplete string) []string {
|
|
||||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
|
||||||
if names == nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// daemonNames contacts the API to get name and ID of the current docker daemon.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func daemonNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
|
||||||
info, err := dockerCLI.Client().Info(cmd.Context())
|
|
||||||
if err != nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return []string{info.Name, info.ID}
|
|
||||||
}
|
|
||||||
|
|
||||||
// imageNames contacts the API to get a list of image names.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func imageNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
|
||||||
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(list))
|
|
||||||
for _, img := range list {
|
|
||||||
names = append(names, img.RepoTags...)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// networkNames contacts the API to get a list of network names.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
|
||||||
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(list))
|
|
||||||
for _, nw := range list {
|
|
||||||
names = append(names, nw.Name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeNames contacts the API to get a list of node names.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
|
||||||
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(list))
|
|
||||||
for _, node := range list {
|
|
||||||
names = append(names, node.Description.Hostname)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// volumeNames contacts the API to get a list of volume names.
|
|
||||||
// In case of an error, an empty list is returned.
|
|
||||||
func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
|
||||||
list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(list.Volumes))
|
|
||||||
for _, v := range list.Volumes {
|
|
||||||
names = append(names, v.Name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
|
||||||
"github.com/docker/cli/internal/test/builders"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/api/types/system"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompleteEventFilter(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
client *fakeClient
|
|
||||||
toComplete string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) {
|
|
||||||
return []container.Summary{
|
|
||||||
*builders.Container("c1"),
|
|
||||||
*builders.Container("c2"),
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "container=",
|
|
||||||
expected: []string{"container=c1", "container=c2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) {
|
|
||||||
return nil, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "container=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
infoFunc: func(ctx context.Context) (system.Info, error) {
|
|
||||||
return system.Info{
|
|
||||||
ID: "daemon-id",
|
|
||||||
Name: "daemon-name",
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "daemon=",
|
|
||||||
expected: []string{"daemon=daemon-name", "daemon=daemon-id"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
infoFunc: func(ctx context.Context) (system.Info, error) {
|
|
||||||
return system.Info{}, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "daemon=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
imageListFunc: func(_ context.Context, _ image.ListOptions) ([]image.Summary, error) {
|
|
||||||
return []image.Summary{
|
|
||||||
{RepoTags: []string{"img:1"}},
|
|
||||||
{RepoTags: []string{"img:2"}},
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "image=",
|
|
||||||
expected: []string{"image=img:1", "image=img:2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
imageListFunc: func(_ context.Context, _ image.ListOptions) ([]image.Summary, error) {
|
|
||||||
return []image.Summary{}, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "image=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
networkListFunc: func(_ context.Context, _ network.ListOptions) ([]network.Summary, error) {
|
|
||||||
return []network.Summary{
|
|
||||||
*builders.NetworkResource(builders.NetworkResourceName("nw1")),
|
|
||||||
*builders.NetworkResource(builders.NetworkResourceName("nw2")),
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "network=",
|
|
||||||
expected: []string{"network=nw1", "network=nw2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
networkListFunc: func(_ context.Context, _ network.ListOptions) ([]network.Summary, error) {
|
|
||||||
return nil, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "network=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
|
|
||||||
return []swarm.Node{
|
|
||||||
*builders.Node(builders.Hostname("n1")),
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "node=",
|
|
||||||
expected: []string{"node=n1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
|
|
||||||
return []swarm.Node{}, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "node=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
volumeListFunc: func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
||||||
return volume.ListResponse{
|
|
||||||
Volumes: []*volume.Volume{
|
|
||||||
builders.Volume(builders.VolumeName("v1")),
|
|
||||||
builders.Volume(builders.VolumeName("v2")),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "volume=",
|
|
||||||
expected: []string{"volume=v1", "volume=v2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
client: &fakeClient{
|
|
||||||
volumeListFunc: func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
||||||
return volume.ListResponse{}, errors.New("API error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toComplete: "volume=",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
cli := test.NewFakeCli(tc.client)
|
|
||||||
|
|
||||||
completions, directive := completeEventFilters(cli)(NewEventsCommand(cli), nil, tc.toComplete)
|
|
||||||
|
|
||||||
assert.DeepEqual(t, completions, tc.expected)
|
|
||||||
assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp, fmt.Sprintf("wrong directive in completion for '%s'", tc.toComplete))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,8 +50,6 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
|
flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("filter", completeEventFilters(dockerCli))
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ func TestEventsFormat(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Set to UTC timezone as timestamps in output are
|
// Set to UTC timezone as timestamps in output are
|
||||||
// printed in the current timezone
|
// printed in the current timezone
|
||||||
|
|
|
@ -374,6 +374,7 @@ func TestPrettyPrintInfo(t *testing.T) {
|
||||||
expectedError: "errors pretty printing info",
|
expectedError: "errors pretty printing info",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{})
|
cli := test.NewFakeCli(&fakeClient{})
|
||||||
err := prettyPrintInfo(cli, tc.dockerInfo)
|
err := prettyPrintInfo(cli, tc.dockerInfo)
|
||||||
|
@ -451,6 +452,7 @@ func TestFormatInfo(t *testing.T) {
|
||||||
expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.dockerInfo`,
|
expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.dockerInfo`,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{})
|
cli := test.NewFakeCli(&fakeClient{})
|
||||||
info := dockerInfo{
|
info := dockerInfo{
|
||||||
|
@ -516,6 +518,7 @@ func TestNeedsServerInfo(t *testing.T) {
|
||||||
|
|
||||||
inf := dockerInfo{ClientInfo: &clientInfo{}}
|
inf := dockerInfo{ClientInfo: &clientInfo{}}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected)
|
assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected)
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,7 +51,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
|
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
|
||||||
var elementSearcher inspect.GetRefFunc
|
var elementSearcher inspect.GetRefFunc
|
||||||
switch opts.inspectType {
|
switch opts.inspectType {
|
||||||
case "", "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume":
|
case "", "container", "image", "node", "network", "service", "volume", "task", "plugin", "secret":
|
||||||
elementSearcher = inspectAll(ctx, dockerCli, opts.size, opts.inspectType)
|
elementSearcher = inspectAll(ctx, dockerCli, opts.size, opts.inspectType)
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("%q is not a valid value for --type", opts.inspectType)
|
return errors.Errorf("%q is not a valid value for --type", opts.inspectType)
|
||||||
|
@ -114,12 +114,6 @@ func inspectSecret(ctx context.Context, dockerCli command.Cli) inspect.GetRefFun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectConfig(ctx context.Context, dockerCLI command.Cli) inspect.GetRefFunc {
|
|
||||||
return func(ref string) (any, []byte, error) {
|
|
||||||
return dockerCLI.Client().ConfigInspectWithRaw(ctx, ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeConstraint string) inspect.GetRefFunc {
|
func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeConstraint string) inspect.GetRefFunc {
|
||||||
inspectAutodetect := []struct {
|
inspectAutodetect := []struct {
|
||||||
objectType string
|
objectType string
|
||||||
|
@ -168,11 +162,6 @@ func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeCo
|
||||||
isSwarmObject: true,
|
isSwarmObject: true,
|
||||||
objectInspector: inspectSecret(ctx, dockerCli),
|
objectInspector: inspectSecret(ctx, dockerCli),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
objectType: "config",
|
|
||||||
isSwarmObject: true,
|
|
||||||
objectInspector: inspectConfig(ctx, dockerCli),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSwarmManager does an Info API call to verify that the daemon is
|
// isSwarmManager does an Info API call to verify that the daemon is
|
||||||
|
|
|
@ -71,6 +71,7 @@ foobar_bar foo2
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
|
@ -56,6 +56,7 @@ func TestGetFullCommandName(t *testing.T) {
|
||||||
expected: "root child grandchild",
|
expected: "root child grandchild",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
actual := getFullCommandName(tc.cmd)
|
actual := getFullCommandName(tc.cmd)
|
||||||
|
@ -90,6 +91,7 @@ func TestGetCommandName(t *testing.T) {
|
||||||
expected: "child grandchild",
|
expected: "child grandchild",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
actual := getCommandName(tc.cmd)
|
actual := getCommandName(tc.cmd)
|
||||||
|
@ -128,6 +130,7 @@ func TestStdioAttributes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.test, func(t *testing.T) {
|
t.Run(tc.test, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cli := &DockerCli{
|
cli := &DockerCli{
|
||||||
|
@ -176,6 +179,7 @@ func TestAttributesFromError(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
tc := tc
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
actual := attributesFromError(tc.err)
|
actual := attributesFromError(tc.err)
|
||||||
|
|
|
@ -127,6 +127,7 @@ tag3 bbbbbbbb
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
@ -230,6 +231,7 @@ eve foobarbazquxquux, key31, key32
|
||||||
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
|
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue