Browse code

Fail when there is an error executing an inspect template.

- Do not execute the template directly in the cli outout, go is not atomic
in this operation and can send bytes before failing the execution.
- Fail after evaluating a raw interface if the typed execution also
failed, assuming there is a template parsing error.

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

David Calavera authored on 2015/10/23 07:42:19
Showing 2 changed files
... ...
@@ -59,7 +59,6 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
59 59
 	}
60 60
 
61 61
 	for _, name := range cmd.Args() {
62
-
63 62
 		if *inspectType == "" || *inspectType == "container" {
64 63
 			obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
65 64
 			if err != nil && *inspectType == "container" {
... ...
@@ -101,42 +100,45 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
101 101
 		} else {
102 102
 			rdr := bytes.NewReader(obj)
103 103
 			dec := json.NewDecoder(rdr)
104
+			buf := bytes.NewBufferString("")
104 105
 
105 106
 			if isImage {
106 107
 				inspPtr := types.ImageInspect{}
107 108
 				if err := dec.Decode(&inspPtr); err != nil {
108
-					fmt.Fprintf(cli.err, "%s\n", err)
109
+					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
109 110
 					status = 1
110
-					continue
111
+					break
111 112
 				}
112
-				if err := tmpl.Execute(cli.out, inspPtr); err != nil {
113
+				if err := tmpl.Execute(buf, inspPtr); err != nil {
113 114
 					rdr.Seek(0, 0)
114
-					var raw interface{}
115
-					if err := dec.Decode(&raw); err != nil {
116
-						return err
117
-					}
118
-					if err = tmpl.Execute(cli.out, raw); err != nil {
119
-						return err
115
+					var ok bool
116
+
117
+					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
118
+						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
119
+						status = 1
120
+						break
120 121
 					}
121 122
 				}
122 123
 			} else {
123 124
 				inspPtr := types.ContainerJSON{}
124 125
 				if err := dec.Decode(&inspPtr); err != nil {
125
-					fmt.Fprintf(cli.err, "%s\n", err)
126
+					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
126 127
 					status = 1
127
-					continue
128
+					break
128 129
 				}
129
-				if err := tmpl.Execute(cli.out, inspPtr); err != nil {
130
+				if err := tmpl.Execute(buf, inspPtr); err != nil {
130 131
 					rdr.Seek(0, 0)
131
-					var raw interface{}
132
-					if err := dec.Decode(&raw); err != nil {
133
-						return err
134
-					}
135
-					if err = tmpl.Execute(cli.out, raw); err != nil {
136
-						return err
132
+					var ok bool
133
+
134
+					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
135
+						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
136
+						status = 1
137
+						break
137 138
 					}
138 139
 				}
139 140
 			}
141
+
142
+			cli.out.Write(buf.Bytes())
140 143
 			cli.out.Write([]byte{'\n'})
141 144
 		}
142 145
 		indented.WriteString(",")
... ...
@@ -162,3 +164,33 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
162 162
 	}
163 163
 	return nil
164 164
 }
165
+
166
+// decodeRawInspect executes the inspect template with a raw interface.
167
+// This allows docker cli to parse inspect structs injected with Swarm fields.
168
+// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
169
+// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
170
+// We assume that the template is invalid when there is a <no value>, if the template was valid
171
+// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
172
+// template with the typed input.
173
+//
174
+// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
175
+// we build Docker with that version:
176
+// https://golang.org/pkg/text/template/#Template.Option
177
+func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
178
+	var raw interface{}
179
+	buf := bytes.NewBufferString("")
180
+
181
+	if rawErr := dec.Decode(&raw); rawErr != nil {
182
+		fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
183
+		return buf, false
184
+	}
185
+
186
+	if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
187
+		return buf, false
188
+	}
189
+
190
+	if strings.Contains(buf.String(), "<no value>") {
191
+		return buf, false
192
+	}
193
+	return buf, true
194
+}
... ...
@@ -82,7 +82,7 @@ func (s *DockerSuite) TestInspectTypeFlagContainer(c *check.C) {
82 82
 
83 83
 	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
84 84
 
85
-	formatStr := fmt.Sprintf("--format='{{.State.Running}}'")
85
+	formatStr := "--format='{{.State.Running}}'"
86 86
 	out, _ := dockerCmd(c, "inspect", "--type=container", formatStr, "busybox")
87 87
 	c.Assert(out, checker.Equals, "true\n") // not a container JSON
88 88
 }
... ...
@@ -290,19 +290,15 @@ func (s *DockerSuite) TestInspectNoSizeFlagContainer(c *check.C) {
290 290
 
291 291
 	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
292 292
 
293
-	formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'")
293
+	formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'"
294 294
 	out, _ := dockerCmd(c, "inspect", "--type=container", formatStr, "busybox")
295 295
 	c.Assert(strings.TrimSpace(out), check.Equals, "<nil>,<nil>", check.Commentf("Exepcted not to display size info: %s", out))
296 296
 }
297 297
 
298 298
 func (s *DockerSuite) TestInspectSizeFlagContainer(c *check.C) {
299
-
300
-	//Both the container and image are named busybox. docker inspect will fetch container
301
-	//JSON SizeRw and SizeRootFs field. If there is a flag --size/-s, the fields are not <no value>.
302
-
303 299
 	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
304 300
 
305
-	formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'")
301
+	formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'"
306 302
 	out, _ := dockerCmd(c, "inspect", "-s", "--type=container", formatStr, "busybox")
307 303
 	sz := strings.Split(out, ",")
308 304
 
... ...
@@ -311,14 +307,25 @@ func (s *DockerSuite) TestInspectSizeFlagContainer(c *check.C) {
311 311
 }
312 312
 
313 313
 func (s *DockerSuite) TestInspectSizeFlagImage(c *check.C) {
314
+	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
314 315
 
315
-	//Both the container and image are named busybox. docker inspect will fetch image
316
-	//JSON SizeRw and SizeRootFs field. There are no these fields since they are only in containers.
316
+	formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'"
317
+	out, _, err := dockerCmdWithError("inspect", "-s", "--type=image", formatStr, "busybox")
318
+
319
+	// Template error rather than <no value>
320
+	// This is a more correct behavior because images don't have sizes associated.
321
+	c.Assert(err, check.Not(check.IsNil))
322
+	c.Assert(out, checker.Contains, "Template parsing error")
323
+}
324
+
325
+func (s *DockerSuite) TestInspectTempateError(c *check.C) {
326
+	//Both the container and image are named busybox. docker inspect will fetch container
327
+	//JSON State.Running field. If the field is true, it's a container.
317 328
 
318 329
 	dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top")
319 330
 
320
-	formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'")
321
-	out, _ := dockerCmd(c, "inspect", "-s", "--type=image", formatStr, "busybox")
331
+	out, _, err := dockerCmdWithError("inspect", "--type=container", "--format='Format container: {{.ThisDoesNotExist}}'", "busybox")
322 332
 
323
-	c.Assert(strings.TrimSpace(out), check.Equals, "<no value>,<no value>", check.Commentf("Fields SizeRw and SizeRootFs are not exepcted to exist"))
333
+	c.Assert(err, check.Not(check.IsNil))
334
+	c.Assert(out, checker.Contains, "Template parsing error")
324 335
 }