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
|
||||
Err() io.Writer
|
||||
In() *InStream
|
||||
SetIn(in *InStream)
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
CredentialsStore(serverAddress string) credentials.Store
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
|
@ -75,6 +77,11 @@ func (cli *DockerCli) Err() io.Writer {
|
|||
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
|
||||
func (cli *DockerCli) In() *InStream {
|
||||
return cli.in
|
||||
|
|
|
@ -118,7 +118,6 @@ type containerOptions struct {
|
|||
runtime string
|
||||
autoRemove bool
|
||||
init bool
|
||||
initPath string
|
||||
|
||||
Image string
|
||||
Args []string
|
||||
|
@ -230,10 +229,10 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
|||
|
||||
// Health-checking
|
||||
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.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.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.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 (ms|s|m|h) (default 0s)")
|
||||
flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
|
||||
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.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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
|
|||
if err != nil {
|
||||
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.
|
||||
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
|
||||
func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts historyOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -42,7 +42,7 @@ func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runHistory(dockerCli *command.DockerCli, opts historyOptions) error {
|
||||
func runHistory(dockerCli command.Cli, opts historyOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
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
|
||||
func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts importOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -48,7 +48,7 @@ func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runImport(dockerCli *command.DockerCli, opts importOptions) error {
|
||||
func runImport(dockerCli command.Cli, opts importOptions) error {
|
||||
var (
|
||||
in io.Reader
|
||||
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`
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -33,7 +33,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -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
|
||||
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := imagesOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -50,14 +50,14 @@ func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := *NewImagesCommand(dockerCli)
|
||||
cmd.Aliases = []string{"images", "list"}
|
||||
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
|
||||
func runImages(dockerCli command.Cli, opts imagesOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
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
|
||||
func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewLoadCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts loadOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -39,7 +39,7 @@ func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runLoad(dockerCli *command.DockerCli, opts loadOptions) error {
|
||||
func runLoad(dockerCli command.Cli, opts loadOptions) error {
|
||||
|
||||
var input io.Reader = dockerCli.In()
|
||||
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
|
||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -55,7 +55,7 @@ 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.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||
|
@ -90,6 +90,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
|
|||
|
||||
// RunPrune calls the Image Prune API
|
||||
// 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})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewPullCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts pullOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -40,7 +40,7 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
|
||||
func runPull(dockerCli command.Cli, opts pullOptions) error {
|
||||
distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
|
||||
if err != nil {
|
||||
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
|
||||
func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push [OPTIONS] NAME[:TAG]",
|
||||
Short: "Push an image or a repository to a registry",
|
||||
|
@ -29,7 +29,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runPush(dockerCli *command.DockerCli, remote string) error {
|
||||
func runPush(dockerCli command.Cli, remote string) error {
|
||||
ref, err := reference.ParseNormalizedNamed(remote)
|
||||
if err != nil {
|
||||
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
|
||||
func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -39,14 +39,14 @@ func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := *NewRemoveCommand(dockerCli)
|
||||
cmd.Aliases = []string{"rmi", "remove"}
|
||||
cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]"
|
||||
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()
|
||||
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
|
||||
func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts saveOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -36,7 +36,7 @@ func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runSave(dockerCli *command.DockerCli, opts saveOptions) error {
|
||||
func runSave(dockerCli command.Cli, opts saveOptions) error {
|
||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||
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
|
||||
func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewTagCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts tagOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -34,7 +34,7 @@ func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runTag(dockerCli *command.DockerCli, opts tagOptions) error {
|
||||
func runTag(dockerCli command.Cli, opts tagOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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
|
||||
// tag provided in the function and then do an AddTarget later.
|
||||
target := &client.Target{}
|
||||
|
@ -203,7 +203,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T
|
|||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -217,7 +217,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
|
|||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
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 (
|
||||
repoInfo *registry.RepositoryInfo
|
||||
err error
|
||||
|
@ -372,7 +372,7 @@ func convertTarget(t client.Target) (target, error) {
|
|||
}
|
||||
|
||||
// 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
|
||||
familiarRef := reference.FamiliarString(ref)
|
||||
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// InStream is an input stream used by the DockerCli to read user input
|
||||
type InStream struct {
|
||||
CommonStream
|
||||
in io.ReadCloser
|
||||
fd uintptr
|
||||
isTerminal bool
|
||||
state *term.State
|
||||
}
|
||||
|
||||
func (i *InStream) Read(p []byte) (int, error) {
|
||||
|
@ -26,32 +24,15 @@ func (i *InStream) Close() error {
|
|||
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
|
||||
func (i *InStream) SetRawTerminal() (err error) {
|
||||
if os.Getenv("NORAW") != "" || !i.isTerminal {
|
||||
if os.Getenv("NORAW") != "" || !i.CommonStream.isTerminal {
|
||||
return nil
|
||||
}
|
||||
i.state, err = term.SetRawTerminal(i.fd)
|
||||
i.CommonStream.state, err = term.SetRawTerminal(i.CommonStream.fd)
|
||||
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
|
||||
// from a non-tty client input stream, and if so, returns an 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
|
||||
func NewInStream(in io.ReadCloser) *InStream {
|
||||
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
|
||||
// output.
|
||||
type OutStream struct {
|
||||
CommonStream
|
||||
out io.Writer
|
||||
fd uintptr
|
||||
isTerminal bool
|
||||
state *term.State
|
||||
}
|
||||
|
||||
func (o *OutStream) Write(p []byte) (int, error) {
|
||||
return o.out.Write(p)
|
||||
}
|
||||
|
||||
// FD returns the file descriptor number for this stream
|
||||
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
|
||||
// SetRawTerminal sets raw mode on the input terminal
|
||||
func (o *OutStream) SetRawTerminal() (err error) {
|
||||
if os.Getenv("NORAW") != "" || !o.isTerminal {
|
||||
if os.Getenv("NORAW") != "" || !o.CommonStream.isTerminal {
|
||||
return nil
|
||||
}
|
||||
o.state, err = term.SetRawTerminalOutput(o.fd)
|
||||
o.CommonStream.state, err = term.SetRawTerminalOutput(o.CommonStream.fd)
|
||||
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
|
||||
func (o *OutStream) GetTtySize() (uint, uint) {
|
||||
if !o.isTerminal {
|
||||
|
@ -65,5 +46,5 @@ func (o *OutStream) GetTtySize() (uint, uint) {
|
|||
// NewOutStream returns a new OutStream object from a Writer
|
||||
func NewOutStream(out io.Writer) *OutStream {
|
||||
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)
|
||||
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
|
||||
// used. This is essential in cross-platforms environment, where for
|
||||
// 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
|
||||
// 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) {
|
||||
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
|
||||
indexServer := registry.GetAuthConfigKey(index)
|
||||
|
@ -62,7 +62,7 @@ func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.I
|
|||
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
||||
// default index, it uses the default index name for the daemon'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
|
||||
if index.Official {
|
||||
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.
|
||||
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
|
||||
if runtime.GOOS == "windows" {
|
||||
cli.in = NewInStream(os.Stdin)
|
||||
cli.SetIn(NewInStream(os.Stdin))
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
|
||||
if err != nil {
|
||||
|
@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin
|
|||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
|
|
|
@ -45,7 +45,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
|||
ctx := context.Background()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -92,7 +92,15 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
|
|||
if !client.IsErrServiceNotFound(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
|
||||
// TODO(dperny) hot fix until we get a nice details system squared away,
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
maxLength = getMaxLength(task.Slot)
|
||||
responseBody, _ = cli.TaskLogs(ctx, opts.target, options)
|
||||
} else {
|
||||
tty = service.Spec.TaskTemplate.ContainerSpec.TTY
|
||||
// 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.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.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.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
|
||||
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.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
|
||||
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 {
|
||||
defer progressWriter.Close()
|
||||
|
||||
progressOut := streamformatter.NewJSONStreamFormatter().NewProgressOutput(progressWriter, false)
|
||||
progressOut := streamformatter.NewJSONProgressOutput(progressWriter, false)
|
||||
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt)
|
||||
|
|
|
@ -18,9 +18,7 @@ func getStackFilter(namespace string) filters.Args {
|
|||
}
|
||||
|
||||
func getServiceFilter(namespace string) filters.Args {
|
||||
filter := getStackFilter(namespace)
|
||||
filter.Add("runtime", string(swarm.RuntimeContainer))
|
||||
return filter
|
||||
return getStackFilter(namespace)
|
||||
}
|
||||
|
||||
func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -20,7 +21,7 @@ import (
|
|||
)
|
||||
|
||||
func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
|
||||
configDetails, err := getConfigDetails(opts)
|
||||
configDetails, err := getConfigDetails(opts.composefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,16 +109,16 @@ func propertyWarnings(properties map[string]string) string {
|
|||
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 err error
|
||||
|
||||
details.WorkingDir, err = os.Getwd()
|
||||
absPath, err := filepath.Abs(composefile)
|
||||
if err != nil {
|
||||
return details, err
|
||||
}
|
||||
details.WorkingDir = filepath.Dir(absPath)
|
||||
|
||||
configFile, err := getConfigFile(opts.composefile)
|
||||
configFile, err := getConfigFile(composefile)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/cli/client"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
@ -17,11 +14,8 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
listItemFmt = "%s\t%s\n"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -48,55 +44,32 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := dockerCli.Out()
|
||||
printTable(out, stacks)
|
||||
return nil
|
||||
format := opts.format
|
||||
if len(format) == 0 {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
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) 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 printTable(out io.Writer, stacks []*stack) {
|
||||
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) {
|
||||
func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
|
||||
services, err := apiclient.ServiceList(
|
||||
ctx,
|
||||
types.ServiceListOptions{Filters: getAllStacksFilter()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]*stack, 0)
|
||||
m := make(map[string]*formatter.Stack, 0)
|
||||
for _, service := range services {
|
||||
labels := service.Spec.Labels
|
||||
name, ok := labels[convert.LabelNamespace]
|
||||
|
@ -106,7 +79,7 @@ func getStacks(
|
|||
}
|
||||
ztack, ok := m[name]
|
||||
if !ok {
|
||||
m[name] = &stack{
|
||||
m[name] = &formatter.Stack{
|
||||
Name: name,
|
||||
Services: 1,
|
||||
}
|
||||
|
@ -114,7 +87,7 @@ func getStacks(
|
|||
ztack.Services++
|
||||
}
|
||||
}
|
||||
var stacks []*stack
|
||||
var stacks []*formatter.Stack
|
||||
for _, stack := range m {
|
||||
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
|
||||
// Not a NodeAddrOption because it has no default port.
|
||||
advertiseAddr string
|
||||
dataPathAddr string
|
||||
forceNewCluster bool
|
||||
availability string
|
||||
}
|
||||
|
@ -40,6 +41,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command {
|
|||
flags := cmd.Flags()
|
||||
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.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.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")`)
|
||||
|
@ -54,6 +56,7 @@ func runInit(dockerCli command.Cli, flags *pflag.FlagSet, opts initOptions) erro
|
|||
req := swarm.InitRequest{
|
||||
ListenAddr: opts.listenAddr.String(),
|
||||
AdvertiseAddr: opts.advertiseAddr,
|
||||
DataPathAddr: opts.dataPathAddr,
|
||||
ForceNewCluster: opts.forceNewCluster,
|
||||
Spec: opts.swarmOptions.ToSpec(flags),
|
||||
AutoLockManagers: opts.swarmOptions.autolock,
|
||||
|
|
|
@ -19,6 +19,7 @@ type joinOptions struct {
|
|||
listenAddr NodeAddrOption
|
||||
// Not a NodeAddrOption because it has no default port.
|
||||
advertiseAddr string
|
||||
dataPathAddr string
|
||||
token string
|
||||
availability string
|
||||
}
|
||||
|
@ -41,6 +42,7 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
|
|||
flags := cmd.Flags()
|
||||
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.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.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`)
|
||||
return cmd
|
||||
|
@ -54,6 +56,7 @@ func runJoin(dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) erro
|
|||
JoinToken: opts.token,
|
||||
ListenAddr: opts.listenAddr.String(),
|
||||
AdvertiseAddr: opts.advertiseAddr,
|
||||
DataPathAddr: opts.dataPathAddr,
|
||||
RemoteAddrs: []string{opts.remote},
|
||||
}
|
||||
if flags.Changed(flagAvailability) {
|
||||
|
|
|
@ -108,10 +108,10 @@ func printJoinCommand(ctx context.Context, dockerCli command.Cli, nodeID string,
|
|||
|
||||
if node.ManagerStatus != nil {
|
||||
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 {
|
||||
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 (
|
||||
"encoding/csv"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +21,7 @@ const (
|
|||
flagDispatcherHeartbeat = "dispatcher-heartbeat"
|
||||
flagListenAddr = "listen-addr"
|
||||
flagAdvertiseAddr = "advertise-addr"
|
||||
flagDataPathAddr = "data-path-addr"
|
||||
flagQuiet = "quiet"
|
||||
flagRotate = "rotate"
|
||||
flagToken = "token"
|
||||
|
@ -154,6 +157,15 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
|
|||
case "url":
|
||||
hasURL = true
|
||||
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:
|
||||
externalCA.Options[key] = value
|
||||
}
|
||||
|
|
|
@ -2,7 +2,4 @@ Successfully rotated manager join token.
|
|||
|
||||
To add a manager to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token manager-join-token \
|
||||
127.0.0.1
|
||||
|
||||
docker swarm join --token manager-join-token 127.0.0.1
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
To add a manager to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token manager-join-token \
|
||||
127.0.0.1
|
||||
|
||||
docker swarm join --token manager-join-token 127.0.0.1
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
To add a worker to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token worker-join-token \
|
||||
127.0.0.1
|
||||
|
||||
docker swarm join --token worker-join-token 127.0.0.1
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
|
@ -96,7 +97,7 @@ func TestSwarmUnlock(t *testing.T) {
|
|||
return nil
|
||||
},
|
||||
}, buf)
|
||||
dockerCli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
dockerCli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||
cmd := newUnlockCommand(dockerCli)
|
||||
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(), " 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)
|
||||
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked {
|
||||
fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
@ -91,7 +92,7 @@ func TestVolumePrunePromptYes(t *testing.T) {
|
|||
volumePruneFunc: simplePruneFunc,
|
||||
}, buf)
|
||||
|
||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||
cmd := NewPruneCommand(
|
||||
cli,
|
||||
)
|
||||
|
@ -113,7 +114,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
|
|||
volumePruneFunc: simplePruneFunc,
|
||||
}, buf)
|
||||
|
||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||
cmd := NewPruneCommand(
|
||||
cli,
|
||||
)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -72,6 +72,7 @@
|
|||
"context": {"type": "string"},
|
||||
"dockerfile": {"type": "string"},
|
||||
"args": {"$ref": "#/definitions/list_or_dict"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"cache_from": {"$ref": "#/definitions/list_of_strings"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/client"
|
||||
)
|
||||
|
||||
|
@ -15,23 +16,24 @@ type FakeCli struct {
|
|||
command.DockerCli
|
||||
client client.APIClient
|
||||
configfile *configfile.ConfigFile
|
||||
out io.Writer
|
||||
out *command.OutStream
|
||||
err io.Writer
|
||||
in io.ReadCloser
|
||||
in *command.InStream
|
||||
store credentials.Store
|
||||
}
|
||||
|
||||
// NewFakeCli returns a Cli backed by the fakeCli
|
||||
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
||||
return &FakeCli{
|
||||
client: client,
|
||||
out: out,
|
||||
out: command.NewOutStream(out),
|
||||
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
|
||||
func (c *FakeCli) SetIn(in io.ReadCloser) {
|
||||
func (c *FakeCli) SetIn(in *command.InStream) {
|
||||
c.in = in
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ func (c *FakeCli) Client() client.APIClient {
|
|||
|
||||
// Out returns the output stream (stdout) the cli should write on
|
||||
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
|
||||
|
@ -62,10 +64,18 @@ func (c *FakeCli) Err() io.Writer {
|
|||
|
||||
// In returns the input stream the cli will use
|
||||
func (c *FakeCli) In() *command.InStream {
|
||||
return command.NewInStream(c.in)
|
||||
return c.in
|
||||
}
|
||||
|
||||
// ConfigFile returns the cli configfile object (to get client configuration)
|
||||
func (c *FakeCli) ConfigFile() *configfile.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