Jelajahi Sumber

Print a status message when pull command is executed
Using repo tag in the status message for better usability, as per review comments
Added documentation and Changed code to print Status after downloads are complete

Addresses #2404

Signed-off-by: Srini Brahmaroutu <srbrahma@us.ibm.com>

Srini Brahmaroutu 11 tahun lalu
induk
melakukan
ecff6303a3

+ 8 - 0
docs/man/docker-pull.1.md

@@ -23,6 +23,8 @@ It is also possible to specify a non-default registry to pull from.
 # EXAMPLES
 
 # Pull a repository with multiple images
+# Note that if the  image is previously downloaded then the status would be
+# 'Status: Image is up to date for fedora'
 
     $ sudo docker pull fedora
     Pulling repository fedora
@@ -31,6 +33,8 @@ It is also possible to specify a non-default registry to pull from.
     511136ea3c5a: Download complete
     73bd853d2ea5: Download complete
 
+    Status: Downloaded newer image for fedora
+
     $ sudo docker images
     REPOSITORY   TAG         IMAGE ID        CREATED      VIRTUAL SIZE
     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.
     fedora       latest      105182bb5e8b    5 days ago   372.7 MB
 
 # Pull an image, manually specifying path to the registry and tag
+# Note that if the  image is previously downloaded then the status would be
+# 'Status: Image is up to date for registry.hub.docker.com/fedora:20'
 
     $ sudo docker pull registry.hub.docker.com/fedora:20
     Pulling repository fedora
@@ -46,6 +52,8 @@ It is also possible to specify a non-default registry to pull from.
     511136ea3c5a: Download complete 
     fd241224e9cf: Download complete 
 
+    Status: Downloaded newer image for registry.hub.docker.com/fedora:20
+
     $ sudo docker images
     REPOSITORY   TAG         IMAGE ID        CREATED      VIRTUAL SIZE
     fedora       20          3f2fed40e4b0    4 days ago   372.7 MB

+ 2 - 0
docs/sources/userguide/dockerimages.md

@@ -93,6 +93,8 @@ download the `centos` image.
     ef52fb1fe610: Download complete
     . . .
 
+    Status: Downloaded newer image for centos
+
 We can see that each layer of the image has been pulled down and now we
 can run a container from this image and we won't have to wait to
 download the image.

+ 2 - 0
docs/sources/userguide/dockerrepos.md

@@ -67,6 +67,8 @@ Once you've found the image you want, you can download it with `docker pull <ima
     511136ea3c5a: Download complete
     7064731afe90: Download complete
 
+    Status: Downloaded newer image for centos
+
 You now have an image from which you can run containers.
 
 ## Contributing to Docker Hub

+ 30 - 10
graph/pull.go

@@ -122,6 +122,8 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
 	}
 
 	errors := make(chan error)
+
+	layers_downloaded := false
 	for _, image := range repoData.ImgList {
 		downloadImage := func(img *registry.ImgData) {
 			if askedTag != "" && img.Tag != askedTag {
@@ -158,15 +160,17 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
 
 			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
 			success := false
-			var lastErr error
+			var lastErr, err error
+			var is_downloaded bool
 			if mirrors != nil {
 				for _, ep := range mirrors {
 					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, localName, ep), nil))
-					if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
+					if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
 						// Don't report errors when pulling from mirrors.
 						log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, localName, ep, err)
 						continue
 					}
+					layers_downloaded = layers_downloaded || is_downloaded
 					success = true
 					break
 				}
@@ -174,13 +178,14 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
 			if !success {
 				for _, ep := range repoData.Endpoints {
 					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
-					if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
+					if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
 						// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
 						// As the error is also given to the output stream the user will see the error.
 						lastErr = err
 						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))
 						continue
 					}
+					layers_downloaded = layers_downloaded || is_downloaded
 					success = true
 					break
 				}
@@ -227,18 +232,24 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
 		}
 	}
 
+	requestedTag := localName
+	if len(askedTag) > 0 {
+		requestedTag = localName + ":" + askedTag
+	}
+	WriteStatus(requestedTag, out, sf, layers_downloaded)
 	return nil
 }
 
-func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
+func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) (bool, error) {
 	history, err := r.GetRemoteHistory(imgID, endpoint, token)
 	if err != nil {
-		return err
+		return false, err
 	}
 	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil))
 	// FIXME: Try to stream the images?
 	// FIXME: Launch the getRemoteImage() in goroutines
 
+	layers_downloaded := false
 	for i := len(history) - 1; i >= 0; i-- {
 		id := history[i]
 
@@ -262,15 +273,16 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
 				imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
 				if err != nil && j == retries {
 					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
-					return err
+					return layers_downloaded, err
 				} else if err != nil {
 					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
 					continue
 				}
 				img, err = image.NewImgJSON(imgJSON)
+				layers_downloaded = true
 				if err != nil && j == retries {
 					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
-					return fmt.Errorf("Failed to parse json: %s", err)
+					return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err)
 				} else if err != nil {
 					time.Sleep(time.Duration(j) * 500 * time.Millisecond)
 					continue
@@ -295,8 +307,9 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
 					continue
 				} else if err != nil {
 					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
-					return err
+					return layers_downloaded, err
 				}
+				layers_downloaded = true
 				defer layer.Close()
 
 				err = s.graph.Register(img, imgJSON,
@@ -306,14 +319,21 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
 					continue
 				} else if err != nil {
 					out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil))
-					return err
+					return layers_downloaded, err
 				} else {
 					break
 				}
 			}
 		}
 		out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil))
+	}
+	return layers_downloaded, nil
+}
 
+func WriteStatus(requestedTag string, out io.Writer, sf *utils.StreamFormatter, layers_downloaded bool) {
+	if layers_downloaded {
+		out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
+	} else {
+		out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
 	}
-	return nil
 }