Browse code

'docker push' shows an additional progress bar while it buffers the archive to disk. Fixes #451.

Solomon Hykes authored on 2013/04/22 07:29:26
Showing 4 changed files
... ...
@@ -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