Update cli folder with newer changes from moby/moby

Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Tibor Vass 2017-05-03 19:25:17 -07:00
commit ae84e6dd5e
80 changed files with 1671 additions and 188 deletions

View File

@ -39,7 +39,9 @@ type Cli interface {
Out() *OutStream Out() *OutStream
Err() io.Writer Err() io.Writer
In() *InStream In() *InStream
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile ConfigFile() *configfile.ConfigFile
CredentialsStore(serverAddress string) credentials.Store
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.
@ -75,6 +77,11 @@ func (cli *DockerCli) Err() io.Writer {
return cli.err return cli.err
} }
// SetIn sets the reader used for stdin
func (cli *DockerCli) SetIn(in *InStream) {
cli.in = in
}
// In returns the reader used for stdin // In returns the reader used for stdin
func (cli *DockerCli) In() *InStream { func (cli *DockerCli) In() *InStream {
return cli.in return cli.in

View File

@ -118,7 +118,6 @@ type containerOptions struct {
runtime string runtime string
autoRemove bool autoRemove bool
init bool init bool
initPath string
Image string Image string
Args []string Args []string
@ -230,10 +229,10 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
// Health-checking // Health-checking
flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)")
flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
@ -284,8 +283,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
flags.SetAnnotation("init", "version", []string{"1.25"}) flags.SetAnnotation("init", "version", []string{"1.25"})
flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
flags.SetAnnotation("init-path", "version", []string{"1.25"})
return copts return copts
} }

View File

@ -0,0 +1,67 @@
package formatter
import (
"strconv"
)
const (
defaultStackTableFormat = "table {{.Name}}\t{{.Services}}"
stackServicesHeader = "SERVICES"
)
// Stack contains deployed stack information.
type Stack struct {
// Name is the name of the stack
Name string
// Services is the number of the services
Services int
}
// NewStackFormat returns a format for use with a stack Context
func NewStackFormat(source string) Format {
switch source {
case TableFormatKey:
return defaultStackTableFormat
}
return Format(source)
}
// StackWrite writes formatted stacks using the Context
func StackWrite(ctx Context, stacks []*Stack) error {
render := func(format func(subContext subContext) error) error {
for _, stack := range stacks {
if err := format(&stackContext{s: stack}); err != nil {
return err
}
}
return nil
}
return ctx.Write(newStackContext(), render)
}
type stackContext struct {
HeaderContext
s *Stack
}
func newStackContext() *stackContext {
stackCtx := stackContext{}
stackCtx.header = map[string]string{
"Name": nameHeader,
"Services": stackServicesHeader,
}
return &stackCtx
}
func (s *stackContext) MarshalJSON() ([]byte, error) {
return marshalJSON(s)
}
func (s *stackContext) Name() string {
return s.s.Name
}
func (s *stackContext) Services() string {
return strconv.Itoa(s.s.Services)
}

View File

@ -0,0 +1,64 @@
package formatter
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStackContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{Format: "{{InvalidFunction}}"},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{Format: "{{nil}}"},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table format
{
Context{Format: NewStackFormat("table")},
`NAME SERVICES
baz 2
bar 1
`,
},
{
Context{Format: NewStackFormat("table {{.Name}}")},
`NAME
baz
bar
`,
},
// Custom Format
{
Context{Format: NewStackFormat("{{.Name}}")},
`baz
bar
`,
},
}
stacks := []*Stack{
{Name: "baz", Services: 2},
{Name: "bar", Services: 1},
}
for _, testcase := range cases {
out := bytes.NewBufferString("")
testcase.context.Output = out
err := StackWrite(testcase.context, stacks)
if err != nil {
assert.Error(t, err, testcase.expected)
} else {
assert.Equal(t, out.String(), testcase.expected)
}
}
}

View File

@ -254,7 +254,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
} }
// Setup an upload progress bar // Setup an upload progress bar
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) progressOutput := streamformatter.NewProgressOutput(progBuff)
if !dockerCli.Out().IsTerminal() { if !dockerCli.Out().IsTerminal() {
progressOutput = &lastProgressOutput{output: progressOutput} progressOutput = &lastProgressOutput{output: progressOutput}
} }

View File

@ -165,7 +165,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
if err != nil { if err != nil {
return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err) return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err)
} }
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true) progressOutput := streamformatter.NewProgressOutput(out)
// Pass the response body through a progress reader. // Pass the response body through a progress reader.
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL)) progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))

View File

@ -0,0 +1,116 @@
package image
import (
"io"
"io/ioutil"
"strings"
"time"
"github.com/docker/cli/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
imageTagFunc func(string, string) error
imageSaveFunc func(images []string) (io.ReadCloser, error)
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
imagePushFunc func(ref string, options types.ImagePushOptions) (io.ReadCloser, error)
infoFunc func() (types.Info, error)
imagePullFunc func(ref string, options types.ImagePullOptions) (io.ReadCloser, error)
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
}
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
if cli.imageTagFunc != nil {
return cli.imageTagFunc(image, ref)
}
return nil
}
func (cli *fakeClient) ImageSave(_ context.Context, images []string) (io.ReadCloser, error) {
if cli.imageSaveFunc != nil {
return cli.imageSaveFunc(images)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImageRemove(_ context.Context, image string,
options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
if cli.imageRemoveFunc != nil {
return cli.imageRemoveFunc(image, options)
}
return []types.ImageDeleteResponseItem{}, nil
}
func (cli *fakeClient) ImagePush(_ context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
if cli.imagePushFunc != nil {
return cli.imagePushFunc(ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) Info(_ context.Context) (types.Info, error) {
if cli.infoFunc != nil {
return cli.infoFunc()
}
return types.Info{}, nil
}
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
if cli.imagePullFunc != nil {
cli.imagePullFunc(ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) {
if cli.imagesPruneFunc != nil {
return cli.imagesPruneFunc(pruneFilter)
}
return types.ImagesPruneReport{}, nil
}
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
if cli.imageLoadFunc != nil {
return cli.imageLoadFunc(input, quiet)
}
return types.ImageLoadResponse{}, nil
}
func (cli *fakeClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
if cli.imageListFunc != nil {
return cli.imageListFunc(options)
}
return []types.ImageSummary{{}}, nil
}
func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, image string) (types.ImageInspect, []byte, error) {
if cli.imageInspectFunc != nil {
return cli.imageInspectFunc(image)
}
return types.ImageInspect{}, nil, nil
}
func (cli *fakeClient) ImageImport(_ context.Context, source types.ImageImportSource, ref string,
options types.ImageImportOptions) (io.ReadCloser, error) {
if cli.imageImportFunc != nil {
return cli.imageImportFunc(source, ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.HistoryResponseItem, error) {
if cli.imageHistoryFunc != nil {
return cli.imageHistoryFunc(img)
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}

View File

@ -19,7 +19,7 @@ type historyOptions struct {
} }
// NewHistoryCommand creates a new `docker history` command // NewHistoryCommand creates a new `docker history` command
func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command { func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
var opts historyOptions var opts historyOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -42,7 +42,7 @@ func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runHistory(dockerCli *command.DockerCli, opts historyOptions) error { func runHistory(dockerCli command.Cli, opts historyOptions) error {
ctx := context.Background() ctx := context.Background()
history, err := dockerCli.Client().ImageHistory(ctx, opts.image) history, err := dockerCli.Client().ImageHistory(ctx, opts.image)

View File

@ -0,0 +1,108 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
"testing"
"time"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewHistoryCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires exactly 1 argument(s).",
},
{
name: "client-error",
args: []string{"image:tag"},
expectedError: "something went wrong",
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewHistoryCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
outputRegex string
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
}{
{
name: "simple",
args: []string{"image:tag"},
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
}}, nil
},
},
{
name: "quiet",
args: []string{"--quiet", "image:tag"},
},
// TODO: This test is failing since the output does not contain an RFC3339 date
//{
// name: "non-human",
// args: []string{"--human=false", "image:tag"},
// outputRegex: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", // RFC3339 date format match
//},
{
name: "non-human-header",
args: []string{"--human=false", "image:tag"},
outputRegex: "CREATED\\sAT",
},
{
name: "quiet-no-trunc",
args: []string{"--quiet", "--no-trunc", "image:tag"},
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.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)
} else {
match, _ := regexp.MatchString(tc.outputRegex, actual)
assert.True(t, match)
}
}
}

View File

@ -23,7 +23,7 @@ type importOptions struct {
} }
// NewImportCommand creates a new `docker import` command // NewImportCommand creates a new `docker import` command
func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command { func NewImportCommand(dockerCli command.Cli) *cobra.Command {
var opts importOptions var opts importOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -48,7 +48,7 @@ func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runImport(dockerCli *command.DockerCli, opts importOptions) error { func runImport(dockerCli command.Cli, opts importOptions) error {
var ( var (
in io.Reader in io.Reader
srcName = opts.source srcName = opts.source

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"io"
"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 TestNewImportCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
{
name: "import-failed",
args: []string{"testdata/import-command-success.input.txt"},
expectedError: "something went wrong",
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
return nil, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewImportCommandInvalidFile(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
}
func TestNewImportCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
}{
{
name: "simple",
args: []string{"testdata/import-command-success.input.txt"},
},
{
name: "terminal-source",
args: []string{"-"},
},
{
name: "double",
args: []string{"-", "image:local"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "image:local", ref)
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
{
name: "message",
args: []string{"--message", "test message", "-"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "test message", options.Message)
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
{
name: "change",
args: []string{"--change", "ENV DEBUG true", "-"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "ENV DEBUG true", options.Changes[0])
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
}
}

View File

@ -15,7 +15,7 @@ type inspectOptions struct {
} }
// newInspectCommand creates a new cobra.Command for `docker image inspect` // newInspectCommand creates a new cobra.Command for `docker image inspect`
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { func newInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions var opts inspectOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -33,7 +33,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { func runInspect(dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()

View File

@ -0,0 +1,92 @@
package image
import (
"bytes"
"fmt"
"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/stretchr/testify/assert"
)
func TestNewInspectCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewInspectCommandSuccess(t *testing.T) {
imageInspectInvocationCount := 0
testCases := []struct {
name string
args []string
imageCount int
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
}{
{
name: "simple",
args: []string{"image"},
imageCount: 1,
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
assert.Equal(t, "image", image)
return types.ImageInspect{}, nil, nil
},
},
{
name: "format",
imageCount: 1,
args: []string{"--format='{{.ID}}'", "image"},
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
return types.ImageInspect{ID: image}, nil, nil
},
},
{
name: "simple-many",
args: []string{"image1", "image2"},
imageCount: 2,
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
if imageInspectInvocationCount == 1 {
assert.Equal(t, "image1", image)
} else {
assert.Equal(t, "image2", image)
}
return types.ImageInspect{}, nil, nil
},
},
}
for _, tc := range testCases {
imageInspectInvocationCount = 0
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("inspect-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
assert.Equal(t, imageInspectInvocationCount, tc.imageCount)
}
}

View File

@ -23,7 +23,7 @@ type imagesOptions struct {
} }
// NewImagesCommand creates a new `docker images` command // NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command { func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
opts := imagesOptions{filter: opts.NewFilterOpt()} opts := imagesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -50,14 +50,14 @@ func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func newListCommand(dockerCli *command.DockerCli) *cobra.Command { func newListCommand(dockerCli command.Cli) *cobra.Command {
cmd := *NewImagesCommand(dockerCli) cmd := *NewImagesCommand(dockerCli)
cmd.Aliases = []string{"images", "list"} cmd.Aliases = []string{"images", "list"}
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]" cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
return &cmd return &cmd
} }
func runImages(dockerCli *command.DockerCli, opts imagesOptions) error { func runImages(dockerCli command.Cli, opts imagesOptions) error {
ctx := context.Background() ctx := context.Background()
filters := opts.filter.Value() filters := opts.filter.Value()

View File

@ -0,0 +1,102 @@
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/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 TestNewImagesCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
}{
{
name: "wrong-args",
args: []string{"arg1", "arg2"},
expectedError: "requires at most 1 argument(s).",
},
{
name: "failed-list",
expectedError: "something went wrong",
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
return []types.ImageSummary{{}}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewImagesCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageFormat string
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
}{
{
name: "simple",
},
{
name: "format",
imageFormat: "raw",
},
{
name: "quiet-format",
args: []string{"-q"},
imageFormat: "table",
},
{
name: "match-name",
args: []string{"image"},
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
assert.Equal(t, "image", options.Filters.Get("reference")[0])
return []types.ImageSummary{{}}, nil
},
},
{
name: "filters",
args: []string{"--filter", "name=value"},
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
assert.Equal(t, "value", options.Filters.Get("name")[0])
return []types.ImageSummary{{}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigfile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cmd := NewImagesCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("list-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}
func TestNewListCommandAlias(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("images"))
assert.True(t, cmd.HasAlias("list"))
assert.False(t, cmd.HasAlias("other"))
}

View File

@ -19,7 +19,7 @@ type loadOptions struct {
} }
// NewLoadCommand creates a new `docker load` command // NewLoadCommand creates a new `docker load` command
func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command { func NewLoadCommand(dockerCli command.Cli) *cobra.Command {
var opts loadOptions var opts loadOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -39,7 +39,7 @@ func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runLoad(dockerCli *command.DockerCli, opts loadOptions) error { func runLoad(dockerCli command.Cli, opts loadOptions) error {
var input io.Reader = dockerCli.In() var input io.Reader = dockerCli.In()
if opts.input != "" { if opts.input != "" {

View File

@ -0,0 +1,105 @@
package image
import (
"bytes"
"fmt"
"io"
"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/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewLoadCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
isTerminalIn bool
expectedError string
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
}{
{
name: "wrong-args",
args: []string{"arg"},
expectedError: "accepts no argument(s).",
},
{
name: "input-to-terminal",
isTerminalIn: true,
expectedError: "requested load from stdin, but stdin is empty",
},
{
name: "pull-error",
expectedError: "something went wrong",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, new(bytes.Buffer))
cli.In().SetIsTerminal(tc.isTerminalIn)
cmd := NewLoadCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewLoadCommandInvalidInput(t *testing.T) {
expectedError := "open *"
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"--input", "*"})
err := cmd.Execute()
testutil.ErrorContains(t, err, expectedError)
}
func TestNewLoadCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
}{
{
name: "simple",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
},
},
{
name: "json",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
json := "{\"ID\": \"1\"}"
return types.ImageLoadResponse{
Body: ioutil.NopCloser(strings.NewReader(json)),
JSON: true,
}, nil
},
},
{
name: "input-file",
args: []string{"--input", "testdata/load-command-success.input.txt"},
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("load-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -19,7 +19,7 @@ type pruneOptions struct {
} }
// NewPruneCommand returns a new cobra prune command for images // NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()} opts := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -55,7 +55,7 @@ Are you sure you want to continue?`
Are you sure you want to continue?` Are you sure you want to continue?`
) )
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := opts.filter.Value() pruneFilters := opts.filter.Value()
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all)) pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
pruneFilters = command.PruneFilters(dockerCli, pruneFilters) pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
@ -90,6 +90,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
// RunPrune calls the Image Prune API // RunPrune calls the Image Prune API
// This returns the amount of space reclaimed and a detailed output string // This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli *command.DockerCli, all bool, filter opts.FilterOpt) (uint64, string, error) { func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter}) return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
} }

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewPruneCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
}{
{
name: "wrong-args",
args: []string{"something"},
expectedError: "accepts no argument(s).",
},
{
name: "prune-error",
args: []string{"--force"},
expectedError: "something went wrong",
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
return types.ImagesPruneReport{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPruneCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
}{
{
name: "all",
args: []string{"--all"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "false", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{}, nil
},
},
{
name: "force-deleted",
args: []string{"--force"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "true", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{
ImagesDeleted: []types.ImageDeleteResponseItem{{Deleted: "image1"}},
SpaceReclaimed: 1,
}, nil
},
},
{
name: "force-untagged",
args: []string{"--force"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "true", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{
ImagesDeleted: []types.ImageDeleteResponseItem{{Untagged: "image1"}},
SpaceReclaimed: 2,
}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("prune-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -19,7 +19,7 @@ type pullOptions struct {
} }
// NewPullCommand creates a new `docker pull` command // NewPullCommand creates a new `docker pull` command
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPullCommand(dockerCli command.Cli) *cobra.Command {
var opts pullOptions var opts pullOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -40,7 +40,7 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runPull(dockerCli *command.DockerCli, opts pullOptions) error { func runPull(dockerCli command.Cli, opts pullOptions) error {
distributionRef, err := reference.ParseNormalizedNamed(opts.remote) distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,85 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/docker/docker/registry"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPullCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "wrong-args",
expectedError: "requires exactly 1 argument(s).",
args: []string{},
},
{
name: "invalid-name",
expectedError: "invalid reference format: repository name must be lowercase",
args: []string{"UPPERCASE_REPO"},
},
{
name: "all-tags-with-tag",
expectedError: "tag can't be used with --all-tags/-a",
args: []string{"--all-tags", "image:tag"},
},
{
name: "pull-error",
args: []string{"--disable-content-trust=false", "image:tag"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPullCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
args: []string{"image:tag"},
},
{
name: "simple-no-tag",
args: []string{"image"},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.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

@ -12,7 +12,7 @@ import (
) )
// NewPushCommand creates a new `docker push` command // NewPushCommand creates a new `docker push` command
func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPushCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [OPTIONS] NAME[:TAG]", Use: "push [OPTIONS] NAME[:TAG]",
Short: "Push an image or a repository to a registry", Short: "Push an image or a repository to a registry",
@ -29,7 +29,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runPush(dockerCli *command.DockerCli, remote string) error { func runPush(dockerCli command.Cli, remote string) error {
ref, err := reference.ParseNormalizedNamed(remote) ref, err := reference.ParseNormalizedNamed(remote)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,85 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPushCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imagePushFunc func(ref string, options types.ImagePushOptions) (io.ReadCloser, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires exactly 1 argument(s).",
},
{
name: "invalid-name",
args: []string{"UPPERCASE_REPO"},
expectedError: "invalid reference format: repository name must be lowercase",
},
{
name: "push-failed",
args: []string{"image:repo"},
expectedError: "Failed to push",
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("Failed to push")
},
},
{
name: "trust-error",
args: []string{"--disable-content-trust=false", "image:repo"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPushCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo,
ref reference.Named, authConfig types.AuthConfig,
requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
}
}

View File

@ -19,7 +19,7 @@ type removeOptions struct {
} }
// NewRemoveCommand creates a new `docker remove` command // NewRemoveCommand creates a new `docker remove` command
func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
var opts removeOptions var opts removeOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -39,14 +39,14 @@ func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
cmd := *NewRemoveCommand(dockerCli) cmd := *NewRemoveCommand(dockerCli)
cmd.Aliases = []string{"rmi", "remove"} cmd.Aliases = []string{"rmi", "remove"}
cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]" cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]"
return &cmd return &cmd
} }
func runRemove(dockerCli *command.DockerCli, opts removeOptions, images []string) error { func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()

View File

@ -0,0 +1,103 @@
package image
import (
"bytes"
"fmt"
"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 TestNewRemoveCommandAlias(t *testing.T) {
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("rmi"))
assert.True(t, cmd.HasAlias("remove"))
assert.False(t, cmd.HasAlias("other"))
}
func TestNewRemoveCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
}{
{
name: "wrong args",
expectedError: "requires at least 1 argument(s).",
},
{
name: "ImageRemove fail",
args: []string{"arg1"},
expectedError: "error removing image",
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.False(t, options.Force)
assert.True(t, options.PruneChildren)
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
},
},
}
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)
}
}
func TestNewRemoveCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
}{
{
name: "Image Deleted",
args: []string{"image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
{
name: "Image Untagged",
args: []string{"image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
},
},
{
name: "Image Deleted and Untagged",
args: []string{"image1", "image2"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
if image == "image1" {
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
}
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.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

@ -16,7 +16,7 @@ type saveOptions struct {
} }
// NewSaveCommand creates a new `docker save` command // NewSaveCommand creates a new `docker save` command
func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command { func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
var opts saveOptions var opts saveOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -36,7 +36,7 @@ func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runSave(dockerCli *command.DockerCli, opts saveOptions) error { func runSave(dockerCli command.Cli, opts saveOptions) error {
if opts.output == "" && dockerCli.Out().IsTerminal() { if opts.output == "" && dockerCli.Out().IsTerminal() {
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect") return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
} }

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSaveCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
isTerminal bool
expectedError string
imageSaveFunc func(images []string) (io.ReadCloser, error)
}{
{
name: "wrong args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
{
name: "output to terminal",
args: []string{"output", "file", "arg1"},
isTerminal: true,
expectedError: "Cowardly refusing to save to a terminal. Use the -o flag or redirect.",
},
{
name: "ImageSave fail",
args: []string{"arg1"},
isTerminal: false,
expectedError: "error saving image",
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("error saving image")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}, new(bytes.Buffer))
cli.Out().SetIsTerminal(tc.isTerminal)
cmd := NewSaveCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewSaveCommandSuccess(t *testing.T) {
testCases := []struct {
args []string
isTerminal bool
imageSaveFunc func(images []string) (io.ReadCloser, error)
deferredFunc func()
}{
{
args: []string{"-o", "save_tmp_file", "arg1"},
isTerminal: true,
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
require.Len(t, images, 1)
assert.Equal(t, "arg1", images[0])
return ioutil.NopCloser(strings.NewReader("")), nil
},
deferredFunc: func() {
os.Remove("save_tmp_file")
},
},
{
args: []string{"arg1", "arg2"},
isTerminal: false,
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
require.Len(t, images, 2)
assert.Equal(t, "arg1", images[0])
assert.Equal(t, "arg2", images[1])
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
}
for _, tc := range testCases {
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
if tc.deferredFunc != nil {
tc.deferredFunc()
}
}
}

View File

@ -14,7 +14,7 @@ type tagOptions struct {
} }
// NewTagCommand creates a new `docker tag` command // NewTagCommand creates a new `docker tag` command
func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command { func NewTagCommand(dockerCli command.Cli) *cobra.Command {
var opts tagOptions var opts tagOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -34,7 +34,7 @@ func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runTag(dockerCli *command.DockerCli, opts tagOptions) error { func runTag(dockerCli command.Cli, opts tagOptions) error {
ctx := context.Background() ctx := context.Background()
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name) return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)

View File

@ -0,0 +1,44 @@
package image
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/pkg/testutil"
"github.com/stretchr/testify/assert"
)
func TestCliNewTagCommandErrors(t *testing.T) {
testCases := [][]string{
{},
{"image1"},
{"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.SetArgs(args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), expectedError)
}
}
func TestCliNewTagCommand(t *testing.T) {
buf := new(bytes.Buffer)
cmd := NewTagCommand(
test.NewFakeCli(&fakeClient{
imageTagFunc: func(image string, ref string) error {
assert.Equal(t, "image1", image)
assert.Equal(t, "image2", ref)
return nil
},
}, buf))
cmd.SetArgs([]string{"image1", "image2"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
value, _ := cmd.Flags().GetBool("interspersed")
assert.False(t, value)
}

View File

@ -0,0 +1 @@
1234567890123456789

View File

@ -0,0 +1 @@
tag

View File

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

View File

@ -0,0 +1 @@
file input test

View File

@ -0,0 +1 @@
'image'

View File

@ -0,0 +1,50 @@
[
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
},
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
}
]

View File

@ -0,0 +1,26 @@
[
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
}
]

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
Success

View File

@ -0,0 +1 @@
file input test

View File

@ -0,0 +1 @@
1:

View File

@ -0,0 +1 @@
Success

View File

@ -0,0 +1,2 @@
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

View File

@ -0,0 +1,4 @@
Deleted Images:
deleted: image1
Total reclaimed space: 1B

View File

@ -0,0 +1,4 @@
Deleted Images:
untagged: image1
Total reclaimed space: 2B

View File

@ -0,0 +1 @@
Using default tag: latest

View File

@ -0,0 +1,4 @@
Untagged: image1
Deleted: image2
Untagged: image1
Deleted: image2

View File

@ -0,0 +1,2 @@
Deleted: image1
Deleted: image1

View File

@ -0,0 +1,2 @@
Untagged: image1
Untagged: image1

View File

@ -29,7 +29,7 @@ type target struct {
} }
// trustedPush handles content trust pushing of an image // trustedPush handles content trust pushing of an image
func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege) responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
if err != nil { if err != nil {
return err return err
@ -42,7 +42,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
// PushTrustedReference pushes a canonical reference to the trust server. // PushTrustedReference pushes a canonical reference to the trust server.
// nolint: gocyclo // nolint: gocyclo
func PushTrustedReference(cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error { func PushTrustedReference(cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
// If it is a trusted push we would like to find the target entry which match the // If it is a trusted push we would like to find the target entry which match the
// tag provided in the function and then do an AddTarget later. // tag provided in the function and then do an AddTarget later.
target := &client.Target{} target := &client.Target{}
@ -203,7 +203,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T
} }
// imagePushPrivileged push the image // imagePushPrivileged push the image
func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@ -217,7 +217,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
} }
// trustedPull handles content trust pulling of an image // trustedPull handles content trust pulling of an image
func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target var refs []target
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull") notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
@ -296,7 +296,7 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
} }
// imagePullPrivileged pulls the image and displays it to the output // imagePullPrivileged pulls the image and displays it to the output
func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error { func imagePullPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
@ -318,7 +318,7 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
} }
// TrustedReference returns the canonical trusted reference for an image reference // TrustedReference returns the canonical trusted reference for an image reference
func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) { func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
var ( var (
repoInfo *registry.RepositoryInfo repoInfo *registry.RepositoryInfo
err error err error
@ -372,7 +372,7 @@ func convertTarget(t client.Target) (target, error) {
} }
// TagTrusted tags a trusted ref // TagTrusted tags a trusted ref
func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef reference.Canonical, ref reference.NamedTagged) error { func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canonical, ref reference.NamedTagged) error {
// Use familiar references when interacting with client and output // Use familiar references when interacting with client and output
familiarRef := reference.FamiliarString(ref) familiarRef := reference.FamiliarString(ref)
trustedFamiliarRef := reference.FamiliarString(trustedRef) trustedFamiliarRef := reference.FamiliarString(trustedRef)

View File

@ -1,20 +1,18 @@
package command package command
import ( import (
"errors"
"io" "io"
"os" "os"
"runtime" "runtime"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
) )
// InStream is an input stream used by the DockerCli to read user input // InStream is an input stream used by the DockerCli to read user input
type InStream struct { type InStream struct {
in io.ReadCloser CommonStream
fd uintptr in io.ReadCloser
isTerminal bool
state *term.State
} }
func (i *InStream) Read(p []byte) (int, error) { func (i *InStream) Read(p []byte) (int, error) {
@ -26,32 +24,15 @@ func (i *InStream) Close() error {
return i.in.Close() return i.in.Close()
} }
// FD returns the file descriptor number for this stream
func (i *InStream) FD() uintptr {
return i.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (i *InStream) IsTerminal() bool {
return i.isTerminal
}
// SetRawTerminal sets raw mode on the input terminal // SetRawTerminal sets raw mode on the input terminal
func (i *InStream) SetRawTerminal() (err error) { func (i *InStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !i.isTerminal { if os.Getenv("NORAW") != "" || !i.CommonStream.isTerminal {
return nil return nil
} }
i.state, err = term.SetRawTerminal(i.fd) i.CommonStream.state, err = term.SetRawTerminal(i.CommonStream.fd)
return err return err
} }
// RestoreTerminal restores normal mode to the terminal
func (i *InStream) RestoreTerminal() {
if i.state != nil {
term.RestoreTerminal(i.fd, i.state)
}
}
// CheckTty checks if we are trying to attach to a container tty // CheckTty checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error. // from a non-tty client input stream, and if so, returns an error.
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error { func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
@ -71,5 +52,5 @@ func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
// NewInStream returns a new InStream object from a ReadCloser // NewInStream returns a new InStream object from a ReadCloser
func NewInStream(in io.ReadCloser) *InStream { func NewInStream(in io.ReadCloser) *InStream {
fd, isTerminal := term.GetFdInfo(in) fd, isTerminal := term.GetFdInfo(in)
return &InStream{in: in, fd: fd, isTerminal: isTerminal} return &InStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, in: in}
} }

View File

@ -11,42 +11,23 @@ import (
// OutStream is an output stream used by the DockerCli to write normal program // OutStream is an output stream used by the DockerCli to write normal program
// output. // output.
type OutStream struct { type OutStream struct {
out io.Writer CommonStream
fd uintptr out io.Writer
isTerminal bool
state *term.State
} }
func (o *OutStream) Write(p []byte) (int, error) { func (o *OutStream) Write(p []byte) (int, error) {
return o.out.Write(p) return o.out.Write(p)
} }
// FD returns the file descriptor number for this stream // SetRawTerminal sets raw mode on the input terminal
func (o *OutStream) FD() uintptr {
return o.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (o *OutStream) IsTerminal() bool {
return o.isTerminal
}
// SetRawTerminal sets raw mode on the output terminal
func (o *OutStream) SetRawTerminal() (err error) { func (o *OutStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !o.isTerminal { if os.Getenv("NORAW") != "" || !o.CommonStream.isTerminal {
return nil return nil
} }
o.state, err = term.SetRawTerminalOutput(o.fd) o.CommonStream.state, err = term.SetRawTerminalOutput(o.CommonStream.fd)
return err return err
} }
// RestoreTerminal restores normal mode to the terminal
func (o *OutStream) RestoreTerminal() {
if o.state != nil {
term.RestoreTerminal(o.fd, o.state)
}
}
// GetTtySize returns the height and width in characters of the tty // GetTtySize returns the height and width in characters of the tty
func (o *OutStream) GetTtySize() (uint, uint) { func (o *OutStream) GetTtySize() (uint, uint) {
if !o.isTerminal { if !o.isTerminal {
@ -65,5 +46,5 @@ func (o *OutStream) GetTtySize() (uint, uint) {
// NewOutStream returns a new OutStream object from a Writer // NewOutStream returns a new OutStream object from a Writer
func NewOutStream(out io.Writer) *OutStream { func NewOutStream(out io.Writer) *OutStream {
fd, isTerminal := term.GetFdInfo(out) fd, isTerminal := term.GetFdInfo(out)
return &OutStream{out: out, fd: fd, isTerminal: isTerminal} return &OutStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, out: out}
} }

View File

@ -21,7 +21,7 @@ import (
) )
// ElectAuthServer returns the default registry to use (by asking the daemon) // ElectAuthServer returns the default registry to use (by asking the daemon)
func ElectAuthServer(ctx context.Context, cli *DockerCli) string { func ElectAuthServer(ctx context.Context, cli Cli) string {
// The daemon `/info` endpoint informs us of the default registry being // The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for // used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence // example a Linux client might be interacting with a Windows daemon, hence
@ -46,7 +46,7 @@ func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command. // for the given command.
func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc { func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
return func() (string, error) { return func() (string, error) {
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName) fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index) indexServer := registry.GetAuthConfigKey(index)
@ -62,7 +62,7 @@ func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.I
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the // ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform, // default index, it uses the default index name for the daemon's platform,
// not the client's platform. // not the client's platform.
func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes.IndexInfo) types.AuthConfig { func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig {
configKey := index.Name configKey := index.Name
if index.Official { if index.Official {
configKey = ElectAuthServer(ctx, cli) configKey = ElectAuthServer(ctx, cli)
@ -73,10 +73,10 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
} }
// ConfigureAuth returns an AuthConfig from the specified user, password and server. // ConfigureAuth returns an AuthConfig from the specified user, password and server.
func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) { func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
cli.in = NewInStream(os.Stdin) cli.SetIn(NewInStream(os.Stdin))
} }
if !isDefaultRegistry { if !isDefaultRegistry {
@ -160,7 +160,7 @@ func promptWithDefault(out io.Writer, prompt string, configDefault string) {
} }
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image string) (string, error) { func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
// Retrieve encoded auth token from the image reference // Retrieve encoded auth token from the image reference
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image) authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
if err != nil { if err != nil {
@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin
} }
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) { func resolveAuthConfigFromImage(ctx context.Context, cli Cli, image string) (types.AuthConfig, error) {
registryRef, err := reference.ParseNormalizedNamed(image) registryRef, err := reference.ParseNormalizedNamed(image)
if err != nil { if err != nil {
return types.AuthConfig{}, err return types.AuthConfig{}, err

View File

@ -45,7 +45,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
ctx := context.Background() ctx := context.Background()
client := dockerCli.Client() client := dockerCli.Client()
services, err := client.ServiceList(ctx, types.ServiceListOptions{}) serviceFilters := opts.filter.Value()
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters})
if err != nil { if err != nil {
return err return err
} }

View File

@ -92,7 +92,15 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
if !client.IsErrServiceNotFound(err) { if !client.IsErrServiceNotFound(err) {
return err return err
} }
task, _, _ := cli.TaskInspectWithRaw(ctx, opts.target) task, _, err := cli.TaskInspectWithRaw(ctx, opts.target)
if err != nil {
if client.IsErrTaskNotFound(err) {
// if the task isn't found, rewrite the error to be clear
// that we looked for services AND tasks and found none
err = fmt.Errorf("no such task or service")
}
return err
}
tty = task.Spec.ContainerSpec.TTY tty = task.Spec.ContainerSpec.TTY
// TODO(dperny) hot fix until we get a nice details system squared away, // TODO(dperny) hot fix until we get a nice details system squared away,
// ignores details (including task context) if we have a TTY log // ignores details (including task context) if we have a TTY log
@ -104,15 +112,10 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
responseBody, err = cli.TaskLogs(ctx, opts.target, options) responseBody, err = cli.TaskLogs(ctx, opts.target, options)
if err != nil { if err != nil {
if client.IsErrTaskNotFound(err) {
// if the task ALSO isn't found, rewrite the error to be clear
// that we looked for services AND tasks
err = fmt.Errorf("No such task or service")
}
return err return err
} }
maxLength = getMaxLength(task.Slot) maxLength = getMaxLength(task.Slot)
responseBody, _ = cli.TaskLogs(ctx, opts.target, options)
} else { } else {
tty = service.Spec.TaskTemplate.ContainerSpec.TTY tty = service.Spec.TaskTemplate.ContainerSpec.TTY
// TODO(dperny) hot fix until we get a nice details system squared away, // TODO(dperny) hot fix until we get a nice details system squared away,

View File

@ -802,13 +802,13 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health") flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ms|s|m|h)")
flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ms|s|m|h)")
flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"})
flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy") flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)")
flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"}) flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"})
flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK") flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"}) flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"})

View File

@ -63,7 +63,7 @@ func stateToProgress(state swarm.TaskState, rollback bool) int64 {
func ServiceProgress(ctx context.Context, client client.APIClient, serviceID string, progressWriter io.WriteCloser) error { func ServiceProgress(ctx context.Context, client client.APIClient, serviceID string, progressWriter io.WriteCloser) error {
defer progressWriter.Close() defer progressWriter.Close()
progressOut := streamformatter.NewJSONStreamFormatter().NewProgressOutput(progressWriter, false) progressOut := streamformatter.NewJSONProgressOutput(progressWriter, false)
sigint := make(chan os.Signal, 1) sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt) signal.Notify(sigint, os.Interrupt)

View File

@ -18,9 +18,7 @@ func getStackFilter(namespace string) filters.Args {
} }
func getServiceFilter(namespace string) filters.Args { func getServiceFilter(namespace string) filters.Args {
filter := getStackFilter(namespace) return getStackFilter(namespace)
filter.Add("runtime", string(swarm.RuntimeContainer))
return filter
} }
func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args { func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
@ -20,7 +21,7 @@ import (
) )
func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error { func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
configDetails, err := getConfigDetails(opts) configDetails, err := getConfigDetails(opts.composefile)
if err != nil { if err != nil {
return err return err
} }
@ -108,16 +109,16 @@ func propertyWarnings(properties map[string]string) string {
return strings.Join(msgs, "\n\n") return strings.Join(msgs, "\n\n")
} }
func getConfigDetails(opts deployOptions) (composetypes.ConfigDetails, error) { func getConfigDetails(composefile string) (composetypes.ConfigDetails, error) {
var details composetypes.ConfigDetails var details composetypes.ConfigDetails
var err error
details.WorkingDir, err = os.Getwd() absPath, err := filepath.Abs(composefile)
if err != nil { if err != nil {
return details, err return details, err
} }
details.WorkingDir = filepath.Dir(absPath)
configFile, err := getConfigFile(opts.composefile) configFile, err := getConfigFile(composefile)
if err != nil { if err != nil {
return details, err return details, err
} }

View File

@ -0,0 +1,28 @@
package stack
import (
"os"
"path/filepath"
"testing"
"github.com/docker/docker/pkg/testutil/tempfile"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetConfigDetails(t *testing.T) {
content := `
version: "3.0"
services:
foo:
image: alpine:3.5
`
file := tempfile.NewTempFile(t, "test-get-config-details", content)
defer file.Remove()
details, err := getConfigDetails(file.Name())
require.NoError(t, err)
assert.Equal(t, filepath.Dir(file.Name()), details.WorkingDir)
assert.Len(t, details.ConfigFiles, 1)
assert.Len(t, details.Environment, len(os.Environ()))
}

View File

@ -1,14 +1,11 @@
package stack package stack
import ( import (
"fmt"
"io"
"sort" "sort"
"strconv"
"text/tabwriter"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/compose/convert" "github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/client" "github.com/docker/cli/client"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -17,11 +14,8 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const (
listItemFmt = "%s\t%s\n"
)
type listOptions struct { type listOptions struct {
format string
} }
func newListCommand(dockerCli *command.DockerCli) *cobra.Command { func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
@ -37,6 +31,8 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
}, },
} }
flags := cmd.Flags()
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
return cmd return cmd
} }
@ -48,55 +44,32 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
if err != nil { if err != nil {
return err return err
} }
format := opts.format
out := dockerCli.Out() if len(format) == 0 {
printTable(out, stacks) format = formatter.TableFormatKey
return nil }
stackCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
} }
type byName []*stack type byName []*formatter.Stack
func (n byName) Len() int { return len(n) } func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name } func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
func printTable(out io.Writer, stacks []*stack) { func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
// Ignore flushing errors
defer writer.Flush()
sort.Sort(byName(stacks))
fmt.Fprintf(writer, listItemFmt, "NAME", "SERVICES")
for _, stack := range stacks {
fmt.Fprintf(
writer,
listItemFmt,
stack.Name,
strconv.Itoa(stack.Services),
)
}
}
type stack struct {
// Name is the name of the stack
Name string
// Services is the number of the services
Services int
}
func getStacks(
ctx context.Context,
apiclient client.APIClient,
) ([]*stack, error) {
services, err := apiclient.ServiceList( services, err := apiclient.ServiceList(
ctx, ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()}) types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil { if err != nil {
return nil, err return nil, err
} }
m := make(map[string]*stack, 0) m := make(map[string]*formatter.Stack, 0)
for _, service := range services { for _, service := range services {
labels := service.Spec.Labels labels := service.Spec.Labels
name, ok := labels[convert.LabelNamespace] name, ok := labels[convert.LabelNamespace]
@ -106,7 +79,7 @@ func getStacks(
} }
ztack, ok := m[name] ztack, ok := m[name]
if !ok { if !ok {
m[name] = &stack{ m[name] = &formatter.Stack{
Name: name, Name: name,
Services: 1, Services: 1,
} }
@ -114,7 +87,7 @@ func getStacks(
ztack.Services++ ztack.Services++
} }
} }
var stacks []*stack var stacks []*formatter.Stack
for _, stack := range m { for _, stack := range m {
stacks = append(stacks, stack) stacks = append(stacks, stack)
} }

34
cli/command/stream.go Normal file
View File

@ -0,0 +1,34 @@
package command
import (
"github.com/docker/docker/pkg/term"
)
// CommonStream is an input stream used by the DockerCli to read user input
type CommonStream struct {
fd uintptr
isTerminal bool
state *term.State
}
// FD returns the file descriptor number for this stream
func (s *CommonStream) FD() uintptr {
return s.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (s *CommonStream) IsTerminal() bool {
return s.isTerminal
}
// RestoreTerminal restores normal mode to the terminal
func (s *CommonStream) RestoreTerminal() {
if s.state != nil {
term.RestoreTerminal(s.fd, s.state)
}
}
// SetIsTerminal sets the boolean used for isTerminal
func (s *CommonStream) SetIsTerminal(isTerminal bool) {
s.isTerminal = isTerminal
}

View File

@ -19,6 +19,7 @@ type initOptions struct {
listenAddr NodeAddrOption listenAddr NodeAddrOption
// Not a NodeAddrOption because it has no default port. // Not a NodeAddrOption because it has no default port.
advertiseAddr string advertiseAddr string
dataPathAddr string
forceNewCluster bool forceNewCluster bool
availability string availability string
} }
@ -40,6 +41,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])") flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])") flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
flags.StringVar(&opts.dataPathAddr, flagDataPathAddr, "", "Address or interface to use for data path traffic (format: <ip|interface>)")
flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state") flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state")
flags.BoolVar(&opts.autolock, flagAutolock, false, "Enable manager autolocking (requiring an unlock key to start a stopped manager)") flags.BoolVar(&opts.autolock, flagAutolock, false, "Enable manager autolocking (requiring an unlock key to start a stopped manager)")
flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`) flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`)
@ -54,6 +56,7 @@ func runInit(dockerCli command.Cli, flags *pflag.FlagSet, opts initOptions) erro
req := swarm.InitRequest{ req := swarm.InitRequest{
ListenAddr: opts.listenAddr.String(), ListenAddr: opts.listenAddr.String(),
AdvertiseAddr: opts.advertiseAddr, AdvertiseAddr: opts.advertiseAddr,
DataPathAddr: opts.dataPathAddr,
ForceNewCluster: opts.forceNewCluster, ForceNewCluster: opts.forceNewCluster,
Spec: opts.swarmOptions.ToSpec(flags), Spec: opts.swarmOptions.ToSpec(flags),
AutoLockManagers: opts.swarmOptions.autolock, AutoLockManagers: opts.swarmOptions.autolock,

View File

@ -19,6 +19,7 @@ type joinOptions struct {
listenAddr NodeAddrOption listenAddr NodeAddrOption
// Not a NodeAddrOption because it has no default port. // Not a NodeAddrOption because it has no default port.
advertiseAddr string advertiseAddr string
dataPathAddr string
token string token string
availability string availability string
} }
@ -41,6 +42,7 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])") flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])") flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
flags.StringVar(&opts.dataPathAddr, flagDataPathAddr, "", "Address or interface to use for data path traffic (format: <ip|interface>)")
flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm") flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm")
flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`) flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`)
return cmd return cmd
@ -54,6 +56,7 @@ func runJoin(dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) erro
JoinToken: opts.token, JoinToken: opts.token,
ListenAddr: opts.listenAddr.String(), ListenAddr: opts.listenAddr.String(),
AdvertiseAddr: opts.advertiseAddr, AdvertiseAddr: opts.advertiseAddr,
DataPathAddr: opts.dataPathAddr,
RemoteAddrs: []string{opts.remote}, RemoteAddrs: []string{opts.remote},
} }
if flags.Changed(flagAvailability) { if flags.Changed(flagAvailability) {

View File

@ -108,10 +108,10 @@ func printJoinCommand(ctx context.Context, dockerCli command.Cli, nodeID string,
if node.ManagerStatus != nil { if node.ManagerStatus != nil {
if worker { if worker {
fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\n docker swarm join \\\n --token %s \\\n %s\n\n", sw.JoinTokens.Worker, node.ManagerStatus.Addr) fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\n docker swarm join --token %s %s\n\n", sw.JoinTokens.Worker, node.ManagerStatus.Addr)
} }
if manager { if manager {
fmt.Fprintf(dockerCli.Out(), "To add a manager to this swarm, run the following command:\n\n docker swarm join \\\n --token %s \\\n %s\n\n", sw.JoinTokens.Manager, node.ManagerStatus.Addr) fmt.Fprintf(dockerCli.Out(), "To add a manager to this swarm, run the following command:\n\n docker swarm join --token %s %s\n\n", sw.JoinTokens.Manager, node.ManagerStatus.Addr)
} }
} }

View File

@ -2,7 +2,9 @@ package swarm
import ( import (
"encoding/csv" "encoding/csv"
"encoding/pem"
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"time" "time"
@ -19,6 +21,7 @@ const (
flagDispatcherHeartbeat = "dispatcher-heartbeat" flagDispatcherHeartbeat = "dispatcher-heartbeat"
flagListenAddr = "listen-addr" flagListenAddr = "listen-addr"
flagAdvertiseAddr = "advertise-addr" flagAdvertiseAddr = "advertise-addr"
flagDataPathAddr = "data-path-addr"
flagQuiet = "quiet" flagQuiet = "quiet"
flagRotate = "rotate" flagRotate = "rotate"
flagToken = "token" flagToken = "token"
@ -154,6 +157,15 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
case "url": case "url":
hasURL = true hasURL = true
externalCA.URL = value externalCA.URL = value
case "cacert":
cacontents, err := ioutil.ReadFile(value)
if err != nil {
return nil, errors.Wrap(err, "unable to read CA cert for external CA")
}
if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {
return nil, errors.New("CA cert for external CA must be in PEM format")
}
externalCA.CACert = string(cacontents)
default: default:
externalCA.Options[key] = value externalCA.Options[key] = value
} }

View File

@ -2,7 +2,4 @@ Successfully rotated manager join token.
To add a manager to this swarm, run the following command: To add a manager to this swarm, run the following command:
docker swarm join \ docker swarm join --token manager-join-token 127.0.0.1
--token manager-join-token \
127.0.0.1

View File

@ -1,6 +1,3 @@
To add a manager to this swarm, run the following command: To add a manager to this swarm, run the following command:
docker swarm join \ docker swarm join --token manager-join-token 127.0.0.1
--token manager-join-token \
127.0.0.1

View File

@ -1,6 +1,3 @@
To add a worker to this swarm, run the following command: To add a worker to this swarm, run the following command:
docker swarm join \ docker swarm join --token worker-join-token 127.0.0.1
--token worker-join-token \
127.0.0.1

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test" "github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
@ -96,7 +97,7 @@ func TestSwarmUnlock(t *testing.T) {
return nil return nil
}, },
}, buf) }, buf)
dockerCli.SetIn(ioutil.NopCloser(strings.NewReader(input))) dockerCli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := newUnlockCommand(dockerCli) cmd := newUnlockCommand(dockerCli)
assert.NoError(t, cmd.Execute()) assert.NoError(t, cmd.Execute())
} }

View File

@ -91,6 +91,10 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
fmt.Fprintf(dockerCli.Out(), "\n") fmt.Fprintf(dockerCli.Out(), "\n")
} }
fmt.Fprintf(dockerCli.Out(), " Log:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Log, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState) fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState)
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked { if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked {
fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID) fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test" "github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -91,7 +92,7 @@ func TestVolumePrunePromptYes(t *testing.T) {
volumePruneFunc: simplePruneFunc, volumePruneFunc: simplePruneFunc,
}, buf) }, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := NewPruneCommand( cmd := NewPruneCommand(
cli, cli,
) )
@ -113,7 +114,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
volumePruneFunc: simplePruneFunc, volumePruneFunc: simplePruneFunc,
}, buf) }, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := NewPruneCommand( cmd := NewPruneCommand(
cli, cli,
) )

File diff suppressed because one or more lines are too long

View File

@ -72,6 +72,7 @@
"context": {"type": "string"}, "context": {"type": "string"},
"dockerfile": {"type": "string"}, "dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}, "args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"$ref": "#/definitions/list_of_strings"} "cache_from": {"$ref": "#/definitions/list_of_strings"}
}, },
"additionalProperties": false "additionalProperties": false

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/client" "github.com/docker/cli/client"
) )
@ -15,23 +16,24 @@ type FakeCli struct {
command.DockerCli command.DockerCli
client client.APIClient client client.APIClient
configfile *configfile.ConfigFile configfile *configfile.ConfigFile
out io.Writer out *command.OutStream
err io.Writer err io.Writer
in io.ReadCloser in *command.InStream
store credentials.Store
} }
// NewFakeCli returns a Cli backed by the fakeCli // NewFakeCli returns a Cli backed by the fakeCli
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli { func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
return &FakeCli{ return &FakeCli{
client: client, client: client,
out: out, out: command.NewOutStream(out),
err: ioutil.Discard, err: ioutil.Discard,
in: ioutil.NopCloser(strings.NewReader("")), in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
} }
} }
// SetIn sets the input of the cli to the specified ReadCloser // SetIn sets the input of the cli to the specified ReadCloser
func (c *FakeCli) SetIn(in io.ReadCloser) { func (c *FakeCli) SetIn(in *command.InStream) {
c.in = in c.in = in
} }
@ -52,7 +54,7 @@ func (c *FakeCli) Client() client.APIClient {
// Out returns the output stream (stdout) the cli should write on // Out returns the output stream (stdout) the cli should write on
func (c *FakeCli) Out() *command.OutStream { func (c *FakeCli) Out() *command.OutStream {
return command.NewOutStream(c.out) return c.out
} }
// Err returns the output stream (stderr) the cli should write on // Err returns the output stream (stderr) the cli should write on
@ -62,10 +64,18 @@ func (c *FakeCli) Err() io.Writer {
// In returns the input stream the cli will use // In returns the input stream the cli will use
func (c *FakeCli) In() *command.InStream { func (c *FakeCli) In() *command.InStream {
return command.NewInStream(c.in) return c.in
} }
// ConfigFile returns the cli configfile object (to get client configuration) // ConfigFile returns the cli configfile object (to get client configuration)
func (c *FakeCli) ConfigFile() *configfile.ConfigFile { func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
return c.configfile return c.configfile
} }
// CredentialsStore returns the fake store the cli will use
func (c *FakeCli) CredentialsStore(serverAddress string) credentials.Store {
if c.store == nil {
c.store = NewFakeStore()
}
return c.store
}

View File

@ -0,0 +1,74 @@
package test
import (
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/docker/api/types"
)
// fake store implements a credentials.Store that only acts as an in memory map
type fakeStore struct {
store map[string]types.AuthConfig
eraseFunc func(serverAddress string) error
getFunc func(serverAddress string) (types.AuthConfig, error)
getAllFunc func() (map[string]types.AuthConfig, error)
storeFunc func(authConfig types.AuthConfig) error
}
// NewFakeStore creates a new file credentials store.
func NewFakeStore() credentials.Store {
return &fakeStore{store: map[string]types.AuthConfig{}}
}
func (c *fakeStore) SetStore(store map[string]types.AuthConfig) {
c.store = store
}
func (c *fakeStore) SetEraseFunc(eraseFunc func(string) error) {
c.eraseFunc = eraseFunc
}
func (c *fakeStore) SetGetFunc(getFunc func(string) (types.AuthConfig, error)) {
c.getFunc = getFunc
}
func (c *fakeStore) SetGetAllFunc(getAllFunc func() (map[string]types.AuthConfig, error)) {
c.getAllFunc = getAllFunc
}
func (c *fakeStore) SetStoreFunc(storeFunc func(types.AuthConfig) error) {
c.storeFunc = storeFunc
}
// Erase removes the given credentials from the map store
func (c *fakeStore) Erase(serverAddress string) error {
if c.eraseFunc != nil {
return c.eraseFunc(serverAddress)
}
delete(c.store, serverAddress)
return nil
}
// Get retrieves credentials for a specific server from the map store.
func (c *fakeStore) Get(serverAddress string) (types.AuthConfig, error) {
if c.getFunc != nil {
return c.getFunc(serverAddress)
}
authConfig, _ := c.store[serverAddress]
return authConfig, nil
}
func (c *fakeStore) GetAll() (map[string]types.AuthConfig, error) {
if c.getAllFunc != nil {
return c.getAllFunc()
}
return c.store, nil
}
// Store saves the given credentials in the map store.
func (c *fakeStore) Store(authConfig types.AuthConfig) error {
if c.storeFunc != nil {
return c.storeFunc(authConfig)
}
c.store[authConfig.ServerAddress] = authConfig
return nil
}