Merge component 'cli' from git@github.com:docker/cli master

This commit is contained in:
Andrew Hsu
2017-07-14 20:04:39 +00:00
145 changed files with 1899 additions and 15757 deletions

8
components/cli/.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,8 @@
# Github code owners
# See https://github.com/blog/2392-introducing-code-owners
cli/compose/* @dnephin @vdemeester
contrib/completion/bash/* @albers
contrib/completion/zsh/* @sdurrheimer
docs/* @mstanleyjones @vdemeester @thaJeztah
scripts/* @dnephin

View File

@ -7,7 +7,9 @@ jobs:
docker: [{image: 'docker:17.05-git'}]
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
reusable: true
exclusive: false
- run:
command: docker version
- run:
@ -15,23 +17,32 @@ jobs:
command: |
dockerfile=dockerfiles/Dockerfile.lint
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-linter .
docker run cli-linter
docker build -f $dockerfile --tag cli-linter:$CIRCLE_BUILD_NUM .
docker run --rm cli-linter:$CIRCLE_BUILD_NUM
cross:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
parallelism: 3
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Cross"
command: |
dockerfile=dockerfiles/Dockerfile.cross
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run --name cross cli-builder make cross
docker cp cross:/go/src/github.com/docker/cli/build /work/build
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
docker run \
-e CROSS_GROUP=$CIRCLE_NODE_INDEX \
--name $name cli-builder:$CIRCLE_BUILD_NUM \
make cross
docker cp \
$name:/go/src/github.com/docker/cli/build \
/work/build
- store_artifacts:
path: /work/build
@ -40,19 +51,25 @@ jobs:
docker: [{image: 'docker:17.05-git'}]
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Unit Test with Coverage"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run --name test cli-builder make test-coverage
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
docker run --name \
test-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \
make test-coverage
- run:
name: "Upload to Codecov"
command: |
docker cp test:/go/src/github.com/docker/cli/coverage.txt coverage.txt
docker cp \
test-$CIRCLE_BUILD_NUM:/go/src/github.com/docker/cli/coverage.txt \
coverage.txt
apk add -U bash curl
curl -s https://codecov.io/bash | bash
@ -60,16 +77,20 @@ jobs:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
steps:
- run: apk add -U git openssh
- checkout
- setup_remote_docker
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Validate Vendor, Docs, and Code Generation"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
rm -f .dockerignore # include .git
docker build -f $dockerfile --tag cli-builder .
docker run cli-builder make -B vendor compose-jsonschema manpages yamldocs
docker build -f $dockerfile --tag cli-builder-with-git:$CIRCLE_BUILD_NUM .
docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \
make -B vendor compose-jsonschema manpages yamldocs
workflows:
version: 2

View File

@ -0,0 +1,35 @@
package checkpoint
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
}
func (cli *fakeClient) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
if cli.checkpointCreateFunc != nil {
return cli.checkpointCreateFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointDelete(ctx context.Context, container string, options types.CheckpointDeleteOptions) error {
if cli.checkpointDeleteFunc != nil {
return cli.checkpointDeleteFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointList(ctx context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
if cli.checkpointListFunc != nil {
return cli.checkpointListFunc(container, options)
}
return []types.Checkpoint{}, nil
}

View File

@ -0,0 +1,72 @@
package checkpoint
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
return errors.Errorf("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: tc.checkpointCreateFunc,
})
cmd := newCreateCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointCreateWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
var exit bool
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
exit = options.Exit
return nil
},
})
cmd := newCreateCommand(cli)
checkpoint := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", checkpoint})
cmd.Flags().Set("leave-running", "true")
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, checkpoint, checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
assert.Equal(t, false, exit)
assert.Equal(t, checkpoint, strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -0,0 +1,71 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
expectedError string
}{
{
args: []string{},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
return []types.Checkpoint{}, errors.Errorf("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: tc.checkpointListFunc,
}, &bytes.Buffer{})
cmd := newListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
containerID = container
checkpointDir = options.CheckpointDir
return []types.Checkpoint{
{Name: "checkpoint-foo"},
}, nil
},
}, buf)
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "/dir/foo", checkpointDir)
actual := buf.String()
expected := golden.Get(t, []byte(actual), "checkpoint-list-with-options.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -0,0 +1,66 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
return errors.Errorf("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: tc.checkpointDeleteFunc,
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return nil
},
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "checkpoint-bar", checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
}

View File

@ -0,0 +1,2 @@
CHECKPOINT NAME
checkpoint-foo

View File

@ -1,7 +1,6 @@
package config
import (
"bytes"
"io/ioutil"
"path/filepath"
"reflect"
@ -41,11 +40,10 @@ func TestConfigCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigCreateCommand(
test.NewFakeCli(&fakeClient{
configCreateFunc: tc.configCreateFunc,
}, buf),
}),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -55,7 +53,6 @@ func TestConfigCreateErrors(t *testing.T) {
func TestConfigCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
var actual []byte
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
@ -69,14 +66,14 @@ func TestConfigCreateWithName(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
}, buf)
})
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
assert.NoError(t, cmd.Execute())
expected := golden.Get(t, actual, configDataFile)
assert.Equal(t, string(expected), string(actual))
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
}
func TestConfigCreateWithLabels(t *testing.T) {
@ -86,7 +83,6 @@ func TestConfigCreateWithLabels(t *testing.T) {
}
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if spec.Name != name {
@ -101,12 +97,12 @@ func TestConfigCreateWithLabels(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
}, buf)
})
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
cmd.Flags().Set("label", "lbl1=Label-foo")
cmd.Flags().Set("label", "lbl2=Label-bar")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -55,7 +55,7 @@ func TestConfigInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -97,7 +97,7 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -137,7 +137,7 @@ func TestConfigInspectWithFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -174,7 +174,7 @@ func TestConfigInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf))
cmd.SetArgs([]string{"configID"})

View File

@ -1,7 +1,6 @@
package config
import (
"bytes"
"io/ioutil"
"testing"
"time"
@ -36,11 +35,10 @@ func TestConfigListErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigListCommand(
test.NewFakeCli(&fakeClient{
configListFunc: tc.configListFunc,
}, buf),
}),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -49,7 +47,6 @@ func TestConfigListErrors(t *testing.T) {
}
func TestConfigList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -67,18 +64,16 @@ func TestConfigList(t *testing.T) {
),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newConfigListCommand(cli)
cmd.SetOutput(buf)
cmd.SetOutput(cli.OutBuffer())
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "config-list.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -88,18 +83,16 @@ func TestConfigListWithQuietOption(t *testing.T) {
})),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "config-list-with-quiet-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -109,19 +102,18 @@ func TestConfigListWithConfigFormat(t *testing.T) {
})),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
})
cli.SetConfigFile(&configfile.ConfigFile{
ConfigFormat: "{{ .Name }} {{ .Labels }}",
})
cmd := newConfigListCommand(cli)
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "config-list-with-config-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -131,17 +123,16 @@ func TestConfigListWithFormat(t *testing.T) {
})),
}, nil
},
}, buf)
})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "config-list-with-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFilter(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
assert.Equal(t, "foo", options.Filters.Get("name")[0])
@ -161,13 +152,12 @@ func TestConfigListWithFilter(t *testing.T) {
),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("filter", "name=foo")
cmd.Flags().Set("filter", "label=lbl1=Label-bar")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "config-list-with-filter.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -33,7 +33,7 @@ func TestConfigRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigRemoveCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
configRemoveFunc: tc.configRemoveFunc,
}, buf),
)
@ -47,7 +47,7 @@ func TestConfigRemoveWithName(t *testing.T) {
names := []string{"foo", "bar"}
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
return nil
@ -65,7 +65,7 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
if name == "foo" {

View File

@ -1,7 +1,6 @@
package container
import (
"bytes"
"io/ioutil"
"testing"
@ -68,8 +67,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}, buf))
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)

View File

@ -1,15 +1,14 @@
package container
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type arguments struct {
@ -79,9 +78,7 @@ func TestParseExec(t *testing.T) {
for valid, expectedExecConfig := range valids {
execConfig, err := parseExec(&valid.options, valid.execCmd)
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
if !compareExecConfig(expectedExecConfig, execConfig) {
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
}
@ -138,10 +135,7 @@ func TestNewExecCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
conf := configfile.ConfigFile{}
cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}, buf)
cli.SetConfigfile(&conf)
cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)

View File

@ -190,10 +190,11 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
}
close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
defer close()
if err != nil {
return err
}
defer close()
}
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)

View File

@ -243,7 +243,7 @@ func (c *diskUsageImagesContext) Reclaimable() string {
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
}
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
return units.HumanSize(float64(reclaimable))
}
type diskUsageContainersContext struct {
@ -305,7 +305,7 @@ func (c *diskUsageContainersContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
return units.HumanSize(float64(reclaimable))
}
type diskUsageVolumesContext struct {
@ -366,7 +366,7 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
return units.HumanSize(float64(reclaimable))
}
type diskUsageBuilderContext struct {

View File

@ -184,7 +184,7 @@ func (c *containerStatsContext) MemUsage() string {
return fmt.Sprintf("-- / --")
}
if c.os == winOSType {
return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
return units.BytesSize(c.s.Memory)
}
return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
}

View File

@ -38,7 +38,7 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild}, ioutil.Discard)
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
dockerfile := bytes.NewBufferString(`
FROM alpine:3.6
COPY foo /

View File

@ -1,7 +1,6 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
@ -38,8 +37,7 @@ func TestNewHistoryCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -90,13 +88,13 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cli := test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc})
cmd := NewHistoryCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
actual := cli.OutBuffer().String()
if tc.outputRegex == "" {
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("history-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)

View File

@ -37,7 +37,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -45,7 +45,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
func TestNewImportCommandInvalidFile(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
@ -92,7 +92,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())

View File

@ -27,7 +27,7 @@ func TestNewInspectCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -79,7 +79,7 @@ func TestNewInspectCommandSuccess(t *testing.T) {
for _, tc := range testCases {
imageInspectInvocationCount = 0
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -36,7 +36,7 @@ func TestNewImagesCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -81,8 +81,8 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigfile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cli := test.NewFakeCliWithOutput(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cmd := NewImagesCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
@ -95,7 +95,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
func TestNewListCommandAlias(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd := newListCommand(test.NewFakeCli(&fakeClient{}))
assert.True(t, cmd.HasAlias("images"))
assert.True(t, cmd.HasAlias("list"))
assert.False(t, cmd.HasAlias("other"))

View File

@ -43,7 +43,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, new(bytes.Buffer))
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
cli.In().SetIsTerminal(tc.isTerminalIn)
cmd := NewLoadCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -54,7 +54,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
func TestNewLoadCommandInvalidInput(t *testing.T) {
expectedError := "open *"
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"--input", "*"})
err := cmd.Execute()
@ -93,7 +93,7 @@ func TestNewLoadCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd := NewLoadCommand(test.NewFakeCliWithOutput(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -38,7 +38,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
@ -86,7 +86,7 @@ func TestNewPruneCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)

View File

@ -1,12 +1,10 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
@ -41,9 +39,7 @@ func TestNewPullCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{}, buf)
cli.SetConfigfile(configfile.New("filename"))
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
@ -66,15 +62,13 @@ func TestNewPullCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{}, buf)
cli.SetConfigfile(configfile.New("filename"))
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
actual := cli.OutBuffer().String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -1,13 +1,11 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
@ -47,9 +45,7 @@ func TestNewPushCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf)
cli.SetConfigfile(configfile.New("filename"))
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
cmd := NewPushCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
@ -68,13 +64,11 @@ func TestNewPushCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
}, buf)
cli.SetConfigfile(configfile.New("filename"))
})
cmd := NewPushCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)

View File

@ -1,7 +1,6 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
@ -15,7 +14,7 @@ import (
)
func TestNewRemoveCommandAlias(t *testing.T) {
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}))
assert.True(t, cmd.HasAlias("rmi"))
assert.True(t, cmd.HasAlias("remove"))
assert.False(t, cmd.HasAlias("other"))
@ -46,7 +45,7 @@ func TestNewRemoveCommandErrors(t *testing.T) {
for _, tc := range testCases {
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, new(bytes.Buffer)))
}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -97,18 +96,15 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
errBuf := new(bytes.Buffer)
fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc}, buf)
fakeCli.SetErr(errBuf)
fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
cmd := NewRemoveCommand(fakeCli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
if tc.expectedErrMsg != "" {
assert.Equal(t, tc.expectedErrMsg, errBuf.String())
assert.Equal(t, tc.expectedErrMsg, fakeCli.ErrBuffer().String())
}
actual := buf.String()
actual := fakeCli.OutBuffer().String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -45,7 +45,7 @@ func TestNewSaveCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}, new(bytes.Buffer))
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
cli.Out().SetIsTerminal(tc.isTerminal)
cmd := NewSaveCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -85,7 +85,7 @@ func TestNewSaveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
cmd := NewSaveCommand(test.NewFakeCliWithOutput(&fakeClient{
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},

View File

@ -1,7 +1,6 @@
package image
import (
"bytes"
"io/ioutil"
"testing"
@ -17,9 +16,8 @@ func TestCliNewTagCommandErrors(t *testing.T) {
{"image1", "image2", "image3"},
}
expectedError := "\"tag\" requires exactly 2 argument(s)."
buf := new(bytes.Buffer)
for _, args := range testCases {
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetArgs(args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), expectedError)
@ -27,7 +25,6 @@ func TestCliNewTagCommandErrors(t *testing.T) {
}
func TestCliNewTagCommand(t *testing.T) {
buf := new(bytes.Buffer)
cmd := NewTagCommand(
test.NewFakeCli(&fakeClient{
imageTagFunc: func(image string, ref string) error {
@ -35,7 +32,7 @@ func TestCliNewTagCommand(t *testing.T) {
assert.Equal(t, "image2", ref)
return nil
},
}, buf))
}))
cmd.SetArgs([]string{"image1", "image2"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())

View File

@ -21,6 +21,9 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
},
{
@ -45,6 +48,9 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -21,6 +21,9 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -0,0 +1,19 @@
package network
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
}
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
if c.networkCreateFunc != nil {
return c.networkCreateFunc(ctx, name, options)
}
return types.NetworkCreateResponse{}, nil
}

View File

@ -8,8 +8,7 @@ import (
)
// NewNetworkCommand returns a cobra command for `network` subcommands
// nolint: interfacer
func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
func NewNetworkCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "network",
Short: "Manage networks",

View File

@ -19,7 +19,7 @@ type connectOptions struct {
linklocalips []string
}
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
func newConnectCommand(dockerCli command.Cli) *cobra.Command {
options := connectOptions{
links: opts.NewListOpts(opts.ValidateLink),
}
@ -45,7 +45,7 @@ func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}
func runConnect(dockerCli *command.DockerCli, options connectOptions) error {
func runConnect(dockerCli command.Cli, options connectOptions) error {
client := dockerCli.Client()
epConfig := &network.EndpointSettings{

View File

@ -36,7 +36,7 @@ type createOptions struct {
ipamOpt opts.MapOpts
}
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
@ -82,7 +82,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}
func runCreate(dockerCli *command.DockerCli, options createOptions) error {
func runCreate(dockerCli command.Cli, options createOptions) error {
client := dockerCli.Client()
ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll())
@ -232,13 +232,13 @@ func subnetMatches(subnet, data string) (bool, error) {
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, errors.Errorf("Invalid subnet %s : %v", s, err)
return false, errors.Wrap(err, "invalid subnet")
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, errors.Errorf("Invalid cidr %s : %v", data, err)
return false, err
}
} else {
ip = net.ParseIP(data)

View File

@ -0,0 +1,175 @@
package network
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestNetworkCreateErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
expectedError string
}{
{
expectedError: "exactly 1 argument",
},
{
args: []string{"toto"},
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
return types.NetworkCreateResponse{}, errors.Errorf("error creating network")
},
expectedError: "error creating network",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0.30.50",
},
expectedError: "invalid CIDR address: 10.1.2.0.30.50",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0.30/24",
"gateway": "255.0.255.0/24",
"subnet": "255.0.0.0/24",
},
expectedError: "invalid CIDR address: 255.255.0.0.30/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0/23,10.1.3.248/30",
},
expectedError: "multiple overlapping subnet configuration is not supported",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24,192.168.1.200/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple ranges (192.168.1.200/24, 192.168.1.0/24) on the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.200.0/24,255.255.120.0/24",
"gateway": "255.0.255.0/24",
"subnet": "255.255.255.0/24,255.255.0.255/24",
},
expectedError: "no matching subnet for range 255.255.200.0/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple gateways (192.168.1.4, 192.168.1.1) for the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.4.1,192.168.5.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "no matching subnet for gateway 192.168.4.1",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.255.0.0/24",
"subnet": "255.255.0.0/24",
"aux-address": "255.255.0.30/24",
},
expectedError: "no matching subnet for aux-address",
},
}
for _, tc := range testCases {
cmd := newCreateCommand(
test.NewFakeCli(&fakeClient{
networkCreateFunc: tc.networkCreateFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
require.NoError(t, cmd.Flags().Set(key, value))
}
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNetworkCreateWithFlags(t *testing.T) {
expectedDriver := "foo"
expectedOpts := []network.IPAMConfig{
{
"192.168.4.0/24",
"192.168.4.0/24",
"192.168.4.1/24",
map[string]string{},
},
}
cli := test.NewFakeCli(&fakeClient{
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
assert.Equal(t, expectedDriver, createBody.Driver, "not expected driver error")
assert.Equal(t, expectedOpts, createBody.IPAM.Config, "not expected driver error")
return types.NetworkCreateResponse{
ID: name,
}, nil
},
})
args := []string{"banana"}
cmd := newCreateCommand(cli)
cmd.SetArgs(args)
cmd.Flags().Set("driver", "foo")
cmd.Flags().Set("ip-range", "192.168.4.0/24")
cmd.Flags().Set("gateway", "192.168.4.1/24")
cmd.Flags().Set("subnet", "192.168.4.0/24")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "banana", strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -14,7 +14,7 @@ type disconnectOptions struct {
force bool
}
func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command {
func newDisconnectCommand(dockerCli command.Cli) *cobra.Command {
opts := disconnectOptions{}
cmd := &cobra.Command{
@ -34,7 +34,7 @@ func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}
func runDisconnect(dockerCli *command.DockerCli, opts disconnectOptions) error {
func runDisconnect(dockerCli command.Cli, opts disconnectOptions) error {
client := dockerCli.Client()
return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)

View File

@ -16,7 +16,7 @@ type inspectOptions struct {
verbose bool
}
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
@ -35,7 +35,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client()
ctx := context.Background()

View File

@ -25,7 +25,7 @@ type listOptions struct {
filter opts.FilterOpt
}
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
func newListCommand(dockerCli command.Cli) *cobra.Command {
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -47,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}
func runList(dockerCli *command.DockerCli, options listOptions) error {
func runList(dockerCli command.Cli, options listOptions) error {
client := dockerCli.Client()
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), listOptions)

View File

@ -11,7 +11,7 @@ import (
"github.com/spf13/cobra"
)
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
return &cobra.Command{
Use: "rm NETWORK [NETWORK...]",
Aliases: []string{"remove"},
@ -28,7 +28,7 @@ const ingressWarning = "WARNING! Before removing the routing-mesh network, " +
"Otherwise, removal may not be effective and functionality of newly create " +
"ingress networks will be impaired.\nAre you sure you want to continue?"
func runRemove(dockerCli *command.DockerCli, networks []string) error {
func runRemove(dockerCli command.Cli, networks []string) error {
client := dockerCli.Client()
ctx := context.Background()
status := 0

View File

@ -1,7 +1,6 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -40,12 +39,11 @@ func TestNodeDemoteErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -53,7 +51,6 @@ func TestNodeDemoteErrors(t *testing.T) {
}
func TestNodeDemoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -65,13 +62,12 @@ func TestNodeDemoteNoChange(t *testing.T) {
}
return nil
},
}, buf))
}))
cmd.SetArgs([]string{"nodeID"})
assert.NoError(t, cmd.Execute())
}
func TestNodeDemoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -83,7 +79,7 @@ func TestNodeDemoteMultipleNode(t *testing.T) {
}
return nil
},
}, buf))
}))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -69,7 +69,7 @@ func TestNodeInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
infoFunc: tc.infoFunc,
}, buf))
@ -111,7 +111,7 @@ func TestNodeInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
}, buf))
cmd.SetArgs([]string{"nodeID"})

View File

@ -42,12 +42,10 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: tc.nodeListFunc,
infoFunc: tc.infoFunc,
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newListCommand(cli)
cmd.SetOutput(ioutil.Discard)
assert.EqualError(t, cmd.Execute(), tc.expectedError)
@ -55,7 +53,6 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
}
func TestNodeList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
@ -71,25 +68,25 @@ func TestNodeList(t *testing.T) {
},
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Contains(t, buf.String(), `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, buf.String(), `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, buf.String(), `nodeID3 nodeHostname3 Ready Active`)
out := cli.OutBuffer().String()
assert.Contains(t, out, `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, out, `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, out, `nodeID3 nodeHostname3 Ready Active`)
}
func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
@ -99,8 +96,7 @@ func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
// Test case for #24090
func TestNodeListContainsHostname(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cli := test.NewFakeCliWithOutput(&fakeClient{}, buf)
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Contains(t, buf.String(), "HOSTNAME")
@ -108,7 +104,7 @@ func TestNodeListContainsHostname(t *testing.T) {
func TestNodeListDefaultFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -124,7 +120,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
cli.SetConfigFile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)
@ -136,7 +132,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
func TestNodeListFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -151,7 +147,7 @@ func TestNodeListFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
cli.SetConfigFile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)

View File

@ -42,7 +42,7 @@ func TestNodePromoteErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
@ -55,7 +55,7 @@ func TestNodePromoteErrors(t *testing.T) {
func TestNodePromoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(Manager()), []byte{}, nil
},
@ -73,7 +73,7 @@ func TestNodePromoteNoChange(t *testing.T) {
func TestNodePromoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(), []byte{}, nil
},

View File

@ -5,7 +5,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
@ -88,11 +87,7 @@ func runPs(dockerCli command.Cli, options psOptions) error {
format := options.format
if len(format) == 0 {
if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
}
if len(errs) == 0 || len(tasks) != 0 {

View File

@ -52,7 +52,7 @@ func TestNodePsErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,
@ -116,7 +116,7 @@ func TestNodePs(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,

View File

@ -31,7 +31,7 @@ func TestNodeRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
nodeRemoveFunc: tc.nodeRemoveFunc,
}, buf))
cmd.SetArgs(tc.args)
@ -42,7 +42,7 @@ func TestNodeRemoveErrors(t *testing.T) {
func TestNodeRemoveMultiple(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd := newRemoveCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -1,7 +1,6 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -57,12 +56,11 @@ func TestNodeUpdateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
@ -158,12 +156,11 @@ func TestNodeUpdate(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)

View File

@ -7,29 +7,8 @@ import (
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
)
// NewContainerPruneCommand returns a cobra prune command for containers
func NewContainerPruneCommand(dockerCli command.Cli) *cobra.Command {
return container.NewPruneCommand(dockerCli)
}
// NewVolumePruneCommand returns a cobra prune command for volumes
func NewVolumePruneCommand(dockerCli command.Cli) *cobra.Command {
return volume.NewPruneCommand(dockerCli)
}
// NewImagePruneCommand returns a cobra prune command for images
func NewImagePruneCommand(dockerCli command.Cli) *cobra.Command {
return image.NewPruneCommand(dockerCli)
}
// NewNetworkPruneCommand returns a cobra prune command for Networks
func NewNetworkPruneCommand(dockerCli command.Cli) *cobra.Command {
return network.NewPruneCommand(dockerCli)
}
// RunContainerPrune executes a prune command for containers
func RunContainerPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return container.RunPrune(dockerCli, filter)

View File

@ -2,6 +2,8 @@ package registry
import (
"fmt"
"io/ioutil"
"strings"
"golang.org/x/net/context"
@ -16,6 +18,7 @@ type loginOptions struct {
serverAddress string
user string
password string
passwordStdin bool
}
// NewLoginCommand creates a new `docker login` command
@ -39,6 +42,7 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&opts.user, "username", "u", "", "Username")
flags.StringVarP(&opts.password, "password", "p", "", "Password")
flags.BoolVarP(&opts.passwordStdin, "password-stdin", "", false, "Take the password from stdin")
return cmd
}
@ -47,6 +51,27 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
ctx := context.Background()
clnt := dockerCli.Client()
if opts.password != "" {
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")
}
}
if opts.passwordStdin {
if opts.user == "" {
return errors.New("Must provide --username with --password-stdin")
}
contents, err := ioutil.ReadAll(dockerCli.In())
if err != nil {
return err
}
opts.password = strings.TrimSuffix(string(contents), "\n")
opts.password = strings.TrimSuffix(opts.password, "\r")
}
var (
serverAddress string
authServer = command.ElectAuthServer(ctx, dockerCli)

View File

@ -1,7 +1,6 @@
package command_test
import (
"bytes"
"testing"
"github.com/pkg/errors"
@ -63,13 +62,10 @@ func TestElectAuthServer(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{infoFunc: tc.infoFunc}, buf)
errBuf := new(bytes.Buffer)
cli.SetErr(errBuf)
cli := test.NewFakeCli(&fakeClient{infoFunc: tc.infoFunc})
server := ElectAuthServer(context.Background(), cli)
assert.Equal(t, tc.expectedAuthServer, server)
actual := errBuf.String()
actual := cli.ErrBuffer().String()
if tc.expectedWarning == "" {
assert.Empty(t, actual)
} else {

View File

@ -41,11 +41,10 @@ func TestSecretCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretCreateCommand(
test.NewFakeCli(&fakeClient{
secretCreateFunc: tc.secretCreateFunc,
}, buf),
}),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -57,7 +56,7 @@ func TestSecretCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
var actual []byte
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) {
if spec.Name != name {
return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name)
@ -87,7 +86,7 @@ func TestSecretCreateWithLabels(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) {
if spec.Name != name {
return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name)

View File

@ -55,7 +55,7 @@ func TestSecretInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,
}, buf),
)
@ -97,7 +97,7 @@ func TestSecretInspectWithoutFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,
}, buf),
)
@ -137,7 +137,7 @@ func TestSecretInspectWithFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,
}, buf),
)
@ -173,7 +173,7 @@ func TestSecretInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretInspectFunc: tc.secretInspectFunc,
}, buf))
cmd.SetArgs([]string{"secretID"})

View File

@ -38,7 +38,7 @@ func TestSecretListErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretListCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: tc.secretListFunc,
}, buf),
)
@ -50,7 +50,7 @@ func TestSecretListErrors(t *testing.T) {
func TestSecretList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
return []swarm.Secret{
*Secret(SecretID("ID-foo"),
@ -68,7 +68,6 @@ func TestSecretList(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newSecretListCommand(cli)
cmd.SetOutput(buf)
assert.NoError(t, cmd.Execute())
@ -79,7 +78,7 @@ func TestSecretList(t *testing.T) {
func TestSecretListWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
return []swarm.Secret{
*Secret(SecretID("ID-foo"), SecretName("foo")),
@ -89,7 +88,6 @@ func TestSecretListWithQuietOption(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newSecretListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
@ -100,7 +98,7 @@ func TestSecretListWithQuietOption(t *testing.T) {
func TestSecretListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
return []swarm.Secret{
*Secret(SecretID("ID-foo"), SecretName("foo")),
@ -110,7 +108,7 @@ func TestSecretListWithConfigFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
cli.SetConfigFile(&configfile.ConfigFile{
SecretFormat: "{{ .Name }} {{ .Labels }}",
})
cmd := newSecretListCommand(cli)
@ -122,7 +120,7 @@ func TestSecretListWithConfigFormat(t *testing.T) {
func TestSecretListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
return []swarm.Secret{
*Secret(SecretID("ID-foo"), SecretName("foo")),
@ -142,7 +140,7 @@ func TestSecretListWithFormat(t *testing.T) {
func TestSecretListWithFilter(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
assert.Equal(t, "foo", options.Filters.Get("name")[0], "foo")
assert.Equal(t, "lbl1=Label-bar", options.Filters.Get("label")[0])
@ -162,7 +160,6 @@ func TestSecretListWithFilter(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newSecretListCommand(cli)
cmd.Flags().Set("filter", "name=foo")
cmd.Flags().Set("filter", "label=lbl1=Label-bar")

View File

@ -33,7 +33,7 @@ func TestSecretRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newSecretRemoveCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
secretRemoveFunc: tc.secretRemoveFunc,
}, buf),
)
@ -47,7 +47,7 @@ func TestSecretRemoveWithName(t *testing.T) {
names := []string{"foo", "bar"}
buf := new(bytes.Buffer)
var removedSecrets []string
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretRemoveFunc: func(name string) error {
removedSecrets = append(removedSecrets, name)
return nil
@ -65,7 +65,7 @@ func TestSecretRemoveContinueAfterError(t *testing.T) {
buf := new(bytes.Buffer)
var removedSecrets []string
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
secretRemoveFunc: func(name string) error {
removedSecrets = append(removedSecrets, name)
if name == "foo" {

View File

@ -6,6 +6,7 @@ import (
"io"
"os"
"os/signal"
"strings"
"time"
"github.com/docker/docker/api/types"
@ -29,6 +30,13 @@ var (
swarm.TaskStateReady: 7,
swarm.TaskStateStarting: 8,
swarm.TaskStateRunning: 9,
// The following states are not actually shown in progress
// output, but are used internally for ordering.
swarm.TaskStateComplete: 10,
swarm.TaskStateShutdown: 11,
swarm.TaskStateFailed: 12,
swarm.TaskStateRejected: 13,
}
longestState int
@ -40,22 +48,26 @@ const (
)
type progressUpdater interface {
update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]swarm.Node, rollback bool) (bool, error)
update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]struct{}, rollback bool) (bool, error)
}
func init() {
for state := range numberedStates {
if len(state) > longestState {
if !terminalState(state) && len(state) > longestState {
longestState = len(state)
}
}
}
func terminalState(state swarm.TaskState) bool {
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
}
func stateToProgress(state swarm.TaskState, rollback bool) int64 {
if !rollback {
return numberedStates[state]
}
return int64(len(numberedStates)) - numberedStates[state]
return numberedStates[swarm.TaskStateRunning] - numberedStates[state]
}
// ServiceProgress outputs progress information for convergence of a service.
@ -192,16 +204,16 @@ func ServiceProgress(ctx context.Context, client client.APIClient, serviceID str
}
}
func getActiveNodes(ctx context.Context, client client.APIClient) (map[string]swarm.Node, error) {
func getActiveNodes(ctx context.Context, client client.APIClient) (map[string]struct{}, error) {
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return nil, err
}
activeNodes := make(map[string]swarm.Node)
activeNodes := make(map[string]struct{})
for _, n := range nodes {
if n.Status.State != swarm.NodeStateDown {
activeNodes[n.ID] = n
activeNodes[n.ID] = struct{}{}
}
}
return activeNodes, nil
@ -235,6 +247,18 @@ func writeOverallProgress(progressOut progress.Output, numerator, denominator in
})
}
func truncError(errMsg string) string {
// Remove newlines from the error, which corrupt the output.
errMsg = strings.Replace(errMsg, "\n", " ", -1)
// Limit the length to 75 characters, so that even on narrow terminals
// this will not overflow to the next line.
if len(errMsg) > 75 {
errMsg = errMsg[:74] + "…"
}
return errMsg
}
type replicatedProgressUpdater struct {
progressOut progress.Output
@ -246,8 +270,7 @@ type replicatedProgressUpdater struct {
done bool
}
// nolint: gocyclo
func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]swarm.Node, rollback bool) (bool, error) {
func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]struct{}, rollback bool) (bool, error) {
if service.Spec.Mode.Replicated == nil || service.Spec.Mode.Replicated.Replicas == nil {
return false, errors.New("no replica count")
}
@ -267,27 +290,7 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
u.initialized = true
}
// If there are multiple tasks with the same slot number, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
tasksBySlot := make(map[int]swarm.Task)
for _, task := range tasks {
if numberedStates[task.DesiredState] == 0 {
continue
}
if existingTask, ok := tasksBySlot[task.Slot]; ok {
if numberedStates[existingTask.DesiredState] <= numberedStates[task.DesiredState] {
continue
}
}
if task.NodeID != "" {
if _, nodeActive := activeNodes[task.NodeID]; nodeActive {
tasksBySlot[task.Slot] = task
}
} else {
tasksBySlot[task.Slot] = task
}
}
tasksBySlot := u.tasksBySlot(tasks, activeNodes)
// If we had reached a converged state, check if we are still converged.
if u.done {
@ -308,18 +311,11 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
u.slotMap[task.Slot] = mappedSlot
}
if !u.done && replicas <= maxProgressBars && uint64(mappedSlot) <= replicas {
u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", mappedSlot, replicas),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
Current: stateToProgress(task.Status.State, rollback),
Total: maxProgress,
HideCounts: true,
})
}
if task.Status.State == swarm.TaskStateRunning {
if !terminalState(task.DesiredState) && task.Status.State == swarm.TaskStateRunning {
running++
}
u.writeTaskProgress(task, mappedSlot, replicas, rollback)
}
if !u.done {
@ -333,6 +329,62 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
return running == replicas, nil
}
func (u *replicatedProgressUpdater) tasksBySlot(tasks []swarm.Task, activeNodes map[string]struct{}) map[int]swarm.Task {
// If there are multiple tasks with the same slot number, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
tasksBySlot := make(map[int]swarm.Task)
for _, task := range tasks {
if numberedStates[task.DesiredState] == 0 || numberedStates[task.Status.State] == 0 {
continue
}
if existingTask, ok := tasksBySlot[task.Slot]; ok {
if numberedStates[existingTask.DesiredState] < numberedStates[task.DesiredState] {
continue
}
// If the desired states match, observed state breaks
// ties. This can happen with the "start first" service
// update mode.
if numberedStates[existingTask.DesiredState] == numberedStates[task.DesiredState] &&
numberedStates[existingTask.Status.State] <= numberedStates[task.Status.State] {
continue
}
}
if task.NodeID != "" {
if _, nodeActive := activeNodes[task.NodeID]; !nodeActive {
continue
}
}
tasksBySlot[task.Slot] = task
}
return tasksBySlot
}
func (u *replicatedProgressUpdater) writeTaskProgress(task swarm.Task, mappedSlot int, replicas uint64, rollback bool) {
if u.done || replicas > maxProgressBars || uint64(mappedSlot) > replicas {
return
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", mappedSlot, replicas),
Action: truncError(task.Status.Err),
})
return
}
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", mappedSlot, replicas),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
Current: stateToProgress(task.Status.State, rollback),
Total: maxProgress,
HideCounts: true,
})
}
}
type globalProgressUpdater struct {
progressOut progress.Output
@ -340,22 +392,8 @@ type globalProgressUpdater struct {
done bool
}
func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]swarm.Node, rollback bool) (bool, error) {
// If there are multiple tasks with the same node ID, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
tasksByNode := make(map[string]swarm.Task)
for _, task := range tasks {
if numberedStates[task.DesiredState] == 0 {
continue
}
if existingTask, ok := tasksByNode[task.NodeID]; ok {
if numberedStates[existingTask.DesiredState] <= numberedStates[task.DesiredState] {
continue
}
}
tasksByNode[task.NodeID] = task
}
func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]struct{}, rollback bool) (bool, error) {
tasksByNode := u.tasksByNode(tasks)
// We don't have perfect knowledge of how many nodes meet the
// constraints for this service. But the orchestrator creates tasks
@ -392,19 +430,12 @@ func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task
running := 0
for _, task := range tasksByNode {
if node, nodeActive := activeNodes[task.NodeID]; nodeActive {
if !u.done && nodeCount <= maxProgressBars {
u.progressOut.WriteProgress(progress.Progress{
ID: stringid.TruncateID(node.ID),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
Current: stateToProgress(task.Status.State, rollback),
Total: maxProgress,
HideCounts: true,
})
}
if task.Status.State == swarm.TaskStateRunning {
if _, nodeActive := activeNodes[task.NodeID]; nodeActive {
if !terminalState(task.DesiredState) && task.Status.State == swarm.TaskStateRunning {
running++
}
u.writeTaskProgress(task, nodeCount, rollback)
}
}
@ -418,3 +449,56 @@ func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task
return running == nodeCount, nil
}
func (u *globalProgressUpdater) tasksByNode(tasks []swarm.Task) map[string]swarm.Task {
// If there are multiple tasks with the same node ID, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
tasksByNode := make(map[string]swarm.Task)
for _, task := range tasks {
if numberedStates[task.DesiredState] == 0 || numberedStates[task.Status.State] == 0 {
continue
}
if existingTask, ok := tasksByNode[task.NodeID]; ok {
if numberedStates[existingTask.DesiredState] < numberedStates[task.DesiredState] {
continue
}
// If the desired states match, observed state breaks
// ties. This can happen with the "start first" service
// update mode.
if numberedStates[existingTask.DesiredState] == numberedStates[task.DesiredState] &&
numberedStates[existingTask.Status.State] <= numberedStates[task.Status.State] {
continue
}
}
tasksByNode[task.NodeID] = task
}
return tasksByNode
}
func (u *globalProgressUpdater) writeTaskProgress(task swarm.Task, nodeCount int, rollback bool) {
if u.done || nodeCount > maxProgressBars {
return
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
ID: stringid.TruncateID(task.NodeID),
Action: truncError(task.Status.Err),
})
return
}
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
u.progressOut.WriteProgress(progress.Progress{
ID: stringid.TruncateID(task.NodeID),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
Current: stateToProgress(task.Status.State, rollback),
Total: maxProgress,
HideCounts: true,
})
}
}

View File

@ -0,0 +1,374 @@
package progress
import (
"fmt"
"strconv"
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/progress"
"github.com/stretchr/testify/assert"
)
type mockProgress struct {
p []progress.Progress
}
func (mp *mockProgress) WriteProgress(p progress.Progress) error {
mp.p = append(mp.p, p)
return nil
}
func (mp *mockProgress) clear() {
mp.p = nil
}
type updaterTester struct {
t *testing.T
updater progressUpdater
p *mockProgress
service swarm.Service
activeNodes map[string]struct{}
rollback bool
}
func (u updaterTester) testUpdater(tasks []swarm.Task, expectedConvergence bool, expectedProgress []progress.Progress) {
u.p.clear()
converged, err := u.updater.update(u.service, tasks, u.activeNodes, u.rollback)
assert.NoError(u.t, err)
assert.Equal(u.t, expectedConvergence, converged)
assert.Equal(u.t, expectedProgress, u.p.p)
}
func TestReplicatedProgressUpdaterOneReplica(t *testing.T) {
replicas := uint64(1)
service := swarm.Service{
Spec: swarm.ServiceSpec{
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{
Replicas: &replicas,
},
},
},
}
p := &mockProgress{}
updaterTester := updaterTester{
t: t,
updater: &replicatedProgressUpdater{
progressOut: p,
},
p: p,
activeNodes: map[string]struct{}{"a": {}, "b": {}},
service: service,
}
tasks := []swarm.Task{}
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: "0 out of 1 tasks"},
{ID: "1/1", Action: " "},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// Task with DesiredState beyond Running is ignored
tasks = append(tasks,
swarm.Task{ID: "1",
NodeID: "a",
DesiredState: swarm.TaskStateShutdown,
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
})
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// Task with valid DesiredState and State updates progress bar
tasks[0].DesiredState = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "1/1", Action: "new ", Current: 1, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// If the task exposes an error, we should show that instead of the
// progress bar.
tasks[0].Status.Err = "something is wrong"
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "1/1", Action: "something is wrong"},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// When the task reaches running, update should return true
tasks[0].Status.Err = ""
tasks[0].Status.State = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, true,
[]progress.Progress{
{ID: "1/1", Action: "running ", Current: 9, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "1 out of 1 tasks"},
})
// If the task fails, update should return false again
tasks[0].Status.Err = "task failed"
tasks[0].Status.State = swarm.TaskStateFailed
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "1/1", Action: "task failed"},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// If the task is restarted, progress output should be shown for the
// replacement task, not the old task.
tasks[0].DesiredState = swarm.TaskStateShutdown
tasks = append(tasks,
swarm.Task{ID: "2",
NodeID: "b",
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStateRunning},
})
updaterTester.testUpdater(tasks, true,
[]progress.Progress{
{ID: "1/1", Action: "running ", Current: 9, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "1 out of 1 tasks"},
})
// Add a new task while the current one is still running, to simulate
// "start-then-stop" updates.
tasks = append(tasks,
swarm.Task{ID: "3",
NodeID: "b",
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStatePreparing},
})
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "1/1", Action: "preparing", Current: 6, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
}
func TestReplicatedProgressUpdaterManyReplicas(t *testing.T) {
replicas := uint64(50)
service := swarm.Service{
Spec: swarm.ServiceSpec{
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{
Replicas: &replicas,
},
},
},
}
p := &mockProgress{}
updaterTester := updaterTester{
t: t,
updater: &replicatedProgressUpdater{
progressOut: p,
},
p: p,
activeNodes: map[string]struct{}{"a": {}, "b": {}},
service: service,
}
tasks := []swarm.Task{}
// No per-task progress bars because there are too many replicas
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", replicas)},
{ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", replicas)},
})
for i := 0; i != int(replicas); i++ {
tasks = append(tasks,
swarm.Task{
ID: strconv.Itoa(i),
Slot: i + 1,
NodeID: "a",
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
})
if i%2 == 1 {
tasks[i].NodeID = "b"
}
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i, replicas)},
})
tasks[i].Status.State = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, uint64(i) == replicas-1,
[]progress.Progress{
{ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i+1, replicas)},
})
}
}
func TestGlobalProgressUpdaterOneNode(t *testing.T) {
service := swarm.Service{
Spec: swarm.ServiceSpec{
Mode: swarm.ServiceMode{
Global: &swarm.GlobalService{},
},
},
}
p := &mockProgress{}
updaterTester := updaterTester{
t: t,
updater: &globalProgressUpdater{
progressOut: p,
},
p: p,
activeNodes: map[string]struct{}{"a": {}, "b": {}},
service: service,
}
tasks := []swarm.Task{}
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: "waiting for new tasks"},
})
// Task with DesiredState beyond Running is ignored
tasks = append(tasks,
swarm.Task{ID: "1",
NodeID: "a",
DesiredState: swarm.TaskStateShutdown,
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
})
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: "0 out of 1 tasks"},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// Task with valid DesiredState and State updates progress bar
tasks[0].DesiredState = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "a", Action: "new ", Current: 1, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// If the task exposes an error, we should show that instead of the
// progress bar.
tasks[0].Status.Err = "something is wrong"
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "a", Action: "something is wrong"},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// When the task reaches running, update should return true
tasks[0].Status.Err = ""
tasks[0].Status.State = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, true,
[]progress.Progress{
{ID: "a", Action: "running ", Current: 9, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "1 out of 1 tasks"},
})
// If the task fails, update should return false again
tasks[0].Status.Err = "task failed"
tasks[0].Status.State = swarm.TaskStateFailed
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "a", Action: "task failed"},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
// If the task is restarted, progress output should be shown for the
// replacement task, not the old task.
tasks[0].DesiredState = swarm.TaskStateShutdown
tasks = append(tasks,
swarm.Task{ID: "2",
NodeID: "a",
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStateRunning},
})
updaterTester.testUpdater(tasks, true,
[]progress.Progress{
{ID: "a", Action: "running ", Current: 9, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "1 out of 1 tasks"},
})
// Add a new task while the current one is still running, to simulate
// "start-then-stop" updates.
tasks = append(tasks,
swarm.Task{ID: "3",
NodeID: "a",
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStatePreparing},
})
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "a", Action: "preparing", Current: 6, Total: 9, HideCounts: true},
{ID: "overall progress", Action: "0 out of 1 tasks"},
})
}
func TestGlobalProgressUpdaterManyNodes(t *testing.T) {
nodes := 50
service := swarm.Service{
Spec: swarm.ServiceSpec{
Mode: swarm.ServiceMode{
Global: &swarm.GlobalService{},
},
},
}
p := &mockProgress{}
updaterTester := updaterTester{
t: t,
updater: &globalProgressUpdater{
progressOut: p,
},
p: p,
activeNodes: map[string]struct{}{},
service: service,
}
for i := 0; i != nodes; i++ {
updaterTester.activeNodes[strconv.Itoa(i)] = struct{}{}
}
tasks := []swarm.Task{}
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: "waiting for new tasks"},
})
for i := 0; i != nodes; i++ {
tasks = append(tasks,
swarm.Task{
ID: "task" + strconv.Itoa(i),
NodeID: strconv.Itoa(i),
DesiredState: swarm.TaskStateRunning,
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
})
}
updaterTester.testUpdater(tasks, false,
[]progress.Progress{
{ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", nodes)},
{ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", nodes)},
})
for i := 0; i != nodes; i++ {
tasks[i].Status.State = swarm.TaskStateRunning
updaterTester.testUpdater(tasks, i == nodes-1,
[]progress.Progress{
{ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i+1, nodes)},
})
}
}

View File

@ -5,7 +5,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/task"
@ -65,11 +64,7 @@ func runPS(dockerCli command.Cli, options psOptions) error {
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
}
if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil {
return err

View File

@ -107,7 +107,7 @@ func TestRunPSWarnsOnNotFound(t *testing.T) {
}
out := new(bytes.Buffer)
cli := test.NewFakeCli(client, out)
cli := test.NewFakeCliWithOutput(client, out)
options := psOptions{
services: []string{"foo", "bar"},
filter: opts.NewFilterOpt(),

View File

@ -12,17 +12,28 @@ import (
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type scaleOptions struct {
detach bool
}
func newScaleCommand(dockerCli *command.DockerCli) *cobra.Command {
return &cobra.Command{
options := &scaleOptions{}
cmd := &cobra.Command{
Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
Short: "Scale one or multiple replicated services",
Args: scaleArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runScale(dockerCli, args)
return runScale(dockerCli, cmd.Flags(), options, args)
},
}
flags := cmd.Flags()
addDetachFlag(flags, &options.detach)
return cmd
}
func scaleArgs(cmd *cobra.Command, args []string) error {
@ -43,8 +54,11 @@ func scaleArgs(cmd *cobra.Command, args []string) error {
return nil
}
func runScale(dockerCli *command.DockerCli, args []string) error {
func runScale(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *scaleOptions, args []string) error {
var errs []string
var serviceIDs []string
ctx := context.Background()
for _, arg := range args {
parts := strings.SplitN(arg, "=", 2)
serviceID, scaleStr := parts[0], parts[1]
@ -56,8 +70,23 @@ func runScale(dockerCli *command.DockerCli, args []string) error {
continue
}
if err := runServiceScale(dockerCli, serviceID, scale); err != nil {
if err := runServiceScale(ctx, dockerCli, serviceID, scale); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
} else {
serviceIDs = append(serviceIDs, serviceID)
}
}
if len(serviceIDs) > 0 {
if options.detach {
warnDetachDefault(dockerCli.Err(), dockerCli.Client().ClientVersion(), flags, "scaled")
} else {
for _, serviceID := range serviceIDs {
if err := waitOnService(ctx, dockerCli, serviceID, false); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
}
}
}
}
@ -67,9 +96,8 @@ func runScale(dockerCli *command.DockerCli, args []string) error {
return errors.Errorf(strings.Join(errs, "\n"))
}
func runServiceScale(dockerCli *command.DockerCli, serviceID string, scale uint64) error {
func runServiceScale(ctx context.Context, dockerCli *command.DockerCli, serviceID string, scale uint64) error {
client := dockerCli.Client()
ctx := context.Background()
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if err != nil {

View File

@ -15,6 +15,8 @@ import (
type fakeClient struct {
client.Client
version string
services []string
networks []string
secrets []string
@ -45,6 +47,10 @@ func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error)
}, nil
}
func (cli *fakeClient) ClientVersion() string {
return cli.version
}
func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
if cli.serviceListFunc != nil {
return cli.serviceListFunc(options)

View File

@ -1,7 +1,6 @@
package stack
import (
"bytes"
"testing"
"github.com/docker/cli/cli/compose/convert"
@ -18,10 +17,8 @@ func TestPruneServices(t *testing.T) {
"keep": {},
}
client := &fakeClient{services: []string{objectName("foo", "keep"), objectName("foo", "remove")}}
dockerCli := test.NewFakeCli(client, &bytes.Buffer{})
dockerCli.SetErr(&bytes.Buffer{})
dockerCli := test.NewFakeCli(client)
pruneServices(ctx, dockerCli, namespace, services)
assert.Equal(t, buildObjectIDs([]string{objectName("foo", "remove")}), client.removedServices)
}

View File

@ -50,7 +50,7 @@ func TestListErrors(t *testing.T) {
for _, tc := range testCases {
cmd := newListCommand(test.NewFakeCli(&fakeClient{
serviceListFunc: tc.serviceListFunc,
}, &bytes.Buffer{}))
}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
for key, value := range tc.flags {
@ -62,7 +62,7 @@ func TestListErrors(t *testing.T) {
func TestListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newListCommand(test.NewFakeCli(&fakeClient{
cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*Service(
@ -81,7 +81,7 @@ func TestListWithFormat(t *testing.T) {
func TestListWithoutFormat(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newListCommand(test.NewFakeCli(&fakeClient{
cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*Service(
@ -99,7 +99,7 @@ func TestListWithoutFormat(t *testing.T) {
func TestListOrder(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newListCommand(test.NewFakeCli(&fakeClient{
cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*Service(

View File

@ -5,7 +5,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
@ -58,17 +57,13 @@ func runPS(dockerCli command.Cli, options psOptions) error {
}
if len(tasks) == 0 {
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
return nil
}
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
}
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format)

View File

@ -1,7 +1,6 @@
package stack
import (
"bytes"
"io/ioutil"
"testing"
"time"
@ -45,7 +44,7 @@ func TestStackPsErrors(t *testing.T) {
for _, tc := range testCases {
cmd := newPsCommand(test.NewFakeCli(&fakeClient{
taskListFunc: tc.taskListFunc,
}, &bytes.Buffer{}))
}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -53,55 +52,52 @@ func TestStackPsErrors(t *testing.T) {
}
func TestStackPsEmptyStack(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPsCommand(test.NewFakeCli(&fakeClient{
fakeCli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{}, nil
},
}, buf))
})
cmd := newPsCommand(fakeCli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
testutil.EqualNormalizedString(t, testutil.RemoveSpace, buf.String(), "Nothing found in stack: foo")
assert.Equal(t, "", fakeCli.OutBuffer().String())
assert.Equal(t, "Nothing found in stack: foo\n", fakeCli.ErrBuffer().String())
}
func TestStackPsWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("id-foo"))}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-with-quiet-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackPsWithNoTruncOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-trunc", "true")
cmd.Flags().Set("format", "{{ .ID }}")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-with-no-trunc-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackPsWithNoResolveOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
@ -111,55 +107,50 @@ func TestStackPsWithNoResolveOption(t *testing.T) {
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-resolve", "true")
cmd.Flags().Set("format", "{{ .Node }}")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-with-no-resolve-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackPsWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("format", "{{ .Name }}")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-with-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackPsWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
})
cli.SetConfigFile(&configfile.ConfigFile{
TasksFormat: "{{ .Name }}",
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-with-config-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackPsWithoutFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
@ -174,12 +165,11 @@ func TestStackPsWithoutFormat(t *testing.T) {
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newPsCommand(cli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-ps-without-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -51,20 +51,16 @@ func runRemove(dockerCli command.Cli, opts removeOptions) error {
return err
}
secrets, err := getStackSecrets(ctx, client, namespace)
if err != nil {
return err
var secrets []swarm.Secret
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
secrets, err = getStackSecrets(ctx, client, namespace)
if err != nil {
return err
}
}
var configs []swarm.Config
version, err := client.ServerVersion(ctx)
if err != nil {
return err
}
if versions.LessThan(version.APIVersion, "1.30") {
fmt.Fprintf(dockerCli.Err(), "WARNING: ignoring \"configs\" (requires API version 1.30, but the Docker daemon API version is %s)\n", version.APIVersion)
} else {
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
configs, err = getStackConfigs(ctx, client, namespace)
if err != nil {
return err
@ -72,7 +68,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions) error {
}
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
continue
}
@ -97,14 +93,15 @@ func removeServices(
dockerCli command.Cli,
services []swarm.Service,
) bool {
var err error
var hasError bool
for _, service := range services {
fmt.Fprintf(dockerCli.Err(), "Removing service %s\n", service.Spec.Name)
if err = dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
}
}
return err != nil
return hasError
}
func removeNetworks(
@ -112,14 +109,15 @@ func removeNetworks(
dockerCli command.Cli,
networks []types.NetworkResource,
) bool {
var err error
var hasError bool
for _, network := range networks {
fmt.Fprintf(dockerCli.Err(), "Removing network %s\n", network.Name)
if err = dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
}
}
return err != nil
return hasError
}
func removeSecrets(
@ -127,14 +125,15 @@ func removeSecrets(
dockerCli command.Cli,
secrets []swarm.Secret,
) bool {
var err error
var hasError bool
for _, secret := range secrets {
fmt.Fprintf(dockerCli.Err(), "Removing secret %s\n", secret.Spec.Name)
if err = dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
}
}
return err != nil
return hasError
}
func removeConfigs(
@ -142,12 +141,13 @@ func removeConfigs(
dockerCli command.Cli,
configs []swarm.Config,
) bool {
var err error
var hasError bool
for _, config := range configs {
fmt.Fprintf(dockerCli.Err(), "Removing config %s\n", config.Spec.Name)
if err = dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
}
}
return err != nil
return hasError
}

View File

@ -1,7 +1,6 @@
package stack
import (
"bytes"
"errors"
"io/ioutil"
"strings"
@ -11,53 +10,73 @@ import (
"github.com/stretchr/testify/assert"
)
func TestRemoveStack(t *testing.T) {
func fakeClientForRemoveStackTest(version string) *fakeClient {
allServices := []string{
objectName("foo", "service1"),
objectName("foo", "service2"),
objectName("bar", "service1"),
objectName("bar", "service2"),
}
allServiceIDs := buildObjectIDs(allServices)
allNetworks := []string{
objectName("foo", "network1"),
objectName("bar", "network1"),
}
allNetworkIDs := buildObjectIDs(allNetworks)
allSecrets := []string{
objectName("foo", "secret1"),
objectName("foo", "secret2"),
objectName("bar", "secret1"),
}
allSecretIDs := buildObjectIDs(allSecrets)
allConfigs := []string{
objectName("foo", "config1"),
objectName("foo", "config2"),
objectName("bar", "config1"),
}
allConfigIDs := buildObjectIDs(allConfigs)
cli := &fakeClient{
return &fakeClient{
version: version,
services: allServices,
networks: allNetworks,
secrets: allSecrets,
configs: allConfigs,
}
cmd := newRemoveCommand(test.NewFakeCli(cli, &bytes.Buffer{}))
}
func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) {
client := fakeClientForRemoveStackTest("1.24")
cmd := newRemoveCommand(test.NewFakeCli(client))
cmd.SetArgs([]string{"foo", "bar"})
assert.NoError(t, cmd.Execute())
assert.Equal(t, allServiceIDs, cli.removedServices)
assert.Equal(t, allNetworkIDs, cli.removedNetworks)
assert.Equal(t, allSecretIDs, cli.removedSecrets)
assert.Equal(t, allConfigIDs, cli.removedConfigs)
assert.Equal(t, buildObjectIDs(client.services), client.removedServices)
assert.Equal(t, buildObjectIDs(client.networks), client.removedNetworks)
assert.Nil(t, client.removedSecrets)
assert.Nil(t, client.removedConfigs)
}
func TestSkipEmptyStack(t *testing.T) {
buf := new(bytes.Buffer)
func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) {
client := fakeClientForRemoveStackTest("1.25")
cmd := newRemoveCommand(test.NewFakeCli(client))
cmd.SetArgs([]string{"foo", "bar"})
assert.NoError(t, cmd.Execute())
assert.Equal(t, buildObjectIDs(client.services), client.removedServices)
assert.Equal(t, buildObjectIDs(client.networks), client.removedNetworks)
assert.Equal(t, buildObjectIDs(client.secrets), client.removedSecrets)
assert.Nil(t, client.removedConfigs)
}
func TestRemoveStackVersion130RemovesEverything(t *testing.T) {
client := fakeClientForRemoveStackTest("1.30")
cmd := newRemoveCommand(test.NewFakeCli(client))
cmd.SetArgs([]string{"foo", "bar"})
assert.NoError(t, cmd.Execute())
assert.Equal(t, buildObjectIDs(client.services), client.removedServices)
assert.Equal(t, buildObjectIDs(client.networks), client.removedNetworks)
assert.Equal(t, buildObjectIDs(client.secrets), client.removedSecrets)
assert.Equal(t, buildObjectIDs(client.configs), client.removedConfigs)
}
func TestRemoveStackSkipEmpty(t *testing.T) {
allServices := []string{objectName("bar", "service1"), objectName("bar", "service2")}
allServiceIDs := buildObjectIDs(allServices)
@ -70,21 +89,24 @@ func TestSkipEmptyStack(t *testing.T) {
allConfigs := []string{objectName("bar", "config1")}
allConfigIDs := buildObjectIDs(allConfigs)
cli := &fakeClient{
fakeClient := &fakeClient{
version: "1.30",
services: allServices,
networks: allNetworks,
secrets: allSecrets,
configs: allConfigs,
}
cmd := newRemoveCommand(test.NewFakeCli(cli, buf))
fakeCli := test.NewFakeCli(fakeClient)
cmd := newRemoveCommand(fakeCli)
cmd.SetArgs([]string{"foo", "bar"})
assert.NoError(t, cmd.Execute())
assert.Contains(t, buf.String(), "Nothing found in stack: foo")
assert.Equal(t, allServiceIDs, cli.removedServices)
assert.Equal(t, allNetworkIDs, cli.removedNetworks)
assert.Equal(t, allSecretIDs, cli.removedSecrets)
assert.Equal(t, allConfigIDs, cli.removedConfigs)
assert.Equal(t, "", fakeCli.OutBuffer().String())
assert.Contains(t, fakeCli.ErrBuffer().String(), "Nothing found in stack: foo\n")
assert.Equal(t, allServiceIDs, fakeClient.removedServices)
assert.Equal(t, allNetworkIDs, fakeClient.removedNetworks)
assert.Equal(t, allSecretIDs, fakeClient.removedSecrets)
assert.Equal(t, allConfigIDs, fakeClient.removedConfigs)
}
func TestRemoveContinueAfterError(t *testing.T) {
@ -102,6 +124,7 @@ func TestRemoveContinueAfterError(t *testing.T) {
removedServices := []string{}
cli := &fakeClient{
version: "1.30",
services: allServices,
networks: allNetworks,
secrets: allSecrets,
@ -116,7 +139,7 @@ func TestRemoveContinueAfterError(t *testing.T) {
return nil
},
}
cmd := newRemoveCommand(test.NewFakeCli(cli, &bytes.Buffer{}))
cmd := newRemoveCommand(test.NewFakeCli(cli))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"foo", "bar"})

View File

@ -51,11 +51,9 @@ func runServices(dockerCli command.Cli, options servicesOptions) error {
return err
}
out := dockerCli.Out()
// if no services in this stack, print message and exit 0
if len(services) == 0 {
fmt.Fprintf(out, "Nothing found in stack: %s\n", options.namespace)
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", options.namespace)
return nil
}

View File

@ -1,7 +1,6 @@
package stack
import (
"bytes"
"io/ioutil"
"testing"
@ -70,8 +69,7 @@ func TestStackServicesErrors(t *testing.T) {
serviceListFunc: tc.serviceListFunc,
nodeListFunc: tc.nodeListFunc,
taskListFunc: tc.taskListFunc,
}, &bytes.Buffer{})
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newServicesCommand(cli)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
@ -83,77 +81,70 @@ func TestStackServicesErrors(t *testing.T) {
}
func TestStackServicesEmptyServiceList(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newServicesCommand(
test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{}, nil
},
}, buf),
)
fakeCli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{}, nil
},
})
cmd := newServicesCommand(fakeCli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
testutil.EqualNormalizedString(t, testutil.RemoveSpace, buf.String(), "Nothing found in stack: foo")
assert.Equal(t, "", fakeCli.OutBuffer().String())
assert.Equal(t, "Nothing found in stack: foo\n", fakeCli.ErrBuffer().String())
}
func TestStackServicesWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*Service(ServiceID("id-foo"))}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newServicesCommand(cli)
cmd.Flags().Set("quiet", "true")
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-services-with-quiet-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackServicesWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*Service(ServiceName("service-name-foo")),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newServicesCommand(cli)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("format", "{{ .Name }}")
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-services-with-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackServicesWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*Service(ServiceName("service-name-foo")),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
})
cli.SetConfigFile(&configfile.ConfigFile{
ServicesFormat: "{{ .Name }}",
})
cmd := newServicesCommand(cli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-services-with-config-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestStackServicesWithoutFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*Service(
@ -169,12 +160,11 @@ func TestStackServicesWithoutFormat(t *testing.T) {
}),
)}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
})
cmd := newServicesCommand(cli)
cmd.SetArgs([]string{"foo"})
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "stack-services-without-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -60,7 +60,7 @@ func runCA(dockerCli command.Cli, flags *pflag.FlagSet, opts caOptions) error {
}
if !opts.rotate {
for _, f := range []string{flagCACert, flagCAKey, flagCACert, flagExternalCA} {
for _, f := range []string{flagCACert, flagCAKey, flagCertExpiry, flagExternalCA} {
if flags.Changed(f) {
return fmt.Errorf("`--%s` flag requires the `--rotate` flag to update the CA", f)
}

View File

@ -2,10 +2,14 @@ package swarm
import (
"bytes"
"io/ioutil"
"os"
"testing"
"time"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -34,6 +38,69 @@ func TestDisplayTrustRootNoRoot(t *testing.T) {
assert.EqualError(t, err, "No CA information available")
}
func TestDisplayTrustRootInvalidFlags(t *testing.T) {
// we need an actual PEMfile to test
tmpfile, err := ioutil.TempFile("", "pemfile")
assert.NoError(t, err)
defer os.Remove(tmpfile.Name())
tmpfile.Write([]byte(`
-----BEGIN CERTIFICATE-----
MIIBajCCARCgAwIBAgIUe0+jYWhxN8fFOByC7yveIYgvx1kwCgYIKoZIzj0EAwIw
EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwNjI3MTUxNDAwWhcNMzcwNjIyMTUx
NDAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABGgbOZLd7b4b262+6m4ignIecbAZKim6djNiIS1Kl5IHciXYn7gnSpsayjn7
GQABpgkdPeM9TEQowmtR1qSnORujQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQ6Rtcn823/fxRZyheRDFpDzuBMpTAKBggqhkjO
PQQDAgNIADBFAiEAqD3Kb2rgsy6NoTk+zEgcUi/aGBCsvQDG3vML1PXN8j0CIBjj
4nDj+GmHXcnKa8wXx70Z8OZEpRQIiKDDLmcXuslp
-----END CERTIFICATE-----
`))
tmpfile.Close()
errorTestCases := [][]string{
{
"--ca-cert=" + tmpfile.Name(),
},
{
"--ca-key=" + tmpfile.Name(),
},
{ // to make sure we're not erroring because we didn't provide a CA key along with the CA cert
"--ca-cert=" + tmpfile.Name(),
"--ca-key=" + tmpfile.Name(),
},
{
"--cert-expiry=2160h0m0s",
},
{
"--external-ca=protocol=cfssl,url=https://some.com/https/url",
},
{ // to make sure we're not erroring because we didn't provide a CA cert and external CA
"--ca-cert=" + tmpfile.Name(),
"--external-ca=protocol=cfssl,url=https://some.com/https/url",
},
}
for _, args := range errorTestCases {
cmd := newCACommand(
test.NewFakeCli(&fakeClient{
swarmInspectFunc: func() (swarm.Swarm, error) {
return swarm.Swarm{
ClusterInfo: swarm.ClusterInfo{
TLSInfo: swarm.TLSInfo{
TrustRoot: "root",
},
},
}, nil
},
}))
assert.NoError(t, cmd.Flags().Parse(args))
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "flag requires the `--rotate` flag to update the CA")
}
}
func TestDisplayTrustRoot(t *testing.T) {
buffer := new(bytes.Buffer)
trustRoot := "trustme"

View File

@ -67,7 +67,7 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInitCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
swarmInitFunc: tc.swarmInitFunc,
swarmInspectFunc: tc.swarmInspectFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
@ -114,7 +114,7 @@ func TestSwarmInit(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInitCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
swarmInitFunc: tc.swarmInitFunc,
swarmInspectFunc: tc.swarmInspectFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,

View File

@ -51,7 +51,7 @@ func TestSwarmJoinErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newJoinCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
swarmJoinFunc: tc.swarmJoinFunc,
infoFunc: tc.infoFunc,
}, buf))
@ -93,7 +93,7 @@ func TestSwarmJoin(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newJoinCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
infoFunc: tc.infoFunc,
}, buf))
cmd.SetArgs([]string{"remote"})

View File

@ -92,7 +92,7 @@ func TestSwarmJoinTokenErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newJoinTokenCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
infoFunc: tc.infoFunc,
@ -200,7 +200,7 @@ func TestSwarmJoinToken(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newJoinTokenCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,

View File

@ -1,7 +1,6 @@
package swarm
import (
"bytes"
"io/ioutil"
"strings"
"testing"
@ -33,11 +32,10 @@ func TestSwarmLeaveErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newLeaveCommand(
test.NewFakeCli(&fakeClient{
swarmLeaveFunc: tc.swarmLeaveFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -45,9 +43,8 @@ func TestSwarmLeaveErrors(t *testing.T) {
}
func TestSwarmLeave(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newLeaveCommand(
test.NewFakeCli(&fakeClient{}, buf))
cli := test.NewFakeCli(&fakeClient{})
cmd := newLeaveCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Equal(t, "Node left the swarm.", strings.TrimSpace(buf.String()))
assert.Equal(t, "Node left the swarm.", strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -1,7 +1,6 @@
package swarm
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
@ -83,13 +82,12 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUnlockKeyCommand(
test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
@ -158,19 +156,18 @@ func TestSwarmUnlockKey(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUnlockKeyCommand(
test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
}, buf))
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
})
cmd := newUnlockKeyCommand(cli)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), fmt.Sprintf("unlockkeys-%s.golden", tc.name))
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -1,7 +1,6 @@
package swarm
import (
"bytes"
"io/ioutil"
"strings"
"testing"
@ -66,12 +65,11 @@ func TestSwarmUnlockErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUnlockCommand(
test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
swarmUnlockFunc: tc.swarmUnlockFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -80,7 +78,6 @@ func TestSwarmUnlockErrors(t *testing.T) {
func TestSwarmUnlock(t *testing.T) {
input := "unlockKey"
buf := new(bytes.Buffer)
dockerCli := test.NewFakeCli(&fakeClient{
infoFunc: func() (types.Info, error) {
return types.Info{
@ -95,7 +92,7 @@ func TestSwarmUnlock(t *testing.T) {
}
return nil
},
}, buf)
})
dockerCli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := newUnlockCommand(dockerCli)
assert.NoError(t, cmd.Execute())

View File

@ -1,7 +1,6 @@
package swarm
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
@ -68,13 +67,12 @@ func TestSwarmUpdateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
}, buf))
}))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
@ -164,20 +162,19 @@ func TestSwarmUpdate(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
}, buf))
cli := test.NewFakeCli(&fakeClient{
swarmInspectFunc: tc.swarmInspectFunc,
swarmUpdateFunc: tc.swarmUpdateFunc,
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
})
cmd := newUpdateCommand(cli)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(buf)
cmd.SetOutput(cli.OutBuffer())
assert.NoError(t, cmd.Execute())
actual := buf.String()
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), fmt.Sprintf("update-%s.golden", tc.name))
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -4,12 +4,12 @@ import (
"fmt"
"sort"
"golang.org/x/net/context"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types/swarm"
"golang.org/x/net/context"
)
type tasksBySlot []swarm.Task
@ -82,3 +82,12 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol
return formatter.TaskWrite(tasksCtx, tasks, names, nodes)
}
// DefaultFormat returns the default format from the config file, or table
// format if nothing is set in the config.
func DefaultFormat(configFile *configfile.ConfigFile, quiet bool) string {
if len(configFile.TasksFormat) > 0 && !quiet {
return configFile.TasksFormat
}
return formatter.TableFormatKey
}

View File

@ -24,7 +24,7 @@ func TestTaskPrintWithQuietOption(t *testing.T) {
noResolve := true
buf := new(bytes.Buffer)
apiClient := &fakeClient{}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(TaskID("id-foo")),
}
@ -41,7 +41,7 @@ func TestTaskPrintWithNoTruncOption(t *testing.T) {
noResolve := true
buf := new(bytes.Buffer)
apiClient := &fakeClient{}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(TaskID("id-foo-yov6omdek8fg3k5stosyp2m50")),
}
@ -58,7 +58,7 @@ func TestTaskPrintWithGlobalService(t *testing.T) {
noResolve := true
buf := new(bytes.Buffer)
apiClient := &fakeClient{}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(TaskServiceID("service-id-foo"), TaskNodeID("node-id-bar"), TaskSlot(0)),
}
@ -75,7 +75,7 @@ func TestTaskPrintWithReplicatedService(t *testing.T) {
noResolve := true
buf := new(bytes.Buffer)
apiClient := &fakeClient{}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(TaskServiceID("service-id-foo"), TaskSlot(1)),
}
@ -99,7 +99,7 @@ func TestTaskPrintWithIndentation(t *testing.T) {
return *Node(NodeName("node-name-bar")), nil, nil
},
}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(
TaskID("id-foo"),
@ -138,7 +138,7 @@ func TestTaskPrintWithResolution(t *testing.T) {
return *Node(NodeName("node-name-bar")), nil, nil
},
}
cli := test.NewFakeCli(apiClient, buf)
cli := test.NewFakeCliWithOutput(apiClient, buf)
tasks := []swarm.Task{
*Task(TaskServiceID("service-id-foo"), TaskSlot(1)),
}

View File

@ -41,11 +41,10 @@ func TestVolumeCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newCreateCommand(
test.NewFakeCli(&fakeClient{
volumeCreateFunc: tc.volumeCreateFunc,
}, buf),
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
@ -59,7 +58,7 @@ func TestVolumeCreateErrors(t *testing.T) {
func TestVolumeCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
if body.Name != name {
return types.Volume{}, errors.Errorf("expected name %q, got %q", name, body.Name)
@ -97,7 +96,7 @@ func TestVolumeCreateWithFlags(t *testing.T) {
name := "banana"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
if body.Name != "" {
return types.Volume{}, errors.Errorf("expected empty name, got %q", body.Name)

View File

@ -56,7 +56,7 @@ func TestVolumeInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)
@ -98,7 +98,7 @@ func TestVolumeInspectWithoutFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)
@ -138,7 +138,7 @@ func TestVolumeInspectWithFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)

View File

@ -39,7 +39,7 @@ func TestVolumeListErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newListCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumeListFunc: tc.volumeListFunc,
}, buf),
)
@ -54,7 +54,7 @@ func TestVolumeListErrors(t *testing.T) {
func TestVolumeListWithoutFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
@ -67,7 +67,6 @@ func TestVolumeListWithoutFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
actual := buf.String()
@ -77,7 +76,7 @@ func TestVolumeListWithoutFormat(t *testing.T) {
func TestVolumeListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
@ -90,7 +89,7 @@ func TestVolumeListWithConfigFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
cli.SetConfigFile(&configfile.ConfigFile{
VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}",
})
cmd := newListCommand(cli)
@ -102,7 +101,7 @@ func TestVolumeListWithConfigFormat(t *testing.T) {
func TestVolumeListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
@ -115,7 +114,6 @@ func TestVolumeListWithFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")
assert.NoError(t, cmd.Execute())

View File

@ -41,7 +41,7 @@ func TestVolumePruneErrors(t *testing.T) {
}
for _, tc := range testCases {
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}, ioutil.Discard),
)
@ -70,7 +70,7 @@ func TestVolumePruneForce(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}, buf),
)
@ -88,7 +88,7 @@ func TestVolumePrunePromptYes(t *testing.T) {
}
for _, input := range []string{"y", "Y"} {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumePruneFunc: simplePruneFunc,
}, buf)
@ -110,7 +110,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
}
for _, input := range []string{"n", "N", "no", "anything", "really"} {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCliWithOutput(&fakeClient{
volumePruneFunc: simplePruneFunc,
}, buf)

View File

@ -31,7 +31,7 @@ func TestVolumeRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(
test.NewFakeCli(&fakeClient{
test.NewFakeCliWithOutput(&fakeClient{
volumeRemoveFunc: tc.volumeRemoveFunc,
}, buf))
cmd.SetArgs(tc.args)
@ -42,7 +42,7 @@ func TestVolumeRemoveErrors(t *testing.T) {
func TestNodeRemoveMultiple(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd := newRemoveCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd.SetArgs([]string{"volume1", "volume2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -1,6 +1,7 @@
package test
import (
"bytes"
"io"
"io/ioutil"
"strings"
@ -16,17 +17,29 @@ type FakeCli struct {
client client.APIClient
configfile *configfile.ConfigFile
out *command.OutStream
err io.Writer
outBuffer *bytes.Buffer
err *bytes.Buffer
in *command.InStream
server command.ServerInfo
}
// NewFakeCli returns a Cli backed by the fakeCli
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
// NewFakeCliWithOutput returns a Cli backed by the fakeCli
// Deprecated: Use NewFakeCli
func NewFakeCliWithOutput(client client.APIClient, out io.Writer) *FakeCli {
cli := NewFakeCli(client)
cli.out = command.NewOutStream(out)
return cli
}
// NewFakeCli returns a fake for the command.Cli interface
func NewFakeCli(client client.APIClient) *FakeCli {
outBuffer := new(bytes.Buffer)
errBuffer := new(bytes.Buffer)
return &FakeCli{
client: client,
out: command.NewOutStream(out),
err: ioutil.Discard,
out: command.NewOutStream(outBuffer),
outBuffer: outBuffer,
err: errBuffer,
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
configfile: configfile.New("configfile"),
}
@ -38,12 +51,12 @@ func (c *FakeCli) SetIn(in *command.InStream) {
}
// SetErr sets the stderr stream for the cli to the specified io.Writer
func (c *FakeCli) SetErr(err io.Writer) {
func (c *FakeCli) SetErr(err *bytes.Buffer) {
c.err = err
}
// SetConfigfile sets the "fake" config file
func (c *FakeCli) SetConfigfile(configfile *configfile.ConfigFile) {
// SetConfigFile sets the "fake" config file
func (c *FakeCli) SetConfigFile(configfile *configfile.ConfigFile) {
c.configfile = configfile
}
@ -76,3 +89,13 @@ func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
func (c *FakeCli) ServerInfo() command.ServerInfo {
return c.server
}
// OutBuffer returns the stdout buffer
func (c *FakeCli) OutBuffer() *bytes.Buffer {
return c.outBuffer
}
// ErrBuffer Buffer returns the stderr buffer
func (c *FakeCli) ErrBuffer() *bytes.Buffer {
return c.err
}

View File

@ -636,18 +636,40 @@ __docker_complete_resolved_hostname() {
COMPREPLY=( $(host 2>/dev/null "${cur%:}" | awk '/has address/ {print $4}') )
}
# __docker_local_interfaces returns a list of the names and addresses of all
# local network interfaces.
# If `--ip-only` is passed as a first argument, only addresses are returned.
__docker_local_interfaces() {
command -v ip >/dev/null 2>&1 || return
ip addr show scope global 2>/dev/null | sed -n 's| \+inet \([0-9.]\+\).* \([^ ]\+\)|\1 \2|p'
local format
if [ "$1" = "--ip-only" ] ; then
format='\1'
shift
else
format='\1 \2'
fi
ip addr show scope global 2>/dev/null | sed -n "s| \+inet \([0-9.]\+\).* \([^ ]\+\)|$format|p"
}
# __docker_complete_local_interfaces applies completion of the names and addresses of all
# local network interfaces based on the current value of `$cur`.
# An additional value can be added to the possible completions with an `--add` argument.
__docker_complete_local_interfaces() {
local additional_interface
if [ "$1" = "--add" ] ; then
additional_interface="$2"
shift 2
fi
COMPREPLY=( $( compgen -W "$(__docker_local_interfaces) $additional_interface" -- "$cur" ) )
COMPREPLY=( $( compgen -W "$(__docker_local_interfaces "$@") $additional_interface" -- "$cur" ) )
}
# __docker_complete_local_ips applies completion of the addresses of all local network
# interfaces based on the current value of `$cur`.
__docker_complete_local_ips() {
__docker_complete_local_interfaces --ip-only
}
# __docker_complete_capabilities_addable completes Linux capabilities which are
@ -1413,7 +1435,7 @@ _docker_container_port() {
_docker_container_prune() {
case "$prev" in
--filter)
COMPREPLY=( $( compgen -W "until" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "label label! until" -S = -- "$cur" ) )
__docker_nospace
return
;;
@ -1962,9 +1984,11 @@ _docker_daemon() {
--iptables=false
--ipv6
--live-restore
--no-new-privileges
--raw-logs
--selinux-enabled
--userland-proxy=false
--version -v
"
local options_with_args="
$global_options_with_args
@ -1980,9 +2004,12 @@ _docker_daemon() {
--cluster-store-opt
--config-file
--containerd
--cpu-rt-period
--cpu-rt-runtime
--data-root
--default-gateway
--default-gateway-v6
--default-runtime
--default-shm-size
--default-ulimit
--dns
@ -2001,6 +2028,7 @@ _docker_daemon() {
--log-opt
--max-concurrent-downloads
--max-concurrent-uploads
--metrics-addr
--mtu
--oom-score-adjust
--pidfile -p
@ -2009,6 +2037,7 @@ _docker_daemon() {
--shutdown-timeout
--storage-driver -s
--storage-opt
--swarm-default-advertise-addr
--userland-proxy-path
--userns-remap
"
@ -2069,7 +2098,7 @@ _docker_daemon() {
return
;;
--storage-driver|-s)
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
return
;;
--storage-opt)
@ -2094,11 +2123,14 @@ _docker_daemon() {
dm.use_deferred_deletion
dm.use_deferred_removal
"
local overlay2_options="overlay2.size"
local zfs_options="zfs.fsname"
local all_options="$btrfs_options $devicemapper_options $overlay2_options $zfs_options"
case $(__docker_value_of_option '--storage-driver|-s') in
'')
COMPREPLY=( $( compgen -W "$btrfs_options $devicemapper_options $zfs_options" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "$all_options" -S = -- "$cur" ) )
;;
btrfs)
COMPREPLY=( $( compgen -W "$btrfs_options" -S = -- "$cur" ) )
@ -2106,6 +2138,9 @@ _docker_daemon() {
devicemapper)
COMPREPLY=( $( compgen -W "$devicemapper_options" -S = -- "$cur" ) )
;;
overlay2)
COMPREPLY=( $( compgen -W "$overlay2_options" -S = -- "$cur" ) )
;;
zfs)
COMPREPLY=( $( compgen -W "$zfs_options" -S = -- "$cur" ) )
;;
@ -2124,10 +2159,20 @@ _docker_daemon() {
__docker_complete_log_options
return
;;
--metrics-addr)
__docker_complete_local_ips
__docker_append_to_completions ":"
__docker_nospace
return
;;
--seccomp-profile)
_filedir json
return
;;
--swarm-default-advertise-addr)
__docker_complete_local_interfaces
return
;;
--userns-remap)
__docker_complete_user_group
return
@ -2428,7 +2473,7 @@ _docker_image_ls() {
_docker_image_prune() {
case "$prev" in
--filter)
COMPREPLY=( $( compgen -W "until" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "label label! until" -S = -- "$cur" ) )
__docker_nospace
return
;;
@ -2707,8 +2752,8 @@ _docker_network_create() {
--aux-address|--gateway|--ip-range|--ipam-opt|--ipv6|--opt|-o|--subnet)
return
;;
--ipam-driver)
COMPREPLY=( $( compgen -W "default" -- "$cur" ) )
--config-from)
__docker_complete_networks
return
;;
--driver|-d)
@ -2716,14 +2761,22 @@ _docker_network_create() {
__docker_complete_plugins_bundled --type Network --remove host --remove null --add macvlan
return
;;
--ipam-driver)
COMPREPLY=( $( compgen -W "default" -- "$cur" ) )
return
;;
--label)
return
;;
--scope)
COMPREPLY=( $( compgen -W "local swarm" -- "$cur" ) )
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--attachable --aux-address --driver -d --gateway --help --ingress --internal --ip-range --ipam-driver --ipam-opt --ipv6 --label --opt -o --subnet" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--attachable --aux-address --config-from --config-only --driver -d --gateway --help --ingress --internal --ip-range --ipam-driver --ipam-opt --ipv6 --label --opt -o --scope --subnet" -- "$cur" ) )
;;
esac
}
@ -2806,7 +2859,7 @@ _docker_network_ls() {
_docker_network_prune() {
case "$prev" in
--filter)
COMPREPLY=( $( compgen -W "until" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "label label! until" -S = -- "$cur" ) )
__docker_nospace
return
;;
@ -3054,7 +3107,6 @@ _docker_service_update_and_create() {
--log-driver
--log-opt
--mount
--network
--replicas
--reserve-cpu
--reserve-memory
@ -3103,6 +3155,7 @@ _docker_service_update_and_create() {
--host
--mode
--name
--network
--placement-pref
--publish -p
--secret
@ -4359,7 +4412,7 @@ _docker_system_info() {
_docker_system_prune() {
case "$prev" in
--filter)
COMPREPLY=( $( compgen -W "until" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "label label! until" -S = -- "$cur" ) )
__docker_nospace
return
;;
@ -4478,9 +4531,17 @@ _docker_volume_ls() {
}
_docker_volume_prune() {
case "$prev" in
--filter)
COMPREPLY=( $( compgen -W "label label!" -S = -- "$cur" ) )
__docker_nospace
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--filter --force -f --help" -- "$cur" ) )
;;
esac
}

View File

@ -14,16 +14,16 @@ ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT
# build docker image (dockerfiles/Dockerfile.build)
.PHONY: build_docker_image
build_docker_image:
docker build -t $(DEV_DOCKER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.dev .
docker build ${DOCKER_BUILD_ARGS} -t $(DEV_DOCKER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.dev .
# build docker image having the linting tools (dockerfiles/Dockerfile.lint)
.PHONY: build_linter_image
build_linter_image:
docker build -t $(LINTER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.lint .
docker build ${DOCKER_BUILD_ARGS} -t $(LINTER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.lint .
.PHONY: build_cross_image
build_cross_image:
docker build -t $(CROSS_IMAGE_NAME) -f ./dockerfiles/Dockerfile.cross .
docker build ${DOCKER_BUILD_ARGS} -t $(CROSS_IMAGE_NAME) -f ./dockerfiles/Dockerfile.cross .
# build executable using a container

View File

@ -1,18 +1,2 @@
FROM golang:1.8.3
# allow replacing httpredir or deb mirror
ARG APT_MIRROR=deb.debian.org
RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
RUN apt-get update -qq && apt-get install -y -q \
libltdl-dev \
gcc-mingw-w64 \
parallel \
;
COPY dockerfiles/osx-cross.sh /tmp/
RUN /tmp/osx-cross.sh
ENV PATH /osxcross/target/bin:$PATH
FROM dockercore/golang-cross@sha256:d24e7affa3a85d460d2303c2549f03fc866f2b97d771ccf07b0e6e2b411dd207
WORKDIR /go/src/github.com/docker/cli

View File

@ -3,16 +3,25 @@ FROM golang:1.8.3-alpine
RUN apk add -U git make bash coreutils
ARG VNDR_SHA=9909bb2b8a0b7ea464527b376dc50389c90df587
RUN go get github.com/LK4D4/vndr && \
cp /go/bin/vndr /usr/bin && \
cd /go/src/github.com/LK4D4/vndr && \
git checkout -q "$VNDR_SHA" && \
go build -v -o /usr/bin/vndr . && \
rm -rf /go/src/* /go/pkg/* /go/bin/*
ARG BINDATA_SHA=a0ff2567cfb70903282db057e799fd826784d41d
RUN go get github.com/jteeuwen/go-bindata/go-bindata && \
cp /go/bin/go-bindata /usr/bin && \
cd /go/src/github.com/jteeuwen/go-bindata/go-bindata && \
git checkout -q "$BINDATA_SHA" && \
go build -v -o /usr/bin/go-bindata . && \
rm -rf /go/src/* /go/pkg/* /go/bin/*
ARG FILEWATCHER_SHA=2e12ea42f6c8c089b19e992145bb94e8adaecedb
RUN go get github.com/dnephin/filewatcher && \
cp /go/bin/filewatcher /usr/bin/ && \
cd /go/src/github.com/dnephin/filewatcher && \
git checkout -q "$FILEWATCHER_SHA" && \
go build -v -o /usr/bin/filewatcher . && \
rm -rf /go/src/* /go/pkg/* /go/bin/*
ENV CGO_ENABLED=0

View File

@ -2,9 +2,13 @@ FROM golang:1.8.3-alpine
RUN apk add -U git
RUN go get -u gopkg.in/dnephin/gometalinter.v1 && \
mv /go/bin/gometalinter.v1 /usr/local/bin/gometalinter && \
gometalinter --install
ARG GOMETALINTER_SHA=4306381615a2ba2a207f8fcea02c08c6b2b0803f
RUN go get github.com/alecthomas/gometalinter && \
cd /go/src/github.com/alecthomas/gometalinter && \
git checkout -q "$GOMETALINTER_SHA" && \
go build -v -o /usr/local/bin/gometalinter . && \
gometalinter --install && \
rm -rf /go/src/* /go/pkg/*
WORKDIR /go/src/github.com/docker/cli
ENV CGO_ENABLED=0

View File

@ -1,29 +0,0 @@
#!/usr/bin/env bash
#
# Install dependencies required to cross compile osx, then cleanup
#
# TODO: this should be a separate build stage when CI supports it
set -eu -o pipefail
PKG_DEPS="patch xz-utils clang"
apt-get update -qq
apt-get install -y -q $PKG_DEPS
OSX_SDK=MacOSX10.11.sdk
OSX_CROSS_COMMIT=a9317c18a3a457ca0a657f08cc4d0d43c6cf8953
OSXCROSS_PATH="/osxcross"
echo "Cloning osxcross"
time git clone https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH
cd $OSXCROSS_PATH
git checkout -q $OSX_CROSS_COMMIT
echo "Downloading OSX SDK"
time curl -sSL https://s3.dockerproject.org/darwin/v2/${OSX_SDK}.tar.xz \
-o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz"
echo "Building osxcross"
UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh > /dev/null

View File

@ -138,7 +138,7 @@ be UPPERCASE to distinguish them from arguments more easily.
Docker runs instructions in a `Dockerfile` in order. A `Dockerfile` **must
start with a \`FROM\` instruction**. The `FROM` instruction specifies the [*Base
Image*](glossary.md#base-image) from which you are building. `FROM` may only be
proceeded by one or more `ARG` instructions, which declare arguments that are used
preceded by one or more `ARG` instructions, which declare arguments that are used
in `FROM` lines in the `Dockerfile`.
Docker treats lines that *begin* with `#` as a comment, unless the line is
@ -499,7 +499,7 @@ valid `Dockerfile` must start with a `FROM` instruction. The image can be
any valid image it is especially easy to start by **pulling an image** from
the [*Public Repositories*](https://docs.docker.com/engine/tutorials/dockerrepos/).
- `ARG` is the only instruction that may proceed `FROM` in the `Dockerfile`.
- `ARG` is the only instruction that may precede `FROM` in the `Dockerfile`.
See [Understand how ARG and FROM interact](#understand-how-arg-and-from-interact).
- `FROM` can appear multiple times within a single `Dockerfile` to

View File

@ -700,6 +700,34 @@ ENOSPC and will shutdown filesystem.
$ sudo dockerd --storage-opt dm.xfs_nospace_max_retries=0
```
##### `dm.libdm_log_level`
Specifies the maxmimum `libdm` log level that will be forwarded to the
`dockerd` log (as specified by `--log-level`). This option is primarily
intended for debugging problems involving `libdm`. Using values other than the
defaults may cause false-positive warnings to be logged.
Values specified must fall within the range of valid `libdm` log levels. At the
time of writing, the following is the list of `libdm` log levels as well as
their corresponding levels when output by `dockerd`.
| `libdm` Level | Value | `--log-level` |
| ------------- | -----:| ------------- |
| `_LOG_FATAL` | 2 | error |
| `_LOG_ERR` | 3 | error |
| `_LOG_WARN` | 4 | warn |
| `_LOG_NOTICE` | 5 | info |
| `_LOG_INFO` | 6 | info |
| `_LOG_DEBUG` | 7 | debug |
###### Example
```bash
$ sudo dockerd \
--log-level debug \
--storage-opt dm.libdm_log_level=7
```
#### ZFS options
##### `zfs.fsname`

View File

@ -167,6 +167,8 @@ $ docker service create --name redis \
4cdgfyky7ozwh3htjfw0d12qv
```
To grant a service access to multiple secrets, use multiple `--secret` flags.
Secrets are located in `/run/secrets` in the container. If no target is
specified, the name of the secret will be used as the in memory file in the
container. If a target is specified, that will be the filename. In the
@ -191,10 +193,26 @@ tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/).
### Set environment variables (-e, --env)
This sets environmental variables for all tasks in a service. For example:
This sets an environmental variable for all tasks in a service. For example:
```bash
$ docker service create --name redis_2 --replicas 5 --env MYVAR=foo redis:3.0.6
$ docker service create \
--name redis_2 \
--replicas 5 \
--env MYVAR=foo \
redis:3.0.6
```
To specify multiple environment variables, specify multiple `--env` flags, each
with a separate key-value pair.
```bash
$ docker service create \
--name redis_2 \
--replicas 5 \
--env MYVAR=foo \
--env MYVAR2=bar \
redis:3.0.6
```
### Create a service with specific hostname (--hostname)

View File

@ -16,11 +16,12 @@ keywords: "service, scale"
# service scale
```markdown
Usage: docker service scale SERVICE=REPLICAS [SERVICE=REPLICAS...]
Usage: docker service scale [OPTIONS] SERVICE=REPLICAS [SERVICE=REPLICAS...]
Scale one or multiple replicated services
Options:
-d, --detach Exit immediately instead of waiting for the service to converge (default true)
--help Print usage
```

View File

@ -87,8 +87,9 @@ default foreground mode:
To start a container in detached mode, you use `-d=true` or just `-d` option. By
design, containers started in detached mode exit when the root process used to
run the container exits. A container in detached mode cannot be automatically
removed when it stops, this means you cannot use the `--rm` option with `-d` option.
run the container exits, unless you also specify the `--rm` option. If you use
`-d` with `--rm`, the container is removed when it exits **or** when the daemon
exits, whichever happens first.
Do not pass a `service x start` command to a detached container. For example, this
command attempts to start the `nginx` service.
@ -149,7 +150,7 @@ is receiving its standard input from a pipe, as in:
The operator can identify a container in three ways:
| Identifier type | Example value |
| --------------------- | ------------------------------------------------------------------ |
|:----------------------|:-------------------------------------------------------------------|
| UUID long identifier | "f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778" |
| UUID short identifier | "f78375b1c487" |
| Name | "evil_ptolemy" |
@ -686,29 +687,29 @@ parent group.
The operator can also adjust the performance parameters of the
container:
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `-m`, `--memory=""` | Memory limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. Minimum is 4M. |
| `--memory-swap=""` | Total memory limit (memory + swap, format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. |
| `--memory-reservation=""` | Memory soft limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. |
| `--kernel-memory=""` | Kernel memory limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. Minimum is 4M. |
| `-c`, `--cpu-shares=0` | CPU shares (relative weight) |
| `--cpus=0.000` | Number of CPUs. Number is a fractional number. 0.000 means no limit. |
| `--cpu-period=0` | Limit the CPU CFS (Completely Fair Scheduler) period |
| `--cpuset-cpus=""` | CPUs in which to allow execution (0-3, 0,1) |
| `--cpuset-mems=""` | Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. |
| `--cpu-quota=0` | Limit the CPU CFS (Completely Fair Scheduler) quota |
| `--cpu-rt-period=0` | Limit the CPU real-time period. In microseconds. Requires parent cgroups be set and cannot be higher than parent. Also check rtprio ulimits. |
| `--cpu-rt-runtime=0` | Limit the CPU real-time runtime. In microseconds. Requires parent cgroups be set and cannot be higher than parent. Also check rtprio ulimits. |
| `--blkio-weight=0` | Block IO weight (relative weight) accepts a weight value between 10 and 1000. |
| `--blkio-weight-device=""` | Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`) |
| `--device-read-bps=""` | Limit read rate from a device (format: `<device-path>:<number>[<unit>]`). Number is a positive integer. Unit can be one of `kb`, `mb`, or `gb`. |
| `--device-write-bps=""` | Limit write rate to a device (format: `<device-path>:<number>[<unit>]`). Number is a positive integer. Unit can be one of `kb`, `mb`, or `gb`. |
| `--device-read-iops="" ` | Limit read rate (IO per second) from a device (format: `<device-path>:<number>`). Number is a positive integer. |
| `--device-write-iops="" ` | Limit write rate (IO per second) to a device (format: `<device-path>:<number>`). Number is a positive integer. |
| `--oom-kill-disable=false` | Whether to disable OOM Killer for the container or not. |
| `--oom-score-adj=0` | Tune container's OOM preferences (-1000 to 1000) |
| `--memory-swappiness=""` | Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. |
| Option | Description |
|:---------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-m`, `--memory=""` | Memory limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. Minimum is 4M. |
| `--memory-swap=""` | Total memory limit (memory + swap, format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. |
| `--memory-reservation=""` | Memory soft limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. |
| `--kernel-memory=""` | Kernel memory limit (format: `<number>[<unit>]`). Number is a positive integer. Unit can be one of `b`, `k`, `m`, or `g`. Minimum is 4M. |
| `-c`, `--cpu-shares=0` | CPU shares (relative weight) |
| `--cpus=0.000` | Number of CPUs. Number is a fractional number. 0.000 means no limit. |
| `--cpu-period=0` | Limit the CPU CFS (Completely Fair Scheduler) period |
| `--cpuset-cpus=""` | CPUs in which to allow execution (0-3, 0,1) |
| `--cpuset-mems=""` | Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. |
| `--cpu-quota=0` | Limit the CPU CFS (Completely Fair Scheduler) quota |
| `--cpu-rt-period=0` | Limit the CPU real-time period. In microseconds. Requires parent cgroups be set and cannot be higher than parent. Also check rtprio ulimits. |
| `--cpu-rt-runtime=0` | Limit the CPU real-time runtime. In microseconds. Requires parent cgroups be set and cannot be higher than parent. Also check rtprio ulimits. |
| `--blkio-weight=0` | Block IO weight (relative weight) accepts a weight value between 10 and 1000. |
| `--blkio-weight-device=""` | Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`) |
| `--device-read-bps=""` | Limit read rate from a device (format: `<device-path>:<number>[<unit>]`). Number is a positive integer. Unit can be one of `kb`, `mb`, or `gb`. |
| `--device-write-bps=""` | Limit write rate to a device (format: `<device-path>:<number>[<unit>]`). Number is a positive integer. Unit can be one of `kb`, `mb`, or `gb`. |
| `--device-read-iops="" ` | Limit read rate (IO per second) from a device (format: `<device-path>:<number>`). Number is a positive integer. |
| `--device-write-iops="" ` | Limit write rate (IO per second) to a device (format: `<device-path>:<number>`). Number is a positive integer. |
| `--oom-kill-disable=false` | Whether to disable OOM Killer for the container or not. |
| `--oom-score-adj=0` | Tune container's OOM preferences (-1000 to 1000) |
| `--memory-swappiness=""` | Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. |
| `--shm-size=""` | Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. |
### User memory constraints
@ -1158,7 +1159,7 @@ list of capabilities that are kept. The following table lists the Linux capabili
options which are allowed by default and can be dropped.
| Capability Key | Capability Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|:-----------------|:------------------------------------------------------------------------------------------------------------------------------|
| SETPCAP | Modify process capabilities. |
| MKNOD | Create special files using mknod(2). |
| AUDIT_WRITE | Write records to kernel auditing log. |
@ -1176,31 +1177,31 @@ options which are allowed by default and can be dropped.
The next table shows the capabilities which are not granted by default and may be added.
| Capability Key | Capability Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| SYS_MODULE | Load and unload kernel modules. |
| SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). |
| SYS_PACCT | Use acct(2), switch process accounting on or off. |
| SYS_ADMIN | Perform a range of system administration operations. |
| SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. |
| SYS_RESOURCE | Override resource Limits. |
| SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. |
| SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. |
| AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. |
| MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. |
| MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). |
| NET_ADMIN | Perform various network-related operations. |
| SYSLOG | Perform privileged syslog(2) operations. |
| DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. |
| LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. |
| NET_BROADCAST | Make socket broadcasts, and listen to multicasts. |
| IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). |
| IPC_OWNER | Bypass permission checks for operations on System V IPC objects. |
| SYS_PTRACE | Trace arbitrary processes using ptrace(2). |
| SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. |
| LEASE | Establish leases on arbitrary files (see fcntl(2)). |
| WAKE_ALARM | Trigger something that will wake up the system. |
| BLOCK_SUSPEND | Employ features that can block system suspend. |
| Capability Key | Capability Description |
|:----------------|:----------------------------------------------------------------------------------------------------------------|
| SYS_MODULE | Load and unload kernel modules. |
| SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). |
| SYS_PACCT | Use acct(2), switch process accounting on or off. |
| SYS_ADMIN | Perform a range of system administration operations. |
| SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. |
| SYS_RESOURCE | Override resource Limits. |
| SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. |
| SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. |
| AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. |
| MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. |
| MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). |
| NET_ADMIN | Perform various network-related operations. |
| SYSLOG | Perform privileged syslog(2) operations. |
| DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. |
| LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. |
| NET_BROADCAST | Make socket broadcasts, and listen to multicasts. |
| IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). |
| IPC_OWNER | Bypass permission checks for operations on System V IPC objects. |
| SYS_PTRACE | Trace arbitrary processes using ptrace(2). |
| SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. |
| LEASE | Establish leases on arbitrary files (see fcntl(2)). |
| WAKE_ALARM | Trigger something that will wake up the system. |
| BLOCK_SUSPEND | Employ features that can block system suspend. |
Further reference information is available on the [capabilities(7) - Linux man page](http://man7.org/linux/man-pages/man7/capabilities.7.html)
@ -1252,7 +1253,7 @@ the `--log-driver=VALUE` with the `docker run` command to configure the
container's logging driver. The following options are supported:
| Driver | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|:------------|:------------------------------------------------------------------------------------------------------------------------------|
| `none` | Disables any logging for the container. `docker logs` won't be available with this driver. |
| `json-file` | Default logging driver for Docker. Writes JSON messages to file. No logging options are supported for this driver. |
| `syslog` | Syslog logging driver for Docker. Writes log messages to syslog. |
@ -1398,12 +1399,12 @@ container.
The following environment variables are set for Linux containers:
| Variable | Value |
| -------- | ----- |
| `HOME` | Set based on the value of `USER` |
| `HOSTNAME` | The hostname associated with the container |
| `PATH` | Includes popular directories, such as `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` |
| `TERM` | `xterm` if the container is allocated a pseudo-TTY |
| Variable | Value |
|:-----------|:-----------------------------------------------------------------------------------------------------|
| `HOME` | Set based on the value of `USER` |
| `HOSTNAME` | The hostname associated with the container |
| `PATH` | Includes popular directories, such as `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` |
| `TERM` | `xterm` if the container is allocated a pseudo-TTY |
Additionally, the operator can **set any environment variable** in the

View File

@ -703,6 +703,31 @@ Example use:
$ sudo dockerd --storage-opt dm.xfs_nospace_max_retries=0
##### dm.libdm_log_level
Specifies the maxmimum libdm log level that will be forwarded to the dockerd
log (as specified by --log-level). This option is primarily intended for
debugging problems involving libdm. Using values other than the defaults may
cause false-positive warnings to be logged.
Values specified must fall within the range of valid libdm log levels. At the
time of writing, the following is the list of libdm log levels as well as their
corresponding levels when output by dockerd.
| libdm Level | Value | --log-level |
| ----------- | -----:| ----------- |
| _LOG_FATAL | 2 | error |
| _LOG_ERR | 3 | error |
| _LOG_WARN | 4 | warn |
| _LOG_NOTICE | 5 | info |
| _LOG_INFO | 6 | info |
| _LOG_DEBUG | 7 | debug |
Example use:
$ sudo dockerd \
--log-level debug \
--storage-opt dm.libdm_log_level=7
## ZFS options

View File

@ -6,13 +6,28 @@
set -eu -o pipefail
BUILDDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export SHELL=bash
echo "Building binaries for all platforms"
SHELL=/bin/bash parallel ::: \
jobs=(
"$BUILDDIR/windows" \
"$BUILDDIR/osx" \
"GOOS=linux GOARCH=amd64 $BUILDDIR/binary" \
"GOOS=linux GOARCH=arm $BUILDDIR/binary" \
"GOOS=linux GOARCH=ppc64le $BUILDDIR/binary" \
"GOOS=linux GOARCH=s390x $BUILDDIR/binary" \
;
)
# Outside of circleCI run all at once. On circleCI run two at a time because
# each continer has access to two cores.
group=${CROSS_GROUP-"all"}
if [ "$group" == "all" ]; then
echo "Building binaries for all platforms"
parallel ::: "${jobs[@]}"
exit 0
fi
declare -i start=$group*2
parallel ::: "${jobs[@]:$start:2}"

View File

@ -1,12 +1,2 @@
#!/bin/sh
set -e
filewatcher \
-L 6 \
-x '**/*.swp' \
-x .git \
-x build \
-x .idea \
-- \
sh -c 'go test -timeout 10s -v ./${dir} || ( echo; echo; exit 1 )'
exec filewatcher -L 6 -x build -x script go test -timeout 10s -v './${dir}'

View File

@ -1,5 +1,5 @@
github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
github.com/Microsoft/go-winio v0.4.1
github.com/Microsoft/go-winio v0.4.2
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/Sirupsen/logrus v0.11.0
github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
@ -7,9 +7,12 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
github.com/docker/docker 050c1bb17bd033e909cb653f5449b683608293d6
github.com/docker/docker 87df0e533b619c088091fd1e2310e92bb9a24822
github.com/docker/docker-credential-helpers v0.5.1
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #?
# the docker/go package contains a customized version of canonical/json
# and is used by Notary. The package is periodically rebased on current Go versions.
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
@ -30,7 +33,7 @@ github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
github.com/opencontainers/selinux v1.0.0-rc1
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
github.com/pmezard/go-difflib v1.0.0
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git

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