github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/graph/pull.go (about)

     1  package graph
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/docker/cliconfig"
     9  	"github.com/docker/docker/pkg/streamformatter"
    10  	"github.com/docker/docker/registry"
    11  	"github.com/docker/docker/utils"
    12  )
    13  
    14  // ImagePullConfig stores pull configuration.
    15  type ImagePullConfig struct {
    16  	// MetaHeaders stores HTTP headers with metadata about the image
    17  	// (DockerHeaders with prefix X-Meta- in the request).
    18  	MetaHeaders map[string][]string
    19  	// AuthConfig holds authentication credentials for authenticating with
    20  	// the registry.
    21  	AuthConfig *cliconfig.AuthConfig
    22  	// OutStream is the output writer for showing the status of the pull
    23  	// operation.
    24  	OutStream io.Writer
    25  }
    26  
    27  // Puller is an interface that abstracts pulling for different API versions.
    28  type Puller interface {
    29  	// Pull tries to pull the image referenced by `tag`
    30  	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
    31  	//
    32  	// TODO(tiborvass): have Pull() take a reference to repository + tag, so that the puller itself is repository-agnostic.
    33  	Pull(tag string) (fallback bool, err error)
    34  }
    35  
    36  // NewPuller returns a Puller interface that will pull from either a v1 or v2
    37  // registry. The endpoint argument contains a Version field that determines
    38  // whether a v1 or v2 puller will be created. The other parameters are passed
    39  // through to the underlying puller implementation for use during the actual
    40  // pull operation.
    41  func NewPuller(s *TagStore, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, sf *streamformatter.StreamFormatter) (Puller, error) {
    42  	switch endpoint.Version {
    43  	case registry.APIVersion2:
    44  		return &v2Puller{
    45  			TagStore: s,
    46  			endpoint: endpoint,
    47  			config:   imagePullConfig,
    48  			sf:       sf,
    49  			repoInfo: repoInfo,
    50  		}, nil
    51  	case registry.APIVersion1:
    52  		return &v1Puller{
    53  			TagStore: s,
    54  			endpoint: endpoint,
    55  			config:   imagePullConfig,
    56  			sf:       sf,
    57  			repoInfo: repoInfo,
    58  		}, nil
    59  	}
    60  	return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
    61  }
    62  
    63  // Pull initiates a pull operation. image is the repository name to pull, and
    64  // tag may be either empty, or indicate a specific tag to pull.
    65  func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
    66  	var sf = streamformatter.NewJSONStreamFormatter()
    67  
    68  	// Resolve the Repository name from fqn to RepositoryInfo
    69  	repoInfo, err := s.registryService.ResolveRepository(image)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	// makes sure name is not empty or `scratch`
    75  	if err := validateRepoName(repoInfo.LocalName); err != nil {
    76  		return err
    77  	}
    78  
    79  	endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	logName := repoInfo.LocalName
    85  	if tag != "" {
    86  		logName = utils.ImageReference(logName, tag)
    87  	}
    88  
    89  	var (
    90  		lastErr error
    91  
    92  		// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
    93  		// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
    94  		// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
    95  		// any subsequent ErrNoSupport errors in lastErr.
    96  		// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
    97  		// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
    98  		// error is the ones from v2 endpoints not v1.
    99  		discardNoSupportErrors bool
   100  	)
   101  	for _, endpoint := range endpoints {
   102  		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)
   103  
   104  		if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
   105  			if repoInfo.Official {
   106  				s.trustService.UpdateBase()
   107  			}
   108  		}
   109  
   110  		puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
   111  		if err != nil {
   112  			lastErr = err
   113  			continue
   114  		}
   115  		if fallback, err := puller.Pull(tag); err != nil {
   116  			if fallback {
   117  				if _, ok := err.(registry.ErrNoSupport); !ok {
   118  					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
   119  					discardNoSupportErrors = true
   120  					// save the current error
   121  					lastErr = err
   122  				} else if !discardNoSupportErrors {
   123  					// Save the ErrNoSupport error, because it's either the first error or all encountered errors
   124  					// were also ErrNoSupport errors.
   125  					lastErr = err
   126  				}
   127  				continue
   128  			}
   129  			logrus.Debugf("Not continuing with error: %v", err)
   130  			return err
   131  
   132  		}
   133  
   134  		s.eventsService.Log("pull", logName, "")
   135  		return nil
   136  	}
   137  
   138  	if lastErr == nil {
   139  		lastErr = fmt.Errorf("no endpoints found for %s", image)
   140  	}
   141  	return lastErr
   142  }
   143  
   144  // writeStatus writes a status message to out. If layersDownloaded is true, the
   145  // status message indicates that a newer image was downloaded. Otherwise, it
   146  // indicates that the image is up to date. requestedTag is the tag the message
   147  // will refer to.
   148  func writeStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
   149  	if layersDownloaded {
   150  		out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
   151  	} else {
   152  		out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
   153  	}
   154  }