Merge pull request #31124 from vdemeester/volume-unit-tests
Add unit tests to cli/command/volume package
This commit is contained in:
commit
822abee151
29 changed files with 814 additions and 39 deletions
53
cli/command/volume/client_test.go
Normal file
53
cli/command/volume/client_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
volumetypes "github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
|
||||
volumeInspectFunc func(volumeID string) (types.Volume, error)
|
||||
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
|
||||
volumeRemoveFunc func(volumeID string, force bool) error
|
||||
volumePruneFunc func(filter filters.Args) (types.VolumesPruneReport, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) {
|
||||
if c.volumeCreateFunc != nil {
|
||||
return c.volumeCreateFunc(options)
|
||||
}
|
||||
return types.Volume{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) {
|
||||
if c.volumeInspectFunc != nil {
|
||||
return c.volumeInspectFunc(volumeID)
|
||||
}
|
||||
return types.Volume{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
||||
if c.volumeListFunc != nil {
|
||||
return c.volumeListFunc(filter)
|
||||
}
|
||||
return volumetypes.VolumesListOKBody{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumesPrune(ctx context.Context, filter filters.Args) (types.VolumesPruneReport, error) {
|
||||
if c.volumePruneFunc != nil {
|
||||
return c.volumePruneFunc(filter)
|
||||
}
|
||||
return types.VolumesPruneReport{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
|
||||
if c.volumeRemoveFunc != nil {
|
||||
return c.volumeRemoveFunc(volumeID, force)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewVolumeCommand returns a cobra command for `volume` subcommands
|
||||
|
|
|
@ -19,7 +19,7 @@ type createOptions struct {
|
|||
labels opts.ListOpts
|
||||
}
|
||||
|
||||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := createOptions{
|
||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
||||
|
@ -32,8 +32,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 1 {
|
||||
if opts.name != "" {
|
||||
fmt.Fprint(dockerCli.Err(), "Conflicting options: either specify --name or provide positional arg, not both\n")
|
||||
return cli.StatusError{StatusCode: 1}
|
||||
return fmt.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
|
||||
}
|
||||
opts.name = args[0]
|
||||
}
|
||||
|
@ -50,7 +49,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
|
||||
func runCreate(dockerCli command.Cli, opts createOptions) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
volReq := volumetypes.VolumesCreateBody{
|
||||
|
|
142
cli/command/volume/create_test.go
Normal file
142
cli/command/volume/create_test.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
volumetypes "github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestVolumeCreateErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{"volumeName"},
|
||||
flags: map[string]string{
|
||||
"name": "volumeName",
|
||||
},
|
||||
expectedError: "Conflicting options: either specify --name or provide positional arg, not both",
|
||||
},
|
||||
{
|
||||
args: []string{"too", "many"},
|
||||
expectedError: "requires at most 1 argument(s)",
|
||||
},
|
||||
{
|
||||
volumeCreateFunc: func(createBody volumetypes.VolumesCreateBody) (types.Volume, error) {
|
||||
return types.Volume{}, fmt.Errorf("error creating volume")
|
||||
},
|
||||
expectedError: "error creating volume",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newCreateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeCreateFunc: tc.volumeCreateFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeCreateWithName(t *testing.T) {
|
||||
name := "foo"
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
|
||||
if body.Name != name {
|
||||
return types.Volume{}, fmt.Errorf("expected name %q, got %q", name, body.Name)
|
||||
}
|
||||
return types.Volume{
|
||||
Name: body.Name,
|
||||
}, nil
|
||||
},
|
||||
}, buf)
|
||||
|
||||
// Test by flags
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.Flags().Set("name", name)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), name)
|
||||
|
||||
// Then by args
|
||||
buf.Reset()
|
||||
cmd = newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{name})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), name)
|
||||
}
|
||||
|
||||
func TestVolumeCreateWithFlags(t *testing.T) {
|
||||
expectedDriver := "foo"
|
||||
expectedOpts := map[string]string{
|
||||
"bar": "1",
|
||||
"baz": "baz",
|
||||
}
|
||||
expectedLabels := map[string]string{
|
||||
"lbl1": "v1",
|
||||
"lbl2": "v2",
|
||||
}
|
||||
name := "banana"
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
|
||||
if body.Name != "" {
|
||||
return types.Volume{}, fmt.Errorf("expected empty name, got %q", body.Name)
|
||||
}
|
||||
if body.Driver != expectedDriver {
|
||||
return types.Volume{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, body.Driver)
|
||||
}
|
||||
if !compareMap(body.DriverOpts, expectedOpts) {
|
||||
return types.Volume{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, body.DriverOpts)
|
||||
}
|
||||
if !compareMap(body.Labels, expectedLabels) {
|
||||
return types.Volume{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, body.Labels)
|
||||
}
|
||||
return types.Volume{
|
||||
Name: name,
|
||||
}, nil
|
||||
},
|
||||
}, buf)
|
||||
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.Flags().Set("driver", "foo")
|
||||
cmd.Flags().Set("opt", "bar=1")
|
||||
cmd.Flags().Set("opt", "baz=baz")
|
||||
cmd.Flags().Set("label", "lbl1=v1")
|
||||
cmd.Flags().Set("label", "lbl2=v2")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), name)
|
||||
}
|
||||
|
||||
func compareMap(actual map[string]string, expected map[string]string) bool {
|
||||
if len(actual) != len(expected) {
|
||||
return false
|
||||
}
|
||||
for key, value := range actual {
|
||||
if expectedValue, ok := expected[key]; ok {
|
||||
if expectedValue != value {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/inspect"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
|
@ -14,7 +13,7 @@ type inspectOptions struct {
|
|||
names []string
|
||||
}
|
||||
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -32,7 +31,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()
|
||||
|
|
150
cli/command/volume/inspect_test.go
Normal file
150
cli/command/volume/inspect_test.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestVolumeInspectErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumeInspectFunc func(volumeID string) (types.Volume, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
||||
return types.Volume{}, fmt.Errorf("error while inspecting the volume")
|
||||
},
|
||||
expectedError: "error while inspecting the volume",
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
flags: map[string]string{
|
||||
"format": "{{invalid format}}",
|
||||
},
|
||||
expectedError: "Template parsing error",
|
||||
},
|
||||
{
|
||||
args: []string{"foo", "bar"},
|
||||
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
||||
if volumeID == "foo" {
|
||||
return types.Volume{
|
||||
Name: "foo",
|
||||
}, nil
|
||||
}
|
||||
return types.Volume{}, fmt.Errorf("error while inspecting the volume")
|
||||
},
|
||||
expectedError: "error while inspecting the volume",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeInspectFunc: tc.volumeInspectFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeInspectWithoutFormat(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
volumeInspectFunc func(volumeID string) (types.Volume, error)
|
||||
}{
|
||||
{
|
||||
name: "single-volume",
|
||||
args: []string{"foo"},
|
||||
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
||||
if volumeID != "foo" {
|
||||
return types.Volume{}, fmt.Errorf("Invalid volumeID, expected %s, got %s", "foo", volumeID)
|
||||
}
|
||||
return *Volume(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-volume-with-labels",
|
||||
args: []string{"foo", "bar"},
|
||||
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
||||
return *Volume(VolumeName(volumeID), VolumeLabels(map[string]string{
|
||||
"foo": "bar",
|
||||
})), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeInspectFunc: tc.volumeInspectFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeInspectWithFormat(t *testing.T) {
|
||||
volumeInspectFunc := func(volumeID string) (types.Volume, error) {
|
||||
return *Volume(VolumeLabels(map[string]string{
|
||||
"foo": "bar",
|
||||
})), nil
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
format string
|
||||
args []string
|
||||
volumeInspectFunc func(volumeID string) (types.Volume, error)
|
||||
}{
|
||||
{
|
||||
name: "simple-template",
|
||||
format: "{{.Name}}",
|
||||
args: []string{"foo"},
|
||||
volumeInspectFunc: volumeInspectFunc,
|
||||
},
|
||||
{
|
||||
name: "json-template",
|
||||
format: "{{json .Labels}}",
|
||||
args: []string{"foo"},
|
||||
volumeInspectFunc: volumeInspectFunc,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeInspectFunc: tc.volumeInspectFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.Flags().Set("format", tc.format)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -3,14 +3,13 @@ package volume
|
|||
import (
|
||||
"sort"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/formatter"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type byVolumeName []*types.Volume
|
||||
|
@ -27,7 +26,7 @@ type listOptions struct {
|
|||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -48,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||
func runList(dockerCli command.Cli, opts listOptions) error {
|
||||
client := dockerCli.Client()
|
||||
volumes, err := client.VolumeList(context.Background(), opts.filter.Value())
|
||||
if err != nil {
|
||||
|
|
124
cli/command/volume/list_test.go
Normal file
124
cli/command/volume/list_test.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
volumetypes "github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestVolumeListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument",
|
||||
},
|
||||
{
|
||||
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
||||
return volumetypes.VolumesListOKBody{}, fmt.Errorf("error listing volumes")
|
||||
},
|
||||
expectedError: "error listing volumes",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newListCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: tc.volumeListFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeListWithoutFormat(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
||||
return volumetypes.VolumesListOKBody{
|
||||
Volumes: []*types.Volume{
|
||||
Volume(),
|
||||
Volume(VolumeName("foo"), VolumeDriver("bar")),
|
||||
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
||||
"foo": "bar",
|
||||
})),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, buf)
|
||||
cli.SetConfigfile(&configfile.ConfigFile{})
|
||||
cmd := newListCommand(cli)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), "volume-list-without-format.golden")
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
|
||||
func TestVolumeListWithConfigFormat(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
||||
return volumetypes.VolumesListOKBody{
|
||||
Volumes: []*types.Volume{
|
||||
Volume(),
|
||||
Volume(VolumeName("foo"), VolumeDriver("bar")),
|
||||
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
||||
"foo": "bar",
|
||||
})),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, buf)
|
||||
cli.SetConfigfile(&configfile.ConfigFile{
|
||||
VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}",
|
||||
})
|
||||
cmd := newListCommand(cli)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), "volume-list-with-config-format.golden")
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
|
||||
func TestVolumeListWithFormat(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
||||
return volumetypes.VolumesListOKBody{
|
||||
Volumes: []*types.Volume{
|
||||
Volume(),
|
||||
Volume(VolumeName("foo"), VolumeDriver("bar")),
|
||||
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
||||
"foo": "bar",
|
||||
})),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, buf)
|
||||
cli.SetConfigfile(&configfile.ConfigFile{})
|
||||
cmd := newListCommand(cli)
|
||||
cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), "volume-list-with-format.golden")
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
|
@ -3,13 +3,12 @@ package volume
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type pruneOptions struct {
|
||||
|
@ -17,7 +16,7 @@ type pruneOptions struct {
|
|||
}
|
||||
|
||||
// NewPruneCommand returns a new cobra prune command for volumes
|
||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts pruneOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -47,7 +46,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
const warning = `WARNING! This will remove all volumes not used by at least one container.
|
||||
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) {
|
||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return
|
||||
}
|
||||
|
|
132
cli/command/volume/prune_test.go
Normal file
132
cli/command/volume/prune_test.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestVolumePruneErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument",
|
||||
},
|
||||
{
|
||||
flags: map[string]string{
|
||||
"force": "true",
|
||||
},
|
||||
volumePruneFunc: func(args filters.Args) (types.VolumesPruneReport, error) {
|
||||
return types.VolumesPruneReport{}, fmt.Errorf("error pruning volumes")
|
||||
},
|
||||
expectedError: "error pruning volumes",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
cmd := NewPruneCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: tc.volumePruneFunc,
|
||||
}, ioutil.Discard),
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumePruneForce(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
},
|
||||
{
|
||||
name: "deletedVolumes",
|
||||
volumePruneFunc: simplePruneFunc,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewPruneCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: tc.volumePruneFunc,
|
||||
}, buf),
|
||||
)
|
||||
cmd.Flags().Set("force", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-prune.%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
func TestVolumePrunePromptYes(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// FIXME(vdemeester) make it work..
|
||||
t.Skip("skipping this test on Windows")
|
||||
}
|
||||
for _, input := range []string{"y", "Y"} {
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: simplePruneFunc,
|
||||
}, buf)
|
||||
|
||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
cmd := NewPruneCommand(
|
||||
cli,
|
||||
)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), "volume-prune-yes.golden")
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumePrunePromptNo(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// FIXME(vdemeester) make it work..
|
||||
t.Skip("skipping this test on Windows")
|
||||
}
|
||||
for _, input := range []string{"n", "N", "no", "anything", "really"} {
|
||||
buf := new(bytes.Buffer)
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: simplePruneFunc,
|
||||
}, buf)
|
||||
|
||||
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
cmd := NewPruneCommand(
|
||||
cli,
|
||||
)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), "volume-prune-no.golden")
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func simplePruneFunc(args filters.Args) (types.VolumesPruneReport, error) {
|
||||
return types.VolumesPruneReport{
|
||||
VolumesDeleted: []string{
|
||||
"foo", "bar", "baz",
|
||||
},
|
||||
SpaceReclaimed: 2000,
|
||||
}, nil
|
||||
}
|
|
@ -2,12 +2,12 @@ package volume
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
|
@ -16,7 +16,7 @@ type removeOptions struct {
|
|||
volumes []string
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -38,22 +38,22 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli *command.DockerCli, opts *removeOptions) error {
|
||||
func runRemove(dockerCli command.Cli, opts *removeOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
status := 0
|
||||
|
||||
var errs []string
|
||||
|
||||
for _, name := range opts.volumes {
|
||||
if err := client.VolumeRemove(ctx, name, opts.force); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "%s\n", err)
|
||||
status = 1
|
||||
errs = append(errs, err.Error())
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return cli.StatusError{StatusCode: status}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
47
cli/command/volume/remove_test.go
Normal file
47
cli/command/volume/remove_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package volume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestVolumeRemoveErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
volumeRemoveFunc func(volumeID string, force bool) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
volumeRemoveFunc: func(volumeID string, force bool) error {
|
||||
return fmt.Errorf("error removing the volume")
|
||||
},
|
||||
expectedError: "error removing the volume",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newRemoveCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
volumeRemoveFunc: tc.volumeRemoveFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeRemoveMultiple(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||
cmd.SetArgs([]string{"volume1", "volume2"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
1
cli/command/volume/testdata/volume-inspect-with-format.json-template.golden
vendored
Normal file
1
cli/command/volume/testdata/volume-inspect-with-format.json-template.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"foo":"bar"}
|
1
cli/command/volume/testdata/volume-inspect-with-format.simple-template.golden
vendored
Normal file
1
cli/command/volume/testdata/volume-inspect-with-format.simple-template.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
volume
|
22
cli/command/volume/testdata/volume-inspect-without-format.multiple-volume-with-labels.golden
vendored
Normal file
22
cli/command/volume/testdata/volume-inspect-without-format.multiple-volume-with-labels.golden
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"Driver": "local",
|
||||
"Labels": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"Mountpoint": "/data/volume",
|
||||
"Name": "foo",
|
||||
"Options": null,
|
||||
"Scope": "local"
|
||||
},
|
||||
{
|
||||
"Driver": "local",
|
||||
"Labels": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"Mountpoint": "/data/volume",
|
||||
"Name": "bar",
|
||||
"Options": null,
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
10
cli/command/volume/testdata/volume-inspect-without-format.single-volume.golden
vendored
Normal file
10
cli/command/volume/testdata/volume-inspect-without-format.single-volume.golden
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"Driver": "local",
|
||||
"Labels": null,
|
||||
"Mountpoint": "/data/volume",
|
||||
"Name": "volume",
|
||||
"Options": null,
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
3
cli/command/volume/testdata/volume-list-with-config-format.golden
vendored
Normal file
3
cli/command/volume/testdata/volume-list-with-config-format.golden
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
baz local foo=bar
|
||||
foo bar
|
||||
volume local
|
3
cli/command/volume/testdata/volume-list-with-format.golden
vendored
Normal file
3
cli/command/volume/testdata/volume-list-with-format.golden
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
baz local foo=bar
|
||||
foo bar
|
||||
volume local
|
4
cli/command/volume/testdata/volume-list-without-format.golden
vendored
Normal file
4
cli/command/volume/testdata/volume-list-without-format.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
DRIVER VOLUME NAME
|
||||
local baz
|
||||
bar foo
|
||||
local volume
|
2
cli/command/volume/testdata/volume-prune-no.golden
vendored
Normal file
2
cli/command/volume/testdata/volume-prune-no.golden
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
WARNING! This will remove all volumes not used by at least one container.
|
||||
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
|
7
cli/command/volume/testdata/volume-prune-yes.golden
vendored
Normal file
7
cli/command/volume/testdata/volume-prune-yes.golden
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
WARNING! This will remove all volumes not used by at least one container.
|
||||
Are you sure you want to continue? [y/N] Deleted Volumes:
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
|
||||
Total reclaimed space: 2kB
|
6
cli/command/volume/testdata/volume-prune.deletedVolumes.golden
vendored
Normal file
6
cli/command/volume/testdata/volume-prune.deletedVolumes.golden
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
Deleted Volumes:
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
|
||||
Total reclaimed space: 2kB
|
1
cli/command/volume/testdata/volume-prune.empty.golden
vendored
Normal file
1
cli/command/volume/testdata/volume-prune.empty.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Total reclaimed space: 0B
|
3
cli/internal/test/builders/doc.go
Normal file
3
cli/internal/test/builders/doc.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package builders helps you create struct for your unit test while keeping them expressive.
|
||||
//
|
||||
package builders
|
|
@ -8,6 +8,9 @@ import (
|
|||
|
||||
// Node creates a node with default values.
|
||||
// Any number of node function builder can be pass to augment it.
|
||||
//
|
||||
// n1 := Node() // Returns a default node
|
||||
// n2 := Node(NodeID("foo"), NodeHostname("bar"), Leader())
|
||||
func Node(builders ...func(*swarm.Node)) *swarm.Node {
|
||||
t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
node := &swarm.Node{
|
||||
|
|
43
cli/internal/test/builders/volume.go
Normal file
43
cli/internal/test/builders/volume.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package builders
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// Volume creates a volume with default values.
|
||||
// Any number of volume function builder can be pass to augment it.
|
||||
func Volume(builders ...func(volume *types.Volume)) *types.Volume {
|
||||
volume := &types.Volume{
|
||||
Name: "volume",
|
||||
Driver: "local",
|
||||
Mountpoint: "/data/volume",
|
||||
Scope: "local",
|
||||
}
|
||||
|
||||
for _, builder := range builders {
|
||||
builder(volume)
|
||||
}
|
||||
|
||||
return volume
|
||||
}
|
||||
|
||||
// VolumeLabels sets the volume labels
|
||||
func VolumeLabels(labels map[string]string) func(volume *types.Volume) {
|
||||
return func(volume *types.Volume) {
|
||||
volume.Labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeName sets the volume labels
|
||||
func VolumeName(name string) func(volume *types.Volume) {
|
||||
return func(volume *types.Volume) {
|
||||
volume.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeDriver sets the volume driver
|
||||
func VolumeDriver(name string) func(volume *types.Volume) {
|
||||
return func(volume *types.Volume) {
|
||||
volume.Driver = name
|
||||
}
|
||||
}
|
|
@ -1,21 +1,23 @@
|
|||
// Package test is a test-only package that can be used by other cli package to write unit test
|
||||
package test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
"github.com/docker/docker/client"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FakeCli emulates the default DockerCli
|
||||
type FakeCli struct {
|
||||
command.DockerCli
|
||||
client client.APIClient
|
||||
out io.Writer
|
||||
in io.ReadCloser
|
||||
client client.APIClient
|
||||
configfile *configfile.ConfigFile
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
in io.ReadCloser
|
||||
}
|
||||
|
||||
// NewFakeCli returns a Cli backed by the fakeCli
|
||||
|
@ -23,6 +25,7 @@ func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
|||
return &FakeCli{
|
||||
client: client,
|
||||
out: out,
|
||||
err: ioutil.Discard,
|
||||
in: ioutil.NopCloser(strings.NewReader("")),
|
||||
}
|
||||
}
|
||||
|
@ -32,17 +35,37 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
|
|||
c.in = in
|
||||
}
|
||||
|
||||
// SetErr sets the standard error stream th cli should write on
|
||||
func (c *FakeCli) SetErr(err io.Writer) {
|
||||
c.err = err
|
||||
}
|
||||
|
||||
// SetConfigfile sets the "fake" config file
|
||||
func (c *FakeCli) SetConfigfile(configfile *configfile.ConfigFile) {
|
||||
c.configfile = configfile
|
||||
}
|
||||
|
||||
// Client returns a docker API client
|
||||
func (c *FakeCli) Client() client.APIClient {
|
||||
return c.client
|
||||
}
|
||||
|
||||
// Out returns the output stream the cli should write on
|
||||
// Out returns the output stream (stdout) the cli should write on
|
||||
func (c *FakeCli) Out() *command.OutStream {
|
||||
return command.NewOutStream(c.out)
|
||||
}
|
||||
|
||||
// In returns thi input stream the cli will use
|
||||
// Err returns the output stream (stderr) the cli should write on
|
||||
func (c *FakeCli) Err() io.Writer {
|
||||
return c.err
|
||||
}
|
||||
|
||||
// In returns the input stream the cli will use
|
||||
func (c *FakeCli) In() *command.InStream {
|
||||
return command.NewInStream(c.in)
|
||||
}
|
||||
|
||||
// ConfigFile returns the cli configfile object (to get client configuration)
|
||||
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
|
||||
return c.configfile
|
||||
}
|
||||
|
|
5
cli/internal/test/doc.go
Normal file
5
cli/internal/test/doc.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Package test is a test-only package that can be used by other cli package to write unit test.
|
||||
//
|
||||
// It as an internal package and cannot be used outside of github.com/docker/docker/cli package.
|
||||
//
|
||||
package test
|
|
@ -292,11 +292,6 @@ func (s *DockerExternalVolumeSuite) TestVolumeCLICreateOptionConflict(c *check.C
|
|||
out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Driver }}", "test")
|
||||
_, _, err = dockerCmdWithError("volume", "create", "test", "--driver", strings.TrimSpace(out))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// make sure hidden --name option conflicts with positional arg name
|
||||
out, _, err = dockerCmdWithError("volume", "create", "--name", "test2", "test2")
|
||||
c.Assert(err, check.NotNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Equals, "Conflicting options: either specify --name or provide positional arg, not both")
|
||||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
|
||||
|
|
Loading…
Reference in a new issue