Browse code

Make docker build return exit code of build step

If a command during build fails, `docker build` now returns with
the exit code of that command.

This makes it necessary to change the build api endpoint to
return a json object stream.

Johannes 'fish' Ziemke authored on 2013/10/12 21:14:52
Showing 9 changed files
... ...
@@ -960,9 +960,17 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
960 960
 		return err
961 961
 	}
962 962
 
963
-	b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache, rm)
963
+	if version > 1.6 {
964
+		w.Header().Set("Content-Type", "application/json")
965
+	}
966
+	sf := utils.NewStreamFormatter(version > 1.6)
967
+	b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache, rm, sf)
964 968
 	id, err := b.Build(context)
965 969
 	if err != nil {
970
+		if sf.Used() {
971
+			w.Write(sf.FormatError(err))
972
+			return nil
973
+		}
966 974
 		return fmt.Errorf("Error build: %s", err)
967 975
 	}
968 976
 	if repoName != "" {
... ...
@@ -37,6 +37,7 @@ type buildFile struct {
37 37
 	tmpImages     map[string]struct{}
38 38
 
39 39
 	out io.Writer
40
+	sf  *utils.StreamFormatter
40 41
 }
41 42
 
42 43
 func (b *buildFile) clearTmp(containers map[string]struct{}) {
... ...
@@ -52,7 +53,7 @@ func (b *buildFile) CmdFrom(name string) error {
52 52
 	if err != nil {
53 53
 		if b.runtime.graph.IsNotExist(err) {
54 54
 			remote, tag := utils.ParseRepositoryTag(name)
55
-			if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil, nil, true); err != nil {
55
+			if err := b.srv.ImagePull(remote, tag, b.out, b.sf, nil, nil, true); err != nil {
56 56
 				return err
57 57
 			}
58 58
 			image, err = b.runtime.repositories.LookupImage(name)
... ...
@@ -100,7 +101,11 @@ func (b *buildFile) CmdRun(args string) error {
100 100
 		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
101 101
 			return err
102 102
 		} else if cache != nil {
103
-			fmt.Fprintf(b.out, " ---> Using cache\n")
103
+			if b.sf.Used() {
104
+				b.out.Write(b.sf.FormatStatus("", " ---> Using cache"))
105
+			} else {
106
+				fmt.Fprintf(b.out, " ---> Using cache\n")
107
+			}
104 108
 			utils.Debugf("[BUILDER] Use cached version")
105 109
 			b.image = cache.ID
106 110
 			return nil
... ...
@@ -376,8 +381,11 @@ func (b *buildFile) run() (string, error) {
376 376
 		return "", err
377 377
 	}
378 378
 	b.tmpContainers[c.ID] = struct{}{}
379
-	fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
380
-
379
+	if b.sf.Used() {
380
+		b.out.Write(b.sf.FormatStatus("", " ---> Running in %s", utils.TruncateID(c.ID)))
381
+	} else {
382
+		fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
383
+	}
381 384
 	// override the entry point that may have been picked up from the base image
382 385
 	c.Path = b.config.Cmd[0]
383 386
 	c.Args = b.config.Cmd[1:]
... ...
@@ -403,7 +411,11 @@ func (b *buildFile) run() (string, error) {
403 403
 
404 404
 	// Wait for it to finish
405 405
 	if ret := c.Wait(); ret != 0 {
406
-		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
406
+		err := &utils.JSONError{
407
+			Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
408
+			Code:    ret,
409
+		}
410
+		return "", err
407 411
 	}
408 412
 
409 413
 	return c.ID, nil
... ...
@@ -424,7 +436,11 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
424 424
 			if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
425 425
 				return err
426 426
 			} else if cache != nil {
427
-				fmt.Fprintf(b.out, " ---> Using cache\n")
427
+				if b.sf.Used() {
428
+					b.out.Write(b.sf.FormatStatus("", " ---> Using cache"))
429
+				} else {
430
+					fmt.Fprintf(b.out, " ---> Using cache\n")
431
+				}
428 432
 				utils.Debugf("[BUILDER] Use cached version")
429 433
 				b.image = cache.ID
430 434
 				return nil
... ...
@@ -441,7 +457,11 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
441 441
 			fmt.Fprintf(b.out, " ---> [Warning] %s\n", warning)
442 442
 		}
443 443
 		b.tmpContainers[container.ID] = struct{}{}
444
-		fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
444
+		if b.sf.Used() {
445
+			b.out.Write(b.sf.FormatStatus("", " ---> Running in %s", utils.TruncateID(container.ID)))
446
+		} else {
447
+			fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
448
+		}
445 449
 		id = container.ID
446 450
 		if err := container.EnsureMounted(); err != nil {
447 451
 			return err
... ...
@@ -507,22 +527,22 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
507 507
 
508 508
 		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
509 509
 		if !exists {
510
-			fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
510
+			b.out.Write(b.sf.FormatStatus("", "# Skipping unknown instruction %s", strings.ToUpper(instruction)))
511 511
 			continue
512 512
 		}
513 513
 
514 514
 		stepN += 1
515
-		fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
515
+		b.out.Write(b.sf.FormatStatus("", "Step %d : %s %s", stepN, strings.ToUpper(instruction), arguments))
516 516
 
517 517
 		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
518 518
 		if ret != nil {
519 519
 			return "", ret.(error)
520 520
 		}
521 521
 
522
-		fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
522
+		b.out.Write(b.sf.FormatStatus("", " ---> %s", utils.TruncateID(b.image)))
523 523
 	}
524 524
 	if b.image != "" {
525
-		fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
525
+		b.out.Write(b.sf.FormatStatus("", "Successfully built %s", utils.TruncateID(b.image)))
526 526
 		if b.rm {
527 527
 			b.clearTmp(b.tmpContainers)
528 528
 		}
... ...
@@ -531,7 +551,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
531 531
 	return "", fmt.Errorf("An error occurred during the build\n")
532 532
 }
533 533
 
534
-func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache, rm bool) BuildFile {
534
+func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache, rm bool, sf *utils.StreamFormatter) BuildFile {
535 535
 	return &buildFile{
536 536
 		runtime:       srv.runtime,
537 537
 		srv:           srv,
... ...
@@ -542,5 +562,6 @@ func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache, rm bool) Bu
542 542
 		verbose:       verbose,
543 543
 		utilizeCache:  utilizeCache,
544 544
 		rm:            rm,
545
+		sf:            sf,
545 546
 	}
546 547
 }
... ...
@@ -220,42 +220,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
220 220
 	if *rm {
221 221
 		v.Set("rm", "1")
222 222
 	}
223
-	req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
224
-	if err != nil {
225
-		return err
226
-	}
223
+
224
+	headers := http.Header(make(map[string][]string))
227 225
 	if context != nil {
228
-		req.Header.Set("Content-Type", "application/tar")
229
-	}
230
-	dial, err := net.Dial(cli.proto, cli.addr)
231
-	if err != nil {
232
-		return err
233
-	}
234
-	clientconn := httputil.NewClientConn(dial, nil)
235
-	resp, err := clientconn.Do(req)
236
-	defer clientconn.Close()
237
-	if err != nil {
238
-		return err
226
+		headers.Set("Content-Type", "application/tar")
239 227
 	}
240
-	defer resp.Body.Close()
241
-	// Check for errors
242
-	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
243
-		body, err := ioutil.ReadAll(resp.Body)
244
-		if err != nil {
245
-			return err
246
-		}
247
-		if len(body) == 0 {
248
-			return fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode))
249
-		}
250
-		return fmt.Errorf("Error: %s", body)
251
-	}
252
-
253
-	// Output the result
254
-	if _, err := io.Copy(cli.out, resp.Body); err != nil {
255
-		return err
228
+	err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers)
229
+	if jerr, ok := err.(*utils.JSONError); ok {
230
+		return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
256 231
 	}
257
-
258
-	return nil
232
+	return err
259 233
 }
260 234
 
261 235
 // 'docker login': login / register a user to registry service.
... ...
@@ -699,7 +673,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
699 699
 	}
700 700
 	fmt.Fprintf(cli.out, "]")
701 701
 	if status != 0 {
702
-		return &utils.StatusError{Status: status}
702
+		return &utils.StatusError{StatusCode: status}
703 703
 	}
704 704
 	return nil
705 705
 }
... ...
@@ -1584,7 +1558,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
1584 1584
 		return err
1585 1585
 	}
1586 1586
 	if status != 0 {
1587
-		return &utils.StatusError{Status: status}
1587
+		return &utils.StatusError{StatusCode: status}
1588 1588
 	}
1589 1589
 
1590 1590
 	return nil
... ...
@@ -2167,7 +2141,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2167 2167
 		}
2168 2168
 	}
2169 2169
 	if status != 0 {
2170
-		return &utils.StatusError{Status: status}
2170
+		return &utils.StatusError{StatusCode: status}
2171 2171
 	}
2172 2172
 	return nil
2173 2173
 }
... ...
@@ -100,7 +100,10 @@ func main() {
100 100
 		protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
101 101
 		if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
102 102
 			if sterr, ok := err.(*utils.StatusError); ok {
103
-				os.Exit(sterr.Status)
103
+				if sterr.Status != "" {
104
+					log.Println(sterr.Status)
105
+				}
106
+				os.Exit(sterr.StatusCode)
104 107
 			}
105 108
 			log.Fatal(err)
106 109
 		}
... ...
@@ -138,6 +138,11 @@ What's new
138 138
    This URI no longer exists.  The ``images -viz`` output is now generated in
139 139
    the client, using the ``/images/json`` data.
140 140
 
141
+.. http:post:: /build
142
+
143
+   **New!** This endpoint now returns build status as json stream. In case
144
+   of a build error, it returns the exit status of the failed command.
145
+
141 146
 v1.6
142 147
 ****
143 148
 
... ...
@@ -990,9 +990,11 @@ Build an image from Dockerfile via stdin
990 990
    .. sourcecode:: http
991 991
 
992 992
       HTTP/1.1 200 OK
993
+      Content-Type: application/json
993 994
 
994
-      {{ STREAM }}
995
-
995
+      {"status":"Step 1..."}
996
+      {"status":"..."}
997
+      {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}}
996 998
 
997 999
    The stream must be a tar archive compressed with one of the
998 1000
    following algorithms: identity (no compression), gzip, bzip2,
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"github.com/dotcloud/docker"
6 6
 	"github.com/dotcloud/docker/archive"
7 7
 	"github.com/dotcloud/docker/engine"
8
+	"github.com/dotcloud/docker/utils"
8 9
 	"io/ioutil"
9 10
 	"net"
10 11
 	"net/http"
... ...
@@ -226,11 +227,14 @@ func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
226 226
 
227 227
 func TestBuild(t *testing.T) {
228 228
 	for _, ctx := range testContexts {
229
-		buildImage(ctx, t, nil, true)
229
+		_, err := buildImage(ctx, t, nil, true)
230
+		if err != nil {
231
+			t.Fatal(err)
232
+		}
230 233
 	}
231 234
 }
232 235
 
233
-func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) *docker.Image {
236
+func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*docker.Image, error) {
234 237
 	if eng == nil {
235 238
 		eng = NewTestEngine(t)
236 239
 		runtime := mkRuntimeFromEngine(eng, t)
... ...
@@ -262,25 +266,24 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u
262 262
 	}
263 263
 	dockerfile := constructDockerfile(context.dockerfile, ip, port)
264 264
 
265
-	buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, useCache, false)
265
+	buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, useCache, false, utils.NewStreamFormatter(false))
266 266
 	id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
267 267
 	if err != nil {
268
-		t.Fatal(err)
268
+		return nil, err
269 269
 	}
270 270
 
271
-	img, err := srv.ImageInspect(id)
272
-	if err != nil {
273
-		t.Fatal(err)
274
-	}
275
-	return img
271
+	return srv.ImageInspect(id)
276 272
 }
277 273
 
278 274
 func TestVolume(t *testing.T) {
279
-	img := buildImage(testContextTemplate{`
275
+	img, err := buildImage(testContextTemplate{`
280 276
         from {IMAGE}
281 277
         volume /test
282 278
         cmd Hello world
283 279
     `, nil, nil}, t, nil, true)
280
+	if err != nil {
281
+		t.Fatal(err)
282
+	}
284 283
 
285 284
 	if len(img.Config.Volumes) == 0 {
286 285
 		t.Fail()
... ...
@@ -293,10 +296,13 @@ func TestVolume(t *testing.T) {
293 293
 }
294 294
 
295 295
 func TestBuildMaintainer(t *testing.T) {
296
-	img := buildImage(testContextTemplate{`
296
+	img, err := buildImage(testContextTemplate{`
297 297
         from {IMAGE}
298 298
         maintainer dockerio
299 299
     `, nil, nil}, t, nil, true)
300
+	if err != nil {
301
+		t.Fatal(err)
302
+	}
300 303
 
301 304
 	if img.Author != "dockerio" {
302 305
 		t.Fail()
... ...
@@ -304,10 +310,13 @@ func TestBuildMaintainer(t *testing.T) {
304 304
 }
305 305
 
306 306
 func TestBuildUser(t *testing.T) {
307
-	img := buildImage(testContextTemplate{`
307
+	img, err := buildImage(testContextTemplate{`
308 308
         from {IMAGE}
309 309
         user dockerio
310 310
     `, nil, nil}, t, nil, true)
311
+	if err != nil {
312
+		t.Fatal(err)
313
+	}
311 314
 
312 315
 	if img.Config.User != "dockerio" {
313 316
 		t.Fail()
... ...
@@ -315,11 +324,15 @@ func TestBuildUser(t *testing.T) {
315 315
 }
316 316
 
317 317
 func TestBuildEnv(t *testing.T) {
318
-	img := buildImage(testContextTemplate{`
318
+	img, err := buildImage(testContextTemplate{`
319 319
         from {IMAGE}
320 320
         env port 4243
321 321
         `,
322 322
 		nil, nil}, t, nil, true)
323
+	if err != nil {
324
+		t.Fatal(err)
325
+	}
326
+
323 327
 	hasEnv := false
324 328
 	for _, envVar := range img.Config.Env {
325 329
 		if envVar == "port=4243" {
... ...
@@ -333,11 +346,14 @@ func TestBuildEnv(t *testing.T) {
333 333
 }
334 334
 
335 335
 func TestBuildCmd(t *testing.T) {
336
-	img := buildImage(testContextTemplate{`
336
+	img, err := buildImage(testContextTemplate{`
337 337
         from {IMAGE}
338 338
         cmd ["/bin/echo", "Hello World"]
339 339
         `,
340 340
 		nil, nil}, t, nil, true)
341
+	if err != nil {
342
+		t.Fatal(err)
343
+	}
341 344
 
342 345
 	if img.Config.Cmd[0] != "/bin/echo" {
343 346
 		t.Log(img.Config.Cmd[0])
... ...
@@ -350,11 +366,14 @@ func TestBuildCmd(t *testing.T) {
350 350
 }
351 351
 
352 352
 func TestBuildExpose(t *testing.T) {
353
-	img := buildImage(testContextTemplate{`
353
+	img, err := buildImage(testContextTemplate{`
354 354
         from {IMAGE}
355 355
         expose 4243
356 356
         `,
357 357
 		nil, nil}, t, nil, true)
358
+	if err != nil {
359
+		t.Fatal(err)
360
+	}
358 361
 
359 362
 	if img.Config.PortSpecs[0] != "4243" {
360 363
 		t.Fail()
... ...
@@ -362,11 +381,14 @@ func TestBuildExpose(t *testing.T) {
362 362
 }
363 363
 
364 364
 func TestBuildEntrypoint(t *testing.T) {
365
-	img := buildImage(testContextTemplate{`
365
+	img, err := buildImage(testContextTemplate{`
366 366
         from {IMAGE}
367 367
         entrypoint ["/bin/echo"]
368 368
         `,
369 369
 		nil, nil}, t, nil, true)
370
+	if err != nil {
371
+		t.Fatal(err)
372
+	}
370 373
 
371 374
 	if img.Config.Entrypoint[0] != "/bin/echo" {
372 375
 	}
... ...
@@ -378,19 +400,25 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
378 378
 	eng := NewTestEngine(t)
379 379
 	defer nuke(mkRuntimeFromEngine(eng, t))
380 380
 
381
-	img := buildImage(testContextTemplate{`
381
+	img, err := buildImage(testContextTemplate{`
382 382
         from {IMAGE}
383 383
         run echo "hello"
384 384
         `,
385 385
 		nil, nil}, t, eng, true)
386
+	if err != nil {
387
+		t.Fatal(err)
388
+	}
386 389
 
387
-	img = buildImage(testContextTemplate{`
390
+	img, err = buildImage(testContextTemplate{`
388 391
         from {IMAGE}
389 392
         run echo "hello"
390 393
         add foo /foo
391 394
         entrypoint ["/bin/echo"]
392 395
         `,
393 396
 		[][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
397
+	if err != nil {
398
+		t.Fatal(err)
399
+	}
394 400
 
395 401
 	if len(img.Config.Cmd) != 0 {
396 402
 		t.Fail()
... ...
@@ -407,11 +435,18 @@ func TestBuildImageWithCache(t *testing.T) {
407 407
         `,
408 408
 		nil, nil}
409 409
 
410
-	img := buildImage(template, t, eng, true)
410
+	img, err := buildImage(template, t, eng, true)
411
+	if err != nil {
412
+		t.Fatal(err)
413
+	}
414
+
411 415
 	imageId := img.ID
412 416
 
413 417
 	img = nil
414
-	img = buildImage(template, t, eng, true)
418
+	img, err = buildImage(template, t, eng, true)
419
+	if err != nil {
420
+		t.Fatal(err)
421
+	}
415 422
 
416 423
 	if imageId != img.ID {
417 424
 		t.Logf("Image ids should match: %s != %s", imageId, img.ID)
... ...
@@ -429,11 +464,17 @@ func TestBuildImageWithoutCache(t *testing.T) {
429 429
         `,
430 430
 		nil, nil}
431 431
 
432
-	img := buildImage(template, t, eng, true)
432
+	img, err := buildImage(template, t, eng, true)
433
+	if err != nil {
434
+		t.Fatal(err)
435
+	}
433 436
 	imageId := img.ID
434 437
 
435 438
 	img = nil
436
-	img = buildImage(template, t, eng, false)
439
+	img, err = buildImage(template, t, eng, false)
440
+	if err != nil {
441
+		t.Fatal(err)
442
+	}
437 443
 
438 444
 	if imageId == img.ID {
439 445
 		t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
... ...
@@ -475,7 +516,7 @@ func TestForbiddenContextPath(t *testing.T) {
475 475
 	}
476 476
 	dockerfile := constructDockerfile(context.dockerfile, ip, port)
477 477
 
478
-	buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, true, false)
478
+	buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, true, false, utils.NewStreamFormatter(false))
479 479
 	_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
480 480
 
481 481
 	if err == nil {
... ...
@@ -521,7 +562,7 @@ func TestBuildADDFileNotFound(t *testing.T) {
521 521
 	}
522 522
 	dockerfile := constructDockerfile(context.dockerfile, ip, port)
523 523
 
524
-	buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, false, true, false)
524
+	buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, false, true, false, utils.NewStreamFormatter(false))
525 525
 	_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
526 526
 
527 527
 	if err == nil {
... ...
@@ -539,18 +580,26 @@ func TestBuildInheritance(t *testing.T) {
539 539
 	eng := NewTestEngine(t)
540 540
 	defer nuke(mkRuntimeFromEngine(eng, t))
541 541
 
542
-	img := buildImage(testContextTemplate{`
542
+	img, err := buildImage(testContextTemplate{`
543 543
             from {IMAGE}
544 544
             expose 4243
545 545
             `,
546 546
 		nil, nil}, t, eng, true)
547 547
 
548
-	img2 := buildImage(testContextTemplate{fmt.Sprintf(`
548
+	if err != nil {
549
+		t.Fatal(err)
550
+	}
551
+
552
+	img2, _ := buildImage(testContextTemplate{fmt.Sprintf(`
549 553
             from %s
550 554
             entrypoint ["/bin/echo"]
551 555
             `, img.ID),
552 556
 		nil, nil}, t, eng, true)
553 557
 
558
+	if err != nil {
559
+		t.Fatal(err)
560
+	}
561
+
554 562
 	// from child
555 563
 	if img2.Config.Entrypoint[0] != "/bin/echo" {
556 564
 		t.Fail()
... ...
@@ -561,3 +610,23 @@ func TestBuildInheritance(t *testing.T) {
561 561
 		t.Fail()
562 562
 	}
563 563
 }
564
+
565
+func TestBuildFails(t *testing.T) {
566
+	_, err := buildImage(testContextTemplate{`
567
+        from {IMAGE}
568
+        run sh -c "exit 23"
569
+        `,
570
+		nil, nil}, t, nil, true)
571
+
572
+	if err == nil {
573
+		t.Fatal("Error should not be nil")
574
+	}
575
+
576
+	sterr, ok := err.(*utils.JSONError)
577
+	if !ok {
578
+		t.Fatalf("Error should be utils.JSONError")
579
+	}
580
+	if sterr.Code != 23 {
581
+		t.Fatalf("StatusCode %d unexpected, should be 23", sterr.Code)
582
+	}
583
+}
... ...
@@ -905,9 +905,12 @@ run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
905 905
 		nil,
906 906
 		nil,
907 907
 	}
908
-	image := buildImage(testBuilder, t, eng, true)
908
+	image, err := buildImage(testBuilder, t, eng, true)
909
+	if err != nil {
910
+		t.Fatal(err)
911
+	}
909 912
 
910
-	err := mkServerFromEngine(eng, t).ContainerTag(image.ID, "test", "latest", false)
913
+	err = mkServerFromEngine(eng, t).ContainerTag(image.ID, "test", "latest", false)
911 914
 	if err != nil {
912 915
 		t.Fatal(err)
913 916
 	}
... ...
@@ -1184,11 +1184,12 @@ func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) {
1184 1184
 
1185 1185
 // An StatusError reports an unsuccessful exit by a command.
1186 1186
 type StatusError struct {
1187
-	Status int
1187
+	Status     string
1188
+	StatusCode int
1188 1189
 }
1189 1190
 
1190 1191
 func (e *StatusError) Error() string {
1191
-	return fmt.Sprintf("Status: %d", e.Status)
1192
+	return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
1192 1193
 }
1193 1194
 
1194 1195
 func quote(word string, buf *bytes.Buffer) {