694df3ff |
package distribution
import (
"fmt"
"github.com/Sirupsen/logrus" |
e6806223 |
"github.com/docker/docker/api" |
694df3ff |
"github.com/docker/docker/distribution/metadata" |
572ce802 |
"github.com/docker/docker/distribution/xfer" |
694df3ff |
"github.com/docker/docker/image" |
572ce802 |
"github.com/docker/docker/pkg/progress" |
2655954c |
"github.com/docker/docker/reference" |
694df3ff |
"github.com/docker/docker/registry" |
907407d0 |
"github.com/docker/engine-api/types" |
572ce802 |
"golang.org/x/net/context" |
694df3ff |
)
// ImagePullConfig stores pull configuration.
type ImagePullConfig struct {
// MetaHeaders stores HTTP headers with metadata about the image
MetaHeaders map[string][]string
// AuthConfig holds authentication credentials for authenticating with
// the registry. |
5b321e32 |
AuthConfig *types.AuthConfig |
572ce802 |
// ProgressOutput is the interface for showing the status of the pull |
694df3ff |
// operation. |
572ce802 |
ProgressOutput progress.Output |
694df3ff |
// RegistryService is the registry service to use for TLS configuration
// and endpoint lookup.
RegistryService *registry.Service |
72f1881d |
// ImageEventLogger notifies events for a given image
ImageEventLogger func(id, name, action string) |
694df3ff |
// MetadataStore is the storage backend for distribution-specific
// metadata.
MetadataStore metadata.Store
// ImageStore manages images.
ImageStore image.Store |
2655954c |
// ReferenceStore manages tags.
ReferenceStore reference.Store |
572ce802 |
// DownloadManager manages concurrent pulls.
DownloadManager *xfer.LayerDownloadManager |
694df3ff |
}
// 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.
// |
a57478d6 |
Pull(ctx context.Context, ref reference.Named) error |
694df3ff |
}
// 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. |
572ce802 |
func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) { |
694df3ff |
switch endpoint.Version {
case registry.APIVersion2:
return &v2Puller{ |
63099477 |
V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
endpoint: endpoint,
config: imagePullConfig,
repoInfo: repoInfo, |
694df3ff |
}, 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. |
572ce802 |
func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error { |
694df3ff |
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
if err != nil {
return err
}
// makes sure name is not empty or `scratch` |
ffded61d |
if err := validateRepoName(repoInfo.Name()); err != nil { |
694df3ff |
return err
}
|
f2d481a2 |
endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo.Hostname()) |
694df3ff |
if err != nil {
return err
}
var ( |
87338bf0 |
lastErr error |
694df3ff |
// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport |
87338bf0 |
// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr. |
694df3ff |
// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of |
87338bf0 |
// any subsequent ErrNoSupport errors in lastErr. |
694df3ff |
// 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 |
a57478d6 |
// 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 |
5e8af46f |
// 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{}) |
694df3ff |
)
for _, endpoint := range endpoints { |
a57478d6 |
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
continue
} |
5e8af46f |
|
79db131a |
if endpoint.URL.Scheme != "https" {
if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { |
5e8af46f |
logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
continue
}
}
|
ffded61d |
logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version) |
694df3ff |
|
572ce802 |
puller, err := newPuller(endpoint, repoInfo, imagePullConfig) |
694df3ff |
if err != nil { |
87338bf0 |
lastErr = err |
694df3ff |
continue
} |
a57478d6 |
if err := puller.Pull(ctx, ref); err != nil { |
572ce802 |
// Was this pull cancelled? If so, don't try to fall
// back. |
a57478d6 |
fallback := false |
572ce802 |
select {
case <-ctx.Done():
default: |
a57478d6 |
if fallbackErr, ok := err.(fallbackError); ok {
fallback = true
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2 |
79db131a |
if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} |
5e8af46f |
} |
a57478d6 |
err = fallbackErr.err
} |
572ce802 |
} |
694df3ff |
if fallback { |
8f26fe4f |
if _, ok := err.(ErrNoSupport); !ok { |
694df3ff |
// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
discardNoSupportErrors = true
// append subsequent errors |
87338bf0 |
lastErr = err |
694df3ff |
} 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 |
87338bf0 |
lastErr = err |
694df3ff |
} |
8f26fe4f |
logrus.Errorf("Attempting next endpoint for pull after error: %v", err) |
694df3ff |
continue
} |
8f26fe4f |
logrus.Errorf("Not continuing with pull after error: %v", err) |
87338bf0 |
return err |
694df3ff |
}
|
72f1881d |
imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull") |
694df3ff |
return nil
}
|
87338bf0 |
if lastErr == nil {
lastErr = fmt.Errorf("no endpoints found for %s", ref.String()) |
694df3ff |
}
|
87338bf0 |
return lastErr |
694df3ff |
}
// 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. |
572ce802 |
func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) { |
694df3ff |
if layersDownloaded { |
572ce802 |
progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag) |
694df3ff |
} else { |
572ce802 |
progress.Message(out, "", "Status: Image is up to date for "+requestedTag) |
694df3ff |
}
}
// validateRepoName validates the name of a repository.
func validateRepoName(name string) error {
if name == "" {
return fmt.Errorf("Repository name can't be empty")
} |
e6806223 |
if name == api.NoBaseImageSpecifier {
return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier) |
694df3ff |
}
return nil
} |