dep: Update containers/image to 1d7e25b91705e4d1cddb5396baf112caeb1119f3
Signed-off-by: Andrew Pilloud <andrewpilloud@igneoussystems.com>
This commit is contained in:
parent
54c176e336
commit
de9995d5f0
84 changed files with 3091 additions and 748 deletions
8
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
8
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
|
@ -91,7 +91,7 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
|
|||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *daemonImageDestination) Close() {
|
||||
func (d *daemonImageDestination) Close() error {
|
||||
if !d.committed {
|
||||
logrus.Debugf("docker-daemon: Closing tar stream to abort loading")
|
||||
// In principle, goroutineCancel() should abort the HTTP request and stop the process from continuing.
|
||||
|
@ -107,10 +107,10 @@ func (d *daemonImageDestination) Close() {
|
|||
d.writer.CloseWithError(errors.New("Aborting upload, daemonImageDestination closed without a previous .Commit()"))
|
||||
}
|
||||
d.goroutineCancel()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
||||
func (d *daemonImageDestination) Reference() types.ImageReference {
|
||||
return d.ref
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func (d *daemonImageDestination) PutManifest(m []byte) error {
|
|||
// a hostname-qualified reference.
|
||||
// See https://github.com/containers/image/issues/72 for a more detailed
|
||||
// analysis and explanation.
|
||||
refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.FullName(), d.namedTaggedRef.Tag())
|
||||
refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.Name(), d.namedTaggedRef.Tag())
|
||||
|
||||
items := []manifestItem{{
|
||||
Config: man.Config.Digest.String(),
|
||||
|
|
49
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
49
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
|
@ -10,6 +10,7 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/compression"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/opencontainers/go-digest"
|
||||
|
@ -91,8 +92,8 @@ func (s *daemonImageSource) Reference() types.ImageReference {
|
|||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *daemonImageSource) Close() {
|
||||
_ = os.Remove(s.tarCopyPath)
|
||||
func (s *daemonImageSource) Close() error {
|
||||
return os.Remove(s.tarCopyPath)
|
||||
}
|
||||
|
||||
// tarReadCloser is a way to close the backing file of a tar.Reader when the user no longer needs the tar component.
|
||||
|
@ -334,6 +335,18 @@ func (s *daemonImageSource) GetTargetManifest(digest digest.Digest) ([]byte, str
|
|||
return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
|
||||
type readCloseWrapper struct {
|
||||
io.Reader
|
||||
closeFunc func() error
|
||||
}
|
||||
|
||||
func (r readCloseWrapper) Close() error {
|
||||
if r.closeFunc != nil {
|
||||
return r.closeFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *daemonImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if err := s.ensureCachedDataIsPresent(); err != nil {
|
||||
|
@ -349,7 +362,37 @@ func (s *daemonImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return stream, li.size, nil
|
||||
|
||||
// In order to handle the fact that digests != diffIDs (and thus that a
|
||||
// caller which is trying to verify the blob will run into problems),
|
||||
// we need to decompress blobs. This is a bit ugly, but it's a
|
||||
// consequence of making everything addressable by their DiffID rather
|
||||
// than by their digest...
|
||||
//
|
||||
// In particular, because the v2s2 manifest being generated uses
|
||||
// DiffIDs, any caller of GetBlob is going to be asking for DiffIDs of
|
||||
// layers not their _actual_ digest. The result is that copy/... will
|
||||
// be verifing a "digest" which is not the actual layer's digest (but
|
||||
// is instead the DiffID).
|
||||
|
||||
decompressFunc, reader, err := compression.DetectCompression(stream)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "Detecting compression in blob %s", info.Digest)
|
||||
}
|
||||
|
||||
if decompressFunc != nil {
|
||||
reader, err = decompressFunc(reader)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "Decompressing blob %s stream", info.Digest)
|
||||
}
|
||||
}
|
||||
|
||||
newStream := readCloseWrapper{
|
||||
Reader: reader,
|
||||
closeFunc: stream.Close,
|
||||
}
|
||||
|
||||
return newStream, li.size, nil
|
||||
}
|
||||
|
||||
return nil, 0, errors.Errorf("Unknown blob %s", info.Digest)
|
||||
|
|
20
vendor/github.com/containers/image/docker/daemon/daemon_transport.go
generated
vendored
20
vendor/github.com/containers/image/docker/daemon/daemon_transport.go
generated
vendored
|
@ -5,10 +5,15 @@ import (
|
|||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// Transport is an ImageTransport for images managed by a local Docker daemon.
|
||||
var Transport = daemonTransport{}
|
||||
|
||||
|
@ -46,11 +51,11 @@ type daemonReference struct {
|
|||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
|
||||
func ParseReference(refString string) (types.ImageReference, error) {
|
||||
// This is intended to be compatible with reference.ParseIDOrReference, but more strict about refusing some of the ambiguous cases.
|
||||
// This is intended to be compatible with reference.ParseAnyReference, but more strict about refusing some of the ambiguous cases.
|
||||
// In particular, this rejects unprefixed digest values (64 hex chars), and sha256 digest prefixes (sha256:fewer-than-64-hex-chars).
|
||||
|
||||
// digest:hexstring is structurally the same as a reponame:tag (meaning docker.io/library/reponame:tag).
|
||||
// reference.ParseIDOrReference interprets such strings as digests.
|
||||
// reference.ParseAnyReference interprets such strings as digests.
|
||||
if dgst, err := digest.Parse(refString); err == nil {
|
||||
// The daemon explicitly refuses to tag images with a reponame equal to digest.Canonical - but _only_ this digest name.
|
||||
// Other digest references are ambiguous, so refuse them.
|
||||
|
@ -60,11 +65,11 @@ func ParseReference(refString string) (types.ImageReference, error) {
|
|||
return NewReference(dgst, nil)
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNamed(refString) // This also rejects unprefixed digest values
|
||||
ref, err := reference.ParseNormalizedNamed(refString) // This also rejects unprefixed digest values
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ref.Name() == digest.Canonical.String() {
|
||||
if reference.FamiliarName(ref) == digest.Canonical.String() {
|
||||
return nil, errors.Errorf("Invalid docker-daemon: reference %s: The %s repository name is reserved for (non-shortened) digest references", refString, digest.Canonical)
|
||||
}
|
||||
return NewReference("", ref)
|
||||
|
@ -77,10 +82,11 @@ func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference,
|
|||
}
|
||||
if ref != nil {
|
||||
if reference.IsNameOnly(ref) {
|
||||
return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", ref.String())
|
||||
return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
}
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// docker/reference does not handle that, so fail.
|
||||
// Most versions of docker/reference do not handle that (ignoring the tag), so reject such input.
|
||||
// This MAY be accepted in the future.
|
||||
_, isTagged := ref.(reference.NamedTagged)
|
||||
_, isDigested := ref.(reference.Canonical)
|
||||
if isTagged && isDigested {
|
||||
|
@ -108,7 +114,7 @@ func (ref daemonReference) StringWithinTransport() string {
|
|||
case ref.id != "":
|
||||
return ref.id.String()
|
||||
case ref.ref != nil:
|
||||
return ref.ref.String()
|
||||
return reference.FamiliarString(ref.ref)
|
||||
default: // Coverage: Should never happen, NewReference above should refuse such values.
|
||||
panic("Internal inconsistency: daemonReference has empty id and nil ref")
|
||||
}
|
||||
|
|
56
vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go
generated
vendored
56
vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go
generated
vendored
|
@ -50,15 +50,12 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err
|
|||
{"sha256:XX23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "", ""}, // Invalid digest value
|
||||
{"UPPERCASEISINVALID", "", ""}, // Invalid reference input
|
||||
{"busybox", "", ""}, // Missing tag or digest
|
||||
{"busybox:latest", "", "busybox:latest"}, // Explicit tag
|
||||
{"busybox@" + sha256digest, "", "busybox@" + sha256digest}, // Explicit digest
|
||||
{"busybox:latest", "", "docker.io/library/busybox:latest"}, // Explicit tag
|
||||
{"busybox@" + sha256digest, "", "docker.io/library/busybox@" + sha256digest}, // Explicit digest
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// github.com/docker/reference handles that by dropping the tag. That is not obviously the
|
||||
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
|
||||
// This test case should not be construed to make this an API promise.
|
||||
// FIXME? Instead work extra hard to reject such input?
|
||||
{"busybox:latest@" + sha256digest, "", "busybox@" + sha256digest}, // Both tag and digest
|
||||
{"docker.io/library/busybox:latest", "", "busybox:latest"}, // All implied values explicitly specified
|
||||
// Most versions of docker/reference do not handle that (ignoring the tag), so we reject such input.
|
||||
{"busybox:latest@" + sha256digest, "", ""}, // Both tag and digest
|
||||
{"docker.io/library/busybox:latest", "", "docker.io/library/busybox:latest"}, // All implied values explicitly specified
|
||||
} {
|
||||
ref, err := fn(c.input)
|
||||
if c.expectedID == "" && c.expectedRef == "" {
|
||||
|
@ -67,43 +64,37 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err
|
|||
require.NoError(t, err, c.input)
|
||||
daemonRef, ok := ref.(daemonReference)
|
||||
require.True(t, ok, c.input)
|
||||
// If we don't reject the input, the interpretation must be consistent for reference.ParseIDOrReference
|
||||
dockerID, dockerRef, err := reference.ParseIDOrReference(c.input)
|
||||
// If we don't reject the input, the interpretation must be consistent with reference.ParseAnyReference
|
||||
dockerRef, err := reference.ParseAnyReference(c.input)
|
||||
require.NoError(t, err, c.input)
|
||||
|
||||
if c.expectedRef == "" {
|
||||
assert.Equal(t, c.expectedID, daemonRef.id.String(), c.input)
|
||||
assert.Nil(t, daemonRef.ref, c.input)
|
||||
|
||||
assert.Equal(t, c.expectedID, dockerID.String(), c.input)
|
||||
assert.Nil(t, dockerRef, c.input)
|
||||
_, ok := dockerRef.(reference.Digested)
|
||||
require.True(t, ok, c.input)
|
||||
assert.Equal(t, c.expectedID, dockerRef.String(), c.input)
|
||||
} else {
|
||||
assert.Equal(t, "", daemonRef.id.String(), c.input)
|
||||
require.NotNil(t, daemonRef.ref, c.input)
|
||||
assert.Equal(t, c.expectedRef, daemonRef.ref.String(), c.input)
|
||||
|
||||
assert.Equal(t, "", dockerID.String(), c.input)
|
||||
require.NotNil(t, dockerRef, c.input)
|
||||
_, ok := dockerRef.(reference.Named)
|
||||
require.True(t, ok, c.input)
|
||||
assert.Equal(t, c.expectedRef, dockerRef.String(), c.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time.
|
||||
type refWithTagAndDigest struct{ reference.Canonical }
|
||||
|
||||
func (ref refWithTagAndDigest) Tag() string {
|
||||
return "notLatest"
|
||||
}
|
||||
|
||||
// A common list of reference formats to test for the various ImageReference methods.
|
||||
// (For IDs it is much simpler, we simply use them unmodified)
|
||||
var validNamedReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport string }{
|
||||
{"busybox:notlatest", "busybox:notlatest", "busybox:notlatest"}, // Explicit tag
|
||||
{"busybox" + sha256digest, "busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest
|
||||
{"docker.io/library/busybox:latest", "busybox:latest", "busybox:latest"}, // All implied values explicitly specified
|
||||
{"example.com/ns/foo:bar", "example.com/ns/foo:bar", "example.com/ns/foo:bar"}, // All values explicitly specified
|
||||
{"busybox:notlatest", "docker.io/library/busybox:notlatest", "busybox:notlatest"}, // Explicit tag
|
||||
{"busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest
|
||||
{"docker.io/library/busybox:latest", "docker.io/library/busybox:latest", "busybox:latest"}, // All implied values explicitly specified
|
||||
{"example.com/ns/foo:bar", "example.com/ns/foo:bar", "example.com/ns/foo:bar"}, // All values explicitly specified
|
||||
}
|
||||
|
||||
func TestNewReference(t *testing.T) {
|
||||
|
@ -119,7 +110,7 @@ func TestNewReference(t *testing.T) {
|
|||
|
||||
// Named references
|
||||
for _, c := range validNamedReferenceTestCases {
|
||||
parsed, err := reference.ParseNamed(c.input)
|
||||
parsed, err := reference.ParseNormalizedNamed(c.input)
|
||||
require.NoError(t, err)
|
||||
ref, err := NewReference("", parsed)
|
||||
require.NoError(t, err, c.input)
|
||||
|
@ -131,24 +122,25 @@ func TestNewReference(t *testing.T) {
|
|||
}
|
||||
|
||||
// Both an ID and a named reference provided
|
||||
parsed, err := reference.ParseNamed("busybox:latest")
|
||||
parsed, err := reference.ParseNormalizedNamed("busybox:latest")
|
||||
require.NoError(t, err)
|
||||
_, err = NewReference(id, parsed)
|
||||
assert.Error(t, err)
|
||||
|
||||
// A reference with neither a tag nor digest
|
||||
parsed, err = reference.ParseNamed("busybox")
|
||||
parsed, err = reference.ParseNormalizedNamed("busybox")
|
||||
require.NoError(t, err)
|
||||
_, err = NewReference("", parsed)
|
||||
assert.Error(t, err)
|
||||
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
parsed, err = reference.ParseNamed("busybox@" + sha256digest)
|
||||
parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@" + sha256digest)
|
||||
require.NoError(t, err)
|
||||
refDigested, ok := parsed.(reference.Canonical)
|
||||
_, ok = parsed.(reference.Canonical)
|
||||
require.True(t, ok)
|
||||
tagDigestRef := refWithTagAndDigest{refDigested}
|
||||
_, err = NewReference("", tagDigestRef)
|
||||
_, ok = parsed.(reference.NamedTagged)
|
||||
require.True(t, ok)
|
||||
_, err = NewReference("", parsed)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
|
7
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
7
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
|
@ -164,11 +165,11 @@ func hasFile(files []os.FileInfo, name string) bool {
|
|||
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
registry := ref.ref.Hostname()
|
||||
registry := reference.Domain(ref.ref)
|
||||
if registry == dockerHostname {
|
||||
registry = dockerRegistry
|
||||
}
|
||||
username, password, err := getAuth(ctx, ref.ref.Hostname())
|
||||
username, password, err := getAuth(ctx, reference.Domain(ref.ref))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -202,7 +203,7 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool,
|
|||
signatureBase: sigBase,
|
||||
scope: authScope{
|
||||
actions: actions,
|
||||
remoteName: ref.ref.RemoteName(),
|
||||
remoteName: reference.Path(ref.ref),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
5
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
5
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -34,12 +35,12 @@ func newImage(ctx *types.SystemContext, ref dockerReference) (types.Image, error
|
|||
|
||||
// SourceRefFullName returns a fully expanded name for the repository this image is in.
|
||||
func (i *Image) SourceRefFullName() string {
|
||||
return i.src.ref.ref.FullName()
|
||||
return i.src.ref.ref.Name()
|
||||
}
|
||||
|
||||
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
|
||||
func (i *Image) GetRepositoryTags() ([]string, error) {
|
||||
url := fmt.Sprintf(tagsURL, i.src.ref.ref.RemoteName())
|
||||
url := fmt.Sprintf(tagsURL, reference.Path(i.src.ref.ref))
|
||||
res, err := i.src.c.makeRequest("GET", url, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
41
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
41
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
|
@ -11,6 +11,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
|
@ -58,7 +59,8 @@ func (d *dockerImageDestination) Reference() types.ImageReference {
|
|||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *dockerImageDestination) Close() {
|
||||
func (d *dockerImageDestination) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
@ -98,31 +100,18 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
|
|||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
if inputInfo.Digest.String() != "" {
|
||||
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), inputInfo.Digest.String())
|
||||
|
||||
logrus.Debugf("Checking %s", checkURL)
|
||||
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
|
||||
if err != nil {
|
||||
haveBlob, size, err := d.HasBlob(inputInfo)
|
||||
if err != nil && err != types.ErrBlobNotFound {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
logrus.Debugf("... already exists, not uploading")
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: getBlobSize(res)}, nil
|
||||
case http.StatusUnauthorized:
|
||||
logrus.Debugf("... not authorized")
|
||||
return types.BlobInfo{}, errors.Errorf("not authorized to read from destination repository %s", d.ref.ref.RemoteName())
|
||||
case http.StatusNotFound:
|
||||
// noop
|
||||
default:
|
||||
return types.BlobInfo{}, errors.Errorf("failed to read from destination repository %s: %v", d.ref.ref.RemoteName(), http.StatusText(res.StatusCode))
|
||||
// Now err == nil || err == types.ErrBlobNotFound
|
||||
if err == nil && haveBlob {
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil
|
||||
}
|
||||
logrus.Debugf("... failed, status %d", res.StatusCode)
|
||||
}
|
||||
|
||||
// FIXME? Chunked upload, progress reporting, etc.
|
||||
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.ref.RemoteName())
|
||||
uploadURL := fmt.Sprintf(blobUploadURL, reference.Path(d.ref.ref))
|
||||
logrus.Debugf("Uploading %s", uploadURL)
|
||||
res, err := d.c.makeRequest("POST", uploadURL, nil, nil)
|
||||
if err != nil {
|
||||
|
@ -178,7 +167,7 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
|||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), info.Digest.String())
|
||||
checkURL := fmt.Sprintf(blobsURL, reference.Path(d.ref.ref), info.Digest.String())
|
||||
|
||||
logrus.Debugf("Checking %s", checkURL)
|
||||
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
|
||||
|
@ -192,15 +181,13 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
|||
return true, getBlobSize(res), nil
|
||||
case http.StatusUnauthorized:
|
||||
logrus.Debugf("... not authorized")
|
||||
return false, -1, errors.Errorf("not authorized to read from destination repository %s", d.ref.ref.RemoteName())
|
||||
return false, -1, errors.Errorf("not authorized to read from destination repository %s", reference.Path(d.ref.ref))
|
||||
case http.StatusNotFound:
|
||||
logrus.Debugf("... not present")
|
||||
return false, -1, types.ErrBlobNotFound
|
||||
default:
|
||||
logrus.Errorf("failed to read from destination repository %s: %v", d.ref.ref.RemoteName(), http.StatusText(res.StatusCode))
|
||||
return false, -1, errors.Errorf("failed to read from destination repository %s: %v", reference.Path(d.ref.ref), http.StatusText(res.StatusCode))
|
||||
}
|
||||
logrus.Debugf("... failed, status %d, ignoring", res.StatusCode)
|
||||
return false, -1, types.ErrBlobNotFound
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
|
@ -214,11 +201,11 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||
}
|
||||
d.manifestDigest = digest
|
||||
|
||||
reference, err := d.ref.tagOrDigest()
|
||||
refTail, err := d.ref.tagOrDigest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), reference)
|
||||
url := fmt.Sprintf(manifestURL, reference.Path(d.ref.ref), refTail)
|
||||
|
||||
headers := map[string][]string{}
|
||||
mimeType := manifest.GuessMIMEType(m)
|
||||
|
|
14
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
14
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
|
@ -11,6 +11,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
|
@ -64,7 +65,8 @@ func (s *dockerImageSource) Reference() types.ImageReference {
|
|||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *dockerImageSource) Close() {
|
||||
func (s *dockerImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
|
||||
|
@ -91,7 +93,7 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
|||
}
|
||||
|
||||
func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) {
|
||||
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), tagOrDigest)
|
||||
url := fmt.Sprintf(manifestURL, reference.Path(s.ref.ref), tagOrDigest)
|
||||
headers := make(map[string][]string)
|
||||
headers["Accept"] = s.requestedManifestMIMETypes
|
||||
res, err := s.c.makeRequest("GET", url, headers, nil)
|
||||
|
@ -177,7 +179,7 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||
return s.getExternalBlob(info.URLs)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(blobsURL, s.ref.ref.RemoteName(), info.Digest.String())
|
||||
url := fmt.Sprintf(blobsURL, reference.Path(s.ref.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", url)
|
||||
res, err := s.c.makeRequest("GET", url, nil, nil)
|
||||
if err != nil {
|
||||
|
@ -271,11 +273,11 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||
headers := make(map[string][]string)
|
||||
headers["Accept"] = []string{manifest.DockerV2Schema2MediaType}
|
||||
|
||||
reference, err := ref.tagOrDigest()
|
||||
refTail, err := ref.tagOrDigest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getURL := fmt.Sprintf(manifestURL, ref.ref.RemoteName(), reference)
|
||||
getURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), refTail)
|
||||
get, err := c.makeRequest("GET", getURL, headers, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -294,7 +296,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||
}
|
||||
|
||||
digest := get.Header.Get("Docker-Content-Digest")
|
||||
deleteURL := fmt.Sprintf(manifestURL, ref.ref.RemoteName(), digest)
|
||||
deleteURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), digest)
|
||||
|
||||
// When retrieving the digest from a registry >= 2.3 use the following header:
|
||||
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
||||
|
|
18
vendor/github.com/containers/image/docker/docker_transport.go
generated
vendored
18
vendor/github.com/containers/image/docker/docker_transport.go
generated
vendored
|
@ -6,10 +6,15 @@ import (
|
|||
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// Transport is an ImageTransport for Docker registry-hosted images.
|
||||
var Transport = dockerTransport{}
|
||||
|
||||
|
@ -45,21 +50,22 @@ func ParseReference(refString string) (types.ImageReference, error) {
|
|||
if !strings.HasPrefix(refString, "//") {
|
||||
return nil, errors.Errorf("docker: image reference %s does not start with //", refString)
|
||||
}
|
||||
ref, err := reference.ParseNamed(strings.TrimPrefix(refString, "//"))
|
||||
ref, err := reference.ParseNormalizedNamed(strings.TrimPrefix(refString, "//"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref = reference.WithDefaultTag(ref)
|
||||
ref = reference.TagNameOnly(ref)
|
||||
return NewReference(ref)
|
||||
}
|
||||
|
||||
// NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.IsNameOnly().
|
||||
func NewReference(ref reference.Named) (types.ImageReference, error) {
|
||||
if reference.IsNameOnly(ref) {
|
||||
return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", ref.String())
|
||||
return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
}
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// docker/reference does not handle that, so fail.
|
||||
// The docker/distribution API does not really support that (we can’t ask for an image with a specific
|
||||
// tag and digest), so fail. This MAY be accepted in the future.
|
||||
// (Even if it were supported, the semantics of policy namespaces are unclear - should we drop
|
||||
// the tag or the digest first?)
|
||||
_, isTagged := ref.(reference.NamedTagged)
|
||||
|
@ -82,7 +88,7 @@ func (ref dockerReference) Transport() types.ImageTransport {
|
|||
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
|
||||
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
|
||||
func (ref dockerReference) StringWithinTransport() string {
|
||||
return "//" + ref.ref.String()
|
||||
return "//" + reference.FamiliarString(ref.ref)
|
||||
}
|
||||
|
||||
// DockerReference returns a Docker reference associated with this reference
|
||||
|
@ -152,5 +158,5 @@ func (ref dockerReference) tagOrDigest() (string, error) {
|
|||
return ref.Tag(), nil
|
||||
}
|
||||
// This should not happen, NewReference above refuses reference.IsNameOnly values.
|
||||
return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", ref.ref.String())
|
||||
return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", reference.FamiliarString(ref.ref))
|
||||
}
|
||||
|
|
50
vendor/github.com/containers/image/docker/docker_transport_test.go
generated
vendored
50
vendor/github.com/containers/image/docker/docker_transport_test.go
generated
vendored
|
@ -42,18 +42,16 @@ func TestParseReference(t *testing.T) {
|
|||
// testParseReference is a test shared for Transport.ParseReference and ParseReference.
|
||||
func testParseReference(t *testing.T, fn func(string) (types.ImageReference, error)) {
|
||||
for _, c := range []struct{ input, expected string }{
|
||||
{"busybox", ""}, // Missing // prefix
|
||||
{"//busybox:notlatest", "busybox:notlatest"}, // Explicit tag
|
||||
{"//busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest
|
||||
{"//busybox", "busybox:latest"}, // Default tag
|
||||
{"busybox", ""}, // Missing // prefix
|
||||
{"//busybox:notlatest", "docker.io/library/busybox:notlatest"}, // Explicit tag
|
||||
{"//busybox" + sha256digest, "docker.io/library/busybox" + sha256digest}, // Explicit digest
|
||||
{"//busybox", "docker.io/library/busybox:latest"}, // Default tag
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// github.com/docker/reference handles that by dropping the tag. That is not obviously the
|
||||
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
|
||||
// This test case should not be construed to make this an API promise.
|
||||
// FIXME? Instead work extra hard to reject such input?
|
||||
{"//busybox:latest" + sha256digest, "busybox" + sha256digest}, // Both tag and digest
|
||||
{"//docker.io/library/busybox:latest", "busybox:latest"}, // All implied values explicitly specified
|
||||
{"//UPPERCASEISINVALID", ""}, // Invalid input
|
||||
// The docker/distribution API does not really support that (we can’t ask for an image with a specific
|
||||
// tag and digest), so fail. This MAY be accepted in the future.
|
||||
{"//busybox:latest" + sha256digest, ""}, // Both tag and digest
|
||||
{"//docker.io/library/busybox:latest", "docker.io/library/busybox:latest"}, // All implied values explicitly specified
|
||||
{"//UPPERCASEISINVALID", ""}, // Invalid input
|
||||
} {
|
||||
ref, err := fn(c.input)
|
||||
if c.expected == "" {
|
||||
|
@ -67,24 +65,17 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err
|
|||
}
|
||||
}
|
||||
|
||||
// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time.
|
||||
type refWithTagAndDigest struct{ reference.Canonical }
|
||||
|
||||
func (ref refWithTagAndDigest) Tag() string {
|
||||
return "notLatest"
|
||||
}
|
||||
|
||||
// A common list of reference formats to test for the various ImageReference methods.
|
||||
var validReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport string }{
|
||||
{"busybox:notlatest", "busybox:notlatest", "//busybox:notlatest"}, // Explicit tag
|
||||
{"busybox" + sha256digest, "busybox" + sha256digest, "//busybox" + sha256digest}, // Explicit digest
|
||||
{"docker.io/library/busybox:latest", "busybox:latest", "//busybox:latest"}, // All implied values explicitly specified
|
||||
{"example.com/ns/foo:bar", "example.com/ns/foo:bar", "//example.com/ns/foo:bar"}, // All values explicitly specified
|
||||
{"busybox:notlatest", "docker.io/library/busybox:notlatest", "//busybox:notlatest"}, // Explicit tag
|
||||
{"busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, "//busybox" + sha256digest}, // Explicit digest
|
||||
{"docker.io/library/busybox:latest", "docker.io/library/busybox:latest", "//busybox:latest"}, // All implied values explicitly specified
|
||||
{"example.com/ns/foo:bar", "example.com/ns/foo:bar", "//example.com/ns/foo:bar"}, // All values explicitly specified
|
||||
}
|
||||
|
||||
func TestNewReference(t *testing.T) {
|
||||
for _, c := range validReferenceTestCases {
|
||||
parsed, err := reference.ParseNamed(c.input)
|
||||
parsed, err := reference.ParseNormalizedNamed(c.input)
|
||||
require.NoError(t, err)
|
||||
ref, err := NewReference(parsed)
|
||||
require.NoError(t, err, c.input)
|
||||
|
@ -94,18 +85,19 @@ func TestNewReference(t *testing.T) {
|
|||
}
|
||||
|
||||
// Neither a tag nor digest
|
||||
parsed, err := reference.ParseNamed("busybox")
|
||||
parsed, err := reference.ParseNormalizedNamed("busybox")
|
||||
require.NoError(t, err)
|
||||
_, err = NewReference(parsed)
|
||||
assert.Error(t, err)
|
||||
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
parsed, err = reference.ParseNamed("busybox" + sha256digest)
|
||||
parsed, err = reference.ParseNormalizedNamed("busybox:notlatest" + sha256digest)
|
||||
require.NoError(t, err)
|
||||
refDigested, ok := parsed.(reference.Canonical)
|
||||
_, ok := parsed.(reference.Canonical)
|
||||
require.True(t, ok)
|
||||
tagDigestRef := refWithTagAndDigest{refDigested}
|
||||
_, err = NewReference(tagDigestRef)
|
||||
_, ok = parsed.(reference.NamedTagged)
|
||||
require.True(t, ok)
|
||||
_, err = NewReference(parsed)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -196,7 +188,7 @@ func TestReferenceTagOrDigest(t *testing.T) {
|
|||
}
|
||||
|
||||
// Invalid input
|
||||
ref, err := reference.ParseNamed("busybox")
|
||||
ref, err := reference.ParseNormalizedNamed("busybox")
|
||||
require.NoError(t, err)
|
||||
dockerRef := dockerReference{ref: ref}
|
||||
_, err = dockerRef.tagOrDigest()
|
||||
|
|
2
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
2
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
|
@ -64,7 +64,7 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc
|
|||
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
|
||||
}
|
||||
// FIXME? Restrict to explicitly supported schemes?
|
||||
repo := ref.ref.FullName() // Note that this is without a tag or digest.
|
||||
repo := ref.ref.Name() // Note that this is without a tag or digest.
|
||||
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||
return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String())
|
||||
}
|
||||
|
|
13
vendor/github.com/containers/image/docker/policyconfiguration/naming.go
generated
vendored
13
vendor/github.com/containers/image/docker/policyconfiguration/naming.go
generated
vendored
|
@ -3,23 +3,22 @@ package policyconfiguration
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup,
|
||||
// as a backend for ImageReference.PolicyConfigurationIdentity.
|
||||
// The reference must satisfy !reference.IsNameOnly().
|
||||
func DockerReferenceIdentity(ref reference.Named) (string, error) {
|
||||
res := ref.FullName()
|
||||
res := ref.Name()
|
||||
tagged, isTagged := ref.(reference.NamedTagged)
|
||||
digested, isDigested := ref.(reference.Canonical)
|
||||
switch {
|
||||
case isTagged && isDigested: // This should not happen, docker/reference.ParseNamed drops the tag.
|
||||
return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", ref.String())
|
||||
case isTagged && isDigested: // Note that this CAN actually happen.
|
||||
return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", reference.FamiliarString(ref))
|
||||
case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.IsNameOnly()
|
||||
return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", ref.String())
|
||||
return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", reference.FamiliarString(ref))
|
||||
case isTagged:
|
||||
res = res + ":" + tagged.Tag()
|
||||
case isDigested:
|
||||
|
@ -43,7 +42,7 @@ func DockerReferenceNamespaces(ref reference.Named) []string {
|
|||
// ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last
|
||||
// iteration matches the host name (for any namespace).
|
||||
res := []string{}
|
||||
name := ref.FullName()
|
||||
name := ref.Name()
|
||||
for {
|
||||
res = append(res, name)
|
||||
|
||||
|
|
28
vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go
generated
vendored
28
vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go
generated
vendored
|
@ -1,11 +1,10 @@
|
|||
package policyconfiguration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -35,14 +34,9 @@ func TestDockerReference(t *testing.T) {
|
|||
for inputSuffix, mappedSuffix := range map[string]string{
|
||||
":tag": ":tag",
|
||||
sha256Digest: sha256Digest,
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// github.com/docker/reference handles that by dropping the tag. That is not obviously the
|
||||
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
|
||||
// This test case should not be construed to make this an API promise.
|
||||
":tag" + sha256Digest: sha256Digest,
|
||||
} {
|
||||
fullInput := inputName + inputSuffix
|
||||
ref, err := reference.ParseNamed(fullInput)
|
||||
ref, err := reference.ParseNormalizedNamed(fullInput)
|
||||
require.NoError(t, err, fullInput)
|
||||
|
||||
identity, err := DockerReferenceIdentity(ref)
|
||||
|
@ -62,30 +56,24 @@ func TestDockerReference(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time.
|
||||
type refWithTagAndDigest struct{ reference.Canonical }
|
||||
|
||||
func (ref refWithTagAndDigest) Tag() string {
|
||||
return "notLatest"
|
||||
}
|
||||
|
||||
func TestDockerReferenceIdentity(t *testing.T) {
|
||||
// TestDockerReference above has tested the core of the functionality, this tests only the failure cases.
|
||||
|
||||
// Neither a tag nor digest
|
||||
parsed, err := reference.ParseNamed("busybox")
|
||||
parsed, err := reference.ParseNormalizedNamed("busybox")
|
||||
require.NoError(t, err)
|
||||
id, err := DockerReferenceIdentity(parsed)
|
||||
assert.Equal(t, "", id)
|
||||
assert.Error(t, err)
|
||||
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
parsed, err = reference.ParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
require.NoError(t, err)
|
||||
refDigested, ok := parsed.(reference.Canonical)
|
||||
_, ok := parsed.(reference.Canonical)
|
||||
require.True(t, ok)
|
||||
tagDigestRef := refWithTagAndDigest{refDigested}
|
||||
id, err = DockerReferenceIdentity(tagDigestRef)
|
||||
_, ok = parsed.(reference.NamedTagged)
|
||||
require.True(t, ok)
|
||||
id, err = DockerReferenceIdentity(parsed)
|
||||
assert.Equal(t, "", id)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
2
vendor/github.com/containers/image/docker/reference/README.md
generated
vendored
Normal file
2
vendor/github.com/containers/image/docker/reference/README.md
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
This is a copy of github.com/docker/distribution/reference as of commit fb0bebc4b64e3881cc52a2478d749845ed76d2a8,
|
||||
except that ParseAnyReferenceWithSet has been removed to drop the dependency on github.com/docker/distribution/digestset.
|
6
vendor/github.com/containers/image/docker/reference/doc.go
generated
vendored
6
vendor/github.com/containers/image/docker/reference/doc.go
generated
vendored
|
@ -1,6 +0,0 @@
|
|||
// Package reference is a fork of the upstream docker/docker/reference package.
|
||||
// The package is forked because we need consistency especially when storing and
|
||||
// checking signatures (RH patches break this consistency because they modify
|
||||
// docker/docker/reference as part of a patch carried in projectatomic/docker).
|
||||
// The version of this package is v1.12.1 from upstream, update as necessary.
|
||||
package reference
|
42
vendor/github.com/containers/image/docker/reference/helpers.go
generated
vendored
Normal file
42
vendor/github.com/containers/image/docker/reference/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
package reference
|
||||
|
||||
import "path"
|
||||
|
||||
// IsNameOnly returns true if reference only contains a repo name.
|
||||
func IsNameOnly(ref Named) bool {
|
||||
if _, ok := ref.(NamedTagged); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := ref.(Canonical); ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FamiliarName returns the familiar name string
|
||||
// for the given named, familiarizing if needed.
|
||||
func FamiliarName(ref Named) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().Name()
|
||||
}
|
||||
return ref.Name()
|
||||
}
|
||||
|
||||
// FamiliarString returns the familiar string representation
|
||||
// for the given reference, familiarizing if needed.
|
||||
func FamiliarString(ref Reference) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().String()
|
||||
}
|
||||
return ref.String()
|
||||
}
|
||||
|
||||
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||
// See https://godoc.org/path#Match for supported patterns.
|
||||
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
|
||||
matched, err := path.Match(pattern, FamiliarString(ref))
|
||||
if namedRef, isNamed := ref.(Named); isNamed && !matched {
|
||||
matched, _ = path.Match(pattern, FamiliarName(namedRef))
|
||||
}
|
||||
return matched, err
|
||||
}
|
152
vendor/github.com/containers/image/docker/reference/normalize.go
generated
vendored
Normal file
152
vendor/github.com/containers/image/docker/reference/normalize.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
legacyDefaultDomain = "index.docker.io"
|
||||
defaultDomain = "docker.io"
|
||||
officialRepoName = "library"
|
||||
defaultTag = "latest"
|
||||
)
|
||||
|
||||
// normalizedNamed represents a name which has been
|
||||
// normalized and has a familiar form. A familiar name
|
||||
// is what is used in Docker UI. An example normalized
|
||||
// name is "docker.io/library/ubuntu" and corresponding
|
||||
// familiar name of "ubuntu".
|
||||
type normalizedNamed interface {
|
||||
Named
|
||||
Familiar() Named
|
||||
}
|
||||
|
||||
// ParseNormalizedNamed parses a string into a named reference
|
||||
// transforming a familiar name from Docker UI to a fully
|
||||
// qualified reference. If the value may be an identifier
|
||||
// use ParseAnyReference.
|
||||
func ParseNormalizedNamed(s string) (Named, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
|
||||
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
||||
}
|
||||
domain, remainder := splitDockerDomain(s)
|
||||
var remoteName string
|
||||
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
||||
remoteName = remainder[:tagSep]
|
||||
} else {
|
||||
remoteName = remainder
|
||||
}
|
||||
if strings.ToLower(remoteName) != remoteName {
|
||||
return nil, errors.New("invalid reference format: repository name must be lowercase")
|
||||
}
|
||||
|
||||
ref, err := Parse(domain + "/" + remainder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
named, isNamed := ref.(Named)
|
||||
if !isNamed {
|
||||
return nil, fmt.Errorf("reference %s has no name", ref.String())
|
||||
}
|
||||
return named, nil
|
||||
}
|
||||
|
||||
// splitDockerDomain splits a repository name to domain and remotename string.
|
||||
// If no valid domain is found, the default domain is used. Repository name
|
||||
// needs to be already validated before.
|
||||
func splitDockerDomain(name string) (domain, remainder string) {
|
||||
i := strings.IndexRune(name, '/')
|
||||
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
||||
domain, remainder = defaultDomain, name
|
||||
} else {
|
||||
domain, remainder = name[:i], name[i+1:]
|
||||
}
|
||||
if domain == legacyDefaultDomain {
|
||||
domain = defaultDomain
|
||||
}
|
||||
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
||||
remainder = officialRepoName + "/" + remainder
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// familiarizeName returns a shortened version of the name familiar
|
||||
// to to the Docker UI. Familiar names have the default domain
|
||||
// "docker.io" and "library/" repository prefix removed.
|
||||
// For example, "docker.io/library/redis" will have the familiar
|
||||
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
||||
// Returns a familiarized named only reference.
|
||||
func familiarizeName(named namedRepository) repository {
|
||||
repo := repository{
|
||||
domain: named.Domain(),
|
||||
path: named.Path(),
|
||||
}
|
||||
|
||||
if repo.domain == defaultDomain {
|
||||
repo.domain = ""
|
||||
// Handle official repositories which have the pattern "library/<official repo name>"
|
||||
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
|
||||
repo.path = split[1]
|
||||
}
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func (r reference) Familiar() Named {
|
||||
return reference{
|
||||
namedRepository: familiarizeName(r.namedRepository),
|
||||
tag: r.tag,
|
||||
digest: r.digest,
|
||||
}
|
||||
}
|
||||
|
||||
func (r repository) Familiar() Named {
|
||||
return familiarizeName(r)
|
||||
}
|
||||
|
||||
func (t taggedReference) Familiar() Named {
|
||||
return taggedReference{
|
||||
namedRepository: familiarizeName(t.namedRepository),
|
||||
tag: t.tag,
|
||||
}
|
||||
}
|
||||
|
||||
func (c canonicalReference) Familiar() Named {
|
||||
return canonicalReference{
|
||||
namedRepository: familiarizeName(c.namedRepository),
|
||||
digest: c.digest,
|
||||
}
|
||||
}
|
||||
|
||||
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
||||
// a repo name.
|
||||
func TagNameOnly(ref Named) Named {
|
||||
if IsNameOnly(ref) {
|
||||
namedTagged, err := WithTag(ref, defaultTag)
|
||||
if err != nil {
|
||||
// Default tag must be valid, to create a NamedTagged
|
||||
// type with non-validated input the WithTag function
|
||||
// should be used instead
|
||||
panic(err)
|
||||
}
|
||||
return namedTagged
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
// ParseAnyReference parses a reference string as a possible identifier,
|
||||
// full digest, or familiar name.
|
||||
func ParseAnyReference(ref string) (Reference, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
|
||||
return digestReference("sha256:" + ref), nil
|
||||
}
|
||||
if dgst, err := digest.Parse(ref); err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
|
||||
return ParseNormalizedNamed(ref)
|
||||
}
|
573
vendor/github.com/containers/image/docker/reference/normalize_test.go
generated
vendored
Normal file
573
vendor/github.com/containers/image/docker/reference/normalize_test.go
generated
vendored
Normal file
|
@ -0,0 +1,573 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func TestValidateReferenceName(t *testing.T) {
|
||||
validRepoNames := []string{
|
||||
"docker/docker",
|
||||
"library/debian",
|
||||
"debian",
|
||||
"docker.io/docker/docker",
|
||||
"docker.io/library/debian",
|
||||
"docker.io/debian",
|
||||
"index.docker.io/docker/docker",
|
||||
"index.docker.io/library/debian",
|
||||
"index.docker.io/debian",
|
||||
"127.0.0.1:5000/docker/docker",
|
||||
"127.0.0.1:5000/library/debian",
|
||||
"127.0.0.1:5000/debian",
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
|
||||
// This test case was moved from invalid to valid since it is valid input
|
||||
// when specified with a hostname, it removes the ambiguity from about
|
||||
// whether the value is an identifier or repository name
|
||||
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
}
|
||||
invalidRepoNames := []string{
|
||||
"https://github.com/docker/docker",
|
||||
"docker/Docker",
|
||||
"-docker",
|
||||
"-docker/docker",
|
||||
"-docker.io/docker/docker",
|
||||
"docker///docker",
|
||||
"docker.io/docker/Docker",
|
||||
"docker.io/docker///docker",
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
}
|
||||
|
||||
for _, name := range invalidRepoNames {
|
||||
_, err := ParseNormalizedNamed(name)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected invalid repo name for %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range validRepoNames {
|
||||
_, err := ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing repo name %s, got: %q", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRemoteName(t *testing.T) {
|
||||
validRepositoryNames := []string{
|
||||
// Sanity check.
|
||||
"docker/docker",
|
||||
|
||||
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
|
||||
// Allow embedded hyphens.
|
||||
"docker-rules/docker",
|
||||
|
||||
// Allow multiple hyphens as well.
|
||||
"docker---rules/docker",
|
||||
|
||||
//Username doc and image name docker being tested.
|
||||
"doc/docker",
|
||||
|
||||
// single character names are now allowed.
|
||||
"d/docker",
|
||||
"jess/t",
|
||||
|
||||
// Consecutive underscores.
|
||||
"dock__er/docker",
|
||||
}
|
||||
for _, repositoryName := range validRepositoryNames {
|
||||
_, err := ParseNormalizedNamed(repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
||||
}
|
||||
}
|
||||
|
||||
invalidRepositoryNames := []string{
|
||||
// Disallow capital letters.
|
||||
"docker/Docker",
|
||||
|
||||
// Only allow one slash.
|
||||
"docker///docker",
|
||||
|
||||
// Disallow 64-character hexadecimal.
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
|
||||
// Disallow leading and trailing hyphens in namespace.
|
||||
"-docker/docker",
|
||||
"docker-/docker",
|
||||
"-docker-/docker",
|
||||
|
||||
// Don't allow underscores everywhere (as opposed to hyphens).
|
||||
"____/____",
|
||||
|
||||
"_docker/_docker",
|
||||
|
||||
// Disallow consecutive periods.
|
||||
"dock..er/docker",
|
||||
"dock_.er/docker",
|
||||
"dock-.er/docker",
|
||||
|
||||
// No repository.
|
||||
"docker/",
|
||||
|
||||
//namespace too long
|
||||
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
|
||||
}
|
||||
for _, repositoryName := range invalidRepositoryNames {
|
||||
if _, err := ParseNormalizedNamed(repositoryName); err == nil {
|
||||
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryInfo(t *testing.T) {
|
||||
type tcase struct {
|
||||
RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{
|
||||
RemoteName: "fooo/bar",
|
||||
FamiliarName: "fooo/bar",
|
||||
FullName: "docker.io/fooo/bar",
|
||||
AmbiguousName: "index.docker.io/fooo/bar",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu",
|
||||
FamiliarName: "ubuntu",
|
||||
FullName: "docker.io/library/ubuntu",
|
||||
AmbiguousName: "library/ubuntu",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "nonlibrary/ubuntu",
|
||||
FamiliarName: "nonlibrary/ubuntu",
|
||||
FullName: "docker.io/nonlibrary/ubuntu",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "other/library",
|
||||
FamiliarName: "other/library",
|
||||
FullName: "docker.io/other/library",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "127.0.0.1:8000/private/moonbase",
|
||||
FullName: "127.0.0.1:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
FamiliarName: "127.0.0.1:8000/privatebase",
|
||||
FullName: "127.0.0.1:8000/privatebase",
|
||||
AmbiguousName: "",
|
||||
Domain: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "example.com/private/moonbase",
|
||||
FullName: "example.com/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
FamiliarName: "example.com/privatebase",
|
||||
FullName: "example.com/privatebase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "example.com:8000/private/moonbase",
|
||||
FullName: "example.com:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebasee",
|
||||
FamiliarName: "example.com:8000/privatebasee",
|
||||
FullName: "example.com:8000/privatebasee",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu-12.04-base",
|
||||
FamiliarName: "ubuntu-12.04-base",
|
||||
FullName: "docker.io/library/ubuntu-12.04-base",
|
||||
AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/foo",
|
||||
FamiliarName: "foo",
|
||||
FullName: "docker.io/library/foo",
|
||||
AmbiguousName: "docker.io/foo",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/foo/bar",
|
||||
FamiliarName: "library/foo/bar",
|
||||
FullName: "docker.io/library/foo/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "store/foo/bar",
|
||||
FamiliarName: "store/foo/bar",
|
||||
FullName: "docker.io/store/foo/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
refStrings := []string{tcase.FamiliarName, tcase.FullName}
|
||||
if tcase.AmbiguousName != "" {
|
||||
refStrings = append(refStrings, tcase.AmbiguousName)
|
||||
}
|
||||
|
||||
var refs []Named
|
||||
for _, r := range refStrings {
|
||||
named, err := ParseNormalizedNamed(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refs = append(refs, named)
|
||||
}
|
||||
|
||||
for _, r := range refs {
|
||||
if expected, actual := tcase.FamiliarName, FamiliarName(r); expected != actual {
|
||||
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.FullName, r.String(); expected != actual {
|
||||
t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.Domain, Domain(r); expected != actual {
|
||||
t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.RemoteName, Path(r); expected != actual {
|
||||
t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReferenceWithTagAndDigest(t *testing.T) {
|
||||
shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
|
||||
ref, err := ParseNormalizedNamed(shortRef)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
|
||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
||||
}
|
||||
|
||||
if _, isTagged := ref.(NamedTagged); !isTagged {
|
||||
t.Fatalf("Reference from %q should support tag", ref)
|
||||
}
|
||||
if _, isCanonical := ref.(Canonical); !isCanonical {
|
||||
t.Fatalf("Reference from %q should support digest", ref)
|
||||
}
|
||||
if expected, actual := shortRef, FamiliarString(ref); actual != expected {
|
||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidReferenceComponents(t *testing.T) {
|
||||
if _, err := ParseNormalizedNamed("-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid name")
|
||||
}
|
||||
ref, err := ParseNormalizedNamed("busybox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := WithTag(ref, "-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid tag")
|
||||
}
|
||||
if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
|
||||
t.Fatal("Expected WithDigest to detect invalid digest")
|
||||
}
|
||||
}
|
||||
|
||||
func equalReference(r1, r2 Reference) bool {
|
||||
switch v1 := r1.(type) {
|
||||
case digestReference:
|
||||
if v2, ok := r2.(digestReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case repository:
|
||||
if v2, ok := r2.(repository); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case taggedReference:
|
||||
if v2, ok := r2.(taggedReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case canonicalReference:
|
||||
if v2, ok := r2.(canonicalReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case reference:
|
||||
if v2, ok := r2.(reference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestParseAnyReference(t *testing.T) {
|
||||
tcases := []struct {
|
||||
Reference string
|
||||
Equivalent string
|
||||
Expected Reference
|
||||
}{
|
||||
{
|
||||
Reference: "redis",
|
||||
Equivalent: "docker.io/library/redis",
|
||||
},
|
||||
{
|
||||
Reference: "redis:latest",
|
||||
Equivalent: "docker.io/library/redis:latest",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/library/redis:latest",
|
||||
Equivalent: "docker.io/library/redis:latest",
|
||||
},
|
||||
{
|
||||
Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp",
|
||||
Equivalent: "docker.io/dmcgowan/myapp",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp:latest",
|
||||
Equivalent: "docker.io/dmcgowan/myapp:latest",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/mcgowan/myapp:latest",
|
||||
Equivalent: "docker.io/mcgowan/myapp:latest",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
var ref Reference
|
||||
var err error
|
||||
ref, err = ParseAnyReference(tcase.Reference)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err)
|
||||
}
|
||||
if ref.String() != tcase.Equivalent {
|
||||
t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent)
|
||||
}
|
||||
|
||||
expected := tcase.Expected
|
||||
if expected == nil {
|
||||
expected, err = Parse(tcase.Equivalent)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err)
|
||||
}
|
||||
}
|
||||
if !equalReference(ref, expected) {
|
||||
t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizedSplitHostname(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
domain: "docker.io",
|
||||
name: "test_com/foo",
|
||||
},
|
||||
{
|
||||
input: "docker/migrator",
|
||||
domain: "docker.io",
|
||||
name: "docker/migrator",
|
||||
},
|
||||
{
|
||||
input: "test.com:8080/foo",
|
||||
domain: "test.com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test-com:8080/foo",
|
||||
domain: "test-com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/foo",
|
||||
domain: "xn--n3h.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com:18080/foo",
|
||||
domain: "xn--n3h.com:18080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo/bar",
|
||||
domain: "docker.io",
|
||||
name: "library/foo/bar",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := ParseNormalizedNamed(testcase.input)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchError(t *testing.T) {
|
||||
named, err := ParseAnyReference("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = FamiliarMatch("[-x]", named)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
matchCases := []struct {
|
||||
reference string
|
||||
pattern string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
reference: "foo",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
reference: "foo/any/bat",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
reference: "foo/a/bar",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/b/baz",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/*/baz:tag",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/c/baz:tag",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "example.com/foo/c/baz:tag",
|
||||
pattern: "*/foo/c/baz",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "example.com/foo/c/baz:tag",
|
||||
pattern: "example.com/foo/c/baz",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, c := range matchCases {
|
||||
named, err := ParseAnyReference(c.reference)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := FamiliarMatch(c.pattern, named)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
520
vendor/github.com/containers/image/docker/reference/reference.go
generated
vendored
520
vendor/github.com/containers/image/docker/reference/reference.go
generated
vendored
|
@ -1,41 +1,120 @@
|
|||
// Package reference provides a general type to represent any way of referencing images within the registry.
|
||||
// Its main purpose is to abstract tags and digests (content-addressable hash).
|
||||
//
|
||||
// Grammar
|
||||
//
|
||||
// reference := name [ ":" tag ] [ "@" digest ]
|
||||
// name := [domain '/'] path-component ['/' path-component]*
|
||||
// domain := domain-component ['.' domain-component]* [':' port-number]
|
||||
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
// port-number := /[0-9]+/
|
||||
// path-component := alpha-numeric [separator alpha-numeric]*
|
||||
// alpha-numeric := /[a-z0-9]+/
|
||||
// separator := /[_.]|__|[-]*/
|
||||
//
|
||||
// tag := /[\w][\w.-]{0,127}/
|
||||
//
|
||||
// digest := digest-algorithm ":" digest-hex
|
||||
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
|
||||
// digest-algorithm-separator := /[+.-_]/
|
||||
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
|
||||
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
||||
//
|
||||
// identifier := /[a-f0-9]{64}/
|
||||
// short-identifier := /[a-f0-9]{6,64}/
|
||||
package reference
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// "opencontainers/go-digest" requires us to load the algorithms that we
|
||||
// want to use into the binary (it calls .Available).
|
||||
_ "crypto/sha256"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTag defines the default tag used when performing images related actions and no tag or digest is specified
|
||||
DefaultTag = "latest"
|
||||
// DefaultHostname is the default built-in hostname
|
||||
DefaultHostname = "docker.io"
|
||||
// LegacyDefaultHostname is automatically converted to DefaultHostname
|
||||
LegacyDefaultHostname = "index.docker.io"
|
||||
// DefaultRepoPrefix is the prefix used for default repositories in default host
|
||||
DefaultRepoPrefix = "library/"
|
||||
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||
NameTotalLengthMax = 255
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||
ErrReferenceInvalidFormat = errors.New("invalid reference format")
|
||||
|
||||
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
ErrTagInvalidFormat = errors.New("invalid tag format")
|
||||
|
||||
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
||||
|
||||
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
|
||||
|
||||
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||
ErrNameEmpty = errors.New("repository name must have at least one component")
|
||||
|
||||
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
|
||||
|
||||
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||
ErrNameNotCanonical = errors.New("repository name must be canonical")
|
||||
)
|
||||
|
||||
// Reference is an opaque object reference identifier that may include
|
||||
// modifiers such as a hostname, name, tag, and digest.
|
||||
type Reference interface {
|
||||
// String returns the full reference
|
||||
String() string
|
||||
}
|
||||
|
||||
// Field provides a wrapper type for resolving correct reference types when
|
||||
// working with encoding.
|
||||
type Field struct {
|
||||
reference Reference
|
||||
}
|
||||
|
||||
// AsField wraps a reference in a Field for encoding.
|
||||
func AsField(reference Reference) Field {
|
||||
return Field{reference}
|
||||
}
|
||||
|
||||
// Reference unwraps the reference type from the field to
|
||||
// return the Reference object. This object should be
|
||||
// of the appropriate type to further check for different
|
||||
// reference types.
|
||||
func (f Field) Reference() Reference {
|
||||
return f.reference
|
||||
}
|
||||
|
||||
// MarshalText serializes the field to byte text which
|
||||
// is the string of the reference.
|
||||
func (f Field) MarshalText() (p []byte, err error) {
|
||||
return []byte(f.reference.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText parses text bytes by invoking the
|
||||
// reference parser to ensure the appropriately
|
||||
// typed reference object is wrapped by field.
|
||||
func (f *Field) UnmarshalText(p []byte) error {
|
||||
r, err := Parse(string(p))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.reference = r
|
||||
return nil
|
||||
}
|
||||
|
||||
// Named is an object with a full name
|
||||
type Named interface {
|
||||
// Name returns normalized repository name, like "ubuntu".
|
||||
Reference
|
||||
Name() string
|
||||
// String returns full reference, like "ubuntu@sha256:abcdef..."
|
||||
String() string
|
||||
// FullName returns full repository name with hostname, like "docker.io/library/ubuntu"
|
||||
FullName() string
|
||||
// Hostname returns hostname for the reference, like "docker.io"
|
||||
Hostname() string
|
||||
// RemoteName returns the repository component of the full name, like "library/ubuntu"
|
||||
RemoteName() string
|
||||
}
|
||||
|
||||
// Tagged is an object which has a tag
|
||||
type Tagged interface {
|
||||
Reference
|
||||
Tag() string
|
||||
}
|
||||
|
||||
// NamedTagged is an object including a name and tag.
|
||||
|
@ -44,174 +123,311 @@ type NamedTagged interface {
|
|||
Tag() string
|
||||
}
|
||||
|
||||
// Digested is an object which has a digest
|
||||
// in which it can be referenced by
|
||||
type Digested interface {
|
||||
Reference
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// Canonical reference is an object with a fully unique
|
||||
// name including a name with hostname and digest
|
||||
// name including a name with domain and digest
|
||||
type Canonical interface {
|
||||
Named
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// ParseNamed parses s and returns a syntactically valid reference implementing
|
||||
// the Named interface. The reference must have a name, otherwise an error is
|
||||
// returned.
|
||||
// namedRepository is a reference to a repository with a name.
|
||||
// A namedRepository has both domain and path components.
|
||||
type namedRepository interface {
|
||||
Named
|
||||
Domain() string
|
||||
Path() string
|
||||
}
|
||||
|
||||
// Domain returns the domain part of the Named reference
|
||||
func Domain(named Named) string {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain()
|
||||
}
|
||||
domain, _ := splitDomain(named.Name())
|
||||
return domain
|
||||
}
|
||||
|
||||
// Path returns the name without the domain part of the Named reference
|
||||
func Path(named Named) (name string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Path()
|
||||
}
|
||||
_, path := splitDomain(named.Name())
|
||||
return path
|
||||
}
|
||||
|
||||
func splitDomain(name string) (string, string) {
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
}
|
||||
|
||||
// SplitHostname splits a named reference into a
|
||||
// hostname and name string. If no valid hostname is
|
||||
// found, the hostname is empty and the full value
|
||||
// is returned as name
|
||||
// DEPRECATED: Use Domain or Path
|
||||
func SplitHostname(named Named) (string, string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain(), r.Path()
|
||||
}
|
||||
return splitDomain(named.Name())
|
||||
}
|
||||
|
||||
// Parse parses s and returns a syntactically valid Reference.
|
||||
// If an error was encountered it is returned, along with a nil Reference.
|
||||
func ParseNamed(s string) (Named, error) {
|
||||
named, err := distreference.ParseNormalizedNamed(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s)
|
||||
// NOTE: Parse will not handle short digests.
|
||||
func Parse(s string) (Reference, error) {
|
||||
matches := ReferenceRegexp.FindStringSubmatch(s)
|
||||
if matches == nil {
|
||||
if s == "" {
|
||||
return nil, ErrNameEmpty
|
||||
}
|
||||
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
|
||||
return nil, ErrNameContainsUppercase
|
||||
}
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
r, err := WithName(named.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
if len(matches[1]) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
if canonical, isCanonical := named.(distreference.Canonical); isCanonical {
|
||||
r, err := distreference.WithDigest(r, canonical.Digest())
|
||||
|
||||
var repo repository
|
||||
|
||||
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
|
||||
if nameMatch != nil && len(nameMatch) == 3 {
|
||||
repo.domain = nameMatch[1]
|
||||
repo.path = nameMatch[2]
|
||||
} else {
|
||||
repo.domain = ""
|
||||
repo.path = matches[1]
|
||||
}
|
||||
|
||||
ref := reference{
|
||||
namedRepository: repo,
|
||||
tag: matches[2],
|
||||
}
|
||||
if matches[3] != "" {
|
||||
var err error
|
||||
ref.digest, err = digest.Parse(matches[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &canonicalRef{namedRef{r}}, nil
|
||||
}
|
||||
if tagged, isTagged := named.(distreference.NamedTagged); isTagged {
|
||||
return WithTag(r, tagged.Tag())
|
||||
|
||||
r := getBestReferenceType(ref)
|
||||
if r == nil {
|
||||
return nil, ErrNameEmpty
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ParseNamed parses s and returns a syntactically valid reference implementing
|
||||
// the Named interface. The reference must have a name and be in the canonical
|
||||
// form, otherwise an error is returned.
|
||||
// If an error was encountered it is returned, along with a nil Reference.
|
||||
// NOTE: ParseNamed will not handle short digests.
|
||||
func ParseNamed(s string) (Named, error) {
|
||||
named, err := ParseNormalizedNamed(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if named.String() != s {
|
||||
return nil, ErrNameNotCanonical
|
||||
}
|
||||
return named, nil
|
||||
}
|
||||
|
||||
// WithName returns a named object representing the given string. If the input
|
||||
// is invalid ErrReferenceInvalidFormat will be returned.
|
||||
func WithName(name string) (Named, error) {
|
||||
name, err := normalize(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(name) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
if err := validateName(name); err != nil {
|
||||
return nil, err
|
||||
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if match == nil || len(match) != 3 {
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
r, err := distreference.WithName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &namedRef{r}, nil
|
||||
return repository{
|
||||
domain: match[1],
|
||||
path: match[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithTag combines the name from "name" and the tag from "tag" to form a
|
||||
// reference incorporating both the name and the tag.
|
||||
func WithTag(name Named, tag string) (NamedTagged, error) {
|
||||
r, err := distreference.WithTag(name, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !anchoredTagRegexp.MatchString(tag) {
|
||||
return nil, ErrTagInvalidFormat
|
||||
}
|
||||
return &taggedRef{namedRef{r}}, nil
|
||||
}
|
||||
|
||||
type namedRef struct {
|
||||
distreference.Named
|
||||
}
|
||||
type taggedRef struct {
|
||||
namedRef
|
||||
}
|
||||
type canonicalRef struct {
|
||||
namedRef
|
||||
}
|
||||
|
||||
func (r *namedRef) FullName() string {
|
||||
hostname, remoteName := splitHostname(r.Name())
|
||||
return hostname + "/" + remoteName
|
||||
}
|
||||
func (r *namedRef) Hostname() string {
|
||||
hostname, _ := splitHostname(r.Name())
|
||||
return hostname
|
||||
}
|
||||
func (r *namedRef) RemoteName() string {
|
||||
_, remoteName := splitHostname(r.Name())
|
||||
return remoteName
|
||||
}
|
||||
func (r *taggedRef) Tag() string {
|
||||
return r.namedRef.Named.(distreference.NamedTagged).Tag()
|
||||
}
|
||||
func (r *canonicalRef) Digest() digest.Digest {
|
||||
return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest())
|
||||
}
|
||||
|
||||
// WithDefaultTag adds a default tag to a reference if it only has a repo name.
|
||||
func WithDefaultTag(ref Named) Named {
|
||||
if IsNameOnly(ref) {
|
||||
ref, _ = WithTag(ref, DefaultTag)
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if canonical, ok := name.(Canonical); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tag,
|
||||
digest: canonical.Digest(),
|
||||
}, nil
|
||||
}
|
||||
return taggedReference{
|
||||
namedRepository: repo,
|
||||
tag: tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithDigest combines the name from "name" and the digest from "digest" to form
|
||||
// a reference incorporating both the name and the digest.
|
||||
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
|
||||
if !anchoredDigestRegexp.MatchString(digest.String()) {
|
||||
return nil, ErrDigestInvalidFormat
|
||||
}
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if tagged, ok := name.(Tagged); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tagged.Tag(),
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
return canonicalReference{
|
||||
namedRepository: repo,
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TrimNamed removes any tag or digest from the named reference.
|
||||
func TrimNamed(ref Named) Named {
|
||||
domain, path := SplitHostname(ref)
|
||||
return repository{
|
||||
domain: domain,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func getBestReferenceType(ref reference) Reference {
|
||||
if ref.Name() == "" {
|
||||
// Allow digest only references
|
||||
if ref.digest != "" {
|
||||
return digestReference(ref.digest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if ref.tag == "" {
|
||||
if ref.digest != "" {
|
||||
return canonicalReference{
|
||||
namedRepository: ref.namedRepository,
|
||||
digest: ref.digest,
|
||||
}
|
||||
}
|
||||
return ref.namedRepository
|
||||
}
|
||||
if ref.digest == "" {
|
||||
return taggedReference{
|
||||
namedRepository: ref.namedRepository,
|
||||
tag: ref.tag,
|
||||
}
|
||||
}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
// IsNameOnly returns true if reference only contains a repo name.
|
||||
func IsNameOnly(ref Named) bool {
|
||||
if _, ok := ref.(NamedTagged); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := ref.(Canonical); ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
type reference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
// ParseIDOrReference parses string for an image ID or a reference. ID can be
|
||||
// without a default prefix.
|
||||
func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) {
|
||||
if err := validateID(idOrRef); err == nil {
|
||||
idOrRef = "sha256:" + idOrRef
|
||||
}
|
||||
if dgst, err := digest.Parse(idOrRef); err == nil {
|
||||
return dgst, nil, nil
|
||||
}
|
||||
ref, err := ParseNamed(idOrRef)
|
||||
return "", ref, err
|
||||
func (r reference) String() string {
|
||||
return r.Name() + ":" + r.tag + "@" + r.digest.String()
|
||||
}
|
||||
|
||||
// splitHostname splits a repository name to hostname and remotename string.
|
||||
// If no valid hostname is found, the default hostname is used. Repository name
|
||||
// needs to be already validated before.
|
||||
func splitHostname(name string) (hostname, remoteName string) {
|
||||
i := strings.IndexRune(name, '/')
|
||||
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
||||
hostname, remoteName = DefaultHostname, name
|
||||
} else {
|
||||
hostname, remoteName = name[:i], name[i+1:]
|
||||
}
|
||||
if hostname == LegacyDefaultHostname {
|
||||
hostname = DefaultHostname
|
||||
}
|
||||
if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') {
|
||||
remoteName = DefaultRepoPrefix + remoteName
|
||||
}
|
||||
return
|
||||
func (r reference) Tag() string {
|
||||
return r.tag
|
||||
}
|
||||
|
||||
// normalize returns a repository name in its normalized form, meaning it
|
||||
// will not contain default hostname nor library/ prefix for official images.
|
||||
func normalize(name string) (string, error) {
|
||||
host, remoteName := splitHostname(name)
|
||||
if strings.ToLower(remoteName) != remoteName {
|
||||
return "", errors.New("invalid reference format: repository name must be lowercase")
|
||||
}
|
||||
if host == DefaultHostname {
|
||||
if strings.HasPrefix(remoteName, DefaultRepoPrefix) {
|
||||
return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil
|
||||
}
|
||||
return remoteName, nil
|
||||
}
|
||||
return name, nil
|
||||
func (r reference) Digest() digest.Digest {
|
||||
return r.digest
|
||||
}
|
||||
|
||||
var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||
|
||||
func validateID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return errors.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
type repository struct {
|
||||
domain string
|
||||
path string
|
||||
}
|
||||
|
||||
func validateName(name string) error {
|
||||
if err := validateID(name); err == nil {
|
||||
return errors.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
|
||||
}
|
||||
return nil
|
||||
func (r repository) String() string {
|
||||
return r.Name()
|
||||
}
|
||||
|
||||
func (r repository) Name() string {
|
||||
if r.domain == "" {
|
||||
return r.path
|
||||
}
|
||||
return r.domain + "/" + r.path
|
||||
}
|
||||
|
||||
func (r repository) Domain() string {
|
||||
return r.domain
|
||||
}
|
||||
|
||||
func (r repository) Path() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
type digestReference digest.Digest
|
||||
|
||||
func (d digestReference) String() string {
|
||||
return digest.Digest(d).String()
|
||||
}
|
||||
|
||||
func (d digestReference) Digest() digest.Digest {
|
||||
return digest.Digest(d)
|
||||
}
|
||||
|
||||
type taggedReference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
}
|
||||
|
||||
func (t taggedReference) String() string {
|
||||
return t.Name() + ":" + t.tag
|
||||
}
|
||||
|
||||
func (t taggedReference) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
type canonicalReference struct {
|
||||
namedRepository
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (c canonicalReference) String() string {
|
||||
return c.Name() + "@" + c.digest.String()
|
||||
}
|
||||
|
||||
func (c canonicalReference) Digest() digest.Digest {
|
||||
return c.digest
|
||||
}
|
||||
|
|
823
vendor/github.com/containers/image/docker/reference/reference_test.go
generated
vendored
823
vendor/github.com/containers/image/docker/reference/reference_test.go
generated
vendored
|
@ -1,272 +1,659 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "crypto/sha256"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func TestValidateReferenceName(t *testing.T) {
|
||||
validRepoNames := []string{
|
||||
"docker/docker",
|
||||
"library/debian",
|
||||
"debian",
|
||||
"docker.io/docker/docker",
|
||||
"docker.io/library/debian",
|
||||
"docker.io/debian",
|
||||
"index.docker.io/docker/docker",
|
||||
"index.docker.io/library/debian",
|
||||
"index.docker.io/debian",
|
||||
"127.0.0.1:5000/docker/docker",
|
||||
"127.0.0.1:5000/library/debian",
|
||||
"127.0.0.1:5000/debian",
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
}
|
||||
invalidRepoNames := []string{
|
||||
"https://github.com/docker/docker",
|
||||
"docker/Docker",
|
||||
"-docker",
|
||||
"-docker/docker",
|
||||
"-docker.io/docker/docker",
|
||||
"docker///docker",
|
||||
"docker.io/docker/Docker",
|
||||
"docker.io/docker///docker",
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
func TestReferenceParse(t *testing.T) {
|
||||
// referenceTestcases is a unified set of testcases for
|
||||
// testing the parsing of references
|
||||
referenceTestcases := []struct {
|
||||
// input is the repository name or name component testcase
|
||||
input string
|
||||
// err is the error expected from Parse, or nil
|
||||
err error
|
||||
// repository is the string representation for the reference
|
||||
repository string
|
||||
// domain is the domain expected in the reference
|
||||
domain string
|
||||
// tag is the tag for the reference
|
||||
tag string
|
||||
// digest is the digest for the reference (enforces digest reference)
|
||||
digest string
|
||||
}{
|
||||
{
|
||||
input: "test_com",
|
||||
repository: "test_com",
|
||||
},
|
||||
{
|
||||
input: "test.com:tag",
|
||||
repository: "test.com",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test.com:5000",
|
||||
repository: "test.com",
|
||||
tag: "5000",
|
||||
},
|
||||
{
|
||||
input: "test.com/repo:tag",
|
||||
domain: "test.com",
|
||||
repository: "test.com/repo",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo:tag",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
tag: "tag",
|
||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
input: ":justtag",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
|
||||
err: digest.ErrDigestInvalidLength,
|
||||
},
|
||||
{
|
||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: digest.ErrDigestUnsupported,
|
||||
},
|
||||
{
|
||||
input: "Uppercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
|
||||
// See https://github.com/docker/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
|
||||
//{
|
||||
// input: "Uppercase/lowercase:tag",
|
||||
// err: ErrNameContainsUppercase,
|
||||
//},
|
||||
{
|
||||
input: "test:5000/Uppercase/lowercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
{
|
||||
input: "lowercase:Uppercase",
|
||||
repository: "lowercase",
|
||||
tag: "Uppercase",
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a:tag",
|
||||
err: ErrNameTooLong,
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
|
||||
domain: "a",
|
||||
repository: strings.Repeat("a/", 127) + "a",
|
||||
tag: "tag-puts-this-over-max",
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
||||
domain: "sub-dom1.foo.com",
|
||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
|
||||
domain: "sub-dom1.foo.com",
|
||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
||||
tag: "some-long-tag",
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app:test.example.com",
|
||||
domain: "b.gcr.io",
|
||||
repository: "b.gcr.io/test.example.com/my-app",
|
||||
tag: "test.example.com",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
|
||||
domain: "xn--n3h.com",
|
||||
repository: "xn--n3h.com/myimage",
|
||||
tag: "xn--n3h.com",
|
||||
},
|
||||
{
|
||||
input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
|
||||
domain: "xn--7o8h.com",
|
||||
repository: "xn--7o8h.com/myimage",
|
||||
tag: "xn--7o8h.com",
|
||||
digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080",
|
||||
repository: "foo_bar.com",
|
||||
tag: "8080",
|
||||
},
|
||||
{
|
||||
input: "foo/foo_bar.com:8080",
|
||||
domain: "foo",
|
||||
repository: "foo/foo_bar.com",
|
||||
tag: "8080",
|
||||
},
|
||||
}
|
||||
for _, testcase := range referenceTestcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
for _, name := range invalidRepoNames {
|
||||
_, err := ParseNamed(name)
|
||||
repo, err := Parse(testcase.input)
|
||||
if testcase.err != nil {
|
||||
if err == nil {
|
||||
failf("missing expected error: %v", testcase.err)
|
||||
} else if testcase.err != err {
|
||||
failf("mismatched error: got %v, expected %v", err, testcase.err)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
failf("unexpected parse error: %v", err)
|
||||
continue
|
||||
}
|
||||
if repo.String() != testcase.input {
|
||||
failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
|
||||
}
|
||||
|
||||
if named, ok := repo.(Named); ok {
|
||||
if named.Name() != testcase.repository {
|
||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
|
||||
}
|
||||
domain, _ := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
} else if testcase.repository != "" || testcase.domain != "" {
|
||||
failf("expected named type, got %T", repo)
|
||||
}
|
||||
|
||||
tagged, ok := repo.(Tagged)
|
||||
if testcase.tag != "" {
|
||||
if ok {
|
||||
if tagged.Tag() != testcase.tag {
|
||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
||||
}
|
||||
} else {
|
||||
failf("expected tagged type, got %T", repo)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected tagged type")
|
||||
}
|
||||
|
||||
digested, ok := repo.(Digested)
|
||||
if testcase.digest != "" {
|
||||
if ok {
|
||||
if digested.Digest().String() != testcase.digest {
|
||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
||||
}
|
||||
} else {
|
||||
failf("expected digested type, got %T", repo)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected digested type")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithNameFailure tests cases where WithName should fail. Cases where it
|
||||
// should succeed are covered by TestSplitHostname, below.
|
||||
func TestWithNameFailure(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
input: ":justtag",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a:tag",
|
||||
err: ErrNameTooLong,
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, err := WithName(testcase.input)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected invalid repo name for %q", name)
|
||||
failf("no error parsing name. expected: %s", testcase.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range validRepoNames {
|
||||
_, err := ParseNamed(name)
|
||||
func TestSplitHostname(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
domain: "",
|
||||
name: "test_com/foo",
|
||||
},
|
||||
{
|
||||
input: "test:8080/foo",
|
||||
domain: "test:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test.com:8080/foo",
|
||||
domain: "test.com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test-com:8080/foo",
|
||||
domain: "test-com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com:18080/foo",
|
||||
domain: "xn--n3h.com:18080",
|
||||
name: "foo",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := WithName(testcase.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing repo name %s, got: %q", name, err)
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRemoteName(t *testing.T) {
|
||||
validRepositoryNames := []string{
|
||||
// Sanity check.
|
||||
"docker/docker",
|
||||
type serializationType struct {
|
||||
Description string
|
||||
Field Field
|
||||
}
|
||||
|
||||
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
|
||||
// Allow embedded hyphens.
|
||||
"docker-rules/docker",
|
||||
|
||||
// Allow multiple hyphens as well.
|
||||
"docker---rules/docker",
|
||||
|
||||
//Username doc and image name docker being tested.
|
||||
"doc/docker",
|
||||
|
||||
// single character names are now allowed.
|
||||
"d/docker",
|
||||
"jess/t",
|
||||
|
||||
// Consecutive underscores.
|
||||
"dock__er/docker",
|
||||
func TestSerialization(t *testing.T) {
|
||||
testcases := []struct {
|
||||
description string
|
||||
input string
|
||||
name string
|
||||
tag string
|
||||
digest string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
description: "empty value",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
description: "just a name",
|
||||
input: "example.com:8000/named",
|
||||
name: "example.com:8000/named",
|
||||
},
|
||||
{
|
||||
description: "name with a tag",
|
||||
input: "example.com:8000/named:tagged",
|
||||
name: "example.com:8000/named",
|
||||
tag: "tagged",
|
||||
},
|
||||
{
|
||||
description: "name with digest",
|
||||
input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
||||
name: "other.com/named",
|
||||
digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
||||
},
|
||||
}
|
||||
for _, repositoryName := range validRepositoryNames {
|
||||
_, err := ParseNamed(repositoryName)
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
m := map[string]string{
|
||||
"Description": testcase.description,
|
||||
"Field": testcase.input,
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
||||
failf("error marshalling: %v", err)
|
||||
}
|
||||
}
|
||||
t := serializationType{}
|
||||
|
||||
invalidRepositoryNames := []string{
|
||||
// Disallow capital letters.
|
||||
"docker/Docker",
|
||||
if err := json.Unmarshal(b, &t); err != nil {
|
||||
if testcase.err == nil {
|
||||
failf("error unmarshalling: %v", err)
|
||||
}
|
||||
if err != testcase.err {
|
||||
failf("wrong error, expected %v, got %v", testcase.err, err)
|
||||
}
|
||||
|
||||
// Only allow one slash.
|
||||
"docker///docker",
|
||||
|
||||
// Disallow 64-character hexadecimal.
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
|
||||
// Disallow leading and trailing hyphens in namespace.
|
||||
"-docker/docker",
|
||||
"docker-/docker",
|
||||
"-docker-/docker",
|
||||
|
||||
// Don't allow underscores everywhere (as opposed to hyphens).
|
||||
"____/____",
|
||||
|
||||
"_docker/_docker",
|
||||
|
||||
// Disallow consecutive periods.
|
||||
"dock..er/docker",
|
||||
"dock_.er/docker",
|
||||
"dock-.er/docker",
|
||||
|
||||
// No repository.
|
||||
"docker/",
|
||||
|
||||
//namespace too long
|
||||
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
|
||||
}
|
||||
for _, repositoryName := range invalidRepositoryNames {
|
||||
if _, err := ParseNamed(repositoryName); err == nil {
|
||||
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
||||
continue
|
||||
} else if testcase.err != nil {
|
||||
failf("expected error unmarshalling: %v", testcase.err)
|
||||
}
|
||||
|
||||
if t.Description != testcase.description {
|
||||
failf("wrong description, expected %q, got %q", testcase.description, t.Description)
|
||||
}
|
||||
|
||||
ref := t.Field.Reference()
|
||||
|
||||
if named, ok := ref.(Named); ok {
|
||||
if named.Name() != testcase.name {
|
||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
|
||||
}
|
||||
} else if testcase.name != "" {
|
||||
failf("expected named type, got %T", ref)
|
||||
}
|
||||
|
||||
tagged, ok := ref.(Tagged)
|
||||
if testcase.tag != "" {
|
||||
if ok {
|
||||
if tagged.Tag() != testcase.tag {
|
||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
||||
}
|
||||
} else {
|
||||
failf("expected tagged type, got %T", ref)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected tagged type")
|
||||
}
|
||||
|
||||
digested, ok := ref.(Digested)
|
||||
if testcase.digest != "" {
|
||||
if ok {
|
||||
if digested.Digest().String() != testcase.digest {
|
||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
||||
}
|
||||
} else {
|
||||
failf("expected digested type, got %T", ref)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected digested type")
|
||||
}
|
||||
|
||||
t = serializationType{
|
||||
Description: testcase.description,
|
||||
Field: AsField(ref),
|
||||
}
|
||||
|
||||
b2, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
failf("error marshing serialization type: %v", err)
|
||||
}
|
||||
|
||||
if string(b) != string(b2) {
|
||||
failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
|
||||
}
|
||||
|
||||
// Ensure t.Field is not implementing "Reference" directly, getting
|
||||
// around the Reference type system
|
||||
var fieldInterface interface{} = t.Field
|
||||
if _, ok := fieldInterface.(Reference); ok {
|
||||
failf("field should not implement Reference interface")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryInfo(t *testing.T) {
|
||||
type tcase struct {
|
||||
RemoteName, NormalizedName, FullName, AmbiguousName, Hostname string
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
func TestWithTag(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
tag string
|
||||
combined string
|
||||
}{
|
||||
{
|
||||
RemoteName: "fooo/bar",
|
||||
NormalizedName: "fooo/bar",
|
||||
FullName: "docker.io/fooo/bar",
|
||||
AmbiguousName: "index.docker.io/fooo/bar",
|
||||
Hostname: "docker.io",
|
||||
name: "test.com/foo",
|
||||
tag: "tag",
|
||||
combined: "test.com/foo:tag",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu",
|
||||
NormalizedName: "ubuntu",
|
||||
FullName: "docker.io/library/ubuntu",
|
||||
AmbiguousName: "library/ubuntu",
|
||||
Hostname: "docker.io",
|
||||
name: "foo",
|
||||
tag: "tag2",
|
||||
combined: "foo:tag2",
|
||||
},
|
||||
{
|
||||
RemoteName: "nonlibrary/ubuntu",
|
||||
NormalizedName: "nonlibrary/ubuntu",
|
||||
FullName: "docker.io/nonlibrary/ubuntu",
|
||||
AmbiguousName: "",
|
||||
Hostname: "docker.io",
|
||||
name: "test.com:8000/foo",
|
||||
tag: "tag4",
|
||||
combined: "test.com:8000/foo:tag4",
|
||||
},
|
||||
{
|
||||
RemoteName: "other/library",
|
||||
NormalizedName: "other/library",
|
||||
FullName: "docker.io/other/library",
|
||||
AmbiguousName: "",
|
||||
Hostname: "docker.io",
|
||||
name: "test.com:8000/foo",
|
||||
tag: "TAG5",
|
||||
combined: "test.com:8000/foo:TAG5",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
NormalizedName: "127.0.0.1:8000/private/moonbase",
|
||||
FullName: "127.0.0.1:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Hostname: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
NormalizedName: "127.0.0.1:8000/privatebase",
|
||||
FullName: "127.0.0.1:8000/privatebase",
|
||||
AmbiguousName: "",
|
||||
Hostname: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
NormalizedName: "example.com/private/moonbase",
|
||||
FullName: "example.com/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Hostname: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
NormalizedName: "example.com/privatebase",
|
||||
FullName: "example.com/privatebase",
|
||||
AmbiguousName: "",
|
||||
Hostname: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
NormalizedName: "example.com:8000/private/moonbase",
|
||||
FullName: "example.com:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Hostname: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebasee",
|
||||
NormalizedName: "example.com:8000/privatebasee",
|
||||
FullName: "example.com:8000/privatebasee",
|
||||
AmbiguousName: "",
|
||||
Hostname: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu-12.04-base",
|
||||
NormalizedName: "ubuntu-12.04-base",
|
||||
FullName: "docker.io/library/ubuntu-12.04-base",
|
||||
AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
|
||||
Hostname: "docker.io",
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
tag: "TAG5",
|
||||
combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
refStrings := []string{tcase.NormalizedName, tcase.FullName}
|
||||
if tcase.AmbiguousName != "" {
|
||||
refStrings = append(refStrings, tcase.AmbiguousName)
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
var refs []Named
|
||||
for _, r := range refStrings {
|
||||
named, err := ParseNamed(r)
|
||||
named, err := WithName(testcase.name)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
if testcase.digest != "" {
|
||||
canonical, err := WithDigest(named, testcase.digest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
failf("error adding digest")
|
||||
}
|
||||
refs = append(refs, named)
|
||||
named, err = WithName(r)
|
||||
named = canonical
|
||||
}
|
||||
|
||||
tagged, err := WithTag(named, testcase.tag)
|
||||
if err != nil {
|
||||
failf("WithTag failed: %s", err)
|
||||
}
|
||||
if tagged.String() != testcase.combined {
|
||||
failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDigest(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
tag string
|
||||
combined string
|
||||
}{
|
||||
{
|
||||
name: "test.com/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
tag: "latest",
|
||||
combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := WithName(testcase.name)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
if testcase.tag != "" {
|
||||
tagged, err := WithTag(named, testcase.tag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
failf("error adding tag")
|
||||
}
|
||||
refs = append(refs, named)
|
||||
named = tagged
|
||||
}
|
||||
|
||||
for _, r := range refs {
|
||||
if expected, actual := tcase.NormalizedName, r.Name(); expected != actual {
|
||||
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.FullName, r.FullName(); expected != actual {
|
||||
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.Hostname, r.Hostname(); expected != actual {
|
||||
t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.RemoteName, r.RemoteName(); expected != actual {
|
||||
t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
|
||||
digested, err := WithDigest(named, testcase.digest)
|
||||
if err != nil {
|
||||
failf("WithDigest failed: %s", err)
|
||||
}
|
||||
if digested.String() != testcase.combined {
|
||||
failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReferenceWithTagAndDigest(t *testing.T) {
|
||||
ref, err := ParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestParseNamed(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test:8080/foo",
|
||||
domain: "test:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "test.com",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "library/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
// Ambiguous case, parser will add "library/" to foo
|
||||
{
|
||||
input: "docker.io/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
}
|
||||
if _, isTagged := ref.(NamedTagged); isTagged {
|
||||
t.Fatalf("Reference from %q should not support tag", ref)
|
||||
}
|
||||
if _, isCanonical := ref.(Canonical); !isCanonical {
|
||||
t.Fatalf("Reference from %q should not support digest", ref)
|
||||
}
|
||||
if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected {
|
||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
||||
}
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func TestInvalidReferenceComponents(t *testing.T) {
|
||||
if _, err := WithName("-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid name")
|
||||
}
|
||||
ref, err := WithName("busybox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := WithTag(ref, "-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid tag")
|
||||
named, err := ParseNamed(testcase.input)
|
||||
if err != nil && testcase.err == nil {
|
||||
failf("error parsing name: %s", err)
|
||||
continue
|
||||
} else if err == nil && testcase.err != nil {
|
||||
failf("parsing succeded: expected error %v", testcase.err)
|
||||
continue
|
||||
} else if err != testcase.err {
|
||||
failf("unexpected error %v, expected %v", err, testcase.err)
|
||||
continue
|
||||
} else if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
143
vendor/github.com/containers/image/docker/reference/regexp.go
generated
vendored
Normal file
143
vendor/github.com/containers/image/docker/reference/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
package reference
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
||||
// component of names. This only allows lower case characters and digits.
|
||||
alphaNumericRegexp = match(`[a-z0-9]+`)
|
||||
|
||||
// separatorRegexp defines the separators allowed to be embedded in name
|
||||
// components. This allow one period, one or two underscore and multiple
|
||||
// dashes.
|
||||
separatorRegexp = match(`(?:[._]|__|[-]*)`)
|
||||
|
||||
// nameComponentRegexp restricts registry path component names to start
|
||||
// with at least one letter or number, with following parts able to be
|
||||
// separated by one period, one or two underscore and multiple dashes.
|
||||
nameComponentRegexp = expression(
|
||||
alphaNumericRegexp,
|
||||
optional(repeated(separatorRegexp, alphaNumericRegexp)))
|
||||
|
||||
// domainComponentRegexp restricts the registry domain component of a
|
||||
// repository name to start with a component as defined by domainRegexp
|
||||
// and followed by an optional port.
|
||||
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
||||
|
||||
// domainRegexp defines the structure of potential domain components
|
||||
// that may be part of image names. This is purposely a subset of what is
|
||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||
// names.
|
||||
domainRegexp = expression(
|
||||
domainComponentRegexp,
|
||||
optional(repeated(literal(`.`), domainComponentRegexp)),
|
||||
optional(literal(`:`), match(`[0-9]+`)))
|
||||
|
||||
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
||||
TagRegexp = match(`[\w][\w.-]{0,127}`)
|
||||
|
||||
// anchoredTagRegexp matches valid tag names, anchored at the start and
|
||||
// end of the matched string.
|
||||
anchoredTagRegexp = anchored(TagRegexp)
|
||||
|
||||
// DigestRegexp matches valid digests.
|
||||
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
|
||||
|
||||
// anchoredDigestRegexp matches valid digests, anchored at the start and
|
||||
// end of the matched string.
|
||||
anchoredDigestRegexp = anchored(DigestRegexp)
|
||||
|
||||
// NameRegexp is the format for the name component of references. The
|
||||
// regexp has capturing groups for the domain and name part omitting
|
||||
// the separating forward slash from either.
|
||||
NameRegexp = expression(
|
||||
optional(domainRegexp, literal(`/`)),
|
||||
nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// domain and trailing components.
|
||||
anchoredNameRegexp = anchored(
|
||||
optional(capture(domainRegexp), literal(`/`)),
|
||||
capture(nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp))))
|
||||
|
||||
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||
// is anchored and has capturing groups for name, tag, and digest
|
||||
// components.
|
||||
ReferenceRegexp = anchored(capture(NameRegexp),
|
||||
optional(literal(":"), capture(TagRegexp)),
|
||||
optional(literal("@"), capture(DigestRegexp)))
|
||||
|
||||
// IdentifierRegexp is the format for string identifier used as a
|
||||
// content addressable identifier using sha256. These identifiers
|
||||
// are like digests without the algorithm, since sha256 is used.
|
||||
IdentifierRegexp = match(`([a-f0-9]{64})`)
|
||||
|
||||
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||
// within a list of trusted identifiers.
|
||||
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
|
||||
|
||||
// anchoredIdentifierRegexp is used to check or match an
|
||||
// identifier value, anchored at start and end of string.
|
||||
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
|
||||
|
||||
// anchoredShortIdentifierRegexp is used to check if a value
|
||||
// is a possible identifier prefix, anchored at start and end
|
||||
// of string.
|
||||
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
|
||||
)
|
||||
|
||||
// match compiles the string to a regular expression.
|
||||
var match = regexp.MustCompile
|
||||
|
||||
// literal compiles s into a literal regular expression, escaping any regexp
|
||||
// reserved characters.
|
||||
func literal(s string) *regexp.Regexp {
|
||||
re := match(regexp.QuoteMeta(s))
|
||||
|
||||
if _, complete := re.LiteralPrefix(); !complete {
|
||||
panic("must be a literal")
|
||||
}
|
||||
|
||||
return re
|
||||
}
|
||||
|
||||
// expression defines a full expression, where each regular expression must
|
||||
// follow the previous.
|
||||
func expression(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
var s string
|
||||
for _, re := range res {
|
||||
s += re.String()
|
||||
}
|
||||
|
||||
return match(s)
|
||||
}
|
||||
|
||||
// optional wraps the expression in a non-capturing group and makes the
|
||||
// production optional.
|
||||
func optional(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `?`)
|
||||
}
|
||||
|
||||
// repeated wraps the regexp in a non-capturing group to get one or more
|
||||
// matches.
|
||||
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `+`)
|
||||
}
|
||||
|
||||
// group wraps the regexp in a non-capturing group.
|
||||
func group(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(?:` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// capture wraps the expression in a capturing group.
|
||||
func capture(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// anchored anchors the regular expression by adding start and end delimiters.
|
||||
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`^` + expression(res...).String() + `$`)
|
||||
}
|
553
vendor/github.com/containers/image/docker/reference/regexp_test.go
generated
vendored
Normal file
553
vendor/github.com/containers/image/docker/reference/regexp_test.go
generated
vendored
Normal file
|
@ -0,0 +1,553 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type regexpMatch struct {
|
||||
input string
|
||||
match bool
|
||||
subs []string
|
||||
}
|
||||
|
||||
func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
|
||||
matches := r.FindStringSubmatch(m.input)
|
||||
if m.match && matches != nil {
|
||||
if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
|
||||
t.Fatalf("Bad match result %#v for %q", matches, m.input)
|
||||
}
|
||||
if len(matches) < (len(m.subs) + 1) {
|
||||
t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
|
||||
}
|
||||
for i := range m.subs {
|
||||
if m.subs[i] != matches[i+1] {
|
||||
t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
|
||||
}
|
||||
}
|
||||
} else if m.match {
|
||||
t.Errorf("Expected match for %q", m.input)
|
||||
} else if matches != nil {
|
||||
t.Errorf("Unexpected match for %q", m.input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainRegexp(t *testing.T) {
|
||||
hostcases := []regexpMatch{
|
||||
{
|
||||
input: "test.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "test.com:10304",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "test.com:http",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "localhost",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "localhost:8080",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a.b",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "ab.cd.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a-b.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "-ab.com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab-.com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab.c-om",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "ab.-com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab.com-",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "0101.com",
|
||||
match: true, // TODO(dmcgowan): valid if this should be allowed
|
||||
},
|
||||
{
|
||||
input: "001a.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "b.gbc.io:443",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "b.gbc.io",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com", // ☃.com in punycode
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "Asdf.com", // uppercase character
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
r := regexp.MustCompile(`^` + domainRegexp.String() + `$`)
|
||||
for i := range hostcases {
|
||||
checkRegexp(t, r, hostcases[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullNameRegexp(t *testing.T) {
|
||||
if anchoredNameRegexp.NumSubexp() != 2 {
|
||||
t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
|
||||
anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
|
||||
}
|
||||
|
||||
testcases := []regexpMatch{
|
||||
{
|
||||
input: "",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "short",
|
||||
match: true,
|
||||
subs: []string{"", "short"},
|
||||
},
|
||||
{
|
||||
input: "simple/name",
|
||||
match: true,
|
||||
subs: []string{"simple", "name"},
|
||||
},
|
||||
{
|
||||
input: "library/ubuntu",
|
||||
match: true,
|
||||
subs: []string{"library", "ubuntu"},
|
||||
},
|
||||
{
|
||||
input: "docker/stevvooe/app",
|
||||
match: true,
|
||||
subs: []string{"docker", "stevvooe/app"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a",
|
||||
match: true,
|
||||
subs: []string{"a", "a/a/a"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a/",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "a//a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
subs: []string{"", "a"},
|
||||
},
|
||||
{
|
||||
input: "a/aa",
|
||||
match: true,
|
||||
subs: []string{"a", "aa"},
|
||||
},
|
||||
{
|
||||
input: "a/aa/a",
|
||||
match: true,
|
||||
subs: []string{"a", "aa/a"},
|
||||
},
|
||||
{
|
||||
input: "foo.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo.com"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com:8080", "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com:http/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080", "bar"},
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
||||
match: true,
|
||||
subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
|
||||
},
|
||||
{
|
||||
input: "blog.foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"blog.foo.com", "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "a^a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "asdf$$^/aa",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "aa-a/a",
|
||||
match: true,
|
||||
subs: []string{"aa-a", "a"},
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a",
|
||||
match: true,
|
||||
subs: []string{"a", strings.Repeat("a/", 127) + "a"},
|
||||
},
|
||||
{
|
||||
input: "a-/a/a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/a-/a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "-foo/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo/bar-",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo-/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo/-bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "_foo/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo_bar",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar.com"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080/app",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/foo_bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "____/____",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "_docker/_docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker_/docker_",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/myimage", // ☃.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--n3h.com", "myimage"},
|
||||
},
|
||||
{
|
||||
input: "xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--7o8h.com", "myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"example.com", "xn--7o8h.com/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/some_separator__underscore/myimage",
|
||||
match: true,
|
||||
subs: []string{"example.com", "some_separator__underscore/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/__underscore/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/.dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/nodouble..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/nodouble..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker./docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: ".docker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker-/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "-docker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do..cker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do__cker:8080/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do__cker/docker",
|
||||
match: true,
|
||||
subs: []string{"", "do__cker/docker"},
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "registry.io/foo/project--id.module--name.ver---sion--name",
|
||||
match: true,
|
||||
subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
|
||||
},
|
||||
{
|
||||
input: "Asdf.com/foo/bar", // uppercase character in hostname
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "Foo/FarB", // uppercase characters in remote name
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
for i := range testcases {
|
||||
checkRegexp(t, anchoredNameRegexp, testcases[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferenceRegexp(t *testing.T) {
|
||||
if ReferenceRegexp.NumSubexp() != 3 {
|
||||
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
|
||||
ReferenceRegexp, ReferenceRegexp.NumSubexp())
|
||||
}
|
||||
|
||||
testcases := []regexpMatch{
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag", ""},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:badbadbadbad",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp:invalid~tag",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "bad_hostname.com:8080/myapp:tag",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input:// localhost treated as name, missing tag with 8080 as tag
|
||||
"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
// localhost will be treated as an image name without a host
|
||||
input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@bad",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@2bad",
|
||||
match: false, // TODO(dmcgowan): Support this as valid
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
checkRegexp(t, ReferenceRegexp, testcases[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIdentifierRegexp(t *testing.T) {
|
||||
fullCases := []regexpMatch{
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
|
||||
shortCases := []regexpMatch{
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e",
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range fullCases {
|
||||
checkRegexp(t, anchoredIdentifierRegexp, fullCases[i])
|
||||
}
|
||||
|
||||
for i := range shortCases {
|
||||
checkRegexp(t, anchoredShortIdentifierRegexp, shortCases[i])
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue