| ... | ... |
@@ -475,7 +475,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . |
| 475 | 475 |
if err != nil {
|
| 476 | 476 |
return err |
| 477 | 477 |
} |
| 478 |
- archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) |
|
| 478 |
+ archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") |
|
| 479 | 479 |
} |
| 480 | 480 |
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") |
| 481 | 481 |
if err != nil {
|
| ... | ... |
@@ -2,6 +2,7 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
+ "io" |
|
| 5 | 6 |
"io/ioutil" |
| 6 | 7 |
"os" |
| 7 | 8 |
"path" |
| ... | ... |
@@ -131,7 +132,9 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
|
| 131 | 131 |
|
| 132 | 132 |
// TempLayerArchive creates a temporary archive of the given image's filesystem layer. |
| 133 | 133 |
// The archive is stored on disk and will be automatically deleted as soon as has been read. |
| 134 |
-func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempArchive, error) {
|
|
| 134 |
+// If output is not nil, a human-readable progress bar will be written to it. |
|
| 135 |
+// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? |
|
| 136 |
+func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) {
|
|
| 135 | 137 |
image, err := graph.Get(id) |
| 136 | 138 |
if err != nil {
|
| 137 | 139 |
return nil, err |
| ... | ... |
@@ -144,7 +147,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempA |
| 144 | 144 |
if err != nil {
|
| 145 | 145 |
return nil, err |
| 146 | 146 |
} |
| 147 |
- return NewTempArchive(archive, tmp.Root) |
|
| 147 |
+ return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root) |
|
| 148 | 148 |
} |
| 149 | 149 |
|
| 150 | 150 |
// Mktemp creates a temporary sub-directory inside the graph's filesystem. |
| ... | ... |
@@ -136,7 +136,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a |
| 136 | 136 |
if err != nil {
|
| 137 | 137 |
return nil, nil, err |
| 138 | 138 |
} |
| 139 |
- return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil |
|
| 139 |
+ return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil |
|
| 140 | 140 |
} |
| 141 | 141 |
|
| 142 | 142 |
func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
|
| ... | ... |
@@ -274,12 +274,12 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth |
| 274 | 274 |
// a) Implementing S3's proprietary streaming logic, or |
| 275 | 275 |
// b) Stream directly to the registry instead of S3. |
| 276 | 276 |
// I prefer option b. because it doesn't lock us into a proprietary cloud service. |
| 277 |
- tmpLayer, err := graph.TempLayerArchive(img.Id, Xz) |
|
| 277 |
+ tmpLayer, err := graph.TempLayerArchive(img.Id, Xz, stdout) |
|
| 278 | 278 |
if err != nil {
|
| 279 | 279 |
return err |
| 280 | 280 |
} |
| 281 | 281 |
defer os.Remove(tmpLayer.Name()) |
| 282 |
- req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout))
|
|
| 282 |
+ req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout, "Uploading %v/%v (%v)"))
|
|
| 283 | 283 |
if err != nil {
|
| 284 | 284 |
return err |
| 285 | 285 |
} |
| ... | ... |
@@ -72,23 +72,30 @@ type progressReader struct {
|
| 72 | 72 |
readTotal int // Expected stream length (bytes) |
| 73 | 73 |
readProgress int // How much has been read so far (bytes) |
| 74 | 74 |
lastUpdate int // How many bytes read at least update |
| 75 |
+ template string // Template to print. Default "%v/%v (%v)" |
|
| 75 | 76 |
} |
| 76 | 77 |
|
| 77 | 78 |
func (r *progressReader) Read(p []byte) (n int, err error) {
|
| 78 | 79 |
read, err := io.ReadCloser(r.reader).Read(p) |
| 79 | 80 |
r.readProgress += read |
| 80 | 81 |
|
| 81 |
- // Only update progress for every 1% read |
|
| 82 |
- updateEvery := int(0.01 * float64(r.readTotal)) |
|
| 83 |
- if r.readProgress-r.lastUpdate > updateEvery || r.readProgress == r.readTotal {
|
|
| 84 |
- fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r", |
|
| 85 |
- r.readProgress, |
|
| 86 |
- r.readTotal, |
|
| 87 |
- float64(r.readProgress)/float64(r.readTotal)*100) |
|
| 82 |
+ updateEvery := 4096 |
|
| 83 |
+ if r.readTotal > 0 {
|
|
| 84 |
+ // Only update progress for every 1% read |
|
| 85 |
+ if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
|
|
| 86 |
+ updateEvery = increment |
|
| 87 |
+ } |
|
| 88 |
+ } |
|
| 89 |
+ if r.readProgress-r.lastUpdate > updateEvery || err != nil {
|
|
| 90 |
+ if r.readTotal > 0 {
|
|
| 91 |
+ fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
|
|
| 92 |
+ } else {
|
|
| 93 |
+ fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a") |
|
| 94 |
+ } |
|
| 88 | 95 |
r.lastUpdate = r.readProgress |
| 89 | 96 |
} |
| 90 | 97 |
// Send newline when complete |
| 91 |
- if err == io.EOF {
|
|
| 98 |
+ if err != nil {
|
|
| 92 | 99 |
fmt.Fprintf(r.output, "\n") |
| 93 | 100 |
} |
| 94 | 101 |
|
| ... | ... |
@@ -97,8 +104,11 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
| 97 | 97 |
func (r *progressReader) Close() error {
|
| 98 | 98 |
return io.ReadCloser(r.reader).Close() |
| 99 | 99 |
} |
| 100 |
-func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader {
|
|
| 101 |
- return &progressReader{r, output, size, 0, 0}
|
|
| 100 |
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
|
|
| 101 |
+ if template == "" {
|
|
| 102 |
+ template = "%v/%v (%v)" |
|
| 103 |
+ } |
|
| 104 |
+ return &progressReader{r, output, size, 0, 0, template}
|
|
| 102 | 105 |
} |
| 103 | 106 |
|
| 104 | 107 |
// HumanDuration returns a human-readable approximation of a duration |
| ... | ... |
@@ -395,6 +405,7 @@ type KernelVersionInfo struct {
|
| 395 | 395 |
Specific int |
| 396 | 396 |
} |
| 397 | 397 |
|
| 398 |
+// FIXME: this doens't build on Darwin |
|
| 398 | 399 |
func GetKernelVersion() (*KernelVersionInfo, error) {
|
| 399 | 400 |
var uts syscall.Utsname |
| 400 | 401 |
|