Signed-off-by: Vincent Demeester <vincent@sbr.pm>
| 44 | 45 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types" |
|
| 4 |
+ "github.com/docker/docker/api/types/filters" |
|
| 5 |
+ volumetypes "github.com/docker/docker/api/types/volume" |
|
| 6 |
+ "github.com/docker/docker/client" |
|
| 7 |
+ "golang.org/x/net/context" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type fakeClient struct {
|
|
| 11 |
+ client.Client |
|
| 12 |
+ volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error) |
|
| 13 |
+ volumeInspectFunc func(volumeID string) (types.Volume, error) |
|
| 14 |
+ volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error) |
|
| 15 |
+ volumeRemoveFunc func(volumeID string, force bool) error |
|
| 16 |
+ volumePruneFunc func(filter filters.Args) (types.VolumesPruneReport, error) |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) {
|
|
| 20 |
+ if c.volumeCreateFunc != nil {
|
|
| 21 |
+ return c.volumeCreateFunc(options) |
|
| 22 |
+ } |
|
| 23 |
+ return types.Volume{}, nil
|
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func (c *fakeClient) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) {
|
|
| 27 |
+ if c.volumeInspectFunc != nil {
|
|
| 28 |
+ return c.volumeInspectFunc(volumeID) |
|
| 29 |
+ } |
|
| 30 |
+ return types.Volume{}, nil
|
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
|
| 34 |
+ if c.volumeListFunc != nil {
|
|
| 35 |
+ return c.volumeListFunc(filter) |
|
| 36 |
+ } |
|
| 37 |
+ return volumetypes.VolumesListOKBody{}, nil
|
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func (c *fakeClient) VolumesPrune(ctx context.Context, filter filters.Args) (types.VolumesPruneReport, error) {
|
|
| 41 |
+ if c.volumePruneFunc != nil {
|
|
| 42 |
+ return c.volumePruneFunc(filter) |
|
| 43 |
+ } |
|
| 44 |
+ return types.VolumesPruneReport{}, nil
|
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+func (c *fakeClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
|
|
| 48 |
+ if c.volumeRemoveFunc != nil {
|
|
| 49 |
+ return c.volumeRemoveFunc(volumeID, force) |
|
| 50 |
+ } |
|
| 51 |
+ return nil |
|
| 52 |
+} |
| ... | ... |
@@ -19,7 +19,7 @@ type createOptions struct {
|
| 19 | 19 |
labels opts.ListOpts |
| 20 | 20 |
} |
| 21 | 21 |
|
| 22 |
-func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 22 |
+func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|
| 23 | 23 |
opts := createOptions{
|
| 24 | 24 |
driverOpts: *opts.NewMapOpts(nil, nil), |
| 25 | 25 |
labels: opts.NewListOpts(opts.ValidateEnv), |
| ... | ... |
@@ -32,8 +32,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 32 | 32 |
RunE: func(cmd *cobra.Command, args []string) error {
|
| 33 | 33 |
if len(args) == 1 {
|
| 34 | 34 |
if opts.name != "" {
|
| 35 |
- fmt.Fprint(dockerCli.Err(), "Conflicting options: either specify --name or provide positional arg, not both\n") |
|
| 36 |
- return cli.StatusError{StatusCode: 1}
|
|
| 35 |
+ return fmt.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
|
|
| 37 | 36 |
} |
| 38 | 37 |
opts.name = args[0] |
| 39 | 38 |
} |
| ... | ... |
@@ -50,7 +49,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 50 | 50 |
return cmd |
| 51 | 51 |
} |
| 52 | 52 |
|
| 53 |
-func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
|
|
| 53 |
+func runCreate(dockerCli command.Cli, opts createOptions) error {
|
|
| 54 | 54 |
client := dockerCli.Client() |
| 55 | 55 |
|
| 56 | 56 |
volReq := volumetypes.VolumesCreateBody{
|
| 57 | 57 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,142 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "testing" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/api/types" |
|
| 10 |
+ volumetypes "github.com/docker/docker/api/types/volume" |
|
| 11 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 12 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func TestVolumeCreateErrors(t *testing.T) {
|
|
| 16 |
+ testCases := []struct {
|
|
| 17 |
+ args []string |
|
| 18 |
+ flags map[string]string |
|
| 19 |
+ volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error) |
|
| 20 |
+ expectedError string |
|
| 21 |
+ }{
|
|
| 22 |
+ {
|
|
| 23 |
+ args: []string{"volumeName"},
|
|
| 24 |
+ flags: map[string]string{
|
|
| 25 |
+ "name": "volumeName", |
|
| 26 |
+ }, |
|
| 27 |
+ expectedError: "Conflicting options: either specify --name or provide positional arg, not both", |
|
| 28 |
+ }, |
|
| 29 |
+ {
|
|
| 30 |
+ args: []string{"too", "many"},
|
|
| 31 |
+ expectedError: "requires at most 1 argument(s)", |
|
| 32 |
+ }, |
|
| 33 |
+ {
|
|
| 34 |
+ volumeCreateFunc: func(createBody volumetypes.VolumesCreateBody) (types.Volume, error) {
|
|
| 35 |
+ return types.Volume{}, fmt.Errorf("error creating volume")
|
|
| 36 |
+ }, |
|
| 37 |
+ expectedError: "error creating volume", |
|
| 38 |
+ }, |
|
| 39 |
+ } |
|
| 40 |
+ for _, tc := range testCases {
|
|
| 41 |
+ buf := new(bytes.Buffer) |
|
| 42 |
+ cmd := newCreateCommand( |
|
| 43 |
+ test.NewFakeCli(&fakeClient{
|
|
| 44 |
+ volumeCreateFunc: tc.volumeCreateFunc, |
|
| 45 |
+ }, buf), |
|
| 46 |
+ ) |
|
| 47 |
+ cmd.SetArgs(tc.args) |
|
| 48 |
+ for key, value := range tc.flags {
|
|
| 49 |
+ cmd.Flags().Set(key, value) |
|
| 50 |
+ } |
|
| 51 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 52 |
+ assert.Error(t, cmd.Execute(), tc.expectedError) |
|
| 53 |
+ } |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+func TestVolumeCreateWithName(t *testing.T) {
|
|
| 57 |
+ name := "foo" |
|
| 58 |
+ buf := new(bytes.Buffer) |
|
| 59 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 60 |
+ volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
|
|
| 61 |
+ if body.Name != name {
|
|
| 62 |
+ return types.Volume{}, fmt.Errorf("expected name %q, got %q", name, body.Name)
|
|
| 63 |
+ } |
|
| 64 |
+ return types.Volume{
|
|
| 65 |
+ Name: body.Name, |
|
| 66 |
+ }, nil |
|
| 67 |
+ }, |
|
| 68 |
+ }, buf) |
|
| 69 |
+ |
|
| 70 |
+ // Test by flags |
|
| 71 |
+ cmd := newCreateCommand(cli) |
|
| 72 |
+ cmd.Flags().Set("name", name)
|
|
| 73 |
+ assert.NilError(t, cmd.Execute()) |
|
| 74 |
+ assert.Equal(t, strings.TrimSpace(buf.String()), name) |
|
| 75 |
+ |
|
| 76 |
+ // Then by args |
|
| 77 |
+ buf.Reset() |
|
| 78 |
+ cmd = newCreateCommand(cli) |
|
| 79 |
+ cmd.SetArgs([]string{name})
|
|
| 80 |
+ assert.NilError(t, cmd.Execute()) |
|
| 81 |
+ assert.Equal(t, strings.TrimSpace(buf.String()), name) |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func TestVolumeCreateWithFlags(t *testing.T) {
|
|
| 85 |
+ expectedDriver := "foo" |
|
| 86 |
+ expectedOpts := map[string]string{
|
|
| 87 |
+ "bar": "1", |
|
| 88 |
+ "baz": "baz", |
|
| 89 |
+ } |
|
| 90 |
+ expectedLabels := map[string]string{
|
|
| 91 |
+ "lbl1": "v1", |
|
| 92 |
+ "lbl2": "v2", |
|
| 93 |
+ } |
|
| 94 |
+ name := "banana" |
|
| 95 |
+ |
|
| 96 |
+ buf := new(bytes.Buffer) |
|
| 97 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 98 |
+ volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
|
|
| 99 |
+ if body.Name != "" {
|
|
| 100 |
+ return types.Volume{}, fmt.Errorf("expected empty name, got %q", body.Name)
|
|
| 101 |
+ } |
|
| 102 |
+ if body.Driver != expectedDriver {
|
|
| 103 |
+ return types.Volume{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, body.Driver)
|
|
| 104 |
+ } |
|
| 105 |
+ if !compareMap(body.DriverOpts, expectedOpts) {
|
|
| 106 |
+ return types.Volume{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, body.DriverOpts)
|
|
| 107 |
+ } |
|
| 108 |
+ if !compareMap(body.Labels, expectedLabels) {
|
|
| 109 |
+ return types.Volume{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, body.Labels)
|
|
| 110 |
+ } |
|
| 111 |
+ return types.Volume{
|
|
| 112 |
+ Name: name, |
|
| 113 |
+ }, nil |
|
| 114 |
+ }, |
|
| 115 |
+ }, buf) |
|
| 116 |
+ |
|
| 117 |
+ cmd := newCreateCommand(cli) |
|
| 118 |
+ cmd.Flags().Set("driver", "foo")
|
|
| 119 |
+ cmd.Flags().Set("opt", "bar=1")
|
|
| 120 |
+ cmd.Flags().Set("opt", "baz=baz")
|
|
| 121 |
+ cmd.Flags().Set("label", "lbl1=v1")
|
|
| 122 |
+ cmd.Flags().Set("label", "lbl2=v2")
|
|
| 123 |
+ assert.NilError(t, cmd.Execute()) |
|
| 124 |
+ assert.Equal(t, strings.TrimSpace(buf.String()), name) |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func compareMap(actual map[string]string, expected map[string]string) bool {
|
|
| 128 |
+ if len(actual) != len(expected) {
|
|
| 129 |
+ return false |
|
| 130 |
+ } |
|
| 131 |
+ for key, value := range actual {
|
|
| 132 |
+ if expectedValue, ok := expected[key]; ok {
|
|
| 133 |
+ if expectedValue != value {
|
|
| 134 |
+ return false |
|
| 135 |
+ } |
|
| 136 |
+ } else {
|
|
| 137 |
+ return false |
|
| 138 |
+ } |
|
| 139 |
+ } |
|
| 140 |
+ return true |
|
| 141 |
+} |
| ... | ... |
@@ -1,12 +1,11 @@ |
| 1 | 1 |
package volume |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "golang.org/x/net/context" |
|
| 5 |
- |
|
| 6 | 4 |
"github.com/docker/docker/cli" |
| 7 | 5 |
"github.com/docker/docker/cli/command" |
| 8 | 6 |
"github.com/docker/docker/cli/command/inspect" |
| 9 | 7 |
"github.com/spf13/cobra" |
| 8 |
+ "golang.org/x/net/context" |
|
| 10 | 9 |
) |
| 11 | 10 |
|
| 12 | 11 |
type inspectOptions struct {
|
| ... | ... |
@@ -14,7 +13,7 @@ type inspectOptions struct {
|
| 14 | 14 |
names []string |
| 15 | 15 |
} |
| 16 | 16 |
|
| 17 |
-func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 17 |
+func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
|
| 18 | 18 |
var opts inspectOptions |
| 19 | 19 |
|
| 20 | 20 |
cmd := &cobra.Command{
|
| ... | ... |
@@ -32,7 +31,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 32 | 32 |
return cmd |
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
-func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
|
| 35 |
+func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
|
| 36 | 36 |
client := dockerCli.Client() |
| 37 | 37 |
|
| 38 | 38 |
ctx := context.Background() |
| 39 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,150 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 10 |
+ // Import builders to get the builder function as package function |
|
| 11 |
+ . "github.com/docker/docker/cli/internal/test/builders" |
|
| 12 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 13 |
+ "github.com/docker/docker/pkg/testutil/golden" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func TestVolumeInspectErrors(t *testing.T) {
|
|
| 17 |
+ testCases := []struct {
|
|
| 18 |
+ args []string |
|
| 19 |
+ flags map[string]string |
|
| 20 |
+ volumeInspectFunc func(volumeID string) (types.Volume, error) |
|
| 21 |
+ expectedError string |
|
| 22 |
+ }{
|
|
| 23 |
+ {
|
|
| 24 |
+ expectedError: "requires at least 1 argument", |
|
| 25 |
+ }, |
|
| 26 |
+ {
|
|
| 27 |
+ args: []string{"foo"},
|
|
| 28 |
+ volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
|
| 29 |
+ return types.Volume{}, fmt.Errorf("error while inspecting the volume")
|
|
| 30 |
+ }, |
|
| 31 |
+ expectedError: "error while inspecting the volume", |
|
| 32 |
+ }, |
|
| 33 |
+ {
|
|
| 34 |
+ args: []string{"foo"},
|
|
| 35 |
+ flags: map[string]string{
|
|
| 36 |
+ "format": "{{invalid format}}",
|
|
| 37 |
+ }, |
|
| 38 |
+ expectedError: "Template parsing error", |
|
| 39 |
+ }, |
|
| 40 |
+ {
|
|
| 41 |
+ args: []string{"foo", "bar"},
|
|
| 42 |
+ volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
|
| 43 |
+ if volumeID == "foo" {
|
|
| 44 |
+ return types.Volume{
|
|
| 45 |
+ Name: "foo", |
|
| 46 |
+ }, nil |
|
| 47 |
+ } |
|
| 48 |
+ return types.Volume{}, fmt.Errorf("error while inspecting the volume")
|
|
| 49 |
+ }, |
|
| 50 |
+ expectedError: "error while inspecting the volume", |
|
| 51 |
+ }, |
|
| 52 |
+ } |
|
| 53 |
+ for _, tc := range testCases {
|
|
| 54 |
+ buf := new(bytes.Buffer) |
|
| 55 |
+ cmd := newInspectCommand( |
|
| 56 |
+ test.NewFakeCli(&fakeClient{
|
|
| 57 |
+ volumeInspectFunc: tc.volumeInspectFunc, |
|
| 58 |
+ }, buf), |
|
| 59 |
+ ) |
|
| 60 |
+ cmd.SetArgs(tc.args) |
|
| 61 |
+ for key, value := range tc.flags {
|
|
| 62 |
+ cmd.Flags().Set(key, value) |
|
| 63 |
+ } |
|
| 64 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 65 |
+ assert.Error(t, cmd.Execute(), tc.expectedError) |
|
| 66 |
+ } |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func TestVolumeInspectWithoutFormat(t *testing.T) {
|
|
| 70 |
+ testCases := []struct {
|
|
| 71 |
+ name string |
|
| 72 |
+ args []string |
|
| 73 |
+ volumeInspectFunc func(volumeID string) (types.Volume, error) |
|
| 74 |
+ }{
|
|
| 75 |
+ {
|
|
| 76 |
+ name: "single-volume", |
|
| 77 |
+ args: []string{"foo"},
|
|
| 78 |
+ volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
|
| 79 |
+ if volumeID != "foo" {
|
|
| 80 |
+ return types.Volume{}, fmt.Errorf("Invalid volumeID, expected %s, got %s", "foo", volumeID)
|
|
| 81 |
+ } |
|
| 82 |
+ return *Volume(), nil |
|
| 83 |
+ }, |
|
| 84 |
+ }, |
|
| 85 |
+ {
|
|
| 86 |
+ name: "multiple-volume-with-labels", |
|
| 87 |
+ args: []string{"foo", "bar"},
|
|
| 88 |
+ volumeInspectFunc: func(volumeID string) (types.Volume, error) {
|
|
| 89 |
+ return *Volume(VolumeName(volumeID), VolumeLabels(map[string]string{
|
|
| 90 |
+ "foo": "bar", |
|
| 91 |
+ })), nil |
|
| 92 |
+ }, |
|
| 93 |
+ }, |
|
| 94 |
+ } |
|
| 95 |
+ for _, tc := range testCases {
|
|
| 96 |
+ buf := new(bytes.Buffer) |
|
| 97 |
+ cmd := newInspectCommand( |
|
| 98 |
+ test.NewFakeCli(&fakeClient{
|
|
| 99 |
+ volumeInspectFunc: tc.volumeInspectFunc, |
|
| 100 |
+ }, buf), |
|
| 101 |
+ ) |
|
| 102 |
+ cmd.SetArgs(tc.args) |
|
| 103 |
+ assert.NilError(t, cmd.Execute()) |
|
| 104 |
+ actual := buf.String() |
|
| 105 |
+ expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name))
|
|
| 106 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 107 |
+ } |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func TestVolumeInspectWithFormat(t *testing.T) {
|
|
| 111 |
+ volumeInspectFunc := func(volumeID string) (types.Volume, error) {
|
|
| 112 |
+ return *Volume(VolumeLabels(map[string]string{
|
|
| 113 |
+ "foo": "bar", |
|
| 114 |
+ })), nil |
|
| 115 |
+ } |
|
| 116 |
+ testCases := []struct {
|
|
| 117 |
+ name string |
|
| 118 |
+ format string |
|
| 119 |
+ args []string |
|
| 120 |
+ volumeInspectFunc func(volumeID string) (types.Volume, error) |
|
| 121 |
+ }{
|
|
| 122 |
+ {
|
|
| 123 |
+ name: "simple-template", |
|
| 124 |
+ format: "{{.Name}}",
|
|
| 125 |
+ args: []string{"foo"},
|
|
| 126 |
+ volumeInspectFunc: volumeInspectFunc, |
|
| 127 |
+ }, |
|
| 128 |
+ {
|
|
| 129 |
+ name: "json-template", |
|
| 130 |
+ format: "{{json .Labels}}",
|
|
| 131 |
+ args: []string{"foo"},
|
|
| 132 |
+ volumeInspectFunc: volumeInspectFunc, |
|
| 133 |
+ }, |
|
| 134 |
+ } |
|
| 135 |
+ for _, tc := range testCases {
|
|
| 136 |
+ buf := new(bytes.Buffer) |
|
| 137 |
+ cmd := newInspectCommand( |
|
| 138 |
+ test.NewFakeCli(&fakeClient{
|
|
| 139 |
+ volumeInspectFunc: tc.volumeInspectFunc, |
|
| 140 |
+ }, buf), |
|
| 141 |
+ ) |
|
| 142 |
+ cmd.SetArgs(tc.args) |
|
| 143 |
+ cmd.Flags().Set("format", tc.format)
|
|
| 144 |
+ assert.NilError(t, cmd.Execute()) |
|
| 145 |
+ actual := buf.String() |
|
| 146 |
+ expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name))
|
|
| 147 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 148 |
+ } |
|
| 149 |
+} |
| ... | ... |
@@ -3,14 +3,13 @@ package volume |
| 3 | 3 |
import ( |
| 4 | 4 |
"sort" |
| 5 | 5 |
|
| 6 |
- "golang.org/x/net/context" |
|
| 7 |
- |
|
| 8 | 6 |
"github.com/docker/docker/api/types" |
| 9 | 7 |
"github.com/docker/docker/cli" |
| 10 | 8 |
"github.com/docker/docker/cli/command" |
| 11 | 9 |
"github.com/docker/docker/cli/command/formatter" |
| 12 | 10 |
"github.com/docker/docker/opts" |
| 13 | 11 |
"github.com/spf13/cobra" |
| 12 |
+ "golang.org/x/net/context" |
|
| 14 | 13 |
) |
| 15 | 14 |
|
| 16 | 15 |
type byVolumeName []*types.Volume |
| ... | ... |
@@ -27,7 +26,7 @@ type listOptions struct {
|
| 27 | 27 |
filter opts.FilterOpt |
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 |
-func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 30 |
+func newListCommand(dockerCli command.Cli) *cobra.Command {
|
|
| 31 | 31 |
opts := listOptions{filter: opts.NewFilterOpt()}
|
| 32 | 32 |
|
| 33 | 33 |
cmd := &cobra.Command{
|
| ... | ... |
@@ -48,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 48 | 48 |
return cmd |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
-func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
|
| 51 |
+func runList(dockerCli command.Cli, opts listOptions) error {
|
|
| 52 | 52 |
client := dockerCli.Client() |
| 53 | 53 |
volumes, err := client.VolumeList(context.Background(), opts.filter.Value()) |
| 54 | 54 |
if err != nil {
|
| 55 | 55 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,124 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/api/types/filters" |
|
| 10 |
+ volumetypes "github.com/docker/docker/api/types/volume" |
|
| 11 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 12 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 13 |
+ // Import builders to get the builder function as package function |
|
| 14 |
+ . "github.com/docker/docker/cli/internal/test/builders" |
|
| 15 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 16 |
+ "github.com/docker/docker/pkg/testutil/golden" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+func TestVolumeListErrors(t *testing.T) {
|
|
| 20 |
+ testCases := []struct {
|
|
| 21 |
+ args []string |
|
| 22 |
+ flags map[string]string |
|
| 23 |
+ volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error) |
|
| 24 |
+ expectedError string |
|
| 25 |
+ }{
|
|
| 26 |
+ {
|
|
| 27 |
+ args: []string{"foo"},
|
|
| 28 |
+ expectedError: "accepts no argument", |
|
| 29 |
+ }, |
|
| 30 |
+ {
|
|
| 31 |
+ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
|
| 32 |
+ return volumetypes.VolumesListOKBody{}, fmt.Errorf("error listing volumes")
|
|
| 33 |
+ }, |
|
| 34 |
+ expectedError: "error listing volumes", |
|
| 35 |
+ }, |
|
| 36 |
+ } |
|
| 37 |
+ for _, tc := range testCases {
|
|
| 38 |
+ buf := new(bytes.Buffer) |
|
| 39 |
+ cmd := newListCommand( |
|
| 40 |
+ test.NewFakeCli(&fakeClient{
|
|
| 41 |
+ volumeListFunc: tc.volumeListFunc, |
|
| 42 |
+ }, buf), |
|
| 43 |
+ ) |
|
| 44 |
+ cmd.SetArgs(tc.args) |
|
| 45 |
+ for key, value := range tc.flags {
|
|
| 46 |
+ cmd.Flags().Set(key, value) |
|
| 47 |
+ } |
|
| 48 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 49 |
+ assert.Error(t, cmd.Execute(), tc.expectedError) |
|
| 50 |
+ } |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+func TestVolumeListWithoutFormat(t *testing.T) {
|
|
| 54 |
+ buf := new(bytes.Buffer) |
|
| 55 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 56 |
+ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
|
| 57 |
+ return volumetypes.VolumesListOKBody{
|
|
| 58 |
+ Volumes: []*types.Volume{
|
|
| 59 |
+ Volume(), |
|
| 60 |
+ Volume(VolumeName("foo"), VolumeDriver("bar")),
|
|
| 61 |
+ Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
|
| 62 |
+ "foo": "bar", |
|
| 63 |
+ })), |
|
| 64 |
+ }, |
|
| 65 |
+ }, nil |
|
| 66 |
+ }, |
|
| 67 |
+ }, buf) |
|
| 68 |
+ cli.SetConfigfile(&configfile.ConfigFile{})
|
|
| 69 |
+ cmd := newListCommand(cli) |
|
| 70 |
+ assert.NilError(t, cmd.Execute()) |
|
| 71 |
+ actual := buf.String() |
|
| 72 |
+ expected := golden.Get(t, []byte(actual), "volume-list-without-format.golden") |
|
| 73 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+func TestVolumeListWithConfigFormat(t *testing.T) {
|
|
| 77 |
+ buf := new(bytes.Buffer) |
|
| 78 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 79 |
+ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
|
| 80 |
+ return volumetypes.VolumesListOKBody{
|
|
| 81 |
+ Volumes: []*types.Volume{
|
|
| 82 |
+ Volume(), |
|
| 83 |
+ Volume(VolumeName("foo"), VolumeDriver("bar")),
|
|
| 84 |
+ Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
|
| 85 |
+ "foo": "bar", |
|
| 86 |
+ })), |
|
| 87 |
+ }, |
|
| 88 |
+ }, nil |
|
| 89 |
+ }, |
|
| 90 |
+ }, buf) |
|
| 91 |
+ cli.SetConfigfile(&configfile.ConfigFile{
|
|
| 92 |
+ VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}",
|
|
| 93 |
+ }) |
|
| 94 |
+ cmd := newListCommand(cli) |
|
| 95 |
+ assert.NilError(t, cmd.Execute()) |
|
| 96 |
+ actual := buf.String() |
|
| 97 |
+ expected := golden.Get(t, []byte(actual), "volume-list-with-config-format.golden") |
|
| 98 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func TestVolumeListWithFormat(t *testing.T) {
|
|
| 102 |
+ buf := new(bytes.Buffer) |
|
| 103 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 104 |
+ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
|
|
| 105 |
+ return volumetypes.VolumesListOKBody{
|
|
| 106 |
+ Volumes: []*types.Volume{
|
|
| 107 |
+ Volume(), |
|
| 108 |
+ Volume(VolumeName("foo"), VolumeDriver("bar")),
|
|
| 109 |
+ Volume(VolumeName("baz"), VolumeLabels(map[string]string{
|
|
| 110 |
+ "foo": "bar", |
|
| 111 |
+ })), |
|
| 112 |
+ }, |
|
| 113 |
+ }, nil |
|
| 114 |
+ }, |
|
| 115 |
+ }, buf) |
|
| 116 |
+ cli.SetConfigfile(&configfile.ConfigFile{})
|
|
| 117 |
+ cmd := newListCommand(cli) |
|
| 118 |
+ cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")
|
|
| 119 |
+ assert.NilError(t, cmd.Execute()) |
|
| 120 |
+ actual := buf.String() |
|
| 121 |
+ expected := golden.Get(t, []byte(actual), "volume-list-with-format.golden") |
|
| 122 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 123 |
+} |
| ... | ... |
@@ -3,13 +3,12 @@ package volume |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
|
| 6 |
- "golang.org/x/net/context" |
|
| 7 |
- |
|
| 8 | 6 |
"github.com/docker/docker/api/types/filters" |
| 9 | 7 |
"github.com/docker/docker/cli" |
| 10 | 8 |
"github.com/docker/docker/cli/command" |
| 11 | 9 |
units "github.com/docker/go-units" |
| 12 | 10 |
"github.com/spf13/cobra" |
| 11 |
+ "golang.org/x/net/context" |
|
| 13 | 12 |
) |
| 14 | 13 |
|
| 15 | 14 |
type pruneOptions struct {
|
| ... | ... |
@@ -17,7 +16,7 @@ type pruneOptions struct {
|
| 17 | 17 |
} |
| 18 | 18 |
|
| 19 | 19 |
// NewPruneCommand returns a new cobra prune command for volumes |
| 20 |
-func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 20 |
+func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
|
| 21 | 21 |
var opts pruneOptions |
| 22 | 22 |
|
| 23 | 23 |
cmd := &cobra.Command{
|
| ... | ... |
@@ -47,7 +46,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 47 | 47 |
const warning = `WARNING! This will remove all volumes not used by at least one container. |
| 48 | 48 |
Are you sure you want to continue?` |
| 49 | 49 |
|
| 50 |
-func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
|
| 50 |
+func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
|
| 51 | 51 |
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
| 52 | 52 |
return |
| 53 | 53 |
} |
| 54 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,132 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "runtime" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "testing" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api/types" |
|
| 11 |
+ "github.com/docker/docker/api/types/filters" |
|
| 12 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 13 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 14 |
+ "github.com/docker/docker/pkg/testutil/golden" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+func TestVolumePruneErrors(t *testing.T) {
|
|
| 18 |
+ testCases := []struct {
|
|
| 19 |
+ args []string |
|
| 20 |
+ flags map[string]string |
|
| 21 |
+ volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error) |
|
| 22 |
+ expectedError string |
|
| 23 |
+ }{
|
|
| 24 |
+ {
|
|
| 25 |
+ args: []string{"foo"},
|
|
| 26 |
+ expectedError: "accepts no argument", |
|
| 27 |
+ }, |
|
| 28 |
+ {
|
|
| 29 |
+ flags: map[string]string{
|
|
| 30 |
+ "force": "true", |
|
| 31 |
+ }, |
|
| 32 |
+ volumePruneFunc: func(args filters.Args) (types.VolumesPruneReport, error) {
|
|
| 33 |
+ return types.VolumesPruneReport{}, fmt.Errorf("error pruning volumes")
|
|
| 34 |
+ }, |
|
| 35 |
+ expectedError: "error pruning volumes", |
|
| 36 |
+ }, |
|
| 37 |
+ } |
|
| 38 |
+ for _, tc := range testCases {
|
|
| 39 |
+ cmd := NewPruneCommand( |
|
| 40 |
+ test.NewFakeCli(&fakeClient{
|
|
| 41 |
+ volumePruneFunc: tc.volumePruneFunc, |
|
| 42 |
+ }, ioutil.Discard), |
|
| 43 |
+ ) |
|
| 44 |
+ cmd.SetArgs(tc.args) |
|
| 45 |
+ for key, value := range tc.flags {
|
|
| 46 |
+ cmd.Flags().Set(key, value) |
|
| 47 |
+ } |
|
| 48 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 49 |
+ assert.Error(t, cmd.Execute(), tc.expectedError) |
|
| 50 |
+ } |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+func TestVolumePruneForce(t *testing.T) {
|
|
| 54 |
+ testCases := []struct {
|
|
| 55 |
+ name string |
|
| 56 |
+ volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error) |
|
| 57 |
+ }{
|
|
| 58 |
+ {
|
|
| 59 |
+ name: "empty", |
|
| 60 |
+ }, |
|
| 61 |
+ {
|
|
| 62 |
+ name: "deletedVolumes", |
|
| 63 |
+ volumePruneFunc: simplePruneFunc, |
|
| 64 |
+ }, |
|
| 65 |
+ } |
|
| 66 |
+ for _, tc := range testCases {
|
|
| 67 |
+ buf := new(bytes.Buffer) |
|
| 68 |
+ cmd := NewPruneCommand( |
|
| 69 |
+ test.NewFakeCli(&fakeClient{
|
|
| 70 |
+ volumePruneFunc: tc.volumePruneFunc, |
|
| 71 |
+ }, buf), |
|
| 72 |
+ ) |
|
| 73 |
+ cmd.Flags().Set("force", "true")
|
|
| 74 |
+ assert.NilError(t, cmd.Execute()) |
|
| 75 |
+ actual := buf.String() |
|
| 76 |
+ expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-prune.%s.golden", tc.name))
|
|
| 77 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 78 |
+ } |
|
| 79 |
+} |
|
| 80 |
+func TestVolumePrunePromptYes(t *testing.T) {
|
|
| 81 |
+ if runtime.GOOS == "windows" {
|
|
| 82 |
+ // FIXME(vdemeester) make it work.. |
|
| 83 |
+ t.Skip("skipping this test on Windows")
|
|
| 84 |
+ } |
|
| 85 |
+ for _, input := range []string{"y", "Y"} {
|
|
| 86 |
+ buf := new(bytes.Buffer) |
|
| 87 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 88 |
+ volumePruneFunc: simplePruneFunc, |
|
| 89 |
+ }, buf) |
|
| 90 |
+ |
|
| 91 |
+ cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) |
|
| 92 |
+ cmd := NewPruneCommand( |
|
| 93 |
+ cli, |
|
| 94 |
+ ) |
|
| 95 |
+ assert.NilError(t, cmd.Execute()) |
|
| 96 |
+ actual := buf.String() |
|
| 97 |
+ expected := golden.Get(t, []byte(actual), "volume-prune-yes.golden") |
|
| 98 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 99 |
+ } |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func TestVolumePrunePromptNo(t *testing.T) {
|
|
| 103 |
+ if runtime.GOOS == "windows" {
|
|
| 104 |
+ // FIXME(vdemeester) make it work.. |
|
| 105 |
+ t.Skip("skipping this test on Windows")
|
|
| 106 |
+ } |
|
| 107 |
+ for _, input := range []string{"n", "N", "no", "anything", "really"} {
|
|
| 108 |
+ buf := new(bytes.Buffer) |
|
| 109 |
+ cli := test.NewFakeCli(&fakeClient{
|
|
| 110 |
+ volumePruneFunc: simplePruneFunc, |
|
| 111 |
+ }, buf) |
|
| 112 |
+ |
|
| 113 |
+ cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) |
|
| 114 |
+ cmd := NewPruneCommand( |
|
| 115 |
+ cli, |
|
| 116 |
+ ) |
|
| 117 |
+ assert.NilError(t, cmd.Execute()) |
|
| 118 |
+ actual := buf.String() |
|
| 119 |
+ expected := golden.Get(t, []byte(actual), "volume-prune-no.golden") |
|
| 120 |
+ assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected)) |
|
| 121 |
+ } |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func simplePruneFunc(args filters.Args) (types.VolumesPruneReport, error) {
|
|
| 125 |
+ return types.VolumesPruneReport{
|
|
| 126 |
+ VolumesDeleted: []string{
|
|
| 127 |
+ "foo", "bar", "baz", |
|
| 128 |
+ }, |
|
| 129 |
+ SpaceReclaimed: 2000, |
|
| 130 |
+ }, nil |
|
| 131 |
+} |
| ... | ... |
@@ -2,12 +2,12 @@ package volume |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- |
|
| 6 |
- "golang.org/x/net/context" |
|
| 5 |
+ "strings" |
|
| 7 | 6 |
|
| 8 | 7 |
"github.com/docker/docker/cli" |
| 9 | 8 |
"github.com/docker/docker/cli/command" |
| 10 | 9 |
"github.com/spf13/cobra" |
| 10 |
+ "golang.org/x/net/context" |
|
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 | 13 |
type removeOptions struct {
|
| ... | ... |
@@ -16,7 +16,7 @@ type removeOptions struct {
|
| 16 | 16 |
volumes []string |
| 17 | 17 |
} |
| 18 | 18 |
|
| 19 |
-func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 19 |
+func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
|
| 20 | 20 |
var opts removeOptions |
| 21 | 21 |
|
| 22 | 22 |
cmd := &cobra.Command{
|
| ... | ... |
@@ -38,22 +38,22 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 38 | 38 |
return cmd |
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
-func runRemove(dockerCli *command.DockerCli, opts *removeOptions) error {
|
|
| 41 |
+func runRemove(dockerCli command.Cli, opts *removeOptions) error {
|
|
| 42 | 42 |
client := dockerCli.Client() |
| 43 | 43 |
ctx := context.Background() |
| 44 |
- status := 0 |
|
| 44 |
+ |
|
| 45 |
+ var errs []string |
|
| 45 | 46 |
|
| 46 | 47 |
for _, name := range opts.volumes {
|
| 47 | 48 |
if err := client.VolumeRemove(ctx, name, opts.force); err != nil {
|
| 48 |
- fmt.Fprintf(dockerCli.Err(), "%s\n", err) |
|
| 49 |
- status = 1 |
|
| 49 |
+ errs = append(errs, err.Error()) |
|
| 50 | 50 |
continue |
| 51 | 51 |
} |
| 52 | 52 |
fmt.Fprintf(dockerCli.Out(), "%s\n", name) |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
- if status != 0 {
|
|
| 56 |
- return cli.StatusError{StatusCode: status}
|
|
| 55 |
+ if len(errs) > 0 {
|
|
| 56 |
+ return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
| 57 | 57 |
} |
| 58 | 58 |
return nil |
| 59 | 59 |
} |
| 60 | 60 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,47 @@ |
| 0 |
+package volume |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 9 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func TestVolumeRemoveErrors(t *testing.T) {
|
|
| 13 |
+ testCases := []struct {
|
|
| 14 |
+ args []string |
|
| 15 |
+ volumeRemoveFunc func(volumeID string, force bool) error |
|
| 16 |
+ expectedError string |
|
| 17 |
+ }{
|
|
| 18 |
+ {
|
|
| 19 |
+ expectedError: "requires at least 1 argument", |
|
| 20 |
+ }, |
|
| 21 |
+ {
|
|
| 22 |
+ args: []string{"nodeID"},
|
|
| 23 |
+ volumeRemoveFunc: func(volumeID string, force bool) error {
|
|
| 24 |
+ return fmt.Errorf("error removing the volume")
|
|
| 25 |
+ }, |
|
| 26 |
+ expectedError: "error removing the volume", |
|
| 27 |
+ }, |
|
| 28 |
+ } |
|
| 29 |
+ for _, tc := range testCases {
|
|
| 30 |
+ buf := new(bytes.Buffer) |
|
| 31 |
+ cmd := newRemoveCommand( |
|
| 32 |
+ test.NewFakeCli(&fakeClient{
|
|
| 33 |
+ volumeRemoveFunc: tc.volumeRemoveFunc, |
|
| 34 |
+ }, buf)) |
|
| 35 |
+ cmd.SetArgs(tc.args) |
|
| 36 |
+ cmd.SetOutput(ioutil.Discard) |
|
| 37 |
+ assert.Error(t, cmd.Execute(), tc.expectedError) |
|
| 38 |
+ } |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+func TestNodeRemoveMultiple(t *testing.T) {
|
|
| 42 |
+ buf := new(bytes.Buffer) |
|
| 43 |
+ cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
|
|
| 44 |
+ cmd.SetArgs([]string{"volume1", "volume2"})
|
|
| 45 |
+ assert.NilError(t, cmd.Execute()) |
|
| 46 |
+} |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+[ |
|
| 1 |
+ {
|
|
| 2 |
+ "Driver": "local", |
|
| 3 |
+ "Labels": {
|
|
| 4 |
+ "foo": "bar" |
|
| 5 |
+ }, |
|
| 6 |
+ "Mountpoint": "/data/volume", |
|
| 7 |
+ "Name": "foo", |
|
| 8 |
+ "Options": null, |
|
| 9 |
+ "Scope": "local" |
|
| 10 |
+ }, |
|
| 11 |
+ {
|
|
| 12 |
+ "Driver": "local", |
|
| 13 |
+ "Labels": {
|
|
| 14 |
+ "foo": "bar" |
|
| 15 |
+ }, |
|
| 16 |
+ "Mountpoint": "/data/volume", |
|
| 17 |
+ "Name": "bar", |
|
| 18 |
+ "Options": null, |
|
| 19 |
+ "Scope": "local" |
|
| 20 |
+ } |
|
| 21 |
+] |
| ... | ... |
@@ -8,6 +8,9 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
// Node creates a node with default values. |
| 10 | 10 |
// Any number of node function builder can be pass to augment it. |
| 11 |
+// |
|
| 12 |
+// n1 := Node() // Returns a default node |
|
| 13 |
+// n2 := Node(NodeID("foo"), NodeHostname("bar"), Leader())
|
|
| 11 | 14 |
func Node(builders ...func(*swarm.Node)) *swarm.Node {
|
| 12 | 15 |
t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) |
| 13 | 16 |
node := &swarm.Node{
|
| 14 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+package builders |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// Volume creates a volume with default values. |
|
| 7 |
+// Any number of volume function builder can be pass to augment it. |
|
| 8 |
+func Volume(builders ...func(volume *types.Volume)) *types.Volume {
|
|
| 9 |
+ volume := &types.Volume{
|
|
| 10 |
+ Name: "volume", |
|
| 11 |
+ Driver: "local", |
|
| 12 |
+ Mountpoint: "/data/volume", |
|
| 13 |
+ Scope: "local", |
|
| 14 |
+ } |
|
| 15 |
+ |
|
| 16 |
+ for _, builder := range builders {
|
|
| 17 |
+ builder(volume) |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ return volume |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// VolumeLabels sets the volume labels |
|
| 24 |
+func VolumeLabels(labels map[string]string) func(volume *types.Volume) {
|
|
| 25 |
+ return func(volume *types.Volume) {
|
|
| 26 |
+ volume.Labels = labels |
|
| 27 |
+ } |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// VolumeName sets the volume labels |
|
| 31 |
+func VolumeName(name string) func(volume *types.Volume) {
|
|
| 32 |
+ return func(volume *types.Volume) {
|
|
| 33 |
+ volume.Name = name |
|
| 34 |
+ } |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// VolumeDriver sets the volume driver |
|
| 38 |
+func VolumeDriver(name string) func(volume *types.Volume) {
|
|
| 39 |
+ return func(volume *types.Volume) {
|
|
| 40 |
+ volume.Driver = name |
|
| 41 |
+ } |
|
| 42 |
+} |
| ... | ... |
@@ -1,21 +1,23 @@ |
| 1 |
-// Package test is a test-only package that can be used by other cli package to write unit test |
|
| 2 | 1 |
package test |
| 3 | 2 |
|
| 4 | 3 |
import ( |
| 5 | 4 |
"io" |
| 6 | 5 |
"io/ioutil" |
| 6 |
+ "strings" |
|
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/cli/command" |
| 9 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 9 | 10 |
"github.com/docker/docker/client" |
| 10 |
- "strings" |
|
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 | 13 |
// FakeCli emulates the default DockerCli |
| 14 | 14 |
type FakeCli struct {
|
| 15 | 15 |
command.DockerCli |
| 16 |
- client client.APIClient |
|
| 17 |
- out io.Writer |
|
| 18 |
- in io.ReadCloser |
|
| 16 |
+ client client.APIClient |
|
| 17 |
+ configfile *configfile.ConfigFile |
|
| 18 |
+ out io.Writer |
|
| 19 |
+ err io.Writer |
|
| 20 |
+ in io.ReadCloser |
|
| 19 | 21 |
} |
| 20 | 22 |
|
| 21 | 23 |
// NewFakeCli returns a Cli backed by the fakeCli |
| ... | ... |
@@ -23,6 +25,7 @@ func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
| 23 | 23 |
return &FakeCli{
|
| 24 | 24 |
client: client, |
| 25 | 25 |
out: out, |
| 26 |
+ err: ioutil.Discard, |
|
| 26 | 27 |
in: ioutil.NopCloser(strings.NewReader("")),
|
| 27 | 28 |
} |
| 28 | 29 |
} |
| ... | ... |
@@ -32,17 +35,37 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
|
| 32 | 32 |
c.in = in |
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
+// SetErr sets the standard error stream th cli should write on |
|
| 36 |
+func (c *FakeCli) SetErr(err io.Writer) {
|
|
| 37 |
+ c.err = err |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+// SetConfigfile sets the "fake" config file |
|
| 41 |
+func (c *FakeCli) SetConfigfile(configfile *configfile.ConfigFile) {
|
|
| 42 |
+ c.configfile = configfile |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 35 | 45 |
// Client returns a docker API client |
| 36 | 46 |
func (c *FakeCli) Client() client.APIClient {
|
| 37 | 47 |
return c.client |
| 38 | 48 |
} |
| 39 | 49 |
|
| 40 |
-// Out returns the output stream the cli should write on |
|
| 50 |
+// Out returns the output stream (stdout) the cli should write on |
|
| 41 | 51 |
func (c *FakeCli) Out() *command.OutStream {
|
| 42 | 52 |
return command.NewOutStream(c.out) |
| 43 | 53 |
} |
| 44 | 54 |
|
| 45 |
-// In returns thi input stream the cli will use |
|
| 55 |
+// Err returns the output stream (stderr) the cli should write on |
|
| 56 |
+func (c *FakeCli) Err() io.Writer {
|
|
| 57 |
+ return c.err |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// In returns the input stream the cli will use |
|
| 46 | 61 |
func (c *FakeCli) In() *command.InStream {
|
| 47 | 62 |
return command.NewInStream(c.in) |
| 48 | 63 |
} |
| 64 |
+ |
|
| 65 |
+// ConfigFile returns the cli configfile object (to get client configuration) |
|
| 66 |
+func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
|
|
| 67 |
+ return c.configfile |
|
| 68 |
+} |
| ... | ... |
@@ -292,11 +292,6 @@ func (s *DockerExternalVolumeSuite) TestVolumeCLICreateOptionConflict(c *check.C |
| 292 | 292 |
out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Driver }}", "test")
|
| 293 | 293 |
_, _, err = dockerCmdWithError("volume", "create", "test", "--driver", strings.TrimSpace(out))
|
| 294 | 294 |
c.Assert(err, check.IsNil) |
| 295 |
- |
|
| 296 |
- // make sure hidden --name option conflicts with positional arg name |
|
| 297 |
- out, _, err = dockerCmdWithError("volume", "create", "--name", "test2", "test2")
|
|
| 298 |
- c.Assert(err, check.NotNil) |
|
| 299 |
- c.Assert(strings.TrimSpace(out), checker.Equals, "Conflicting options: either specify --name or provide positional arg, not both") |
|
| 300 | 295 |
} |
| 301 | 296 |
|
| 302 | 297 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
|