From 495d623ae5b1b601667d82682c5c265be189a29b Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 3 Dec 2020 21:15:18 +0000 Subject: [PATCH] Add fallback for pull by tag Some registries seem to be non-conformant and return a not found error when pulling by digest (which docker now does all the time). To work around this, fallback when all of the following are true: 1. Image reference is a tag 2. Tag->digest resolution succeeds 3. Fetch by resolved digest fails with a "not found" error. This is intentionally not caching the manifests to reduce complexity for this edge case. Signed-off-by: Brian Goff --- distribution/errors.go | 17 ++++++++++++++++ distribution/pull_v2.go | 43 +++++++++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/distribution/errors.go b/distribution/errors.go index 194287d8a2..02f587020e 100644 --- a/distribution/errors.go +++ b/distribution/errors.go @@ -112,6 +112,23 @@ func TranslatePullError(err error, ref reference.Named) error { return errdefs.Unknown(err) } +func isNotFound(err error) bool { + switch v := err.(type) { + case errcode.Errors: + for _, e := range v { + if isNotFound(e) { + return true + } + } + case errcode.Error: + switch v.Code { + case errcode.ErrorCodeDenied, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown: + return true + } + } + return false +} + // continueOnError returns true if we should fallback to the next endpoint // as a result of this error. func continueOnError(err error, mirrorEndpoint bool) bool { diff --git a/distribution/pull_v2.go b/distribution/pull_v2.go index b5db4ee006..12497ea890 100644 --- a/distribution/pull_v2.go +++ b/distribution/pull_v2.go @@ -343,16 +343,19 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform dgst digest.Digest mt string size int64 + tagged reference.NamedTagged + isTagged bool ) if digested, isDigested := ref.(reference.Canonical); isDigested { dgst = digested.Digest() tagOrDigest = digested.String() - } else if tagged, isTagged := ref.(reference.NamedTagged); isTagged { + } else if tagged, isTagged = ref.(reference.NamedTagged); isTagged { tagService := p.repo.Tags(ctx) desc, err := tagService.Get(ctx, tagged.Tag()) if err != nil { return false, allowV1Fallback(err) } + dgst = desc.Digest tagOrDigest = tagged.Tag() mt = desc.MediaType @@ -367,13 +370,40 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform "remote": ref, })) - manifest, err := p.manifestStore.Get(ctx, specs.Descriptor{ + desc := specs.Descriptor{ MediaType: mt, Digest: dgst, Size: size, - }) + } + manifest, err := p.manifestStore.Get(ctx, desc) if err != nil { - return false, err + if isTagged && isNotFound(errors.Cause(err)) { + logrus.WithField("ref", ref).WithError(err).Debug("Falling back to pull manifest by tag") + + msg := `%s Failed to pull manifest by the resolved digest. This registry does not + appear to conform to the distribution registry specification; falling back to + pull by tag. This fallback is DEPRECATED, and will be removed in a future + release. Please contact admins of %s. %s +` + + warnEmoji := "\U000026A0\U0000FE0F" + progress.Messagef(p.config.ProgressOutput, "WARNING", msg, warnEmoji, p.endpoint.URL, warnEmoji) + + // Fetch by tag worked, but fetch by digest didn't. + // This is a broken registry implementation. + // We'll fallback to the old behavior and get the manifest by tag. + var ms distribution.ManifestService + ms, err = p.repo.Manifests(ctx) + if err != nil { + return false, err + } + + manifest, err = ms.Get(ctx, "", distribution.WithTag(tagged.Tag())) + err = errors.Wrap(err, "error after falling back to get manifest by tag") + } + if err != nil { + return false, err + } } if manifest == nil { @@ -818,11 +848,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf return "", "", err } - manifest, err := p.manifestStore.Get(ctx, specs.Descriptor{ + desc := specs.Descriptor{ Digest: match.Digest, Size: match.Size, MediaType: match.MediaType, - }) + } + manifest, err := p.manifestStore.Get(ctx, desc) if err != nil { return "", "", err }