github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/distribution/pull.go (about)

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