123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package distribution
- import (
- "fmt"
- "github.com/Sirupsen/logrus"
- "github.com/docker/distribution/reference"
- "github.com/docker/docker/api"
- "github.com/docker/docker/distribution/metadata"
- "github.com/docker/docker/pkg/progress"
- refstore "github.com/docker/docker/reference"
- "github.com/docker/docker/registry"
- "github.com/opencontainers/go-digest"
- "golang.org/x/net/context"
- )
- // Puller is an interface that abstracts pulling for different API versions.
- 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.
- //
- Pull(ctx context.Context, ref reference.Named) error
- }
- // newPuller returns a Puller interface that will pull from either a v1 or v2
- // registry. The endpoint argument contains a Version field that determines
- // whether a v1 or v2 puller will be created. The other parameters are passed
- // through to the underlying puller implementation for use during the actual
- // pull operation.
- func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) {
- switch endpoint.Version {
- case registry.APIVersion2:
- return &v2Puller{
- V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
- endpoint: endpoint,
- config: imagePullConfig,
- repoInfo: repoInfo,
- }, nil
- case registry.APIVersion1:
- return &v1Puller{
- v1IDService: metadata.NewV1IDService(imagePullConfig.MetadataStore),
- endpoint: endpoint,
- config: imagePullConfig,
- repoInfo: repoInfo,
- }, nil
- }
- return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
- }
- // Pull initiates a pull operation. image is the repository name to pull, and
- // tag may be either empty, or indicate a specific tag to pull.
- func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
- // Resolve the Repository name from fqn to RepositoryInfo
- repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
- if err != nil {
- return err
- }
- // makes sure name is not `scratch`
- if err := ValidateRepoName(repoInfo.Name); err != nil {
- return err
- }
- endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
- if err != nil {
- return err
- }
- 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 an 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
- // confirmedV2 is set to true if a pull attempt managed to
- // confirm that it was talking to a v2 registry. This will
- // prevent fallback to the v1 protocol.
- confirmedV2 bool
- // confirmedTLSRegistries is a map indicating which registries
- // are known to be using TLS. There should never be a plaintext
- // retry for any of these.
- confirmedTLSRegistries = make(map[string]struct{})
- )
- for _, endpoint := range endpoints {
- if imagePullConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 {
- continue
- }
- if confirmedV2 && endpoint.Version == registry.APIVersion1 {
- logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
- continue
- }
- if endpoint.URL.Scheme != "https" {
- if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
- logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
- continue
- }
- }
- logrus.Debugf("Trying to pull %s from %s %s", reference.FamiliarName(repoInfo.Name), endpoint.URL, endpoint.Version)
- puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
- if err != nil {
- lastErr = err
- continue
- }
- if err := puller.Pull(ctx, ref); err != nil {
- // Was this pull cancelled? If so, don't try to fall
- // back.
- fallback := false
- select {
- case <-ctx.Done():
- default:
- if fallbackErr, ok := err.(fallbackError); ok {
- fallback = true
- confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
- if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
- confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
- }
- err = fallbackErr.err
- }
- }
- if fallback {
- if _, ok := err.(ErrNoSupport); !ok {
- // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
- discardNoSupportErrors = true
- // append subsequent errors
- lastErr = err
- } else if !discardNoSupportErrors {
- // Save the ErrNoSupport error, because it's either the first error or all encountered errors
- // were also ErrNoSupport errors.
- // append subsequent errors
- lastErr = err
- }
- logrus.Infof("Attempting next endpoint for pull after error: %v", err)
- continue
- }
- logrus.Errorf("Not continuing with pull after error: %v", err)
- return TranslatePullError(err, ref)
- }
- imagePullConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "pull")
- return nil
- }
- if lastErr == nil {
- lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref))
- }
- return TranslatePullError(lastErr, ref)
- }
- // writeStatus writes a status message to out. If layersDownloaded is true, the
- // status message indicates that a newer image was downloaded. Otherwise, it
- // indicates that the image is up to date. requestedTag is the tag the message
- // will refer to.
- func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) {
- if layersDownloaded {
- progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag)
- } else {
- progress.Message(out, "", "Status: Image is up to date for "+requestedTag)
- }
- }
- // ValidateRepoName validates the name of a repository.
- func ValidateRepoName(name reference.Named) error {
- if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
- return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier)
- }
- return nil
- }
- func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error {
- dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst)
- if err != nil {
- return err
- }
- if oldTagID, err := store.Get(dgstRef); err == nil {
- if oldTagID != id {
- // Updating digests not supported by reference store
- logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id)
- }
- return nil
- } else if err != refstore.ErrDoesNotExist {
- return err
- }
- return store.AddDigest(dgstRef, id, true)
- }
|