* API: push, pull, import, insert -> Json Stream
Guillaume J. Charmes authored on 2013/06/01 08:05:15... | ... |
@@ -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 |
} |