Addresses #2404
Signed-off-by: Srini Brahmaroutu <srbrahma@us.ibm.com>
| ... | ... |
@@ -23,6 +23,8 @@ It is also possible to specify a non-default registry to pull from. |
| 23 | 23 |
# EXAMPLES |
| 24 | 24 |
|
| 25 | 25 |
# Pull a repository with multiple images |
| 26 |
+# Note that if the image is previously downloaded then the status would be |
|
| 27 |
+# 'Status: Image is up to date for fedora' |
|
| 26 | 28 |
|
| 27 | 29 |
$ sudo docker pull fedora |
| 28 | 30 |
Pulling repository fedora |
| ... | ... |
@@ -31,6 +33,8 @@ It is also possible to specify a non-default registry to pull from. |
| 31 | 31 |
511136ea3c5a: Download complete |
| 32 | 32 |
73bd853d2ea5: Download complete |
| 33 | 33 |
|
| 34 |
+ Status: Downloaded newer image for fedora |
|
| 35 |
+ |
|
| 34 | 36 |
$ sudo docker images |
| 35 | 37 |
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE |
| 36 | 38 |
fedora rawhide ad57ef8d78d7 5 days ago 359.3 MB |
| ... | ... |
@@ -39,6 +43,8 @@ It is also possible to specify a non-default registry to pull from. |
| 39 | 39 |
fedora latest 105182bb5e8b 5 days ago 372.7 MB |
| 40 | 40 |
|
| 41 | 41 |
# Pull an image, manually specifying path to the registry and tag |
| 42 |
+# Note that if the image is previously downloaded then the status would be |
|
| 43 |
+# 'Status: Image is up to date for registry.hub.docker.com/fedora:20' |
|
| 42 | 44 |
|
| 43 | 45 |
$ sudo docker pull registry.hub.docker.com/fedora:20 |
| 44 | 46 |
Pulling repository fedora |
| ... | ... |
@@ -46,6 +52,8 @@ It is also possible to specify a non-default registry to pull from. |
| 46 | 46 |
511136ea3c5a: Download complete |
| 47 | 47 |
fd241224e9cf: Download complete |
| 48 | 48 |
|
| 49 |
+ Status: Downloaded newer image for registry.hub.docker.com/fedora:20 |
|
| 50 |
+ |
|
| 49 | 51 |
$ sudo docker images |
| 50 | 52 |
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE |
| 51 | 53 |
fedora 20 3f2fed40e4b0 4 days ago 372.7 MB |
| ... | ... |
@@ -93,6 +93,8 @@ download the `centos` image. |
| 93 | 93 |
ef52fb1fe610: Download complete |
| 94 | 94 |
. . . |
| 95 | 95 |
|
| 96 |
+ Status: Downloaded newer image for centos |
|
| 97 |
+ |
|
| 96 | 98 |
We can see that each layer of the image has been pulled down and now we |
| 97 | 99 |
can run a container from this image and we won't have to wait to |
| 98 | 100 |
download the image. |
| ... | ... |
@@ -67,6 +67,8 @@ Once you've found the image you want, you can download it with `docker pull <ima |
| 67 | 67 |
511136ea3c5a: Download complete |
| 68 | 68 |
7064731afe90: Download complete |
| 69 | 69 |
|
| 70 |
+ Status: Downloaded newer image for centos |
|
| 71 |
+ |
|
| 70 | 72 |
You now have an image from which you can run containers. |
| 71 | 73 |
|
| 72 | 74 |
## Contributing to Docker Hub |
| ... | ... |
@@ -122,6 +122,8 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 122 | 122 |
} |
| 123 | 123 |
|
| 124 | 124 |
errors := make(chan error) |
| 125 |
+ |
|
| 126 |
+ layers_downloaded := false |
|
| 125 | 127 |
for _, image := range repoData.ImgList {
|
| 126 | 128 |
downloadImage := func(img *registry.ImgData) {
|
| 127 | 129 |
if askedTag != "" && img.Tag != askedTag {
|
| ... | ... |
@@ -158,15 +160,17 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 158 | 158 |
|
| 159 | 159 |
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
|
| 160 | 160 |
success := false |
| 161 |
- var lastErr error |
|
| 161 |
+ var lastErr, err error |
|
| 162 |
+ var is_downloaded bool |
|
| 162 | 163 |
if mirrors != nil {
|
| 163 | 164 |
for _, ep := range mirrors {
|
| 164 | 165 |
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, localName, ep), nil))
|
| 165 |
- if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 166 |
+ if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 166 | 167 |
// Don't report errors when pulling from mirrors. |
| 167 | 168 |
log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, localName, ep, err)
|
| 168 | 169 |
continue |
| 169 | 170 |
} |
| 171 |
+ layers_downloaded = layers_downloaded || is_downloaded |
|
| 170 | 172 |
success = true |
| 171 | 173 |
break |
| 172 | 174 |
} |
| ... | ... |
@@ -174,13 +178,14 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 174 | 174 |
if !success {
|
| 175 | 175 |
for _, ep := range repoData.Endpoints {
|
| 176 | 176 |
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
|
| 177 |
- if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 177 |
+ if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 178 | 178 |
// It's not ideal that only the last error is returned, it would be better to concatenate the errors. |
| 179 | 179 |
// As the error is also given to the output stream the user will see the error. |
| 180 | 180 |
lastErr = err |
| 181 | 181 |
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
|
| 182 | 182 |
continue |
| 183 | 183 |
} |
| 184 |
+ layers_downloaded = layers_downloaded || is_downloaded |
|
| 184 | 185 |
success = true |
| 185 | 186 |
break |
| 186 | 187 |
} |
| ... | ... |
@@ -227,18 +232,24 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 227 | 227 |
} |
| 228 | 228 |
} |
| 229 | 229 |
|
| 230 |
+ requestedTag := localName |
|
| 231 |
+ if len(askedTag) > 0 {
|
|
| 232 |
+ requestedTag = localName + ":" + askedTag |
|
| 233 |
+ } |
|
| 234 |
+ WriteStatus(requestedTag, out, sf, layers_downloaded) |
|
| 230 | 235 |
return nil |
| 231 | 236 |
} |
| 232 | 237 |
|
| 233 |
-func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
|
| 238 |
+func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) (bool, error) {
|
|
| 234 | 239 |
history, err := r.GetRemoteHistory(imgID, endpoint, token) |
| 235 | 240 |
if err != nil {
|
| 236 |
- return err |
|
| 241 |
+ return false, err |
|
| 237 | 242 |
} |
| 238 | 243 |
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil)) |
| 239 | 244 |
// FIXME: Try to stream the images? |
| 240 | 245 |
// FIXME: Launch the getRemoteImage() in goroutines |
| 241 | 246 |
|
| 247 |
+ layers_downloaded := false |
|
| 242 | 248 |
for i := len(history) - 1; i >= 0; i-- {
|
| 243 | 249 |
id := history[i] |
| 244 | 250 |
|
| ... | ... |
@@ -262,15 +273,16 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint |
| 262 | 262 |
imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) |
| 263 | 263 |
if err != nil && j == retries {
|
| 264 | 264 |
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
| 265 |
- return err |
|
| 265 |
+ return layers_downloaded, err |
|
| 266 | 266 |
} else if err != nil {
|
| 267 | 267 |
time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
| 268 | 268 |
continue |
| 269 | 269 |
} |
| 270 | 270 |
img, err = image.NewImgJSON(imgJSON) |
| 271 |
+ layers_downloaded = true |
|
| 271 | 272 |
if err != nil && j == retries {
|
| 272 | 273 |
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
| 273 |
- return fmt.Errorf("Failed to parse json: %s", err)
|
|
| 274 |
+ return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err)
|
|
| 274 | 275 |
} else if err != nil {
|
| 275 | 276 |
time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
| 276 | 277 |
continue |
| ... | ... |
@@ -295,8 +307,9 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint |
| 295 | 295 |
continue |
| 296 | 296 |
} else if err != nil {
|
| 297 | 297 |
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
| 298 |
- return err |
|
| 298 |
+ return layers_downloaded, err |
|
| 299 | 299 |
} |
| 300 |
+ layers_downloaded = true |
|
| 300 | 301 |
defer layer.Close() |
| 301 | 302 |
|
| 302 | 303 |
err = s.graph.Register(img, imgJSON, |
| ... | ... |
@@ -306,14 +319,21 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint |
| 306 | 306 |
continue |
| 307 | 307 |
} else if err != nil {
|
| 308 | 308 |
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) |
| 309 |
- return err |
|
| 309 |
+ return layers_downloaded, err |
|
| 310 | 310 |
} else {
|
| 311 | 311 |
break |
| 312 | 312 |
} |
| 313 | 313 |
} |
| 314 | 314 |
} |
| 315 | 315 |
out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil)) |
| 316 |
+ } |
|
| 317 |
+ return layers_downloaded, nil |
|
| 318 |
+} |
|
| 316 | 319 |
|
| 320 |
+func WriteStatus(requestedTag string, out io.Writer, sf *utils.StreamFormatter, layers_downloaded bool) {
|
|
| 321 |
+ if layers_downloaded {
|
|
| 322 |
+ out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
|
|
| 323 |
+ } else {
|
|
| 324 |
+ out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
|
|
| 317 | 325 |
} |
| 318 |
- return nil |
|
| 319 | 326 |
} |