Browse code

Implement docker inspect with standalone client lib.

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

David Calavera authored on 2015/12/07 08:34:25
Showing 6 changed files
... ...
@@ -26,6 +26,7 @@ type apiClient interface {
26 26
 	ContainerExecStart(execID string, config types.ExecStartCheck) error
27 27
 	ContainerExport(containerID string) (io.ReadCloser, error)
28 28
 	ContainerInspect(containerID string) (types.ContainerJSON, error)
29
+	ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error)
29 30
 	ContainerKill(containerID, signal string) error
30 31
 	ContainerList(options types.ContainerListOptions) ([]types.Container, error)
31 32
 	ContainerLogs(options types.ContainerLogsOptions) (io.ReadCloser, error)
... ...
@@ -47,6 +48,7 @@ type apiClient interface {
47 47
 	ImageCreate(options types.ImageCreateOptions) (io.ReadCloser, error)
48 48
 	ImageHistory(imageID string) ([]types.ImageHistory, error)
49 49
 	ImageImport(options types.ImageImportOptions) (io.ReadCloser, error)
50
+	ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error)
50 51
 	ImageList(options types.ImageListOptions) ([]types.Image, error)
51 52
 	ImageLoad(input io.Reader) (io.ReadCloser, error)
52 53
 	ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
... ...
@@ -1,16 +1,12 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"encoding/json"
6 5
 	"fmt"
7
-	"io"
8
-	"net/http"
9
-	"net/url"
10
-	"strings"
11 6
 	"text/template"
12 7
 
13
-	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/api/client/inspect"
9
+	"github.com/docker/docker/api/client/lib"
14 10
 	Cli "github.com/docker/docker/cli"
15 11
 	flag "github.com/docker/docker/pkg/mflag"
16 12
 )
... ...
@@ -34,10 +30,15 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
34 34
 
35 35
 	cmd.ParseFlags(args, true)
36 36
 
37
-	var tmpl *template.Template
38
-	var err error
39
-	var obj []byte
40
-	var statusCode int
37
+	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
38
+		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
39
+	}
40
+
41
+	var (
42
+		err              error
43
+		tmpl             *template.Template
44
+		elementInspector inspect.Inspector
45
+	)
41 46
 
42 47
 	if *tmplStr != "" {
43 48
 		if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
... ...
@@ -46,160 +47,84 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
46 46
 		}
47 47
 	}
48 48
 
49
-	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
50
-		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
49
+	if tmpl != nil {
50
+		elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
51
+	} else {
52
+		elementInspector = inspect.NewIndentedInspector(cli.out)
51 53
 	}
52 54
 
53
-	indented := new(bytes.Buffer)
54
-	indented.WriteString("[\n")
55
-	status := 0
56
-	isImage := false
55
+	switch *inspectType {
56
+	case "container":
57
+		err = cli.inspectContainers(cmd.Args(), *size, elementInspector)
58
+	case "images":
59
+		err = cli.inspectImages(cmd.Args(), *size, elementInspector)
60
+	default:
61
+		err = cli.inspectAll(cmd.Args(), *size, elementInspector)
62
+	}
57 63
 
58
-	v := url.Values{}
59
-	if *size {
60
-		v.Set("size", "1")
64
+	if err := elementInspector.Flush(); err != nil {
65
+		return err
61 66
 	}
67
+	return err
68
+}
62 69
 
63
-	for _, name := range cmd.Args() {
64
-		if *inspectType == "" || *inspectType == "container" {
65
-			obj, statusCode, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
66
-			if err != nil {
67
-				if err == errConnectionFailed {
68
-					return err
69
-				}
70
-				if *inspectType == "container" {
71
-					if statusCode == http.StatusNotFound {
72
-						fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
73
-					} else {
74
-						fmt.Fprintf(cli.err, "%s", err)
75
-					}
76
-					status = 1
77
-					continue
78
-				}
70
+func (cli *DockerCli) inspectContainers(containerIDs []string, getSize bool, elementInspector inspect.Inspector) error {
71
+	for _, containerID := range containerIDs {
72
+		if err := cli.inspectContainer(containerID, getSize, elementInspector); err != nil {
73
+			if lib.IsErrContainerNotFound(err) {
74
+				return fmt.Errorf("Error: No such container: %s\n", containerID)
79 75
 			}
76
+			return err
80 77
 		}
78
+	}
79
+	return nil
80
+}
81 81
 
82
-		if obj == nil && (*inspectType == "" || *inspectType == "image") {
83
-			obj, statusCode, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
84
-			isImage = true
85
-			if err != nil {
86
-				if err == errConnectionFailed {
87
-					return err
88
-				}
89
-				if statusCode == http.StatusNotFound {
90
-					if *inspectType == "" {
91
-						fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
92
-					} else {
93
-						fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
94
-					}
95
-				} else {
96
-					fmt.Fprintf(cli.err, "%s", err)
97
-				}
98
-				status = 1
99
-				continue
82
+func (cli *DockerCli) inspectImages(imageIDs []string, getSize bool, elementInspector inspect.Inspector) error {
83
+	for _, imageID := range imageIDs {
84
+		if err := cli.inspectImage(imageID, getSize, elementInspector); err != nil {
85
+			if lib.IsErrImageNotFound(err) {
86
+				return fmt.Errorf("Error: No such image: %s\n", imageID)
100 87
 			}
88
+			return err
101 89
 		}
90
+	}
91
+	return nil
92
+}
102 93
 
103
-		if tmpl == nil {
104
-			if err := json.Indent(indented, obj, "", "    "); err != nil {
105
-				fmt.Fprintf(cli.err, "%s\n", err)
106
-				status = 1
107
-				continue
108
-			}
109
-		} else {
110
-			rdr := bytes.NewReader(obj)
111
-			dec := json.NewDecoder(rdr)
112
-			buf := bytes.NewBufferString("")
113
-
114
-			if isImage {
115
-				inspPtr := types.ImageInspect{}
116
-				if err := dec.Decode(&inspPtr); err != nil {
117
-					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
118
-					status = 1
119
-					break
120
-				}
121
-				if err := tmpl.Execute(buf, inspPtr); err != nil {
122
-					rdr.Seek(0, 0)
123
-					var ok bool
124
-
125
-					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
126
-						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
127
-						status = 1
128
-						break
129
-					}
130
-				}
131
-			} else {
132
-				inspPtr := types.ContainerJSON{}
133
-				if err := dec.Decode(&inspPtr); err != nil {
134
-					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
135
-					status = 1
136
-					break
137
-				}
138
-				if err := tmpl.Execute(buf, inspPtr); err != nil {
139
-					rdr.Seek(0, 0)
140
-					var ok bool
141
-
142
-					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
143
-						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
144
-						status = 1
145
-						break
94
+func (cli *DockerCli) inspectAll(ids []string, getSize bool, elementInspector inspect.Inspector) error {
95
+	for _, id := range ids {
96
+		if err := cli.inspectContainer(id, getSize, elementInspector); err != nil {
97
+			// Search for image with that id if a container doesn't exist.
98
+			if lib.IsErrContainerNotFound(err) {
99
+				if err := cli.inspectImage(id, getSize, elementInspector); err != nil {
100
+					if lib.IsErrImageNotFound(err) {
101
+						return fmt.Errorf("Error: No such image or container: %s", id)
146 102
 					}
103
+					return err
147 104
 				}
105
+				continue
148 106
 			}
149
-
150
-			cli.out.Write(buf.Bytes())
151
-			cli.out.Write([]byte{'\n'})
152
-		}
153
-		indented.WriteString(",")
154
-	}
155
-
156
-	if indented.Len() > 1 {
157
-		// Remove trailing ','
158
-		indented.Truncate(indented.Len() - 1)
159
-	}
160
-	indented.WriteString("]\n")
161
-
162
-	if tmpl == nil {
163
-		// Note that we will always write "[]" when "-f" isn't specified,
164
-		// to make sure the output would always be array, see
165
-		// https://github.com/docker/docker/pull/9500#issuecomment-65846734
166
-		if _, err := io.Copy(cli.out, indented); err != nil {
167 107
 			return err
168 108
 		}
169 109
 	}
170
-
171
-	if status != 0 {
172
-		return Cli.StatusError{StatusCode: status}
173
-	}
174 110
 	return nil
175 111
 }
176 112
 
177
-// decodeRawInspect executes the inspect template with a raw interface.
178
-// This allows docker cli to parse inspect structs injected with Swarm fields.
179
-// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
180
-// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
181
-// We assume that the template is invalid when there is a <no value>, if the template was valid
182
-// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
183
-// template with the typed input.
184
-//
185
-// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
186
-// we build Docker with that version:
187
-// https://golang.org/pkg/text/template/#Template.Option
188
-func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
189
-	var raw interface{}
190
-	buf := bytes.NewBufferString("")
191
-
192
-	if rawErr := dec.Decode(&raw); rawErr != nil {
193
-		fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
194
-		return buf, false
113
+func (cli *DockerCli) inspectContainer(containerID string, getSize bool, elementInspector inspect.Inspector) error {
114
+	c, raw, err := cli.client.ContainerInspectWithRaw(containerID, getSize)
115
+	if err != nil {
116
+		return err
195 117
 	}
196 118
 
197
-	if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
198
-		return buf, false
199
-	}
119
+	return elementInspector.Inspect(c, raw)
120
+}
200 121
 
201
-	if strings.Contains(buf.String(), "<no value>") {
202
-		return buf, false
122
+func (cli *DockerCli) inspectImage(imageID string, getSize bool, elementInspector inspect.Inspector) error {
123
+	i, raw, err := cli.client.ImageInspectWithRaw(imageID, getSize)
124
+	if err != nil {
125
+		return err
203 126
 	}
204
-	return buf, true
127
+
128
+	return elementInspector.Inspect(i, raw)
205 129
 }
206 130
new file mode 100644
... ...
@@ -0,0 +1,101 @@
0
+package inspect
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"text/template"
8
+)
9
+
10
+// Inspector defines an interface to implement to process elements
11
+type Inspector interface {
12
+	Inspect(typedElement interface{}, rawElement []byte) error
13
+	Flush() error
14
+}
15
+
16
+// TemplateInspector uses a text template to inspect elements.
17
+type TemplateInspector struct {
18
+	outputStream io.Writer
19
+	buffer       *bytes.Buffer
20
+	tmpl         *template.Template
21
+}
22
+
23
+// NewTemplateInspector creates a new inspector with a template.
24
+func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
25
+	return &TemplateInspector{
26
+		outputStream: outputStream,
27
+		buffer:       new(bytes.Buffer),
28
+		tmpl:         tmpl,
29
+	}
30
+}
31
+
32
+// Inspect executes the inspect template.
33
+// It decodes the raw element into a map if the initial execution fails.
34
+// This allows docker cli to parse inspect structs injected with Swarm fields.
35
+func (i TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
36
+	buffer := new(bytes.Buffer)
37
+	if err := i.tmpl.Execute(buffer, typedElement); err != nil {
38
+		var raw interface{}
39
+		rdr := bytes.NewReader(rawElement)
40
+		dec := json.NewDecoder(rdr)
41
+
42
+		if rawErr := dec.Decode(&raw); rawErr != nil {
43
+			return fmt.Errorf("unable to read inspect data: %v\n", rawErr)
44
+		}
45
+
46
+		tmplMissingKey := i.tmpl.Option("missingkey=error")
47
+		if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
48
+			return fmt.Errorf("Template parsing error: %v\n", err)
49
+		}
50
+	}
51
+	i.buffer.Write(buffer.Bytes())
52
+	i.buffer.WriteByte('\n')
53
+	return nil
54
+}
55
+
56
+// Flush write the result of inspecting all elements into the output stream.
57
+func (i TemplateInspector) Flush() error {
58
+	_, err := io.Copy(i.outputStream, i.buffer)
59
+	return err
60
+}
61
+
62
+// IndentedInspector uses a buffer to stop the indented representation of an element.
63
+type IndentedInspector struct {
64
+	outputStream io.Writer
65
+	indented     *bytes.Buffer
66
+}
67
+
68
+// NewIndentedInspector generates a new IndentedInspector.
69
+func NewIndentedInspector(outputStream io.Writer) Inspector {
70
+	indented := new(bytes.Buffer)
71
+	indented.WriteString("[\n")
72
+	return &IndentedInspector{
73
+		outputStream: outputStream,
74
+		indented:     indented,
75
+	}
76
+}
77
+
78
+// Inspect writes the raw element with an indented json format.
79
+func (i IndentedInspector) Inspect(_ interface{}, rawElement []byte) error {
80
+	if err := json.Indent(i.indented, rawElement, "", "    "); err != nil {
81
+		return err
82
+	}
83
+	i.indented.WriteByte(',')
84
+	return nil
85
+}
86
+
87
+// Flush write the result of inspecting all elements into the output stream.
88
+func (i IndentedInspector) Flush() error {
89
+	if i.indented.Len() > 1 {
90
+		// Remove trailing ','
91
+		i.indented.Truncate(i.indented.Len() - 1)
92
+	}
93
+	i.indented.WriteString("]\n")
94
+
95
+	// Note that we will always write "[]" when "-f" isn't specified,
96
+	// to make sure the output would always be array, see
97
+	// https://github.com/docker/docker/pull/9500#issuecomment-65846734
98
+	_, err := io.Copy(i.outputStream, i.indented)
99
+	return err
100
+}
... ...
@@ -1,20 +1,64 @@
1 1
 package lib
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"encoding/json"
6
+	"io/ioutil"
7
+	"net/http"
8
+	"net/url"
5 9
 
6 10
 	"github.com/docker/docker/api/types"
7 11
 )
8 12
 
9
-// ContainerInspect returns the all the container information.
13
+// ContainerInspect returns the container information.
10 14
 func (cli *Client) ContainerInspect(containerID string) (types.ContainerJSON, error) {
11 15
 	serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
12 16
 	if err != nil {
17
+		if serverResp.statusCode == http.StatusNotFound {
18
+			return types.ContainerJSON{}, containerNotFoundError{containerID}
19
+		}
13 20
 		return types.ContainerJSON{}, err
14 21
 	}
15 22
 	defer ensureReaderClosed(serverResp)
16 23
 
17 24
 	var response types.ContainerJSON
18
-	json.NewDecoder(serverResp.body).Decode(&response)
25
+	err = json.NewDecoder(serverResp.body).Decode(&response)
19 26
 	return response, err
20 27
 }
28
+
29
+// ContainerInspectWithRaw returns the container information and it's raw representation.
30
+func (cli *Client) ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
31
+	query := url.Values{}
32
+	if getSize {
33
+		query.Set("size", "1")
34
+	}
35
+	serverResp, err := cli.get("/containers/"+containerID+"/json", query, nil)
36
+	if err != nil {
37
+		if serverResp.statusCode == http.StatusNotFound {
38
+			return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
39
+		}
40
+		return types.ContainerJSON{}, nil, err
41
+	}
42
+	defer ensureReaderClosed(serverResp)
43
+
44
+	body, err := ioutil.ReadAll(serverResp.body)
45
+	if err != nil {
46
+		return types.ContainerJSON{}, nil, err
47
+	}
48
+
49
+	var response types.ContainerJSON
50
+	rdr := bytes.NewReader(body)
51
+	err = json.NewDecoder(rdr).Decode(&response)
52
+	return response, body, err
53
+}
54
+
55
+func (cli *Client) containerInspectWithResponse(containerID string, query url.Values) (types.ContainerJSON, *serverResponse, error) {
56
+	serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
57
+	if err != nil {
58
+		return types.ContainerJSON{}, serverResp, err
59
+	}
60
+
61
+	var response types.ContainerJSON
62
+	err = json.NewDecoder(serverResp.body).Decode(&response)
63
+	return response, serverResp, err
64
+}
... ...
@@ -25,6 +25,23 @@ func IsErrImageNotFound(err error) bool {
25 25
 	return ok
26 26
 }
27 27
 
28
+// containerNotFoundError implements an error returned when a container is not in the docker host.
29
+type containerNotFoundError struct {
30
+	containerID string
31
+}
32
+
33
+// Error returns a string representation of an containerNotFoundError
34
+func (e containerNotFoundError) Error() string {
35
+	return fmt.Sprintf("Container not found: %s", e.containerID)
36
+}
37
+
38
+// IsErrContainerNotFound returns true if the error is caused
39
+// when a container is not found in the docker host.
40
+func IsErrContainerNotFound(err error) bool {
41
+	_, ok := err.(containerNotFoundError)
42
+	return ok
43
+}
44
+
28 45
 // unauthorizedError represents an authorization error in a remote registry.
29 46
 type unauthorizedError struct {
30 47
 	cause error
31 48
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package lib
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"io/ioutil"
6
+	"net/http"
7
+	"net/url"
8
+
9
+	"github.com/docker/docker/api/types"
10
+)
11
+
12
+// ImageInspectWithRaw returns the image information and it's raw representation.
13
+func (cli *Client) ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error) {
14
+	query := url.Values{}
15
+	if getSize {
16
+		query.Set("size", "1")
17
+	}
18
+	serverResp, err := cli.get("/images/"+imageID+"/json", query, nil)
19
+	if err != nil {
20
+		if serverResp.statusCode == http.StatusNotFound {
21
+			return types.ImageInspect{}, nil, imageNotFoundError{imageID}
22
+		}
23
+		return types.ImageInspect{}, nil, err
24
+	}
25
+	defer ensureReaderClosed(serverResp)
26
+
27
+	body, err := ioutil.ReadAll(serverResp.body)
28
+	if err != nil {
29
+		return types.ImageInspect{}, nil, err
30
+	}
31
+
32
+	var response types.ImageInspect
33
+	rdr := bytes.NewReader(body)
34
+	err = json.NewDecoder(rdr).Decode(&response)
35
+	return response, body, err
36
+}