Compare commits

..

1 Commits

Author SHA1 Message Date
Nate 95500effc3
Merge a418c68e38 into 185622986e 2024-10-08 12:17:46 -04:00
849 changed files with 14291 additions and 42567 deletions

View File

@ -62,7 +62,7 @@ jobs:
name: Update Go
uses: actions/setup-go@v5
with:
go-version: 1.23.2
go-version: 1.22.8
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@ -68,7 +68,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.23.2
go-version: 1.22.8
-
name: Test
run: |

View File

@ -1,12 +1,12 @@
linters:
enable:
- bodyclose
- copyloopvar # Detects places where loop variables are copied.
- depguard
- dogsled
- dupword # Detects duplicate words.
- durationcheck
- errchkjson
- exportloopref # Detects pointers to enclosing loop variables.
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocyclo
- gofumpt # Detects whether code was gofumpt-ed.
@ -41,9 +41,6 @@ linters:
- errcheck
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
linters-settings:
@ -55,13 +52,6 @@ linters-settings:
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
gocyclo:
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:
enable:
- shadow
@ -97,10 +87,6 @@ issues:
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
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:
- parameter .* always receives
@ -118,9 +104,6 @@ issues:
#
# 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
#
# The default list of exclusions can be found at:
# https://golangci-lint.run/usage/false-positives/#default-exclusions
# EXC0001
- 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)"
linters:
- gosec
# EXC0008
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
- text: "G307"
linters:
- gosec
# EXC0009
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
linters:
@ -147,6 +135,26 @@ issues:
linters:
- 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.
- text: "package-comments: should have a package comment"
linters:

View File

@ -66,7 +66,7 @@ anybody starts working on it.
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,
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
@ -124,8 +124,8 @@ submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build. See our contributors guide for [our style
guide](https://docs.docker.com/contribute/style/grammar/) and instructions on [building
the documentation](https://docs.docker.com/contribute/).
guide](https://docs.docker.com/opensource/doc-style) and instructions on [building
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,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before

View File

@ -4,7 +4,7 @@ ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.20
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.23.2
ARG GO_VERSION=1.22.8
ARG XX_VERSION=1.5.0
ARG GOVERSIONINFO_VERSION=v1.3.0
ARG GOTESTSUM_VERSION=v1.10.0

View File

@ -1,10 +1,9 @@
# 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)
[![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)
[![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)
## About

View File

@ -17,5 +17,5 @@ func (c *candidate) Path() string {
}
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()
}

View File

@ -52,6 +52,7 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
return
}
for _, p := range plugins {
p := p
vendor := p.Vendor
if vendor == "" {
vendor = "unknown"

View File

@ -240,8 +240,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
// TODO: why are we not returning plugin.Err?
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.
// See: - https://github.com/golang/go/issues/10338
// - https://github.com/golang/go/commit/d000e8742a173aa0659584aa01b7ba2834ba28ab

View File

@ -112,7 +112,7 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
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 = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
hookCmdOutput, err := pCmd.Output()

View File

@ -187,18 +187,19 @@ func TestInitializeFromClient(t *testing.T) {
},
}
for _, tc := range testcases {
t.Run(tc.doc, func(t *testing.T) {
for _, testcase := range testcases {
testcase := testcase
t.Run(testcase.doc, func(t *testing.T) {
apiclient := &fakeClient{
pingFunc: tc.pingFunc,
pingFunc: testcase.pingFunc,
version: defaultVersion,
}
cli := &DockerCli{client: apiclient}
err := cli.Initialize(flags.NewClientOptions())
assert.NilError(t, err)
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
assert.Equal(t, apiclient.negotiated, tc.negotiated)
assert.DeepEqual(t, cli.ServerInfo(), testcase.expectedServer)
assert.Equal(t, apiclient.negotiated, testcase.negotiated)
})
}
}
@ -276,9 +277,10 @@ func TestExperimentalCLI(t *testing.T) {
},
}
for _, tc := range testcases {
t.Run(tc.doc, func(t *testing.T) {
dir := fs.NewDir(t, tc.doc, fs.WithFile("config.json", tc.configfile))
for _, testcase := range testcases {
testcase := testcase
t.Run(testcase.doc, func(t *testing.T) {
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
defer dir.Remove()
apiclient := &fakeClient{
version: defaultVersion,

View File

@ -59,7 +59,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
for _, ctr := range list {
skip := false
for _, fn := range filters {
if fn != nil && !fn(ctr) {
if !fn(ctr) {
skip = true
break
}

View File

@ -1,346 +1,15 @@
package completion
import (
"context"
"errors"
"sort"
"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"
"gotest.tools/v3/assert"
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) {
values, directives := Platforms(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
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))
})
}
}

View File

@ -43,6 +43,7 @@ func TestConfigCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cmd := newConfigCreateCommand(
test.NewFakeCli(&fakeClient{

View File

@ -61,6 +61,7 @@ id_rsa
},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -73,6 +73,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
cmd.SetOut(io.Discard)

View File

@ -178,6 +178,7 @@ func TestSplitCpArg(t *testing.T) {
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
if tc.os == "windows" && runtime.GOOS != "windows" {
t.Skip("skipping windows test on non-windows platform")

View File

@ -113,6 +113,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
pullCounter := 0
@ -175,6 +176,7 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
dockerCli := test.NewFakeCli(&fakeClient{})
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`,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
@ -248,6 +251,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
@ -308,6 +312,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,

View File

@ -47,6 +47,7 @@ D: /usr/app/old_app.js
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
out := bytes.NewBufferString("")
tc.context.Output = out

View File

@ -178,6 +178,7 @@ container2 -- --
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
@ -222,6 +223,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
assert.NilError(t, err)
@ -263,6 +265,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
assert.NilError(t, err)

View File

@ -127,6 +127,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
func TestContainerListErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
containerListFunc func(container.ListOptions) ([]container.Summary, error)
expectedError string
@ -156,10 +157,10 @@ func TestContainerListErrors(t *testing.T) {
containerListFunc: tc.containerListFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
assert.Check(t, cmd.Flags().Set(key, value))
}
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -179,9 +180,6 @@ func TestContainerListWithoutFormat(t *testing.T) {
},
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format.golden")
}
@ -196,9 +194,6 @@ func TestContainerListNoTrunc(t *testing.T) {
},
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.Check(t, cmd.Flags().Set("no-trunc", "true"))
assert.NilError(t, cmd.Execute())
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.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}"))
assert.NilError(t, cmd.Execute())
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.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`))
assert.NilError(t, cmd.Execute())
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 {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(options container.ListOptions) ([]container.Summary, error) {
@ -285,9 +275,6 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
},
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.Check(t, cmd.Flags().Set("format", tc.format))
if 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}}",
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.NilError(t, cmd.Execute())
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) {
cli.OutBuffer().Reset()
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.NilError(t, cmd.Execute())
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) {
cli.OutBuffer().Reset()
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("quiet", "true"))
assert.NilError(t, cmd.Execute())

View File

@ -23,6 +23,7 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
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)
}
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
volumes := copts.volumes.GetMap()
// 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,
Sysctls: copts.sysctls.GetAll(),
Runtime: copts.runtime,
Mounts: copts.mounts.Value(),
Mounts: mounts,
MaskedPaths: maskedPaths,
ReadonlyPaths: readonlyPaths,
Annotations: copts.annotations.GetAll(),
@ -762,6 +767,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
}
for i, n := range copts.netMode.Value() {
n := n
if container.NetworkMode(n.Target).IsUserDefined() {
hasUserDefined = true
} else {

View File

@ -126,6 +126,7 @@ func TestParseRunAttach(t *testing.T) {
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) {
config, _, _ := mustParse(t, tc.input)
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
@ -801,6 +802,7 @@ func TestParseRestartPolicy(t *testing.T) {
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) {
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
if tc.expectedErr != "" {

View File

@ -43,6 +43,7 @@ func TestNewPortCommandOutput(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
inspectFunc: func(string) (container.InspectResponse, error) {

View File

@ -21,7 +21,6 @@ func TestContainerPrunePromptTermination(t *testing.T) {
},
})
cmd := NewPruneCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
test.TerminatePrompt(ctx, t, cmd, cli)

View File

@ -58,6 +58,7 @@ func TestRestart(t *testing.T) {
expectedErr: "conflicting options: cannot specify both --timeout and --time",
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var restarted []string
mutex := new(sync.Mutex)

View File

@ -38,9 +38,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
Annotations: map[string]string{
"aliases": "docker container rm, docker container remove, docker rm",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(ctr container.Summary) bool {
return opts.force || ctr.State == "exited" || ctr.State == "created"
}),
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
}
flags := cmd.Flags()

View File

@ -23,6 +23,7 @@ func TestRemoveForce(t *testing.T) {
{name: "without force", args: []string{"nosuchcontainer", "mycontainer"}, expectedErr: "no such container"},
{name: "with force", args: []string{"--force", "nosuchcontainer", "mycontainer"}, expectedErr: ""},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var removed []string
mutex := new(sync.Mutex)

View File

@ -35,6 +35,7 @@ func TestRunValidateFlags(t *testing.T) {
expectedErr: "conflicting options: cannot specify both --attach and --detach",
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewRunCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
@ -244,6 +245,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
@ -284,6 +286,7 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
dockerCli := test.NewFakeCli(&fakeClient{})
err := runRun(

View File

@ -1,7 +1,6 @@
package container
import (
"bytes"
"context"
"fmt"
"io"
@ -265,40 +264,31 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
// so we unlikely hit this code in practice.
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{
Output: &statsTextBuffer,
Output: dockerCLI.Out(),
Format: NewStatsFormat(format, daemonOSType),
}
cleanScreen := func() {
if !options.NoStream {
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[2J")
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[H")
}
}
var err error
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
cleanScreen()
var ccStats []StatsEntry
cStats.mu.RLock()
for _, c := range cStats.cs {
ccStats = append(ccStats, c.GetStatistics())
}
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 {
break
}
_, _ = fmt.Fprint(dockerCLI.Out(), statsTextBuffer.String())
statsTextBuffer.Reset()
if len(cStats.cs) == 0 && !showAll {
break
}

View File

@ -58,6 +58,7 @@ func TestStop(t *testing.T) {
expectedErr: "conflicting options: cannot specify both --timeout and --time",
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var stopped []string
mutex := new(sync.Mutex)

View File

@ -38,7 +38,7 @@ func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
}
func TestWaitExitOrRemoved(t *testing.T) {
tests := []struct {
testcases := []struct {
cid string
exitCode int
}{
@ -61,11 +61,9 @@ func TestWaitExitOrRemoved(t *testing.T) {
}
client := &fakeClient{waitFunc: waitFn, Version: api.DefaultVersion}
for _, tc := range tests {
t.Run(tc.cid, func(t *testing.T) {
statusC := waitExitOrRemoved(context.Background(), client, tc.cid, true)
for _, testcase := range testcases {
statusC := waitExitOrRemoved(context.Background(), client, testcase.cid, true)
exitCode := <-statusC
assert.Check(t, is.Equal(tc.exitCode, exitCode))
})
assert.Check(t, is.Equal(testcase.exitCode, exitCode))
}
}

View File

@ -94,6 +94,7 @@ func TestCreate(t *testing.T) {
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.options.Name, func(t *testing.T) {
err := RunCreate(cli, &tc.options)
if tc.expecterErr == "" {
@ -163,24 +164,25 @@ func TestCreateFromContext(t *testing.T) {
cli.SetCurrentContext("dummy")
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
cli.ResetOutputBuffers()
err := RunCreate(cli, &CreateOptions{
From: "original",
Name: tc.name,
Description: tc.description,
Docker: tc.docker,
Name: c.name,
Description: c.description,
Docker: c.docker,
})
assert.NilError(t, err)
assertContextCreateLogging(t, cli, tc.name)
newContext, err := cli.ContextStore().GetMetadata(tc.name)
assertContextCreateLogging(t, cli, c.name)
newContext, err := cli.ContextStore().GetMetadata(c.name)
assert.NilError(t, err)
newContextTyped, err := command.GetDockerContext(newContext)
assert.NilError(t, err)
dockerEndpoint, err := docker.EndpointFromContext(newContext)
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")
})
}
@ -217,22 +219,23 @@ func TestCreateFromCurrent(t *testing.T) {
cli.SetCurrentContext("original")
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
cli.ResetOutputBuffers()
err := RunCreate(cli, &CreateOptions{
Name: tc.name,
Description: tc.description,
Name: c.name,
Description: c.description,
})
assert.NilError(t, err)
assertContextCreateLogging(t, cli, tc.name)
newContext, err := cli.ContextStore().GetMetadata(tc.name)
assertContextCreateLogging(t, cli, c.name)
newContext, err := cli.ContextStore().GetMetadata(c.name)
assert.NilError(t, err)
newContextTyped, err := command.GetDockerContext(newContext)
assert.NilError(t, err)
dockerEndpoint, err := docker.EndpointFromContext(newContext)
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")
})
}

View File

@ -346,6 +346,7 @@ size: 0B
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
@ -410,6 +411,7 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := ContainerWrite(tc.context, containers)
assert.NilError(t, err)

View File

@ -106,6 +106,7 @@ Build Cache 0 0 0B 0B
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -309,6 +309,7 @@ image_id: imageID3
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
@ -369,6 +370,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := ImageWrite(tc.context, images)
assert.NilError(t, err)

View File

@ -131,6 +131,7 @@ foobar_bar
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -255,6 +255,7 @@ imageID6 17 years ago /bin/bash echo 183MB
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := HistoryWrite(tc.context, true, histories)
assert.NilError(t, err)

View File

@ -3,20 +3,17 @@ package image
import (
"context"
"github.com/containerd/platforms"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types/image"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type historyOptions struct {
image string
platform string
human 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.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
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
}
func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) error {
var options 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)
history, err := dockerCli.Client().ImageHistory(ctx, opts.image, image.HistoryOptions{})
if err != nil {
return err
}

View File

@ -8,10 +8,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
@ -35,13 +33,9 @@ func TestNewHistoryCommandErrors(t *testing.T) {
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
},
},
{
name: "invalid platform",
args: []string{"--platform", "<invalid>", "arg1"},
expectedError: `invalid platform`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
cmd.SetOut(io.Discard)
@ -95,19 +89,9 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
}}, 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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Set to UTC timezone as timestamps in output are
// printed in the current timezone

View File

@ -98,6 +98,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}))
cmd.SetOut(io.Discard)

View File

@ -25,6 +25,7 @@ func TestNewInspectCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
@ -78,6 +79,7 @@ func TestNewInspectCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
imageInspectInvocationCount = 0
cli := test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc})

View File

@ -35,6 +35,7 @@ func TestNewImagesCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
cmd.SetOut(io.Discard)
@ -82,6 +83,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})

View File

@ -4,7 +4,6 @@ import (
"context"
"io"
"github.com/containerd/platforms"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
@ -18,7 +17,6 @@ import (
type loadOptions struct {
input string
quiet bool
platform string
}
// 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.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
}
@ -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")
}
var options image.LoadOptions
var loadOpts image.LoadOptions
if opts.quiet || !dockerCli.Out().IsTerminal() {
options.Quiet = true
loadOpts.Quiet = true
}
if opts.platform != "" {
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)
response, err := dockerCli.Client().ImageLoad(ctx, input, loadOpts)
if err != nil {
return err
}

View File

@ -8,10 +8,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
@ -30,28 +28,19 @@ func TestNewLoadCommandErrors(t *testing.T) {
},
{
name: "input-to-terminal",
args: []string{},
isTerminalIn: true,
expectedError: "requested load from stdin, but stdin is empty",
},
{
name: "pull-error",
args: []string{},
expectedError: "something went wrong",
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
cli.In().SetIsTerminal(tc.isTerminalIn)
@ -82,14 +71,12 @@ func TestNewLoadCommandSuccess(t *testing.T) {
}{
{
name: "simple",
args: []string{},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
},
},
{
name: "json",
args: []string{},
imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) {
json := "{\"ID\": \"1\"}"
return image.LoadResponse{
@ -105,16 +92,9 @@ func TestNewLoadCommandSuccess(t *testing.T) {
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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
cmd := NewLoadCommand(cli)

View File

@ -39,6 +39,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
@ -97,6 +98,7 @@ func TestNewPruneCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imagesPruneFunc: tc.imagesPruneFunc})
// when prompted, answer "Y" to confirm the prune.

View File

@ -38,6 +38,7 @@ func TestNewPullCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
@ -72,6 +73,7 @@ func TestNewPullCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
@ -117,6 +119,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {

View File

@ -38,6 +38,7 @@ func TestNewPushCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
cmd := NewPushCommand(cli)
@ -67,6 +68,7 @@ func TestNewPushCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options image.PushOptions) (io.ReadCloser, error) {

View File

@ -62,6 +62,7 @@ func TestNewRemoveCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
@ -120,6 +121,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
cmd := NewRemoveCommand(cli)

View File

@ -4,7 +4,6 @@ import (
"context"
"io"
"github.com/containerd/platforms"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
@ -16,7 +15,6 @@ import (
type saveOptions struct {
images []string
output string
platform string
}
// NewSaveCommand creates a new `docker save` command
@ -40,10 +38,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
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
}
@ -57,16 +52,7 @@ func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error
return errors.Wrap(err, "failed to save image")
}
var options 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)
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, image.SaveOptions{})
if err != nil {
return err
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/image"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -52,13 +51,9 @@ func TestNewSaveCommandErrors(t *testing.T) {
args: []string{"-o", "/dev/null", "arg1"},
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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
cli.Out().SetIsTerminal(tc.isTerminal)
@ -75,13 +70,13 @@ func TestNewSaveCommandSuccess(t *testing.T) {
testCases := []struct {
args []string
isTerminal bool
imageSaveFunc func(images []string, options image.SaveOptions) (io.ReadCloser, error)
imageSaveFunc func(images []string) (io.ReadCloser, error)
deferredFunc func()
}{
{
args: []string{"-o", "save_tmp_file", "arg1"},
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.Check(t, is.Equal("arg1", images[0]))
return io.NopCloser(strings.NewReader("")), nil
@ -93,28 +88,21 @@ func TestNewSaveCommandSuccess(t *testing.T) {
{
args: []string{"arg1", "arg2"},
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.Check(t, is.Equal("arg1", images[0]))
assert.Check(t, is.Equal("arg2", images[1]))
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 {
tc := tc
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
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.SetErr(io.Discard)

View File

@ -1,2 +0,0 @@
IMAGE CREATED CREATED BY SIZE COMMENT
123456789012 Less than a second ago 0B

View File

@ -56,6 +56,7 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
continue
}
im := im
sub := subImage{
Platform: platforms.Format(im.ImageData.Platform),
Available: im.Available,

View File

@ -31,6 +31,7 @@ func TestManifestCreateErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cli := test.NewFakeCli(nil)
cmd := newCreateListCommand(cli)

View File

@ -218,6 +218,7 @@ func TestNetworkCreateIPv6(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {

View File

@ -161,6 +161,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -83,6 +83,7 @@ func TestNetworkList(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc})
cmd := newListCommand(cli)

View File

@ -63,6 +63,7 @@ func TestNetworkRemoveForce(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
fakeCli := test.NewFakeCli(&fakeClient{
networkRemoveFunc: func(ctx context.Context, networkID string) error {

View File

@ -202,6 +202,7 @@ foobar_boo Unknown
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -106,6 +106,7 @@ func TestNodeInspectPretty(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,

View File

@ -134,6 +134,7 @@ func TestNodePs(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,

View File

@ -131,6 +131,7 @@ foobar_bar
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -66,6 +66,7 @@ func TestInspectErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
cmd := newInspectCommand(cli)
@ -137,6 +138,7 @@ func TestInspect(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
cmd := newInspectCommand(cli)

View File

@ -54,6 +54,7 @@ func TestInstallErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
cmd := newInstallCommand(cli)
@ -93,6 +94,7 @@ func TestInstallContentTrustErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
@ -136,6 +138,7 @@ func TestInstall(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
cmd := newInstallCommand(cli)

View File

@ -46,6 +46,7 @@ func TestListErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
cmd := newListCommand(cli)
@ -165,6 +166,7 @@ func TestList(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
cmd := newListCommand(cli)

View File

@ -2,7 +2,6 @@ package plugin
import (
"context"
"errors"
"fmt"
"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 {
var errs error
var errs cli.Errors
for _, name := range opts.plugins {
if err := dockerCli.Client().PluginRemove(ctx, name, types.PluginRemoveOptions{Force: opts.force}); err != nil {
errs = errors.Join(errs, err)
errs = append(errs, err)
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 nil
}

View File

@ -18,24 +18,20 @@ import (
"github.com/pkg/errors"
)
const (
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 " +
const 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 " +
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
)
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command.
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
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)
isDefaultRegistry := indexServer == registry.IndexServer
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
if err != nil {
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
}
select {
@ -90,8 +86,7 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
}
// 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 {
defaultUsername := authConfig.Username
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
// and the user can hit enter without inputting a username to use that default
// 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.
//
// See:
@ -128,71 +123,57 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
cli.SetIn(streams.NewIn(os.Stdin))
}
argUser = strings.TrimSpace(argUser)
if argUser == "" {
if serverAddress == registry.IndexServer {
// When signing in to the default (Docker Hub) registry, we display
// hints for creating an account, and (if hints are enabled), using
// a token instead of a password.
_, _ = fmt.Fprintln(cli.Out(), registerSuggest)
isDefaultRegistry := serverAddress == registry.IndexServer
defaultUsername = strings.TrimSpace(defaultUsername)
if argUser = strings.TrimSpace(argUser); argUser == "" {
if isDefaultRegistry {
// if this is a default registry (docker hub), then display the following message.
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() {
_, _ = fmt.Fprintln(cli.Out(), patSuggest)
_, _ = fmt.Fprintln(cli.Out())
fmt.Fprintln(cli.Out(), patSuggest)
fmt.Fprintln(cli.Out())
}
}
var prompt string
defaultUsername = strings.TrimSpace(defaultUsername)
if defaultUsername == "" {
prompt = "Username: "
} else {
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
}
var err error
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
if err != nil {
return registrytypes.AuthConfig{}, err
return authConfig, err
}
if argUser == "" {
argUser = defaultUsername
}
}
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 == "" {
restoreInput, err := DisableInputEcho(cli.In())
if err != nil {
return registrytypes.AuthConfig{}, err
return authConfig, err
}
defer func() {
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)
}
}()
defer restoreInput()
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
if err != nil {
return registrytypes.AuthConfig{}, err
return authConfig, err
}
_, _ = fmt.Fprintln(cli.Out())
fmt.Fprint(cli.Out(), "\n")
if argPassword == "" {
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
return authConfig, errors.Errorf("Error: Password Required")
}
}
return registrytypes.AuthConfig{
Username: argUser,
Password: argPassword,
ServerAddress: serverAddress,
}, nil
authConfig.Username = argUser
authConfig.Password = argPassword
authConfig.ServerAddress = serverAddress
return authConfig, nil
}
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete

View File

@ -203,6 +203,7 @@ result2 5
}
for _, tc := range cases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
var out bytes.Buffer
err := SearchWrite(formatter.Context{Format: tc.format, Output: &out}, results)

View File

@ -58,9 +58,9 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error {
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 {
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 {
if err := verifyLoginOptions(dockerCli, &opts); err != nil {
if err := verifyloginOptions(dockerCli, &opts); err != nil {
return err
}
var (
@ -174,7 +174,7 @@ func loginUser(ctx context.Context, dockerCli command.Cli, opts loginOptions, de
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
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)

View File

@ -61,6 +61,7 @@ id_rsa
},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -93,6 +93,7 @@ func TestSecretInspectWithoutFormat(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,
@ -131,6 +132,7 @@ func TestSecretInspectWithFormat(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,

View File

@ -223,6 +223,7 @@ zarp2
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -168,6 +168,7 @@ func TestServiceListServiceStatus(t *testing.T) {
}
for _, tc := range matrix {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
if tc.cluster == nil {
tc.cluster = generateCluster(t, tc.opts)

View File

@ -50,6 +50,7 @@ func TestCredentialSpecOpt(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var cs credentialSpecOpt

View File

@ -91,6 +91,7 @@ func TestRollbackWithErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newRollbackCommand(
test.NewFakeCli(&fakeClient{

View File

@ -1058,6 +1058,7 @@ func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
// Build the current list of portConfig
for _, entry := range *portConfig {
entry := entry
if _, ok := portSet[portConfigToString(&entry)]; !ok {
portSet[portConfigToString(&entry)] = entry
}
@ -1085,6 +1086,7 @@ portLoop:
ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
for _, port := range ports {
port := port
if _, ok := portSet[portConfigToString(&port)]; ok {
continue
}

View File

@ -1690,6 +1690,7 @@ func TestUpdateUlimits(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
svc := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{

View File

@ -51,6 +51,7 @@ bar
{Name: "bar", Services: 1},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -48,6 +48,7 @@ func TestListErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{
serviceListFunc: tc.serviceListFunc,
@ -103,6 +104,7 @@ func TestStackList(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
var services []swarm.Service
for _, name := range tc.serviceNames {

View File

@ -40,6 +40,7 @@ func TestStackPsErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cmd := newPsCommand(test.NewFakeCli(&fakeClient{
taskListFunc: tc.taskListFunc,
@ -159,6 +160,7 @@ func TestStackPs(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: tc.taskListFunc,

View File

@ -67,6 +67,7 @@ func TestStackServicesErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: tc.serviceListFunc,

View File

@ -88,6 +88,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
ctx := context.Background()
for _, tc := range testcases {
tc := tc
t.Run(tc.image, func(t *testing.T) {
spec := map[string]swarm.ServiceSpec{
"myservice": {

View File

@ -63,6 +63,7 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newInitCommand(
test.NewFakeCli(&fakeClient{

View File

@ -48,6 +48,7 @@ func TestSwarmJoinErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newJoinCommand(
test.NewFakeCli(&fakeClient{
@ -92,6 +93,7 @@ func TestSwarmJoin(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,

View File

@ -87,6 +87,7 @@ func TestSwarmJoinTokenErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
@ -197,6 +198,7 @@ func TestSwarmJoinToken(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,

View File

@ -32,6 +32,7 @@ func TestSwarmLeaveErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newLeaveCommand(
test.NewFakeCli(&fakeClient{

View File

@ -80,6 +80,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newUnlockKeyCommand(
test.NewFakeCli(&fakeClient{
@ -157,6 +158,7 @@ func TestSwarmUnlockKey(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,

View File

@ -64,6 +64,7 @@ func TestSwarmUnlockErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newUnlockCommand(
test.NewFakeCli(&fakeClient{

View File

@ -65,6 +65,7 @@ func TestSwarmUpdateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
@ -168,6 +169,7 @@ func TestSwarmUpdate(t *testing.T) {
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,

View File

@ -7,11 +7,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"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/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
)
@ -19,27 +15,22 @@ type fakeClient struct {
client.Client
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)
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 {
return cli.version
}
func (cli *fakeClient) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) {
if cli.containerListFunc != nil {
return cli.containerListFunc(ctx, options)
}
return []container.Summary{}, nil
func (cli *fakeClient) Events(ctx context.Context, opts events.ListOptions) (<-chan events.Message, <-chan error) {
return cli.eventsFn(ctx, opts)
}
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
}
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) {
if cli.networkPruneFunc != nil {
return cli.networkPruneFunc(ctx, pruneFilter)
}
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
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -50,8 +50,6 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
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.
_ = cmd.RegisterFlagCompletionFunc("filter", completeEventFilters(dockerCli))
return cmd
}

View File

@ -53,6 +53,7 @@ func TestEventsFormat(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Set to UTC timezone as timestamps in output are
// printed in the current timezone

View File

@ -374,6 +374,7 @@ func TestPrettyPrintInfo(t *testing.T) {
expectedError: "errors pretty printing info",
},
} {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
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`,
},
} {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
info := dockerInfo{
@ -516,6 +518,7 @@ func TestNeedsServerInfo(t *testing.T) {
inf := dockerInfo{ClientInfo: &clientInfo{}}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected)
})

View File

@ -51,7 +51,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command {
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
var elementSearcher inspect.GetRefFunc
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)
default:
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 {
inspectAutodetect := []struct {
objectType string
@ -168,11 +162,6 @@ func inspectAll(ctx context.Context, dockerCli command.Cli, getSize bool, typeCo
isSwarmObject: true,
objectInspector: inspectSecret(ctx, dockerCli),
},
{
objectType: "config",
isSwarmObject: true,
objectInspector: inspectConfig(ctx, dockerCli),
},
}
// isSwarmManager does an Info API call to verify that the daemon is

View File

@ -71,6 +71,7 @@ foobar_bar foo2
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

View File

@ -56,6 +56,7 @@ func TestGetFullCommandName(t *testing.T) {
expected: "root child grandchild",
},
} {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
actual := getFullCommandName(tc.cmd)
@ -90,6 +91,7 @@ func TestGetCommandName(t *testing.T) {
expected: "child grandchild",
},
} {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
actual := getCommandName(tc.cmd)
@ -128,6 +130,7 @@ func TestStdioAttributes(t *testing.T) {
},
},
} {
tc := tc
t.Run(tc.test, func(t *testing.T) {
t.Parallel()
cli := &DockerCli{
@ -176,6 +179,7 @@ func TestAttributesFromError(t *testing.T) {
},
},
} {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
actual := attributesFromError(tc.err)

View File

@ -127,6 +127,7 @@ tag3 bbbbbbbb
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
@ -230,6 +231,7 @@ eve foobarbazquxquux, key31, key32
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out

Some files were not shown because too many files have changed in this diff Show More