mirror of https://github.com/docker/cli.git
Unit tests for cli/commands/image (except build and tag)
Signed-off-by: Ignacio Capurro <icapurrofagian@gmail.com>
This commit is contained in:
parent
2b31a4bf8d
commit
e7793092a2
|
@ -39,7 +39,9 @@ type Cli interface {
|
||||||
Out() *OutStream
|
Out() *OutStream
|
||||||
Err() io.Writer
|
Err() io.Writer
|
||||||
In() *InStream
|
In() *InStream
|
||||||
|
SetIn(in *InStream)
|
||||||
ConfigFile() *configfile.ConfigFile
|
ConfigFile() *configfile.ConfigFile
|
||||||
|
CredentialsStore(serverAddress string) credentials.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerCli is an instance the docker command line client.
|
// DockerCli is an instance the docker command line client.
|
||||||
|
@ -75,6 +77,11 @@ func (cli *DockerCli) Err() io.Writer {
|
||||||
return cli.err
|
return cli.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIn sets the reader used for stdin
|
||||||
|
func (cli *DockerCli) SetIn(in *InStream) {
|
||||||
|
cli.in = in
|
||||||
|
}
|
||||||
|
|
||||||
// In returns the reader used for stdin
|
// In returns the reader used for stdin
|
||||||
func (cli *DockerCli) In() *InStream {
|
func (cli *DockerCli) In() *InStream {
|
||||||
return cli.in
|
return cli.in
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/client"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeClient struct {
|
||||||
|
client.Client
|
||||||
|
imageTagFunc func(string, string) error
|
||||||
|
imageSaveFunc func(images []string) (io.ReadCloser, error)
|
||||||
|
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
|
||||||
|
imagePushFunc func(ref string, options types.ImagePushOptions) (io.ReadCloser, error)
|
||||||
|
infoFunc func() (types.Info, error)
|
||||||
|
imagePullFunc func(ref string, options types.ImagePullOptions) (io.ReadCloser, error)
|
||||||
|
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
||||||
|
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||||
|
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
|
||||||
|
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
|
||||||
|
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||||
|
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
|
||||||
|
if cli.imageTagFunc != nil {
|
||||||
|
return cli.imageTagFunc(image, ref)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageSave(_ context.Context, images []string) (io.ReadCloser, error) {
|
||||||
|
if cli.imageSaveFunc != nil {
|
||||||
|
return cli.imageSaveFunc(images)
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageRemove(_ context.Context, image string,
|
||||||
|
options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||||
|
if cli.imageRemoveFunc != nil {
|
||||||
|
return cli.imageRemoveFunc(image, options)
|
||||||
|
}
|
||||||
|
return []types.ImageDeleteResponseItem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImagePush(_ context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||||
|
if cli.imagePushFunc != nil {
|
||||||
|
return cli.imagePushFunc(ref, options)
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) Info(_ context.Context) (types.Info, error) {
|
||||||
|
if cli.infoFunc != nil {
|
||||||
|
return cli.infoFunc()
|
||||||
|
}
|
||||||
|
return types.Info{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
|
||||||
|
if cli.imagePullFunc != nil {
|
||||||
|
cli.imagePullFunc(ref, options)
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||||
|
if cli.imagesPruneFunc != nil {
|
||||||
|
return cli.imagesPruneFunc(pruneFilter)
|
||||||
|
}
|
||||||
|
return types.ImagesPruneReport{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||||
|
if cli.imageLoadFunc != nil {
|
||||||
|
return cli.imageLoadFunc(input, quiet)
|
||||||
|
}
|
||||||
|
return types.ImageLoadResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
|
||||||
|
if cli.imageListFunc != nil {
|
||||||
|
return cli.imageListFunc(options)
|
||||||
|
}
|
||||||
|
return []types.ImageSummary{{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, image string) (types.ImageInspect, []byte, error) {
|
||||||
|
if cli.imageInspectFunc != nil {
|
||||||
|
return cli.imageInspectFunc(image)
|
||||||
|
}
|
||||||
|
return types.ImageInspect{}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageImport(_ context.Context, source types.ImageImportSource, ref string,
|
||||||
|
options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||||
|
if cli.imageImportFunc != nil {
|
||||||
|
return cli.imageImportFunc(source, ref, options)
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.HistoryResponseItem, error) {
|
||||||
|
if cli.imageHistoryFunc != nil {
|
||||||
|
return cli.imageHistoryFunc(img)
|
||||||
|
}
|
||||||
|
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type historyOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHistoryCommand creates a new `docker history` command
|
// NewHistoryCommand creates a new `docker history` command
|
||||||
func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts historyOptions
|
var opts historyOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -42,7 +42,7 @@ func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHistory(dockerCli *command.DockerCli, opts historyOptions) error {
|
func runHistory(dockerCli command.Cli, opts historyOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
history, err := dockerCli.Client().ImageHistory(ctx, opts.image)
|
history, err := dockerCli.Client().ImageHistory(ctx, opts.image)
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewHistoryCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
args: []string{},
|
||||||
|
expectedError: "requires exactly 1 argument(s).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "client-error",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
expectedError: "something went wrong",
|
||||||
|
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
|
||||||
|
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewHistoryCommandSuccess(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
outputRegex string
|
||||||
|
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
|
||||||
|
return []image.HistoryResponseItem{{
|
||||||
|
ID: "1234567890123456789",
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quiet",
|
||||||
|
args: []string{"--quiet", "image:tag"},
|
||||||
|
},
|
||||||
|
// TODO: This test is failing since the output does not contain an RFC3339 date
|
||||||
|
//{
|
||||||
|
// name: "non-human",
|
||||||
|
// args: []string{"--human=false", "image:tag"},
|
||||||
|
// outputRegex: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", // RFC3339 date format match
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
name: "non-human-header",
|
||||||
|
args: []string{"--human=false", "image:tag"},
|
||||||
|
outputRegex: "CREATED\\sAT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quiet-no-trunc",
|
||||||
|
args: []string{"--quiet", "--no-trunc", "image:tag"},
|
||||||
|
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
|
||||||
|
return []image.HistoryResponseItem{{
|
||||||
|
ID: "1234567890123456789",
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual := buf.String()
|
||||||
|
if tc.outputRegex == "" {
|
||||||
|
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("history-command-success.%s.golden", tc.name))[:])
|
||||||
|
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
|
||||||
|
} else {
|
||||||
|
match, _ := regexp.MatchString(tc.outputRegex, actual)
|
||||||
|
assert.Equal(t, match, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ type importOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImportCommand creates a new `docker import` command
|
// NewImportCommand creates a new `docker import` command
|
||||||
func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts importOptions
|
var opts importOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -48,7 +48,7 @@ func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runImport(dockerCli *command.DockerCli, opts importOptions) error {
|
func runImport(dockerCli command.Cli, opts importOptions) error {
|
||||||
var (
|
var (
|
||||||
in io.Reader
|
in io.Reader
|
||||||
srcName = opts.source
|
srcName = opts.source
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewImportCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
args: []string{},
|
||||||
|
expectedError: "requires at least 1 argument(s).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "import-failed",
|
||||||
|
args: []string{"testdata/import-command-success.input.txt"},
|
||||||
|
expectedError: "something went wrong",
|
||||||
|
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||||
|
return nil, errors.Errorf("something went wrong")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewImportCommandInvalidFile(t *testing.T) {
|
||||||
|
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
|
||||||
|
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewImportCommandSuccess(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
args: []string{"testdata/import-command-success.input.txt"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "terminal-source",
|
||||||
|
args: []string{"-"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double",
|
||||||
|
args: []string{"-", "image:local"},
|
||||||
|
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||||
|
assert.Equal(t, ref, "image:local")
|
||||||
|
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, options.Message, "test 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, options.Changes[0], "ENV DEBUG true")
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.NoError(t, cmd.Execute())
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ type inspectOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newInspectCommand creates a new cobra.Command for `docker image inspect`
|
// newInspectCommand creates a new cobra.Command for `docker image inspect`
|
||||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts inspectOptions
|
var opts inspectOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -33,7 +33,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewInspectCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
args: []string{},
|
||||||
|
expectedError: "requires at least 1 argument(s).",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInspectCommandSuccess(t *testing.T) {
|
||||||
|
imageInspectInvocationCount := 0
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
imageCount int
|
||||||
|
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
args: []string{"image"},
|
||||||
|
imageCount: 1,
|
||||||
|
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
|
||||||
|
imageInspectInvocationCount++
|
||||||
|
assert.Equal(t, image, "image")
|
||||||
|
return types.ImageInspect{}, nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format",
|
||||||
|
imageCount: 1,
|
||||||
|
args: []string{"--format='{{.ID}}'", "image"},
|
||||||
|
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
|
||||||
|
imageInspectInvocationCount++
|
||||||
|
return types.ImageInspect{ID: image}, nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple-many",
|
||||||
|
args: []string{"image1", "image2"},
|
||||||
|
imageCount: 2,
|
||||||
|
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
|
||||||
|
imageInspectInvocationCount++
|
||||||
|
if imageInspectInvocationCount == 1 {
|
||||||
|
assert.Equal(t, image, "image1")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, image, "image2")
|
||||||
|
}
|
||||||
|
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, tc.imageCount, imageInspectInvocationCount)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ type imagesOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImagesCommand creates a new `docker images` command
|
// NewImagesCommand creates a new `docker images` command
|
||||||
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := imagesOptions{filter: opts.NewFilterOpt()}
|
opts := imagesOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -50,14 +50,14 @@ func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := *NewImagesCommand(dockerCli)
|
cmd := *NewImagesCommand(dockerCli)
|
||||||
cmd.Aliases = []string{"images", "list"}
|
cmd.Aliases = []string{"images", "list"}
|
||||||
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
|
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
|
||||||
return &cmd
|
return &cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
|
func runImages(dockerCli command.Cli, opts imagesOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
filters := opts.filter.Value()
|
filters := opts.filter.Value()
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewImagesCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
args: []string{"arg1", "arg2"},
|
||||||
|
expectedError: "requires at most 1 argument(s).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed-list",
|
||||||
|
expectedError: "something went wrong",
|
||||||
|
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
|
||||||
|
return []types.ImageSummary{{}}, errors.Errorf("something went wrong")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.Error(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, options.Filters.Get("reference")[0], "image")
|
||||||
|
return []types.ImageSummary{{}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filters",
|
||||||
|
args: []string{"--filter", "name=value"},
|
||||||
|
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
|
||||||
|
assert.Equal(t, options.Filters.Get("name")[0], "value")
|
||||||
|
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.Equal(t, cmd.HasAlias("images"), true)
|
||||||
|
assert.Equal(t, cmd.HasAlias("list"), true)
|
||||||
|
assert.Equal(t, cmd.HasAlias("other"), false)
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type loadOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLoadCommand creates a new `docker load` command
|
// NewLoadCommand creates a new `docker load` command
|
||||||
func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewLoadCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts loadOptions
|
var opts loadOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -39,7 +39,7 @@ func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLoad(dockerCli *command.DockerCli, opts loadOptions) error {
|
func runLoad(dockerCli command.Cli, opts loadOptions) error {
|
||||||
|
|
||||||
var input io.Reader = dockerCli.In()
|
var input io.Reader = dockerCli.In()
|
||||||
if opts.input != "" {
|
if opts.input != "" {
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
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)
|
||||||
|
assert.Error(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()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewLoadCommandSuccess(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||||
|
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json",
|
||||||
|
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||||
|
json := "{\"ID\": \"1\"}"
|
||||||
|
return types.ImageLoadResponse{
|
||||||
|
Body: ioutil.NopCloser(strings.NewReader(json)),
|
||||||
|
JSON: true,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "input-file",
|
||||||
|
args: []string{"--input", "testdata/load-command-success.input.txt"},
|
||||||
|
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||||
|
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual := buf.String()
|
||||||
|
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("load-command-success.%s.golden", tc.name))[:])
|
||||||
|
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type pruneOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// NewPruneCommand returns a new cobra prune command for images
|
||||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -55,7 +55,7 @@ Are you sure you want to continue?`
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := opts.filter.Value()
|
pruneFilters := opts.filter.Value()
|
||||||
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
||||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
@ -90,6 +90,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
|
||||||
|
|
||||||
// RunPrune calls the Image Prune API
|
// RunPrune calls the Image Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli *command.DockerCli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewPruneCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
args: []string{"something"},
|
||||||
|
expectedError: "accepts no argument(s).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prune-error",
|
||||||
|
args: []string{"--force"},
|
||||||
|
expectedError: "something went wrong",
|
||||||
|
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||||
|
return types.ImagesPruneReport{}, errors.Errorf("something went wrong")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
|
||||||
|
imagesPruneFunc: tc.imagesPruneFunc,
|
||||||
|
}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.Error(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, pruneFilter.Get("dangling")[0], "false")
|
||||||
|
return types.ImagesPruneReport{}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "force-deleted",
|
||||||
|
args: []string{"--force"},
|
||||||
|
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||||
|
assert.Equal(t, pruneFilter.Get("dangling")[0], "true")
|
||||||
|
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, pruneFilter.Get("dangling")[0], "true")
|
||||||
|
return types.ImagesPruneReport{
|
||||||
|
ImagesDeleted: []types.ImageDeleteResponseItem{{Untagged: "image1"}},
|
||||||
|
SpaceReclaimed: 2,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
|
||||||
|
imagesPruneFunc: tc.imagesPruneFunc,
|
||||||
|
}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual := buf.String()
|
||||||
|
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("prune-command-success.%s.golden", tc.name))[:])
|
||||||
|
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type pullOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPullCommand creates a new `docker pull` command
|
// NewPullCommand creates a new `docker pull` command
|
||||||
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPullCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts pullOptions
|
var opts pullOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -40,7 +40,7 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
|
func runPull(dockerCli command.Cli, opts pullOptions) error {
|
||||||
distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
|
distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewPullCommandErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
|
||||||
|
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "wrong-args",
|
||||||
|
expectedError: "requires exactly 1 argument(s).",
|
||||||
|
args: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid-name",
|
||||||
|
expectedError: "invalid reference format: repository name must be lowercase",
|
||||||
|
args: []string{"UPPERCASE_REPO"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all-tags-with-tag",
|
||||||
|
expectedError: "tag can't be used with --all-tags/-a",
|
||||||
|
args: []string{"--all-tags", "image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pull-error",
|
||||||
|
args: []string{"--disable-content-trust=false", "image:tag"},
|
||||||
|
expectedError: "you are not authorized to perform this operation: server returned 401.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewPullCommandSuccess(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
|
||||||
|
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple-no-tag",
|
||||||
|
args: []string{"image"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual := buf.String()
|
||||||
|
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:])
|
||||||
|
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPushCommand creates a new `docker push` command
|
// NewPushCommand creates a new `docker push` command
|
||||||
func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "push [OPTIONS] NAME[:TAG]",
|
Use: "push [OPTIONS] NAME[:TAG]",
|
||||||
Short: "Push an image or a repository to a registry",
|
Short: "Push an image or a repository to a registry",
|
||||||
|
@ -29,7 +29,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPush(dockerCli *command.DockerCli, remote string) error {
|
func runPush(dockerCli command.Cli, remote string) error {
|
||||||
ref, err := reference.ParseNormalizedNamed(remote)
|
ref, err := reference.ParseNormalizedNamed(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
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/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)
|
||||||
|
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewPushCommandSuccess(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo,
|
||||||
|
ref reference.Named, authConfig types.AuthConfig,
|
||||||
|
requestPrivilege types.RequestPrivilegeFunc) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
|
||||||
|
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
},
|
||||||
|
}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.NoError(t, cmd.Execute())
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type removeOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRemoveCommand creates a new `docker remove` command
|
// NewRemoveCommand creates a new `docker remove` command
|
||||||
func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts removeOptions
|
var opts removeOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -39,14 +39,14 @@ func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := *NewRemoveCommand(dockerCli)
|
cmd := *NewRemoveCommand(dockerCli)
|
||||||
cmd.Aliases = []string{"rmi", "remove"}
|
cmd.Aliases = []string{"rmi", "remove"}
|
||||||
cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]"
|
cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]"
|
||||||
return &cmd
|
return &cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRemove(dockerCli *command.DockerCli, opts removeOptions, images []string) error {
|
func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/testutil"
|
||||||
|
"github.com/docker/docker/pkg/testutil/golden"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRemoveCommandAlias(t *testing.T) {
|
||||||
|
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
|
||||||
|
assert.Equal(t, cmd.HasAlias("rmi"), true)
|
||||||
|
assert.Equal(t, cmd.HasAlias("remove"), true)
|
||||||
|
assert.Equal(t, cmd.HasAlias("other"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Equal(t, options.Force, false)
|
||||||
|
assert.Equal(t, options.PruneChildren, true)
|
||||||
|
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)
|
||||||
|
assert.Error(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, image, "image1")
|
||||||
|
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, image, "image1")
|
||||||
|
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Image Deleted and Untagged",
|
||||||
|
args: []string{"image1", "image2"},
|
||||||
|
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||||
|
if image == "image1" {
|
||||||
|
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
|
||||||
|
}
|
||||||
|
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||||
|
imageRemoveFunc: tc.imageRemoveFunc,
|
||||||
|
}, buf))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.NoError(t, cmd.Execute())
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual := buf.String()
|
||||||
|
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:])
|
||||||
|
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ type saveOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSaveCommand creates a new `docker save` command
|
// NewSaveCommand creates a new `docker save` command
|
||||||
func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts saveOptions
|
var opts saveOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -36,7 +36,7 @@ func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSave(dockerCli *command.DockerCli, opts saveOptions) error {
|
func runSave(dockerCli command.Cli, opts saveOptions) error {
|
||||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||||
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
|
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
assert.Error(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) {
|
||||||
|
assert.Equal(t, len(images), 1)
|
||||||
|
assert.Equal(t, images[0], "arg1")
|
||||||
|
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) {
|
||||||
|
assert.Equal(t, len(images), 2)
|
||||||
|
assert.Equal(t, images[0], "arg1")
|
||||||
|
assert.Equal(t, images[1], "arg2")
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
|
||||||
|
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
|
||||||
|
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||||
|
},
|
||||||
|
}, new(bytes.Buffer)))
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
assert.NoError(t, cmd.Execute())
|
||||||
|
if tc.deferredFunc != nil {
|
||||||
|
tc.deferredFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ type tagOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTagCommand creates a new `docker tag` command
|
// NewTagCommand creates a new `docker tag` command
|
||||||
func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewTagCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts tagOptions
|
var opts tagOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -34,7 +34,7 @@ func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTag(dockerCli *command.DockerCli, opts tagOptions) error {
|
func runTag(dockerCli command.Cli, opts tagOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)
|
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/internal/test"
|
||||||
|
"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)
|
||||||
|
assert.Error(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, image, "image1")
|
||||||
|
assert.Equal(t, ref, "image2")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, buf))
|
||||||
|
cmd.SetArgs([]string{"image1", "image2"})
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
assert.NoError(t, cmd.Execute())
|
||||||
|
value, _ := cmd.Flags().GetBool("interspersed")
|
||||||
|
assert.Equal(t, value, false)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
1234567890123456789
|
|
@ -0,0 +1 @@
|
||||||
|
tag
|
|
@ -0,0 +1,2 @@
|
||||||
|
IMAGE CREATED CREATED BY SIZE COMMENT
|
||||||
|
123456789012 Less than a second ago 0B
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
'image'
|
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Id": "",
|
||||||
|
"RepoTags": null,
|
||||||
|
"RepoDigests": null,
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "",
|
||||||
|
"Created": "",
|
||||||
|
"Container": "",
|
||||||
|
"ContainerConfig": null,
|
||||||
|
"DockerVersion": "",
|
||||||
|
"Author": "",
|
||||||
|
"Config": null,
|
||||||
|
"Architecture": "",
|
||||||
|
"Os": "",
|
||||||
|
"Size": 0,
|
||||||
|
"VirtualSize": 0,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": null,
|
||||||
|
"Name": ""
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "",
|
||||||
|
"RepoTags": null,
|
||||||
|
"RepoDigests": null,
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "",
|
||||||
|
"Created": "",
|
||||||
|
"Container": "",
|
||||||
|
"ContainerConfig": null,
|
||||||
|
"DockerVersion": "",
|
||||||
|
"Author": "",
|
||||||
|
"Config": null,
|
||||||
|
"Architecture": "",
|
||||||
|
"Os": "",
|
||||||
|
"Size": 0,
|
||||||
|
"VirtualSize": 0,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": null,
|
||||||
|
"Name": ""
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Id": "",
|
||||||
|
"RepoTags": null,
|
||||||
|
"RepoDigests": null,
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "",
|
||||||
|
"Created": "",
|
||||||
|
"Container": "",
|
||||||
|
"ContainerConfig": null,
|
||||||
|
"DockerVersion": "",
|
||||||
|
"Author": "",
|
||||||
|
"Config": null,
|
||||||
|
"Architecture": "",
|
||||||
|
"Os": "",
|
||||||
|
"Size": 0,
|
||||||
|
"VirtualSize": 0,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": null,
|
||||||
|
"Name": ""
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
1:
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -0,0 +1,2 @@
|
||||||
|
WARNING! This will remove all images without at least one container associated to them.
|
||||||
|
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
deleted: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 1B
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
untagged: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 2B
|
|
@ -0,0 +1 @@
|
||||||
|
Using default tag: latest
|
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
|
@ -0,0 +1,2 @@
|
||||||
|
Deleted: image1
|
||||||
|
Deleted: image1
|
|
@ -0,0 +1,2 @@
|
||||||
|
Untagged: image1
|
||||||
|
Untagged: image1
|
|
@ -29,7 +29,7 @@ type target struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// trustedPush handles content trust pushing of an image
|
// trustedPush handles content trust pushing of an image
|
||||||
func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
||||||
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
|
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -42,7 +42,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
|
||||||
|
|
||||||
// PushTrustedReference pushes a canonical reference to the trust server.
|
// PushTrustedReference pushes a canonical reference to the trust server.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func PushTrustedReference(cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
|
func PushTrustedReference(cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
|
||||||
// If it is a trusted push we would like to find the target entry which match the
|
// If it is a trusted push we would like to find the target entry which match the
|
||||||
// tag provided in the function and then do an AddTarget later.
|
// tag provided in the function and then do an AddTarget later.
|
||||||
target := &client.Target{}
|
target := &client.Target{}
|
||||||
|
@ -203,7 +203,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T
|
||||||
}
|
}
|
||||||
|
|
||||||
// imagePushPrivileged push the image
|
// imagePushPrivileged push the image
|
||||||
func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
|
func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
|
||||||
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -217,7 +217,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// trustedPull handles content trust pulling of an image
|
// trustedPull handles content trust pulling of an image
|
||||||
func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
||||||
var refs []target
|
var refs []target
|
||||||
|
|
||||||
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
|
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
|
||||||
|
@ -296,7 +296,7 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
|
||||||
}
|
}
|
||||||
|
|
||||||
// imagePullPrivileged pulls the image and displays it to the output
|
// imagePullPrivileged pulls the image and displays it to the output
|
||||||
func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
|
func imagePullPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
|
||||||
|
|
||||||
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -318,7 +318,7 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrustedReference returns the canonical trusted reference for an image reference
|
// TrustedReference returns the canonical trusted reference for an image reference
|
||||||
func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
|
func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
|
||||||
var (
|
var (
|
||||||
repoInfo *registry.RepositoryInfo
|
repoInfo *registry.RepositoryInfo
|
||||||
err error
|
err error
|
||||||
|
@ -372,7 +372,7 @@ func convertTarget(t client.Target) (target, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagTrusted tags a trusted ref
|
// TagTrusted tags a trusted ref
|
||||||
func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef reference.Canonical, ref reference.NamedTagged) error {
|
func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canonical, ref reference.NamedTagged) error {
|
||||||
// Use familiar references when interacting with client and output
|
// Use familiar references when interacting with client and output
|
||||||
familiarRef := reference.FamiliarString(ref)
|
familiarRef := reference.FamiliarString(ref)
|
||||||
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"errors"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/pkg/errors"
|
"io"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InStream is an input stream used by the DockerCli to read user input
|
// InStream is an input stream used by the DockerCli to read user input
|
||||||
type InStream struct {
|
type InStream struct {
|
||||||
|
CommonStream
|
||||||
in io.ReadCloser
|
in io.ReadCloser
|
||||||
fd uintptr
|
|
||||||
isTerminal bool
|
|
||||||
state *term.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InStream) Read(p []byte) (int, error) {
|
func (i *InStream) Read(p []byte) (int, error) {
|
||||||
|
@ -26,32 +22,6 @@ func (i *InStream) Close() error {
|
||||||
return i.in.Close()
|
return i.in.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FD returns the file descriptor number for this stream
|
|
||||||
func (i *InStream) FD() uintptr {
|
|
||||||
return i.fd
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if this stream is connected to a terminal
|
|
||||||
func (i *InStream) IsTerminal() bool {
|
|
||||||
return i.isTerminal
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRawTerminal sets raw mode on the input terminal
|
|
||||||
func (i *InStream) SetRawTerminal() (err error) {
|
|
||||||
if os.Getenv("NORAW") != "" || !i.isTerminal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
i.state, err = term.SetRawTerminal(i.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
|
// CheckTty checks if we are trying to attach to a container tty
|
||||||
// from a non-tty client input stream, and if so, returns an error.
|
// from a non-tty client input stream, and if so, returns an error.
|
||||||
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
|
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
|
||||||
|
@ -71,5 +41,5 @@ func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
|
||||||
// NewInStream returns a new InStream object from a ReadCloser
|
// NewInStream returns a new InStream object from a ReadCloser
|
||||||
func NewInStream(in io.ReadCloser) *InStream {
|
func NewInStream(in io.ReadCloser) *InStream {
|
||||||
fd, isTerminal := term.GetFdInfo(in)
|
fd, isTerminal := term.GetFdInfo(in)
|
||||||
return &InStream{in: in, fd: fd, isTerminal: isTerminal}
|
return &InStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, in: in}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,22 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutStream is an output stream used by the DockerCli to write normal program
|
// OutStream is an output stream used by the DockerCli to write normal program
|
||||||
// output.
|
// output.
|
||||||
type OutStream struct {
|
type OutStream struct {
|
||||||
|
CommonStream
|
||||||
out io.Writer
|
out io.Writer
|
||||||
fd uintptr
|
|
||||||
isTerminal bool
|
|
||||||
state *term.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OutStream) Write(p []byte) (int, error) {
|
func (o *OutStream) Write(p []byte) (int, error) {
|
||||||
return o.out.Write(p)
|
return o.out.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FD returns the file descriptor number for this stream
|
|
||||||
func (o *OutStream) FD() uintptr {
|
|
||||||
return o.fd
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if this stream is connected to a terminal
|
|
||||||
func (o *OutStream) IsTerminal() bool {
|
|
||||||
return o.isTerminal
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRawTerminal sets raw mode on the output terminal
|
|
||||||
func (o *OutStream) SetRawTerminal() (err error) {
|
|
||||||
if os.Getenv("NORAW") != "" || !o.isTerminal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
o.state, err = term.SetRawTerminalOutput(o.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
|
// GetTtySize returns the height and width in characters of the tty
|
||||||
func (o *OutStream) GetTtySize() (uint, uint) {
|
func (o *OutStream) GetTtySize() (uint, uint) {
|
||||||
if !o.isTerminal {
|
if !o.isTerminal {
|
||||||
|
@ -65,5 +35,5 @@ func (o *OutStream) GetTtySize() (uint, uint) {
|
||||||
// NewOutStream returns a new OutStream object from a Writer
|
// NewOutStream returns a new OutStream object from a Writer
|
||||||
func NewOutStream(out io.Writer) *OutStream {
|
func NewOutStream(out io.Writer) *OutStream {
|
||||||
fd, isTerminal := term.GetFdInfo(out)
|
fd, isTerminal := term.GetFdInfo(out)
|
||||||
return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
|
return &OutStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, out: out}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ElectAuthServer returns the default registry to use (by asking the daemon)
|
// ElectAuthServer returns the default registry to use (by asking the daemon)
|
||||||
func ElectAuthServer(ctx context.Context, cli *DockerCli) string {
|
func ElectAuthServer(ctx context.Context, cli Cli) string {
|
||||||
// The daemon `/info` endpoint informs us of the default registry being
|
// The daemon `/info` endpoint informs us of the default registry being
|
||||||
// used. This is essential in cross-platforms environment, where for
|
// used. This is essential in cross-platforms environment, where for
|
||||||
// example a Linux client might be interacting with a Windows daemon, hence
|
// example a Linux client might be interacting with a Windows daemon, hence
|
||||||
|
@ -46,7 +46,7 @@ func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||||
// for the given command.
|
// for the given command.
|
||||||
func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
||||||
return func() (string, error) {
|
return func() (string, error) {
|
||||||
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
|
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
|
||||||
indexServer := registry.GetAuthConfigKey(index)
|
indexServer := registry.GetAuthConfigKey(index)
|
||||||
|
@ -62,7 +62,7 @@ func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.I
|
||||||
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
||||||
// default index, it uses the default index name for the daemon's platform,
|
// default index, it uses the default index name for the daemon's platform,
|
||||||
// not the client's platform.
|
// not the client's platform.
|
||||||
func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes.IndexInfo) types.AuthConfig {
|
func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||||
configKey := index.Name
|
configKey := index.Name
|
||||||
if index.Official {
|
if index.Official {
|
||||||
configKey = ElectAuthServer(ctx, cli)
|
configKey = ElectAuthServer(ctx, cli)
|
||||||
|
@ -73,10 +73,10 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureAuth returns an AuthConfig from the specified user, password and server.
|
// ConfigureAuth returns an AuthConfig from the specified user, password and server.
|
||||||
func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
||||||
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
cli.in = NewInStream(os.Stdin)
|
cli.SetIn(NewInStream(os.Stdin))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDefaultRegistry {
|
if !isDefaultRegistry {
|
||||||
|
@ -160,7 +160,7 @@ func promptWithDefault(out io.Writer, prompt string, configDefault string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
|
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
|
||||||
func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image string) (string, error) {
|
func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
|
||||||
// Retrieve encoded auth token from the image reference
|
// Retrieve encoded auth token from the image reference
|
||||||
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
|
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
||||||
func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) {
|
func resolveAuthConfigFromImage(ctx context.Context, cli Cli, image string) (types.AuthConfig, error) {
|
||||||
registryRef, err := reference.ParseNormalizedNamed(image)
|
registryRef, err := reference.ParseNormalizedNamed(image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.AuthConfig{}, err
|
return types.AuthConfig{}, err
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal sets raw mode on the input terminal
|
||||||
|
func (s *CommonStream) SetRawTerminal() (err error) {
|
||||||
|
if os.Getenv("NORAW") != "" || !s.isTerminal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.state, err = term.SetRawTerminal(s.fd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/internal/test"
|
"github.com/docker/cli/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
@ -96,7 +97,7 @@ func TestSwarmUnlock(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}, buf)
|
}, buf)
|
||||||
dockerCli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
dockerCli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||||
cmd := newUnlockCommand(dockerCli)
|
cmd := newUnlockCommand(dockerCli)
|
||||||
assert.NoError(t, cmd.Execute())
|
assert.NoError(t, cmd.Execute())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/internal/test"
|
"github.com/docker/cli/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
@ -91,7 +92,7 @@ func TestVolumePrunePromptYes(t *testing.T) {
|
||||||
volumePruneFunc: simplePruneFunc,
|
volumePruneFunc: simplePruneFunc,
|
||||||
}, buf)
|
}, buf)
|
||||||
|
|
||||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||||
cmd := NewPruneCommand(
|
cmd := NewPruneCommand(
|
||||||
cli,
|
cli,
|
||||||
)
|
)
|
||||||
|
@ -113,7 +114,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
|
||||||
volumePruneFunc: simplePruneFunc,
|
volumePruneFunc: simplePruneFunc,
|
||||||
}, buf)
|
}, buf)
|
||||||
|
|
||||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
|
||||||
cmd := NewPruneCommand(
|
cmd := NewPruneCommand(
|
||||||
cli,
|
cli,
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/config/credentials"
|
||||||
"github.com/docker/cli/client"
|
"github.com/docker/cli/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,23 +16,24 @@ type FakeCli struct {
|
||||||
command.DockerCli
|
command.DockerCli
|
||||||
client client.APIClient
|
client client.APIClient
|
||||||
configfile *configfile.ConfigFile
|
configfile *configfile.ConfigFile
|
||||||
out io.Writer
|
out *command.OutStream
|
||||||
err io.Writer
|
err io.Writer
|
||||||
in io.ReadCloser
|
in *command.InStream
|
||||||
|
store credentials.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFakeCli returns a Cli backed by the fakeCli
|
// NewFakeCli returns a Cli backed by the fakeCli
|
||||||
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
||||||
return &FakeCli{
|
return &FakeCli{
|
||||||
client: client,
|
client: client,
|
||||||
out: out,
|
out: command.NewOutStream(out),
|
||||||
err: ioutil.Discard,
|
err: ioutil.Discard,
|
||||||
in: ioutil.NopCloser(strings.NewReader("")),
|
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIn sets the input of the cli to the specified ReadCloser
|
// SetIn sets the input of the cli to the specified ReadCloser
|
||||||
func (c *FakeCli) SetIn(in io.ReadCloser) {
|
func (c *FakeCli) SetIn(in *command.InStream) {
|
||||||
c.in = in
|
c.in = in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ func (c *FakeCli) Client() client.APIClient {
|
||||||
|
|
||||||
// Out returns the output stream (stdout) the cli should write on
|
// Out returns the output stream (stdout) the cli should write on
|
||||||
func (c *FakeCli) Out() *command.OutStream {
|
func (c *FakeCli) Out() *command.OutStream {
|
||||||
return command.NewOutStream(c.out)
|
return c.out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns the output stream (stderr) the cli should write on
|
// Err returns the output stream (stderr) the cli should write on
|
||||||
|
@ -62,10 +64,18 @@ func (c *FakeCli) Err() io.Writer {
|
||||||
|
|
||||||
// In returns the input stream the cli will use
|
// In returns the input stream the cli will use
|
||||||
func (c *FakeCli) In() *command.InStream {
|
func (c *FakeCli) In() *command.InStream {
|
||||||
return command.NewInStream(c.in)
|
return c.in
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigFile returns the cli configfile object (to get client configuration)
|
// ConfigFile returns the cli configfile object (to get client configuration)
|
||||||
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
|
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
|
||||||
return c.configfile
|
return c.configfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CredentialsStore returns the fake store the cli will use
|
||||||
|
func (c *FakeCli) CredentialsStore(serverAddress string) credentials.Store {
|
||||||
|
if c.store == nil {
|
||||||
|
c.store = NewFakeStore()
|
||||||
|
}
|
||||||
|
return c.store
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/cli/cli/config/credentials"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fake store implements a credentials.Store that only acts as an in memory map
|
||||||
|
type fakeStore struct {
|
||||||
|
store map[string]types.AuthConfig
|
||||||
|
eraseFunc func(serverAddress string) error
|
||||||
|
getFunc func(serverAddress string) (types.AuthConfig, error)
|
||||||
|
getAllFunc func() (map[string]types.AuthConfig, error)
|
||||||
|
storeFunc func(authConfig types.AuthConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakeStore creates a new file credentials store.
|
||||||
|
func NewFakeStore() credentials.Store {
|
||||||
|
return &fakeStore{store: map[string]types.AuthConfig{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) SetStore(store map[string]types.AuthConfig) {
|
||||||
|
c.store = store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) SetEraseFunc(eraseFunc func(string) error) {
|
||||||
|
c.eraseFunc = eraseFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) SetGetFunc(getFunc func(string) (types.AuthConfig, error)) {
|
||||||
|
c.getFunc = getFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) SetGetAllFunc(getAllFunc func() (map[string]types.AuthConfig, error)) {
|
||||||
|
c.getAllFunc = getAllFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) SetStoreFunc(storeFunc func(types.AuthConfig) error) {
|
||||||
|
c.storeFunc = storeFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase removes the given credentials from the map store
|
||||||
|
func (c *fakeStore) Erase(serverAddress string) error {
|
||||||
|
if c.eraseFunc != nil {
|
||||||
|
return c.eraseFunc(serverAddress)
|
||||||
|
}
|
||||||
|
delete(c.store, serverAddress)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials for a specific server from the map store.
|
||||||
|
func (c *fakeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
if c.getFunc != nil {
|
||||||
|
return c.getFunc(serverAddress)
|
||||||
|
}
|
||||||
|
authConfig, _ := c.store[serverAddress]
|
||||||
|
return authConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||||
|
if c.getAllFunc != nil {
|
||||||
|
return c.getAllFunc()
|
||||||
|
}
|
||||||
|
return c.store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the given credentials in the map store.
|
||||||
|
func (c *fakeStore) Store(authConfig types.AuthConfig) error {
|
||||||
|
if c.storeFunc != nil {
|
||||||
|
return c.storeFunc(authConfig)
|
||||||
|
}
|
||||||
|
c.store[authConfig.ServerAddress] = authConfig
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue