github.com/a4a881d4/docker@v1.9.0-rc2/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  		puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
   105  		if err != nil {
   106  			lastErr = err
   107  			continue
   108  		}
   109  		if fallback, err := puller.Pull(tag); err != nil {
   110  			if fallback {
   111  				if _, ok := err.(registry.ErrNoSupport); !ok {
   112  					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
   113  					discardNoSupportErrors = true
   114  					// save the current error
   115  					lastErr = err
   116  				} else if !discardNoSupportErrors {
   117  					// Save the ErrNoSupport error, because it's either the first error or all encountered errors
   118  					// were also ErrNoSupport errors.
   119  					lastErr = err
   120  				}
   121  				continue
   122  			}
   123  			logrus.Debugf("Not continuing with error: %v", err)
   124  			return err
   125  
   126  		}
   127  
   128  		s.eventsService.Log("pull", logName, "")
   129  		return nil
   130  	}
   131  
   132  	if lastErr == nil {
   133  		lastErr = fmt.Errorf("no endpoints found for %s", image)
   134  	}
   135  	return lastErr
   136  }
   137  
   138  // writeStatus writes a status message to out. If layersDownloaded is true, the
   139  // status message indicates that a newer image was downloaded. Otherwise, it
   140  // indicates that the image is up to date. requestedTag is the tag the message
   141  // will refer to.
   142  func writeStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
   143  	if layersDownloaded {
   144  		out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
   145  	} else {
   146  		out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
   147  	}
   148  }