694df3ff |
package distribution
import (
"fmt" |
0380fbff |
"runtime" |
694df3ff |
|
3a127939 |
"github.com/docker/distribution/reference" |
e6806223 |
"github.com/docker/docker/api" |
694df3ff |
"github.com/docker/docker/distribution/metadata" |
572ce802 |
"github.com/docker/docker/pkg/progress" |
3a127939 |
refstore "github.com/docker/docker/reference" |
694df3ff |
"github.com/docker/docker/registry" |
7a855799 |
"github.com/opencontainers/go-digest" |
ebcb7d6b |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
572ce802 |
"golang.org/x/net/context" |
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.
// |
ce8e529e |
Pull(ctx context.Context, ref reference.Named, os string) 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
}
|
3a127939 |
// makes sure name is not `scratch`
if err := ValidateRepoName(repoInfo.Name); err != nil { |
694df3ff |
return err
}
|
3a127939 |
endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) |
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 |
1ac4c61c |
// By default it is false, which means that if an 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 { |
3c7676a0 |
if imagePullConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 {
continue
}
|
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
}
}
|
3a127939 |
logrus.Debugf("Trying to pull %s from %s %s", reference.FamiliarName(repoInfo.Name), endpoint.URL, endpoint.Version) |
694df3ff |
|
572ce802 |
puller, err := newPuller(endpoint, repoInfo, imagePullConfig) |
694df3ff |
if err != nil { |
87338bf0 |
lastErr = err |
694df3ff |
continue
} |
0380fbff |
|
ce8e529e |
// Make sure we default the OS if it hasn't been supplied
if imagePullConfig.OS == "" {
imagePullConfig.OS = runtime.GOOS |
0380fbff |
}
|
ce8e529e |
if err := puller.Pull(ctx, ref, imagePullConfig.OS); 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 |
} |
86061441 |
logrus.Infof("Attempting next endpoint for pull after error: %v", err) |
694df3ff |
continue
} |
8f26fe4f |
logrus.Errorf("Not continuing with pull after error: %v", err) |
a12b4661 |
return TranslatePullError(err, ref) |
694df3ff |
}
|
3a127939 |
imagePullConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "pull") |
694df3ff |
return nil
}
|
87338bf0 |
if lastErr == nil { |
3a127939 |
lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref)) |
694df3ff |
}
|
a12b4661 |
return TranslatePullError(lastErr, ref) |
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 |
}
}
|
f3711704 |
// ValidateRepoName validates the name of a repository. |
3a127939 |
func ValidateRepoName(name reference.Named) error {
if reference.FamiliarName(name) == api.NoBaseImageSpecifier { |
ebcb7d6b |
return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier)) |
694df3ff |
}
return nil
} |
33984f25 |
|
3a127939 |
func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error { |
c85eb008 |
dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst) |
33984f25 |
if err != nil {
return err
}
|
80522398 |
if oldTagID, err := store.Get(dgstRef); err == nil {
if oldTagID != id { |
33984f25 |
// Updating digests not supported by reference store |
80522398 |
logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id) |
33984f25 |
}
return nil |
3a127939 |
} else if err != refstore.ErrDoesNotExist { |
33984f25 |
return err
}
|
80522398 |
return store.AddDigest(dgstRef, id, true) |
33984f25 |
} |