support tag@digest notation

Vendor in the latest HEAd of containers/common to implicitly support the
tag@digest notation for images.  To remain compatible with Docker, the
tag will be stripped off the image reference and is entirely ignored.

Fixes: #6721
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2021-05-27 16:13:54 +02:00
parent 59236762ec
commit fb4a0c572e
17 changed files with 161 additions and 30 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/containers/image/v5/docker/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// NormalizeName normalizes the provided name according to the conventions by
@ -40,6 +41,11 @@ func NormalizeName(name string) (reference.Named, error) {
}
if _, hasTag := named.(reference.NamedTagged); hasTag {
// Strip off the tag of a tagged and digested reference.
named, err = normalizeTaggedDigestedNamed(named)
if err != nil {
return nil, err
}
return named, nil
}
if _, hasDigest := named.(reference.Digested); hasDigest {
@ -90,3 +96,48 @@ func ToNameTagPairs(repoTags []reference.Named) ([]NameTagPair, error) {
}
return pairs, nil
}
// normalizeTaggedDigestedString strips the tag off the specified string iff it
// is tagged and digested. Note that the tag is entirely ignored to match
// Docker behavior.
func normalizeTaggedDigestedString(s string) (string, error) {
// Note that the input string is not expected to be parseable, so we
// return it verbatim in error cases.
ref, err := reference.Parse(s)
if err != nil {
return "", err
}
named, ok := ref.(reference.Named)
if !ok {
return s, nil
}
named, err = normalizeTaggedDigestedNamed(named)
if err != nil {
return "", err
}
return named.String(), nil
}
// normalizeTaggedDigestedNamed strips the tag off the specified named
// reference iff it is tagged and digested. Note that the tag is entirely
// ignored to match Docker behavior.
func normalizeTaggedDigestedNamed(named reference.Named) (reference.Named, error) {
_, isTagged := named.(reference.NamedTagged)
if !isTagged {
return named, nil
}
digested, isDigested := named.(reference.Digested)
if !isDigested {
return named, nil
}
// Now strip off the tag.
newNamed := reference.TrimNamed(named)
// And re-add the digest.
newNamed, err := reference.WithDigest(newNamed, digested.Digest())
if err != nil {
return named, err
}
logrus.Debugf("Stripped off tag from tagged and digested reference %q", named.String())
return newNamed, nil
}

View File

@ -52,6 +52,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
options = &PullOptions{}
}
var possiblyUnqualifiedName string // used for short-name resolution
ref, err := alltransports.ParseImageName(name)
if err != nil {
// If the image clearly refers to a local one, we can look it up directly.
@ -67,6 +68,15 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
return []*Image{local}, err
}
// Docker compat: strip off the tag iff name is tagged and digested
// (e.g., fedora:latest@sha256...). In that case, the tag is stripped
// off and entirely ignored. The digest is the sole source of truth.
normalizedName, normalizeError := normalizeTaggedDigestedString(name)
if normalizeError != nil {
return nil, normalizeError
}
name = normalizedName
// If the input does not include a transport assume it refers
// to a registry.
dockerRef, dockerErr := alltransports.ParseImageName("docker://" + name)
@ -74,6 +84,17 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
return nil, err
}
ref = dockerRef
possiblyUnqualifiedName = name
} else if ref.Transport().Name() == registryTransport.Transport.Name() {
// Normalize the input if we're referring to the docker
// transport directly. That makes sure that a `docker://fedora`
// will resolve directly to `docker.io/library/fedora:latest`
// and not be subject to short-name resolution.
named := ref.DockerReference()
if named == nil {
return nil, errors.New("internal error: unexpected nil reference")
}
possiblyUnqualifiedName = named.String()
}
if options.AllTags && ref.Transport().Name() != registryTransport.Transport.Name() {
@ -94,7 +115,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
// DOCKER REGISTRY
case registryTransport.Transport.Name():
pulledImages, pullError = r.copyFromRegistry(ctx, ref, strings.TrimPrefix(name, "docker://"), pullPolicy, options)
pulledImages, pullError = r.copyFromRegistry(ctx, ref, possiblyUnqualifiedName, pullPolicy, options)
// DOCKER ARCHIVE
case dockerArchiveTransport.Transport.Name():

View File

@ -180,6 +180,15 @@ func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image,
}
logrus.Debugf("Found image %q in local containers storage (%s)", name, storageRef.StringWithinTransport())
return r.storageToImage(img, storageRef), "", nil
} else {
// Docker compat: strip off the tag iff name is tagged and digested
// (e.g., fedora:latest@sha256...). In that case, the tag is stripped
// off and entirely ignored. The digest is the sole source of truth.
normalizedName, err := normalizeTaggedDigestedString(name)
if err != nil {
return nil, "", err
}
name = normalizedName
}
originalName := name