github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/distribution/pull.go (about)

     1  package distribution
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/Sirupsen/logrus"
     9  	"github.com/docker/docker/api"
    10  	"github.com/docker/docker/distribution/metadata"
    11  	"github.com/docker/docker/distribution/xfer"
    12  	"github.com/docker/docker/image"
    13  	"github.com/docker/docker/pkg/progress"
    14  	"github.com/docker/docker/reference"
    15  	"github.com/docker/docker/registry"
    16  	"github.com/docker/engine-api/types"
    17  	"golang.org/x/net/context"
    18  )
    19  
    20  // ImagePullConfig stores pull configuration.
    21  type ImagePullConfig struct {
    22  	// MetaHeaders stores HTTP headers with metadata about the image
    23  	// (DockerHeaders with prefix X-Meta- in the request).
    24  	MetaHeaders map[string][]string
    25  	// AuthConfig holds authentication credentials for authenticating with
    26  	// the registry.
    27  	AuthConfig *types.AuthConfig
    28  	// ProgressOutput is the interface for showing the status of the pull
    29  	// operation.
    30  	ProgressOutput progress.Output
    31  	// RegistryService is the registry service to use for TLS configuration
    32  	// and endpoint lookup.
    33  	RegistryService *registry.Service
    34  	// ImageEventLogger notifies events for a given image
    35  	ImageEventLogger func(id, name, action string)
    36  	// MetadataStore is the storage backend for distribution-specific
    37  	// metadata.
    38  	MetadataStore metadata.Store
    39  	// ImageStore manages images.
    40  	ImageStore image.Store
    41  	// ReferenceStore manages tags.
    42  	ReferenceStore reference.Store
    43  	// DownloadManager manages concurrent pulls.
    44  	DownloadManager *xfer.LayerDownloadManager
    45  }
    46  
    47  // Puller is an interface that abstracts pulling for different API versions.
    48  type Puller interface {
    49  	// Pull tries to pull the image referenced by `tag`
    50  	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
    51  	//
    52  	Pull(ctx context.Context, ref reference.Named) error
    53  }
    54  
    55  // newPuller returns a Puller interface that will pull from either a v1 or v2
    56  // registry. The endpoint argument contains a Version field that determines
    57  // whether a v1 or v2 puller will be created. The other parameters are passed
    58  // through to the underlying puller implementation for use during the actual
    59  // pull operation.
    60  func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) {
    61  	switch endpoint.Version {
    62  	case registry.APIVersion2:
    63  		return &v2Puller{
    64  			V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
    65  			endpoint:          endpoint,
    66  			config:            imagePullConfig,
    67  			repoInfo:          repoInfo,
    68  		}, nil
    69  	case registry.APIVersion1:
    70  		return &v1Puller{
    71  			v1IDService: metadata.NewV1IDService(imagePullConfig.MetadataStore),
    72  			endpoint:    endpoint,
    73  			config:      imagePullConfig,
    74  			repoInfo:    repoInfo,
    75  		}, nil
    76  	}
    77  	return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
    78  }
    79  
    80  // Pull initiates a pull operation. image is the repository name to pull, and
    81  // tag may be either empty, or indicate a specific tag to pull.
    82  func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
    83  	// Resolve the Repository name from fqn to RepositoryInfo
    84  	repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	// makes sure name is not empty or `scratch`
    90  	if err := validateRepoName(repoInfo.Name()); err != nil {
    91  		return err
    92  	}
    93  
    94  	endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	var (
   100  		// use a slice to append the error strings and return a joined string to caller
   101  		errors []string
   102  
   103  		// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
   104  		// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in errors.
   105  		// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
   106  		// any subsequent ErrNoSupport errors in errors.
   107  		// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
   108  		// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
   109  		// error is the ones from v2 endpoints not v1.
   110  		discardNoSupportErrors bool
   111  
   112  		// confirmedV2 is set to true if a pull attempt managed to
   113  		// confirm that it was talking to a v2 registry. This will
   114  		// prevent fallback to the v1 protocol.
   115  		confirmedV2 bool
   116  	)
   117  	for _, endpoint := range endpoints {
   118  		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
   119  			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
   120  			continue
   121  		}
   122  		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
   123  
   124  		puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
   125  		if err != nil {
   126  			errors = append(errors, err.Error())
   127  			continue
   128  		}
   129  		if err := puller.Pull(ctx, ref); err != nil {
   130  			// Was this pull cancelled? If so, don't try to fall
   131  			// back.
   132  			fallback := false
   133  			select {
   134  			case <-ctx.Done():
   135  			default:
   136  				if fallbackErr, ok := err.(fallbackError); ok {
   137  					fallback = true
   138  					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
   139  					err = fallbackErr.err
   140  				}
   141  			}
   142  			if fallback {
   143  				if _, ok := err.(registry.ErrNoSupport); !ok {
   144  					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
   145  					discardNoSupportErrors = true
   146  					// append subsequent errors
   147  					errors = append(errors, err.Error())
   148  				} else if !discardNoSupportErrors {
   149  					// Save the ErrNoSupport error, because it's either the first error or all encountered errors
   150  					// were also ErrNoSupport errors.
   151  					// append subsequent errors
   152  					errors = append(errors, err.Error())
   153  				}
   154  				continue
   155  			}
   156  			errors = append(errors, err.Error())
   157  			logrus.Debugf("Not continuing with error: %v", fmt.Errorf(strings.Join(errors, "\n")))
   158  			if len(errors) > 0 {
   159  				return fmt.Errorf(strings.Join(errors, "\n"))
   160  			}
   161  		}
   162  
   163  		imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
   164  		return nil
   165  	}
   166  
   167  	if len(errors) == 0 {
   168  		return fmt.Errorf("no endpoints found for %s", ref.String())
   169  	}
   170  
   171  	if len(errors) > 0 {
   172  		return fmt.Errorf(strings.Join(errors, "\n"))
   173  	}
   174  	return nil
   175  }
   176  
   177  // writeStatus writes a status message to out. If layersDownloaded is true, the
   178  // status message indicates that a newer image was downloaded. Otherwise, it
   179  // indicates that the image is up to date. requestedTag is the tag the message
   180  // will refer to.
   181  func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) {
   182  	if layersDownloaded {
   183  		progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag)
   184  	} else {
   185  		progress.Message(out, "", "Status: Image is up to date for "+requestedTag)
   186  	}
   187  }
   188  
   189  // validateRepoName validates the name of a repository.
   190  func validateRepoName(name string) error {
   191  	if name == "" {
   192  		return fmt.Errorf("Repository name can't be empty")
   193  	}
   194  	if name == api.NoBaseImageSpecifier {
   195  		return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier)
   196  	}
   197  	return nil
   198  }
   199  
   200  // tmpFileClose creates a closer function for a temporary file that closes the file
   201  // and also deletes it.
   202  func tmpFileCloser(tmpFile *os.File) func() error {
   203  	return func() error {
   204  		tmpFile.Close()
   205  		if err := os.RemoveAll(tmpFile.Name()); err != nil {
   206  			logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name())
   207  		}
   208  
   209  		return nil
   210  	}
   211  }