graph/pull.go
6856a6b1
 package graph
 
 import (
 	"fmt"
 	"io"
 
6f4d8470
 	"github.com/Sirupsen/logrus"
bb9da6ba
 	"github.com/docker/docker/cliconfig"
0cd6c05d
 	"github.com/docker/docker/pkg/streamformatter"
6856a6b1
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/utils"
 )
 
6e38a53f
 type ImagePullConfig struct {
 	MetaHeaders map[string][]string
bb9da6ba
 	AuthConfig  *cliconfig.AuthConfig
6e38a53f
 	OutStream   io.Writer
 }
 
19515a7a
 type Puller interface {
 	// Pull tries to pull the image referenced by `tag`
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
 	//
 	// TODO(tiborvass): have Pull() take a reference to repository + tag, so that the puller itself is repository-agnostic.
 	Pull(tag string) (fallback bool, err error)
 }
 
 func NewPuller(s *TagStore, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, sf *streamformatter.StreamFormatter) (Puller, error) {
 	switch endpoint.Version {
 	case registry.APIVersion2:
 		return &v2Puller{
 			TagStore: s,
 			endpoint: endpoint,
 			config:   imagePullConfig,
 			sf:       sf,
 			repoInfo: repoInfo,
 		}, nil
 	case registry.APIVersion1:
 		return &v1Puller{
 			TagStore: s,
 			endpoint: endpoint,
 			config:   imagePullConfig,
 			sf:       sf,
 			repoInfo: repoInfo,
 		}, nil
 	}
 	return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
 }
 
a2f74aa4
 func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
19515a7a
 	var sf = streamformatter.NewJSONStreamFormatter()
1c62e0ae
 
568f86eb
 	// Resolve the Repository name from fqn to RepositoryInfo
6e38a53f
 	repoInfo, err := s.registryService.ResolveRepository(image)
568f86eb
 	if err != nil {
c79b9bab
 		return err
568f86eb
 	}
 
19515a7a
 	// makes sure name is not empty or `scratch`
18f46883
 	if err := validateRepoName(repoInfo.LocalName); err != nil {
 		return err
 	}
 
29ea36a8
 	endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
6856a6b1
 	if err != nil {
c79b9bab
 		return err
6856a6b1
 	}
 
13deed38
 	logName := repoInfo.LocalName
 	if tag != "" {
 		logName = utils.ImageReference(logName, tag)
 	}
 
19515a7a
 	var (
 		lastErr error
 
 		// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
 		// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
 		// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
 		// any subsequent ErrNoSupport errors in lastErr.
 		// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
 		// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
 		// error is the ones from v2 endpoints not v1.
 		discardNoSupportErrors bool
73823e5e
 	)
19515a7a
 	for _, endpoint := range endpoints {
 		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)
3be4551a
 
19515a7a
 		if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
 			if repoInfo.Official {
 				s.trustService.UpdateBase()
 			}
3be4551a
 		}
213e3d11
 
19515a7a
 		puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
13deed38
 		if err != nil {
19515a7a
 			lastErr = err
13deed38
 			continue
 		}
19515a7a
 		if fallback, err := puller.Pull(tag); err != nil {
 			if fallback {
 				if _, ok := err.(registry.ErrNoSupport); !ok {
 					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
 					discardNoSupportErrors = true
 					// save the current error
 					lastErr = err
 				} else if !discardNoSupportErrors {
 					// Save the ErrNoSupport error, because it's either the first error or all encountered errors
 					// were also ErrNoSupport errors.
 					lastErr = err
 				}
 				continue
6856a6b1
 			}
19515a7a
 			logrus.Debugf("Not continuing with error: %v", err)
6856a6b1
 			return err
 
 		}
 
19515a7a
 		s.eventsService.Log("pull", logName, "")
 		return nil
 	}
6856a6b1
 
19515a7a
 	if lastErr == nil {
 		lastErr = fmt.Errorf("no endpoints found for %s", image)
ecff6303
 	}
19515a7a
 	return lastErr
ecff6303
 }
6856a6b1
 
ae907e7a
 func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
 	if layersDownloaded {
ecff6303
 		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))
6856a6b1
 	}
 }