Browse code

Implement all inspect commands with the new inspector interface.

It makes the behavior completely consistent across commands.
It adds tests to check that execution stops when an element is not
found.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/12/08 12:04:38
Showing 10 changed files
... ...
@@ -34,97 +34,100 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
34 34
 		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
35 35
 	}
36 36
 
37
-	var (
38
-		err              error
39
-		tmpl             *template.Template
40
-		elementInspector inspect.Inspector
41
-	)
42
-
43
-	if *tmplStr != "" {
44
-		if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
45
-			return Cli.StatusError{StatusCode: 64,
46
-				Status: "Template parsing error: " + err.Error()}
47
-		}
48
-	}
49
-
50
-	if tmpl != nil {
51
-		elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
52
-	} else {
53
-		elementInspector = inspect.NewIndentedInspector(cli.out)
54
-	}
55
-
37
+	var elementSearcher inspectSearcher
56 38
 	switch *inspectType {
57 39
 	case "container":
58
-		err = cli.inspectContainers(cmd.Args(), *size, elementInspector)
59
-	case "images":
60
-		err = cli.inspectImages(cmd.Args(), *size, elementInspector)
40
+		elementSearcher = cli.inspectContainers(*size)
41
+	case "image":
42
+		elementSearcher = cli.inspectImages(*size)
61 43
 	default:
62
-		err = cli.inspectAll(cmd.Args(), *size, elementInspector)
44
+		elementSearcher = cli.inspectAll(*size)
63 45
 	}
64 46
 
65
-	if err := elementInspector.Flush(); err != nil {
66
-		return err
67
-	}
68
-	return err
47
+	return cli.inspectElements(*tmplStr, cmd.Args(), elementSearcher)
69 48
 }
70 49
 
71
-func (cli *DockerCli) inspectContainers(containerIDs []string, getSize bool, elementInspector inspect.Inspector) error {
72
-	for _, containerID := range containerIDs {
73
-		if err := cli.inspectContainer(containerID, getSize, elementInspector); err != nil {
74
-			if lib.IsErrContainerNotFound(err) {
75
-				return fmt.Errorf("Error: No such container: %s\n", containerID)
76
-			}
77
-			return err
78
-		}
50
+func (cli *DockerCli) inspectContainers(getSize bool) inspectSearcher {
51
+	return func(ref string) (interface{}, []byte, error) {
52
+		return cli.client.ContainerInspectWithRaw(ref, getSize)
79 53
 	}
80
-	return nil
81 54
 }
82 55
 
83
-func (cli *DockerCli) inspectImages(imageIDs []string, getSize bool, elementInspector inspect.Inspector) error {
84
-	for _, imageID := range imageIDs {
85
-		if err := cli.inspectImage(imageID, getSize, elementInspector); err != nil {
86
-			if lib.IsErrImageNotFound(err) {
87
-				return fmt.Errorf("Error: No such image: %s\n", imageID)
88
-			}
89
-			return err
90
-		}
56
+func (cli *DockerCli) inspectImages(getSize bool) inspectSearcher {
57
+	return func(ref string) (interface{}, []byte, error) {
58
+		return cli.client.ImageInspectWithRaw(ref, getSize)
91 59
 	}
92
-	return nil
93 60
 }
94 61
 
95
-func (cli *DockerCli) inspectAll(ids []string, getSize bool, elementInspector inspect.Inspector) error {
96
-	for _, id := range ids {
97
-		if err := cli.inspectContainer(id, getSize, elementInspector); err != nil {
62
+func (cli *DockerCli) inspectAll(getSize bool) inspectSearcher {
63
+	return func(ref string) (interface{}, []byte, error) {
64
+		c, rawContainer, err := cli.client.ContainerInspectWithRaw(ref, getSize)
65
+		if err != nil {
98 66
 			// Search for image with that id if a container doesn't exist.
99 67
 			if lib.IsErrContainerNotFound(err) {
100
-				if err := cli.inspectImage(id, getSize, elementInspector); err != nil {
68
+				i, rawImage, err := cli.client.ImageInspectWithRaw(ref, getSize)
69
+				if err != nil {
101 70
 					if lib.IsErrImageNotFound(err) {
102
-						return fmt.Errorf("Error: No such image or container: %s", id)
71
+						return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
103 72
 					}
104
-					return err
73
+					return nil, nil, err
105 74
 				}
106
-				continue
75
+				return i, rawImage, err
107 76
 			}
108
-			return err
77
+			return nil, nil, err
109 78
 		}
79
+		return c, rawContainer, err
110 80
 	}
111
-	return nil
112 81
 }
113 82
 
114
-func (cli *DockerCli) inspectContainer(containerID string, getSize bool, elementInspector inspect.Inspector) error {
115
-	c, raw, err := cli.client.ContainerInspectWithRaw(containerID, getSize)
83
+type inspectSearcher func(ref string) (interface{}, []byte, error)
84
+
85
+func (cli *DockerCli) inspectElements(tmplStr string, references []string, searchByReference inspectSearcher) error {
86
+	elementInspector, err := cli.newInspectorWithTemplate(tmplStr)
116 87
 	if err != nil {
117
-		return err
88
+		return Cli.StatusError{StatusCode: 64, Status: err.Error()}
89
+	}
90
+
91
+	var inspectErr error
92
+	for _, ref := range references {
93
+		element, raw, err := searchByReference(ref)
94
+		if err != nil {
95
+			inspectErr = err
96
+			break
97
+		}
98
+
99
+		if err := elementInspector.Inspect(element, raw); err != nil {
100
+			inspectErr = err
101
+			break
102
+		}
118 103
 	}
119 104
 
120
-	return elementInspector.Inspect(c, raw)
105
+	if err := elementInspector.Flush(); err != nil {
106
+		cli.inspectErrorStatus(err)
107
+	}
108
+
109
+	if status := cli.inspectErrorStatus(inspectErr); status != 0 {
110
+		return Cli.StatusError{StatusCode: status}
111
+	}
112
+	return nil
121 113
 }
122 114
 
123
-func (cli *DockerCli) inspectImage(imageID string, getSize bool, elementInspector inspect.Inspector) error {
124
-	i, raw, err := cli.client.ImageInspectWithRaw(imageID, getSize)
115
+func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
125 116
 	if err != nil {
126
-		return err
117
+		fmt.Fprintf(cli.err, "%s\n", err)
118
+		status = 1
127 119
 	}
120
+	return
121
+}
128 122
 
129
-	return elementInspector.Inspect(i, raw)
123
+func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
124
+	elementInspector := inspect.NewIndentedInspector(cli.out)
125
+	if tmplStr != "" {
126
+		tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
127
+		if err != nil {
128
+			return nil, fmt.Errorf("Template parsing error: %s", err)
129
+		}
130
+		elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
131
+	}
132
+	return elementInspector, nil
130 133
 }
... ...
@@ -33,29 +33,41 @@ func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspe
33 33
 // Inspect executes the inspect template.
34 34
 // It decodes the raw element into a map if the initial execution fails.
35 35
 // This allows docker cli to parse inspect structs injected with Swarm fields.
36
-func (i TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
36
+func (i *TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
37 37
 	buffer := new(bytes.Buffer)
38 38
 	if err := i.tmpl.Execute(buffer, typedElement); err != nil {
39
-		var raw interface{}
40
-		rdr := bytes.NewReader(rawElement)
41
-		dec := json.NewDecoder(rdr)
42
-
43
-		if rawErr := dec.Decode(&raw); rawErr != nil {
44
-			return fmt.Errorf("unable to read inspect data: %v\n", rawErr)
39
+		if rawElement == nil {
40
+			return fmt.Errorf("Template parsing error: %v", err)
45 41
 		}
42
+		return i.tryRawInspectFallback(rawElement)
43
+	}
44
+	i.buffer.Write(buffer.Bytes())
45
+	i.buffer.WriteByte('\n')
46
+	return nil
47
+}
46 48
 
47
-		tmplMissingKey := i.tmpl.Option("missingkey=error")
48
-		if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
49
-			return fmt.Errorf("Template parsing error: %v\n", err)
50
-		}
49
+func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
50
+	var raw interface{}
51
+	buffer := new(bytes.Buffer)
52
+	rdr := bytes.NewReader(rawElement)
53
+	dec := json.NewDecoder(rdr)
54
+
55
+	if rawErr := dec.Decode(&raw); rawErr != nil {
56
+		return fmt.Errorf("unable to read inspect data: %v", rawErr)
57
+	}
58
+
59
+	tmplMissingKey := i.tmpl.Option("missingkey=error")
60
+	if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
61
+		return fmt.Errorf("Template parsing error: %v", rawErr)
51 62
 	}
63
+
52 64
 	i.buffer.Write(buffer.Bytes())
53 65
 	i.buffer.WriteByte('\n')
54 66
 	return nil
55 67
 }
56 68
 
57 69
 // Flush write the result of inspecting all elements into the output stream.
58
-func (i TemplateInspector) Flush() error {
70
+func (i *TemplateInspector) Flush() error {
59 71
 	_, err := io.Copy(i.outputStream, i.buffer)
60 72
 	return err
61 73
 }
... ...
@@ -63,39 +75,37 @@ func (i TemplateInspector) Flush() error {
63 63
 // IndentedInspector uses a buffer to stop the indented representation of an element.
64 64
 type IndentedInspector struct {
65 65
 	outputStream io.Writer
66
-	indented     *bytes.Buffer
66
+	elements     []interface{}
67 67
 }
68 68
 
69 69
 // NewIndentedInspector generates a new IndentedInspector.
70 70
 func NewIndentedInspector(outputStream io.Writer) Inspector {
71
-	indented := new(bytes.Buffer)
72
-	indented.WriteString("[\n")
73 71
 	return &IndentedInspector{
74 72
 		outputStream: outputStream,
75
-		indented:     indented,
76 73
 	}
77 74
 }
78 75
 
79 76
 // Inspect writes the raw element with an indented json format.
80
-func (i IndentedInspector) Inspect(_ interface{}, rawElement []byte) error {
81
-	if err := json.Indent(i.indented, rawElement, "", "    "); err != nil {
82
-		return err
83
-	}
84
-	i.indented.WriteByte(',')
77
+func (i *IndentedInspector) Inspect(typedElement interface{}, _ []byte) error {
78
+	i.elements = append(i.elements, typedElement)
85 79
 	return nil
86 80
 }
87 81
 
88 82
 // Flush write the result of inspecting all elements into the output stream.
89
-func (i IndentedInspector) Flush() error {
90
-	if i.indented.Len() > 1 {
91
-		// Remove trailing ','
92
-		i.indented.Truncate(i.indented.Len() - 1)
83
+func (i *IndentedInspector) Flush() error {
84
+	if len(i.elements) == 0 {
85
+		_, err := io.WriteString(i.outputStream, "[]\n")
86
+		return err
93 87
 	}
94
-	i.indented.WriteString("]\n")
95 88
 
96
-	// Note that we will always write "[]" when "-f" isn't specified,
97
-	// to make sure the output would always be array, see
98
-	// https://github.com/docker/docker/pull/9500#issuecomment-65846734
99
-	_, err := io.Copy(i.outputStream, i.indented)
89
+	buffer, err := json.MarshalIndent(i.elements, "", "    ")
90
+	if err != nil {
91
+		return err
92
+	}
93
+
94
+	if _, err := io.Copy(i.outputStream, bytes.NewReader(buffer)); err != nil {
95
+		return err
96
+	}
97
+	_, err = io.WriteString(i.outputStream, "\n")
100 98
 	return err
101 99
 }
... ...
@@ -15,7 +15,7 @@ type imageNotFoundError struct {
15 15
 
16 16
 // Error returns a string representation of an imageNotFoundError
17 17
 func (i imageNotFoundError) Error() string {
18
-	return fmt.Sprintf("Image not found: %s", i.imageID)
18
+	return fmt.Sprintf("Error: No such image: %s", i.imageID)
19 19
 }
20 20
 
21 21
 // IsErrImageNotFound returns true if the error is caused
... ...
@@ -32,7 +32,7 @@ type containerNotFoundError struct {
32 32
 
33 33
 // Error returns a string representation of an containerNotFoundError
34 34
 func (e containerNotFoundError) Error() string {
35
-	return fmt.Sprintf("Container not found: %s", e.containerID)
35
+	return fmt.Sprintf("Error: No such container: %s", e.containerID)
36 36
 }
37 37
 
38 38
 // IsErrContainerNotFound returns true if the error is caused
... ...
@@ -42,6 +42,40 @@ func IsErrContainerNotFound(err error) bool {
42 42
 	return ok
43 43
 }
44 44
 
45
+// networkNotFoundError implements an error returned when a network is not in the docker host.
46
+type networkNotFoundError struct {
47
+	networkID string
48
+}
49
+
50
+// Error returns a string representation of an networkNotFoundError
51
+func (e networkNotFoundError) Error() string {
52
+	return fmt.Sprintf("Error: No such network: %s", e.networkID)
53
+}
54
+
55
+// IsErrNetworkNotFound returns true if the error is caused
56
+// when a network is not found in the docker host.
57
+func IsErrNetworkNotFound(err error) bool {
58
+	_, ok := err.(networkNotFoundError)
59
+	return ok
60
+}
61
+
62
+// volumeNotFoundError implements an error returned when a volume is not in the docker host.
63
+type volumeNotFoundError struct {
64
+	volumeID string
65
+}
66
+
67
+// Error returns a string representation of an networkNotFoundError
68
+func (e volumeNotFoundError) Error() string {
69
+	return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
70
+}
71
+
72
+// IsErrVolumeNotFound returns true if the error is caused
73
+// when a volume is not found in the docker host.
74
+func IsErrVolumeNotFound(err error) bool {
75
+	_, ok := err.(networkNotFoundError)
76
+	return ok
77
+}
78
+
45 79
 // unauthorizedError represents an authorization error in a remote registry.
46 80
 type unauthorizedError struct {
47 81
 	cause error
... ...
@@ -2,6 +2,7 @@ package lib
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"net/http"
5 6
 
6 7
 	"github.com/docker/docker/api/types"
7 8
 )
... ...
@@ -59,6 +60,9 @@ func (cli *Client) NetworkInspect(networkID string) (types.NetworkResource, erro
59 59
 	var networkResource types.NetworkResource
60 60
 	resp, err := cli.get("/networks/"+networkID, nil, nil)
61 61
 	if err != nil {
62
+		if resp.statusCode == http.StatusNotFound {
63
+			return networkResource, networkNotFoundError{networkID}
64
+		}
62 65
 		return networkResource, err
63 66
 	}
64 67
 	defer ensureReaderClosed(resp)
... ...
@@ -2,6 +2,7 @@ package lib
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"net/http"
5 6
 	"net/url"
6 7
 
7 8
 	"github.com/docker/docker/api/types"
... ...
@@ -35,6 +36,9 @@ func (cli *Client) VolumeInspect(volumeID string) (types.Volume, error) {
35 35
 	var volume types.Volume
36 36
 	resp, err := cli.get("/volumes/"+volumeID, nil, nil)
37 37
 	if err != nil {
38
+		if resp.statusCode == http.StatusNotFound {
39
+			return volume, volumeNotFoundError{volumeID}
40
+		}
38 41
 		return volume, err
39 42
 	}
40 43
 	defer ensureReaderClosed(resp)
... ...
@@ -1,14 +1,10 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"encoding/json"
6 4
 	"fmt"
7
-	"io"
8 5
 	"net"
9 6
 	"strings"
10 7
 	"text/tabwriter"
11
-	"text/template"
12 8
 
13 9
 	"github.com/docker/docker/api/types"
14 10
 	Cli "github.com/docker/docker/cli"
... ...
@@ -192,61 +188,12 @@ func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
192 192
 		return err
193 193
 	}
194 194
 
195
-	var tmpl *template.Template
196
-	if *tmplStr != "" {
197
-		var err error
198
-		tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
199
-		if err != nil {
200
-			return err
201
-		}
195
+	inspectSearcher := func(name string) (interface{}, []byte, error) {
196
+		i, err := cli.client.NetworkInspect(name)
197
+		return i, nil, err
202 198
 	}
203 199
 
204
-	status := 0
205
-	var networks []types.NetworkResource
206
-	buf := new(bytes.Buffer)
207
-	for _, name := range cmd.Args() {
208
-		networkResource, err := cli.client.NetworkInspect(name)
209
-		if err != nil {
210
-			fmt.Fprintf(cli.err, "%s\n", err)
211
-			return Cli.StatusError{StatusCode: 1}
212
-		}
213
-		if tmpl == nil {
214
-			networks = append(networks, networkResource)
215
-			continue
216
-		}
217
-
218
-		if err := tmpl.Execute(buf, &networkResource); err != nil {
219
-			fmt.Fprintf(cli.err, "%s\n", err)
220
-			return Cli.StatusError{StatusCode: 1}
221
-		}
222
-		buf.WriteString("\n")
223
-	}
224
-
225
-	if tmpl != nil {
226
-		if _, err := io.Copy(cli.out, buf); err != nil {
227
-			return err
228
-		}
229
-		return nil
230
-	}
231
-
232
-	if len(networks) == 0 {
233
-		io.WriteString(cli.out, "[]")
234
-	}
235
-
236
-	b, err := json.MarshalIndent(networks, "", "    ")
237
-	if err != nil {
238
-		return err
239
-	}
240
-
241
-	if _, err := io.Copy(cli.out, bytes.NewReader(b)); err != nil {
242
-		return err
243
-	}
244
-	io.WriteString(cli.out, "\n")
245
-
246
-	if status != 0 {
247
-		return Cli.StatusError{StatusCode: status}
248
-	}
249
-	return nil
200
+	return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
250 201
 }
251 202
 
252 203
 // Consolidates the ipam configuration as a group from different related configurations
... ...
@@ -1,12 +1,8 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"encoding/json"
6 4
 	"fmt"
7
-	"io"
8 5
 	"text/tabwriter"
9
-	"text/template"
10 6
 
11 7
 	"github.com/docker/docker/api/types"
12 8
 	Cli "github.com/docker/docker/cli"
... ...
@@ -98,58 +94,12 @@ func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
98 98
 		return nil
99 99
 	}
100 100
 
101
-	var tmpl *template.Template
102
-	if *tmplStr != "" {
103
-		var err error
104
-		tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
105
-		if err != nil {
106
-			return err
107
-		}
101
+	inspectSearcher := func(name string) (interface{}, []byte, error) {
102
+		i, err := cli.client.VolumeInspect(name)
103
+		return i, nil, err
108 104
 	}
109 105
 
110
-	var status = 0
111
-	var volumes []*types.Volume
112
-
113
-	for _, name := range cmd.Args() {
114
-		volume, err := cli.client.VolumeInspect(name)
115
-		if err != nil {
116
-			fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
117
-			status = 1
118
-			break
119
-		}
120
-
121
-		if tmpl == nil {
122
-			volumes = append(volumes, &volume)
123
-			continue
124
-		}
125
-
126
-		buf := bytes.NewBufferString("")
127
-		if err := tmpl.Execute(buf, &volume); err != nil {
128
-			fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
129
-			status = 1
130
-			break
131
-		}
132
-
133
-		cli.out.Write(buf.Bytes())
134
-		cli.out.Write([]byte{'\n'})
135
-	}
136
-
137
-	if tmpl == nil {
138
-		b, err := json.MarshalIndent(volumes, "", "    ")
139
-		if err != nil {
140
-			return err
141
-		}
142
-		_, err = io.Copy(cli.out, bytes.NewReader(b))
143
-		if err != nil {
144
-			return err
145
-		}
146
-		io.WriteString(cli.out, "\n")
147
-	}
148
-
149
-	if status != 0 {
150
-		return Cli.StatusError{StatusCode: status}
151
-	}
152
-	return nil
106
+	return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
153 107
 }
154 108
 
155 109
 // CmdVolumeCreate creates a new container from a given image.
... ...
@@ -356,3 +356,14 @@ func (s *DockerSuite) TestInspectByPrefix(c *check.C) {
356 356
 	c.Assert(err, checker.IsNil)
357 357
 	c.Assert(id, checker.Equals, id3)
358 358
 }
359
+
360
+func (s *DockerSuite) TestInspectStopWhenNotFound(c *check.C) {
361
+	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
362
+	dockerCmd(c, "run", "--name=not-shown", "-d", "busybox", "top")
363
+	out, _, err := dockerCmdWithError("inspect", "--type=container", "--format='{{.Name}}'", "busybox", "missing", "not-shown")
364
+
365
+	c.Assert(err, checker.Not(check.IsNil))
366
+	c.Assert(out, checker.Contains, "busybox")
367
+	c.Assert(out, checker.Not(checker.Contains), "not-shown")
368
+	c.Assert(out, checker.Contains, "Error: No such container: missing")
369
+}
... ...
@@ -317,7 +317,7 @@ func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) {
317 317
 	c.Assert(exitCode, checker.Equals, 1)
318 318
 	c.Assert(out, checker.Contains, "Error: No such network: nonexistent")
319 319
 	networkResources = []types.NetworkResource{}
320
-	inspectOut := strings.SplitN(out, "\n", 2)[1]
320
+	inspectOut := strings.SplitN(out, "\nError: No such network: nonexistent\n", 2)[0]
321 321
 	err = json.Unmarshal([]byte(inspectOut), &networkResources)
322 322
 	c.Assert(networkResources, checker.HasLen, 1)
323 323
 
... ...
@@ -53,15 +53,17 @@ func (s *DockerSuite) TestVolumeCliInspect(c *check.C) {
53 53
 func (s *DockerSuite) TestVolumeCliInspectMulti(c *check.C) {
54 54
 	dockerCmd(c, "volume", "create", "--name", "test1")
55 55
 	dockerCmd(c, "volume", "create", "--name", "test2")
56
+	dockerCmd(c, "volume", "create", "--name", "not-shown")
56 57
 
57
-	out, _, err := dockerCmdWithError("volume", "inspect", "--format='{{ .Name }}'", "test1", "test2", "doesntexist")
58
+	out, _, err := dockerCmdWithError("volume", "inspect", "--format='{{ .Name }}'", "test1", "test2", "doesntexist", "not-shown")
58 59
 	c.Assert(err, checker.NotNil)
59 60
 	outArr := strings.Split(strings.TrimSpace(out), "\n")
60 61
 	c.Assert(len(outArr), check.Equals, 3, check.Commentf("\n%s", out))
61 62
 
62
-	c.Assert(strings.Contains(out, "test1\n"), check.Equals, true)
63
-	c.Assert(strings.Contains(out, "test2\n"), check.Equals, true)
64
-	c.Assert(strings.Contains(out, "Error: No such volume: doesntexist\n"), check.Equals, true)
63
+	c.Assert(out, checker.Contains, "test1")
64
+	c.Assert(out, checker.Contains, "test2")
65
+	c.Assert(out, checker.Contains, "Error: No such volume: doesntexist")
66
+	c.Assert(out, checker.Not(checker.Contains), "not-shown")
65 67
 }
66 68
 
67 69
 func (s *DockerSuite) TestVolumeCliLs(c *check.C) {