Browse code

Merge pull request #719 from dotcloud/json_stream-feature

* API: push, pull, import, insert -> Json Stream

Guillaume J. Charmes authored on 2013/06/01 08:05:15
Showing 8 changed files
... ...
@@ -314,16 +314,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
314 314
 	tag := r.Form.Get("tag")
315 315
 	repo := r.Form.Get("repo")
316 316
 
317
+	if version > 1.0 {
318
+		w.Header().Set("Content-Type", "application/json")
319
+	}
320
+	sf := utils.NewStreamFormatter(version > 1.0)
317 321
 	if image != "" { //pull
318 322
 		registry := r.Form.Get("registry")
319
-		if version > 1.0 {
320
-			w.Header().Set("Content-Type", "application/json")
321
-		}
322
-		if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
323
+		if err := srv.ImagePull(image, tag, registry, w, sf); err != nil {
324
+			if sf.Used() {
325
+				w.Write(sf.FormatError(err))
326
+				return nil
327
+			}
323 328
 			return err
324 329
 		}
325 330
 	} else { //import
326
-		if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
331
+		if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
332
+			if sf.Used() {
333
+				w.Write(sf.FormatError(err))
334
+				return nil
335
+			}
327 336
 			return err
328 337
 		}
329 338
 	}
... ...
@@ -359,10 +368,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
359 359
 		return fmt.Errorf("Missing parameter")
360 360
 	}
361 361
 	name := vars["name"]
362
-
363
-	imgId, err := srv.ImageInsert(name, url, path, w)
362
+	if version > 1.0 {
363
+		w.Header().Set("Content-Type", "application/json")
364
+	}
365
+	sf := utils.NewStreamFormatter(version > 1.0)
366
+	imgId, err := srv.ImageInsert(name, url, path, w, sf)
364 367
 	if err != nil {
365
-		return err
368
+		if sf.Used() {
369
+			w.Write(sf.FormatError(err))
370
+			return nil
371
+		}
366 372
 	}
367 373
 	b, err := json.Marshal(&ApiId{Id: imgId})
368 374
 	if err != nil {
... ...
@@ -382,8 +397,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
382 382
 		return fmt.Errorf("Missing parameter")
383 383
 	}
384 384
 	name := vars["name"]
385
-
386
-	if err := srv.ImagePush(name, registry, w); err != nil {
385
+	if version > 1.0 {
386
+		w.Header().Set("Content-Type", "application/json")
387
+	}
388
+	sf := utils.NewStreamFormatter(version > 1.0)
389
+	if err := srv.ImagePush(name, registry, w, sf); err != nil {
390
+		if sf.Used() {
391
+			w.Write(sf.FormatError(err))
392
+			return nil
393
+		}
387 394
 		return err
388 395
 	}
389 396
 	return nil
... ...
@@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
61 61
 				remote = name
62 62
 			}
63 63
 
64
-			if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
64
+			if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false)); err != nil {
65 65
 				return err
66 66
 			}
67 67
 
... ...
@@ -180,7 +180,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
180 180
 			return err
181 181
 		} else {
182 182
 			// FIXME: Find a way to have a progressbar for the upload too
183
-			io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
183
+			sf := utils.NewStreamFormatter(false)
184
+			io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
184 185
 		}
185 186
 		multipartBody = io.MultiReader(multipartBody, boundary)
186 187
 	}
... ...
@@ -1367,13 +1368,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1367 1367
 	}
1368 1368
 
1369 1369
 	if resp.Header.Get("Content-Type") == "application/json" {
1370
-		type Message struct {
1371
-			Status   string `json:"status,omitempty"`
1372
-			Progress string `json:"progress,omitempty"`
1373
-		}
1374 1370
 		dec := json.NewDecoder(resp.Body)
1375 1371
 		for {
1376
-			var m Message
1372
+			var m utils.JsonMessage
1377 1373
 			if err := dec.Decode(&m); err == io.EOF {
1378 1374
 				break
1379 1375
 			} else if err != nil {
... ...
@@ -1381,6 +1378,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1381 1381
 			}
1382 1382
 			if m.Progress != "" {
1383 1383
 				fmt.Fprintf(out, "Downloading %s\r", m.Progress)
1384
+			} else if m.Error != "" {
1385
+				return fmt.Errorf(m.Error)
1384 1386
 			} else {
1385 1387
 				fmt.Fprintf(out, "%s\n", m.Status)
1386 1388
 			}
... ...
@@ -15,10 +15,17 @@ Docker Remote API
15 15
 - Default port in the docker deamon is 4243 
16 16
 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
17 17
 
18
-2. Endpoints
18
+2. Version
19
+==========
20
+
21
+The current verson of the API is 1.1
22
+Calling /images/<name>/insert is the same as calling /v1.1/images/<name>/insert
23
+You can still call an old version of the api using /v1.0/images/<name>/insert
24
+
25
+3. Endpoints
19 26
 ============
20 27
 
21
-2.1 Containers
28
+3.1 Containers
22 29
 --------------
23 30
 
24 31
 List containers
... ...
@@ -460,7 +467,7 @@ Remove a container
460 460
         :statuscode 500: server error
461 461
 
462 462
 
463
-2.2 Images
463
+3.2 Images
464 464
 ----------
465 465
 
466 466
 List Images
... ...
@@ -549,7 +556,19 @@ Create an image
549 549
 
550 550
            POST /images/create?fromImage=base HTTP/1.1
551 551
 
552
-        **Example response**:
552
+        **Example response v1.1**:
553
+
554
+        .. sourcecode:: http
555
+
556
+           HTTP/1.1 200 OK
557
+	   Content-Type: application/json
558
+
559
+	   {"status":"Pulling..."}
560
+	   {"progress":"1/? (n/a)"}
561
+	   {"error":"Invalid..."}
562
+	   ...
563
+
564
+        **Example response v1.0**:
553 565
 
554 566
         .. sourcecode:: http
555 567
 
... ...
@@ -580,7 +599,19 @@ Insert a file in a image
580 580
 
581 581
            POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
582 582
 
583
-	**Example response**:
583
+	**Example response v1.1**:
584
+
585
+        .. sourcecode:: http
586
+
587
+           HTTP/1.1 200 OK
588
+	   Content-Type: application/json
589
+
590
+	   {"status":"Inserting..."}
591
+	   {"progress":"1/? (n/a)"}
592
+	   {"error":"Invalid..."}
593
+	   ...
594
+
595
+	**Example response v1.0**:
584 596
 
585 597
         .. sourcecode:: http
586 598
 
... ...
@@ -695,7 +726,19 @@ Push an image on the registry
695 695
 
696 696
 	    POST /images/test/push HTTP/1.1
697 697
 
698
-	 **Example response**:
698
+	 **Example response v1.1**:
699
+
700
+        .. sourcecode:: http
701
+
702
+           HTTP/1.1 200 OK
703
+	   Content-Type: application/json
704
+
705
+	   {"status":"Pushing..."}
706
+	   {"progress":"1/? (n/a)"}
707
+	   {"error":"Invalid..."}
708
+	   ...
709
+
710
+	 **Example response v1.0**:
699 711
 
700 712
         .. sourcecode:: http
701 713
 
... ...
@@ -801,7 +844,7 @@ Search images
801 801
 	   :statuscode 500: server error
802 802
 
803 803
 
804
-2.3 Misc
804
+3.3 Misc
805 805
 --------
806 806
 
807 807
 Build an image from Dockerfile via stdin
... ...
@@ -165,7 +165,8 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
165 165
 	if err != nil {
166 166
 		return nil, err
167 167
 	}
168
-	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
168
+	sf := utils.NewStreamFormatter(false)
169
+	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
169 170
 }
170 171
 
171 172
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
... ...
@@ -68,7 +68,7 @@ func init() {
68 68
 		runtime: runtime,
69 69
 	}
70 70
 	// Retrieve the Image
71
-	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
71
+	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false)); err != nil {
72 72
 		panic(err)
73 73
 	}
74 74
 }
... ...
@@ -68,7 +68,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
68 68
 	return outs, nil
69 69
 }
70 70
 
71
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
71
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
72 72
 	out = utils.NewWriteFlusher(out)
73 73
 	img, err := srv.runtime.repositories.LookupImage(name)
74 74
 	if err != nil {
... ...
@@ -92,7 +92,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
92 92
 		return "", err
93 93
 	}
94 94
 
95
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
95
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
96 96
 		return "", err
97 97
 	}
98 98
 	// FIXME: Handle custom repo, tag comment, author
... ...
@@ -100,7 +100,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
100 100
 	if err != nil {
101 101
 		return "", err
102 102
 	}
103
-	fmt.Fprintf(out, "%s\n", img.Id)
103
+	out.Write(sf.FormatStatus(img.Id))
104 104
 	return img.ShortId(), nil
105 105
 }
106 106
 
... ...
@@ -292,7 +292,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
292 292
 	return nil
293 293
 }
294 294
 
295
-func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, json bool) error {
295
+func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
296 296
 	history, err := r.GetRemoteHistory(imgId, endpoint, token)
297 297
 	if err != nil {
298 298
 		return err
... ...
@@ -302,7 +302,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
302 302
 	// FIXME: Launch the getRemoteImage() in goroutines
303 303
 	for _, id := range history {
304 304
 		if !srv.runtime.graph.Exists(id) {
305
-			fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
305
+			out.Write(sf.FormatStatus("Pulling %s metadata", id))
306 306
 			imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
307 307
 			if err != nil {
308 308
 				// FIXME: Keep goging in case of error?
... ...
@@ -314,12 +314,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
314 314
 			}
315 315
 
316 316
 			// Get the layer
317
-			fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
317
+			out.Write(sf.FormatStatus("Pulling %s fs layer", id))
318 318
 			layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
319 319
 			if err != nil {
320 320
 				return err
321 321
 			}
322
-			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
322
+			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
323 323
 				return err
324 324
 			}
325 325
 		}
... ...
@@ -327,8 +327,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
327 327
 	return nil
328 328
 }
329 329
 
330
-func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
331
-	fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
330
+func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
331
+	out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
332 332
 	repoData, err := r.GetRepositoryData(remote)
333 333
 	if err != nil {
334 334
 		return err
... ...
@@ -365,11 +365,11 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
365 365
 			utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
366 366
 			continue
367 367
 		}
368
-		fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
368
+		out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.Id, img.Tag, remote))
369 369
 		success := false
370 370
 		for _, ep := range repoData.Endpoints {
371
-			if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
372
-				fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
371
+			if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
372
+				out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
373 373
 				continue
374 374
 			}
375 375
 			success = true
... ...
@@ -394,17 +394,17 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
394 394
 	return nil
395 395
 }
396 396
 
397
-func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, json bool) error {
397
+func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
398 398
 	r := registry.NewRegistry(srv.runtime.root)
399 399
 	out = utils.NewWriteFlusher(out)
400 400
 	if endpoint != "" {
401
-		if err := srv.pullImage(r, out, name, endpoint, nil, json); err != nil {
401
+		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
402 402
 			return err
403 403
 		}
404 404
 		return nil
405 405
 	}
406 406
 
407
-	if err := srv.pullRepository(r, out, name, tag, json); err != nil {
407
+	if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
408 408
 		return err
409 409
 	}
410 410
 
... ...
@@ -477,14 +477,14 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
477 477
 	return imgList, nil
478 478
 }
479 479
 
480
-func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string) error {
480
+func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
481 481
 	out = utils.NewWriteFlusher(out)
482
-	fmt.Fprintf(out, "Processing checksums\n")
482
+	out.Write(sf.FormatStatus("Processing checksums"))
483 483
 	imgList, err := srv.getImageList(localRepo)
484 484
 	if err != nil {
485 485
 		return err
486 486
 	}
487
-	fmt.Fprintf(out, "Sending images list\n")
487
+	out.Write(sf.FormatStatus("Sending image list"))
488 488
 
489 489
 	repoData, err := r.PushImageJsonIndex(name, imgList, false)
490 490
 	if err != nil {
... ...
@@ -492,18 +492,18 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
492 492
 	}
493 493
 
494 494
 	for _, ep := range repoData.Endpoints {
495
-		fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
495
+		out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
496 496
 		// For each image within the repo, push them
497 497
 		for _, elem := range imgList {
498 498
 			if _, exists := repoData.ImgList[elem.Id]; exists {
499
-				fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
499
+				out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
500 500
 				continue
501 501
 			}
502
-			if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens); err != nil {
502
+			if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens, sf); err != nil {
503 503
 				// FIXME: Continue on error?
504 504
 				return err
505 505
 			}
506
-			fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
506
+			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.Id, ep+"/users/"+name+"/"+elem.Tag))
507 507
 			if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
508 508
 				return err
509 509
 			}
... ...
@@ -516,13 +516,13 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
516 516
 	return nil
517 517
 }
518 518
 
519
-func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string) error {
519
+func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
520 520
 	out = utils.NewWriteFlusher(out)
521 521
 	jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
522 522
 	if err != nil {
523 523
 		return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
524 524
 	}
525
-	fmt.Fprintf(out, "Pushing %s\r\n", imgId)
525
+	out.Write(sf.FormatStatus("Pushing %s", imgId))
526 526
 
527 527
 	// Make sure we have the image's checksum
528 528
 	checksum, err := srv.getChecksum(imgId)
... ...
@@ -537,7 +537,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
537 537
 	// Send the json
538 538
 	if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
539 539
 		if err == registry.ErrAlreadyExists {
540
-			fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
540
+			out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.Id))
541 541
 			return nil
542 542
 		}
543 543
 		return err
... ...
@@ -570,22 +570,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
570 570
 	}
571 571
 
572 572
 	// Send the layer
573
-	if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
573
+	if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "%v/%v (%v)"), sf), ep, token); err != nil {
574 574
 		return err
575 575
 	}
576 576
 	return nil
577 577
 }
578 578
 
579
-func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
579
+func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
580 580
 	out = utils.NewWriteFlusher(out)
581 581
 	img, err := srv.runtime.graph.Get(name)
582 582
 	r := registry.NewRegistry(srv.runtime.root)
583 583
 
584 584
 	if err != nil {
585
-		fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
585
+		out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
586 586
 		// If it fails, try to get the repository
587 587
 		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
588
-			if err := srv.pushRepository(r, out, name, localRepo); err != nil {
588
+			if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
589 589
 				return err
590 590
 			}
591 591
 			return nil
... ...
@@ -593,14 +593,14 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
593 593
 
594 594
 		return err
595 595
 	}
596
-	fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
597
-	if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
596
+	out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
597
+	if err := srv.pushImage(r, out, name, img.Id, endpoint, nil, sf); err != nil {
598 598
 		return err
599 599
 	}
600 600
 	return nil
601 601
 }
602 602
 
603
-func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
603
+func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
604 604
 	var archive io.Reader
605 605
 	var resp *http.Response
606 606
 
... ...
@@ -609,21 +609,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
609 609
 	} else {
610 610
 		u, err := url.Parse(src)
611 611
 		if err != nil {
612
-			fmt.Fprintf(out, "Error: %s\n", err)
612
+			return err
613 613
 		}
614 614
 		if u.Scheme == "" {
615 615
 			u.Scheme = "http"
616 616
 			u.Host = src
617 617
 			u.Path = ""
618 618
 		}
619
-		fmt.Fprintf(out, "Downloading from %s\n", u)
619
+		out.Write(sf.FormatStatus("Downloading from %s", u))
620 620
 		// Download with curl (pretty progress bar)
621 621
 		// If curl is not available, fallback to http.Get()
622 622
 		resp, err = utils.Download(u.String(), out)
623 623
 		if err != nil {
624 624
 			return err
625 625
 		}
626
-		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
626
+		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
627 627
 	}
628 628
 	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
629 629
 	if err != nil {
... ...
@@ -635,7 +635,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
635 635
 			return err
636 636
 		}
637 637
 	}
638
-	fmt.Fprintf(out, "%s\n", img.ShortId())
638
+	out.Write(sf.FormatStatus(img.ShortId()))
639 639
 	return nil
640 640
 }
641 641
 
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"bytes"
5 5
 	"crypto/sha256"
6 6
 	"encoding/hex"
7
+	"encoding/json"
7 8
 	"errors"
8 9
 	"fmt"
9 10
 	"index/suffixarray"
... ...
@@ -69,7 +70,7 @@ type progressReader struct {
69 69
 	readProgress int           // How much has been read so far (bytes)
70 70
 	lastUpdate   int           // How many bytes read at least update
71 71
 	template     string        // Template to print. Default "%v/%v (%v)"
72
-	json         bool
72
+	sf *StreamFormatter
73 73
 }
74 74
 
75 75
 func (r *progressReader) Read(p []byte) (n int, err error) {
... ...
@@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
93 93
 	}
94 94
 	// Send newline when complete
95 95
 	if err != nil {
96
-		fmt.Fprintf(r.output, FormatStatus("", r.json))
96
+		r.output.Write(r.sf.FormatStatus(""))
97 97
 	}
98 98
 
99 99
 	return read, err
... ...
@@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
101 101
 func (r *progressReader) Close() error {
102 102
 	return io.ReadCloser(r.reader).Close()
103 103
 }
104
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
105
-	if template == "" {
106
-		template = "%v/%v (%v)\r"
104
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
105
+      	tpl := string(template)
106
+	if tpl == "" {
107
+		tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
107 108
 	}
108
-	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
109
+	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
109 110
 }
110 111
 
111 112
 // HumanDuration returns a human-readable approximation of a duration
... ...
@@ -564,16 +566,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
564 564
 	return &WriteFlusher{w: w, flusher: flusher}
565 565
 }
566 566
 
567
-func FormatStatus(str string, json bool) string {
568
-	if json {
569
-		return "{\"status\" : \"" + str + "\"}"
567
+type JsonMessage struct {
568
+	Status   string `json:"status,omitempty"`
569
+	Progress string `json:"progress,omitempty"`
570
+	Error    string `json:"error,omitempty"`
571
+}
572
+
573
+type StreamFormatter struct {
574
+	json bool
575
+	used bool
576
+}
577
+
578
+func NewStreamFormatter(json bool) *StreamFormatter {
579
+	return &StreamFormatter{json, false}
580
+}
581
+
582
+func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
583
+	sf.used = true
584
+	str := fmt.Sprintf(format, a...)
585
+	if sf.json {
586
+		b, err := json.Marshal(&JsonMessage{Status:str});
587
+		if err != nil {
588
+			return sf.FormatError(err)
589
+		}
590
+		return b
570 591
 	}
571
-	return str + "\r\n"
592
+	return []byte(str + "\r\n")
572 593
 }
573 594
 
574
-func FormatProgress(str string, json bool) string {
575
-	if json {
576
-		return "{\"progress\" : \"" + str + "\"}"
595
+func (sf *StreamFormatter) FormatError(err error) []byte {
596
+	sf.used = true
597
+	if sf.json {
598
+		if b, err := json.Marshal(&JsonMessage{Error:err.Error()}); err == nil {
599
+			return b
600
+		}
601
+		return []byte("{\"error\":\"format error\"}")
577 602
 	}
578
-	return "Downloading " + str + "\r"
603
+	return []byte("Error: " + err.Error() + "\r\n")
604
+}
605
+
606
+func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
607
+	sf.used = true
608
+	if sf.json {
609
+		b, err := json.Marshal(&JsonMessage{Progress:str})
610
+		if err != nil {
611
+                        return nil
612
+                }
613
+		return b
614
+	}
615
+	return []byte(action + " " + str + "\r")
616
+}
617
+
618
+func (sf *StreamFormatter) Used() bool {
619
+	return sf.used
579 620
 }