Skip to content

Commit

Permalink
Add fallback for pull by tag
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
cpuguy83 committed Dec 4, 2020
1 parent ab373df commit 495d623
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 6 deletions.
17 changes: 17 additions & 0 deletions distribution/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
43 changes: 37 additions & 6 deletions distribution/pull_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 495d623

Please sign in to comment.