mirror of https://github.com/docker/cli.git
Update cli folder with newer changes from moby/moby
Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
commit
ae84e6dd5e
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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"))
|
||||||
|
}
|
|
@ -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 != "" {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
1234567890123456789
|
|
@ -0,0 +1 @@
|
||||||
|
tag
|
|
@ -0,0 +1,2 @@
|
||||||
|
IMAGE CREATED CREATED BY SIZE COMMENT
|
||||||
|
123456789012 Less than a second ago 0B
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
'image'
|
|
@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
1:
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
deleted: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 1B
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
untagged: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 2B
|
|
@ -0,0 +1 @@
|
||||||
|
Using default tag: latest
|
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
|
@ -0,0 +1,2 @@
|
||||||
|
Deleted: image1
|
||||||
|
Deleted: image1
|
|
@ -0,0 +1,2 @@
|
||||||
|
Untagged: image1
|
||||||
|
Untagged: image1
|
|
@ -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)
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue