From dda360d9ec612c553e57fc8387eed7136194f85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:20:55 +0100 Subject: [PATCH 01/38] API separation: Add an 'X' to all public names from c/i/docker/daemon/reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an intermediate step which will eventually go away. The goal of this PR is to get rid of c/i/docker/daemon/reference and to replace uses of it by direct calls to docker/distribution/reference. We can't do that safely and easily, because the two have different semantics for reference.Named.Name() and reference.Named.String(): we return a minimized version, e.g. "busybox", upstream returns an expanded version, e.g. "docker.io/library/busybox". BEFORE this commit the difference is hidden by using docker/distribution/reference.WithName, which allows using the minimized version, and works with it correctly; but because we want to use the upstream canonicalization code, which will change semantics, we can't just mix and match. To make the distinction explicit, this commmit adds an X to ALL public names from c/i/docker/daemon/reference. E.g. a reference.XNamed type, which has methods XName and XString. This is pretty large, but does not change behavior at all. By inspection it is clear to see that reference.XNamed and subtypes does not expose any of the non-X, conflicting, method names. Using e.g. > git diff --word-diff-regex=.|grep -F '{+'|grep -v '^\([^{]\|{+X+}\)*{\?$' it is possible to see that most lines in this diff only add a single X letter, and manually inspect the few lines which don't match the regexp. The only REALLY new code is an explicit definition of namedRef.XName() and namedRef.XString(), and two newly added casts to namedRef in cases where we need to use the underlying distreference.Reference within a reference.XNamed value. Strictly speaking these changes change behavior, in that third-party implementations of reference.XNamed are no longer accepted; but we broke them by renaming at all. Signed-off-by: Miloslav Trmač --- copy/copy.go | 2 +- directory/directory_transport.go | 2 +- docker/daemon/daemon_dest.go | 6 +- docker/daemon/daemon_transport.go | 28 ++--- docker/daemon/daemon_transport_test.go | 28 ++--- docker/docker_client.go | 6 +- docker/docker_image.go | 4 +- docker/docker_image_dest.go | 16 +-- docker/docker_image_src.go | 8 +- docker/docker_transport.go | 36 +++--- docker/docker_transport_test.go | 22 ++-- docker/lookaside.go | 4 +- docker/policyconfiguration/naming.go | 28 ++--- docker/policyconfiguration/naming_test.go | 14 +-- docker/reference/reference.go | 138 ++++++++++++---------- docker/reference/reference_test.go | 36 +++--- image/docker_schema1.go | 8 +- image/docker_schema2_test.go | 12 +- image/oci_test.go | 4 +- image/unparsed.go | 4 +- oci/layout/oci_transport.go | 4 +- openshift/openshift.go | 6 +- openshift/openshift_transport.go | 20 ++-- openshift/openshift_transport_test.go | 14 +-- signature/docker.go | 6 +- signature/policy_config.go | 6 +- signature/policy_eval_signedby_test.go | 2 +- signature/policy_eval_simple_test.go | 2 +- signature/policy_eval_test.go | 10 +- signature/policy_reference_match.go | 34 +++--- signature/policy_reference_match_test.go | 38 +++--- storage/storage_reference.go | 8 +- storage/storage_reference_test.go | 2 +- storage/storage_transport.go | 22 ++-- storage/storage_transport_test.go | 4 +- types/types.go | 2 +- 36 files changed, 297 insertions(+), 289 deletions(-) diff --git a/copy/copy.go b/copy/copy.go index d27e634ce5..214ed45f76 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -210,7 +210,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe } writeReport("Signing manifest\n") - newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, options.SignBy) + newSig, err := signature.SignDockerManifest(manifest, dockerReference.XString(), mech, options.SignBy) if err != nil { return errors.Wrap(err, "Error creating signature") } diff --git a/directory/directory_transport.go b/directory/directory_transport.go index 89d2565aac..4f294c92e8 100644 --- a/directory/directory_transport.go +++ b/directory/directory_transport.go @@ -93,7 +93,7 @@ func (ref dirReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref dirReference) DockerReference() reference.Named { +func (ref dirReference) DockerReference() reference.XNamed { return nil } diff --git a/docker/daemon/daemon_dest.go b/docker/daemon/daemon_dest.go index 816a62261e..a18c0a6e88 100644 --- a/docker/daemon/daemon_dest.go +++ b/docker/daemon/daemon_dest.go @@ -22,7 +22,7 @@ import ( type daemonImageDestination struct { ref daemonReference - namedTaggedRef reference.NamedTagged // Strictly speaking redundant with ref above; having the field makes it structurally impossible for later users to fail. + namedTaggedRef reference.XNamedTagged // Strictly speaking redundant with ref above; having the field makes it structurally impossible for later users to fail. // For talking to imageLoadGoroutine goroutineCancel context.CancelFunc statusChannel <-chan error @@ -38,7 +38,7 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t if ref.ref == nil { return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport()) } - namedTaggedRef, ok := ref.ref.(reference.NamedTagged) + namedTaggedRef, ok := ref.ref.(reference.XNamedTagged) if !ok { return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport()) } @@ -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.XFullName(), d.namedTaggedRef.XTag()) items := []manifestItem{{ Config: man.Config.Digest.String(), diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index c8e40aed28..1d0ac42116 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -41,16 +41,16 @@ func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error { // Using the config digest requires the caller to parse the manifest themselves, which is very cumbersome; so, for now, we don’t bother.) type daemonReference struct { id digest.Digest - ref reference.Named // !reference.IsNameOnly + ref reference.XNamed // !reference.XIsNameOnly } // 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.XParseIDOrReference, 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.XParseIDOrReference 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,29 +60,29 @@ 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.XParseNamed(refString) // This also rejects unprefixed digest values if err != nil { return nil, err } - if ref.Name() == digest.Canonical.String() { + if ref.XName() == 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) } -// NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.IsNameOnly) -func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference, error) { +// NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.XIsNameOnly) +func NewReference(id digest.Digest, ref reference.XNamed) (types.ImageReference, error) { if id != "" && ref != nil { return nil, errors.New("docker-daemon: reference must not have an image ID and a reference string specified at the same time") } if ref != nil { - if reference.IsNameOnly(ref) { - return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", ref.String()) + if reference.XIsNameOnly(ref) { + return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", ref.XString()) } // 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. - _, isTagged := ref.(reference.NamedTagged) - _, isDigested := ref.(reference.Canonical) + _, isTagged := ref.(reference.XNamedTagged) + _, isDigested := ref.(reference.XCanonical) if isTagged && isDigested { return nil, errors.Errorf("docker-daemon: references with both a tag and digest are currently not supported") } @@ -108,16 +108,16 @@ func (ref daemonReference) StringWithinTransport() string { case ref.id != "": return ref.id.String() case ref.ref != nil: - return ref.ref.String() + return ref.ref.XString() default: // Coverage: Should never happen, NewReference above should refuse such values. panic("Internal inconsistency: daemonReference has empty id and nil ref") } } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref daemonReference) DockerReference() reference.Named { +func (ref daemonReference) DockerReference() reference.XNamed { return ref.ref // May be nil } diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 68ad255367..5a5bd97727 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -67,8 +67,8 @@ 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 for reference.XParseIDOrReference + dockerID, dockerRef, err := reference.XParseIDOrReference(c.input) require.NoError(t, err, c.input) if c.expectedRef == "" { @@ -80,20 +80,20 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } 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, c.expectedRef, daemonRef.ref.XString(), c.input) assert.Equal(t, "", dockerID.String(), c.input) require.NotNil(t, dockerRef, c.input) - assert.Equal(t, c.expectedRef, dockerRef.String(), c.input) + assert.Equal(t, c.expectedRef, dockerRef.XString(), c.input) } } } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } +// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) Tag() string { +func (ref refWithTagAndDigest) XTag() string { return "notLatest" } @@ -119,7 +119,7 @@ func TestNewReference(t *testing.T) { // Named references for _, c := range validNamedReferenceTestCases { - parsed, err := reference.ParseNamed(c.input) + parsed, err := reference.XParseNamed(c.input) require.NoError(t, err) ref, err := NewReference("", parsed) require.NoError(t, err, c.input) @@ -127,25 +127,25 @@ func TestNewReference(t *testing.T) { require.True(t, ok, c.input) assert.Equal(t, "", daemonRef.id.String()) require.NotNil(t, daemonRef.ref) - assert.Equal(t, c.dockerRef, daemonRef.ref.String(), c.input) + assert.Equal(t, c.dockerRef, daemonRef.ref.XString(), c.input) } // Both an ID and a named reference provided - parsed, err := reference.ParseNamed("busybox:latest") + parsed, err := reference.XParseNamed("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.XParseNamed("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.XParseNamed("busybox@" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + refDigested, ok := parsed.(reference.XCanonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} _, err = NewReference("", tagDigestRef) @@ -190,7 +190,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err, c.input) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef, c.input) - assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.XString(), c.input) } } diff --git a/docker/docker_client.go b/docker/docker_client.go index 0605cad2e1..9937208c19 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -164,11 +164,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 := ref.ref.XHostname() if registry == dockerHostname { registry = dockerRegistry } - username, password, err := getAuth(ctx, ref.ref.Hostname()) + username, password, err := getAuth(ctx, ref.ref.XHostname()) if err != nil { return nil, err } @@ -202,7 +202,7 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, signatureBase: sigBase, scope: authScope{ actions: actions, - remoteName: ref.ref.RemoteName(), + remoteName: ref.ref.XRemoteName(), }, }, nil } diff --git a/docker/docker_image.go b/docker/docker_image.go index ce769c0a3c..98137e9f84 100644 --- a/docker/docker_image.go +++ b/docker/docker_image.go @@ -34,12 +34,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.XFullName() } // 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, i.src.ref.ref.XRemoteName()) res, err := i.src.c.makeRequest("GET", url, nil, nil) if err != nil { return nil, err diff --git a/docker/docker_image_dest.go b/docker/docker_image_dest.go index 78ccc27afb..cd2d0f857c 100644 --- a/docker/docker_image_dest.go +++ b/docker/docker_image_dest.go @@ -98,7 +98,7 @@ 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()) + checkURL := fmt.Sprintf(blobsURL, d.ref.ref.XRemoteName(), inputInfo.Digest.String()) logrus.Debugf("Checking %s", checkURL) res, err := d.c.makeRequest("HEAD", checkURL, nil, nil) @@ -112,17 +112,17 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI 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()) + return types.BlobInfo{}, errors.Errorf("not authorized to read from destination repository %s", d.ref.ref.XRemoteName()) 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)) + return types.BlobInfo{}, errors.Errorf("failed to read from destination repository %s: %v", d.ref.ref.XRemoteName(), http.StatusText(res.StatusCode)) } 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, d.ref.ref.XRemoteName()) logrus.Debugf("Uploading %s", uploadURL) res, err := d.c.makeRequest("POST", uploadURL, nil, nil) if err != nil { @@ -178,7 +178,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, d.ref.ref.XRemoteName(), info.Digest.String()) logrus.Debugf("Checking %s", checkURL) res, err := d.c.makeRequest("HEAD", checkURL, nil, nil) @@ -192,12 +192,12 @@ 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", d.ref.ref.XRemoteName()) 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)) + logrus.Errorf("failed to read from destination repository %s: %v", d.ref.ref.XRemoteName(), http.StatusText(res.StatusCode)) } logrus.Debugf("... failed, status %d, ignoring", res.StatusCode) return false, -1, types.ErrBlobNotFound @@ -218,7 +218,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error { if err != nil { return err } - url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), reference) + url := fmt.Sprintf(manifestURL, d.ref.ref.XRemoteName(), reference) headers := map[string][]string{} mimeType := manifest.GuessMIMEType(m) diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index f87a5fc23c..f92ab6fd8e 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -91,7 +91,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, s.ref.ref.XRemoteName(), tagOrDigest) headers := make(map[string][]string) headers["Accept"] = s.requestedManifestMIMETypes res, err := s.c.makeRequest("GET", url, headers, nil) @@ -177,7 +177,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, s.ref.ref.XRemoteName(), info.Digest.String()) logrus.Debugf("Downloading %s", url) res, err := s.c.makeRequest("GET", url, nil, nil) if err != nil { @@ -275,7 +275,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error { if err != nil { return err } - getURL := fmt.Sprintf(manifestURL, ref.ref.RemoteName(), reference) + getURL := fmt.Sprintf(manifestURL, ref.ref.XRemoteName(), reference) get, err := c.makeRequest("GET", getURL, headers, nil) if err != nil { return err @@ -294,7 +294,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, ref.ref.XRemoteName(), digest) // When retrieving the digest from a registry >= 2.3 use the following header: // "Accept": "application/vnd.docker.distribution.manifest.v2+json" diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 00d0b7c9bf..14fe9d191a 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -37,7 +37,7 @@ func (t dockerTransport) ValidatePolicyConfigurationScope(scope string) error { // dockerReference is an ImageReference for Docker images. type dockerReference struct { - ref reference.Named // By construction we know that !reference.IsNameOnly(ref) + ref reference.XNamed // By construction we know that !reference.XIsNameOnly(ref) } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference. @@ -45,25 +45,25 @@ 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.XParseNamed(strings.TrimPrefix(refString, "//")) if err != nil { return nil, err } - ref = reference.WithDefaultTag(ref) + ref = reference.XWithDefaultTag(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()) +// NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.XIsNameOnly(). +func NewReference(ref reference.XNamed) (types.ImageReference, error) { + if reference.XIsNameOnly(ref) { + return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", ref.XString()) } // 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. // (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) - _, isDigested := ref.(reference.Canonical) + _, isTagged := ref.(reference.XNamedTagged) + _, isDigested := ref.(reference.XCanonical) if isTagged && isDigested { return nil, errors.Errorf("Docker references with both a tag and digest are currently not supported") } @@ -82,13 +82,13 @@ 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 "//" + ref.ref.XString() } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref dockerReference) DockerReference() reference.Named { +func (ref dockerReference) DockerReference() reference.XNamed { return ref.ref } @@ -145,12 +145,12 @@ func (ref dockerReference) DeleteImage(ctx *types.SystemContext) error { // tagOrDigest returns a tag or digest from the reference. func (ref dockerReference) tagOrDigest() (string, error) { - if ref, ok := ref.ref.(reference.Canonical); ok { - return ref.Digest().String(), nil + if ref, ok := ref.ref.(reference.XCanonical); ok { + return ref.XDigest().String(), nil } - if ref, ok := ref.ref.(reference.NamedTagged); ok { - return ref.Tag(), nil + if ref, ok := ref.ref.(reference.XNamedTagged); ok { + return ref.XTag(), 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()) + // This should not happen, NewReference above refuses reference.XIsNameOnly values. + return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", ref.ref.XString()) } diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index c6c83623b8..8d8d27af10 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -62,15 +62,15 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) - assert.Equal(t, c.expected, dockerRef.ref.String(), c.input) + assert.Equal(t, c.expected, dockerRef.ref.XString(), c.input) } } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } +// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) Tag() string { +func (ref refWithTagAndDigest) XTag() string { return "notLatest" } @@ -84,25 +84,25 @@ var validReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport func TestNewReference(t *testing.T) { for _, c := range validReferenceTestCases { - parsed, err := reference.ParseNamed(c.input) + parsed, err := reference.XParseNamed(c.input) require.NoError(t, err) ref, err := NewReference(parsed) require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) - assert.Equal(t, c.dockerRef, dockerRef.ref.String(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.ref.XString(), c.input) } // Neither a tag nor digest - parsed, err := reference.ParseNamed("busybox") + parsed, err := reference.XParseNamed("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.XParseNamed("busybox" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + refDigested, ok := parsed.(reference.XCanonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} _, err = NewReference(tagDigestRef) @@ -135,7 +135,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err, c.input) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef, c.input) - assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.XString(), c.input) } } @@ -196,7 +196,7 @@ func TestReferenceTagOrDigest(t *testing.T) { } // Invalid input - ref, err := reference.ParseNamed("busybox") + ref, err := reference.XParseNamed("busybox") require.NoError(t, err) dockerRef := dockerReference{ref: ref} _, err = dockerRef.tagOrDigest() diff --git a/docker/lookaside.go b/docker/lookaside.go index e8f3a5be26..0b4334db10 100644 --- a/docker/lookaside.go +++ b/docker/lookaside.go @@ -64,9 +64,9 @@ 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.XFullName() // 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()) + return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.XString()) } url.Path = url.Path + "/" + repo return url, nil diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index a40fa3807b..af2c73e7a4 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -10,20 +10,20 @@ import ( // 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() - tagged, isTagged := ref.(reference.NamedTagged) - digested, isDigested := ref.(reference.Canonical) +// The reference must satisfy !reference.XIsNameOnly(). +func DockerReferenceIdentity(ref reference.XNamed) (string, error) { + res := ref.XFullName() + tagged, isTagged := ref.(reference.XNamedTagged) + digested, isDigested := ref.(reference.XCanonical) 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: // 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()) + case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. + return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", ref.XString()) + case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.XIsNameOnly() + return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", ref.XString()) case isTagged: - res = res + ":" + tagged.Tag() + res = res + ":" + tagged.XTag() case isDigested: - res = res + "@" + digested.Digest().String() + res = res + "@" + digested.XDigest().String() default: // Coverage: The above was supposed to be exhaustive. return "", errors.New("Internal inconsistency, unexpected default branch") } @@ -32,8 +32,8 @@ func DockerReferenceIdentity(ref reference.Named) (string, error) { // DockerReferenceNamespaces returns a list of other policy configuration namespaces to search, // as a backend for ImageReference.PolicyConfigurationIdentity. -// The reference must satisfy !reference.IsNameOnly(). -func DockerReferenceNamespaces(ref reference.Named) []string { +// The reference must satisfy !reference.XIsNameOnly(). +func DockerReferenceNamespaces(ref reference.XNamed) []string { // Look for a match of the repository, and then of the possible parent // namespaces. Note that this only happens on the expanded host names // and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox", @@ -43,7 +43,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.XFullName() for { res = append(res, name) diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index 0269db95cf..4425cb9ba9 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -42,7 +42,7 @@ func TestDockerReference(t *testing.T) { ":tag" + sha256Digest: sha256Digest, } { fullInput := inputName + inputSuffix - ref, err := reference.ParseNamed(fullInput) + ref, err := reference.XParseNamed(fullInput) require.NoError(t, err, fullInput) identity, err := DockerReferenceIdentity(ref) @@ -62,10 +62,10 @@ func TestDockerReference(t *testing.T) { } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } +// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) Tag() string { +func (ref refWithTagAndDigest) XTag() string { return "notLatest" } @@ -73,16 +73,16 @@ 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.XParseNamed("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.XParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + refDigested, ok := parsed.(reference.XCanonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} id, err = DockerReferenceIdentity(tagDigestRef) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 38c30e2dbb..c521ae7c87 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -14,72 +14,73 @@ import ( ) 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/" + // XDefaultTag defines the default tag used when performing images related actions and no tag or digest is specified + XDefaultTag = "latest" + // XDefaultHostname is the default built-in hostname + XDefaultHostname = "docker.io" + // XLegacyDefaultHostname is automatically converted to DefaultHostname + XLegacyDefaultHostname = "index.docker.io" + // XDefaultRepoPrefix is the prefix used for default repositories in default host + XDefaultRepoPrefix = "library/" ) -// Named is an object with a full name -type Named interface { - // Name returns normalized repository name, like "ubuntu". - 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 +// XNamed is an object with a full name +type XNamed interface { + // XName returns normalized repository name, like "ubuntu". + XName() string + // XString returns full reference, like "ubuntu@sha256:abcdef..." + XString() string + // XFullName returns full repository name with hostname, like "docker.io/library/ubuntu" + XFullName() string + // XHostname returns hostname for the reference, like "docker.io" + XHostname() string + // XRemoteName returns the repository component of the full name, like "library/ubuntu" + XRemoteName() string } -// NamedTagged is an object including a name and tag. -type NamedTagged interface { - Named - Tag() string +// XNamedTagged is an object including a name and tag. +type XNamedTagged interface { + XNamed + XTag() string } -// Canonical reference is an object with a fully unique +// XCanonical reference is an object with a fully unique // name including a name with hostname and digest -type Canonical interface { - Named - Digest() digest.Digest +type XCanonical interface { + XNamed + XDigest() digest.Digest } -// ParseNamed parses s and returns a syntactically valid reference implementing +// XParseNamed parses s and returns a syntactically valid reference implementing // the Named interface. The reference must have a name, otherwise an error is // returned. // If an error was encountered it is returned, along with a nil Reference. -func ParseNamed(s string) (Named, error) { +func XParseNamed(s string) (XNamed, 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) } - r, err := WithName(named.Name()) + r, err := XWithName(named.Name()) if err != nil { return nil, err } if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - r, err := distreference.WithDigest(r, canonical.Digest()) + // FIXME: depends on XWithName returning a *namedRef. + r, err := distreference.WithDigest(r.(*namedRef).Named, canonical.Digest()) if err != nil { return nil, err } return &canonicalRef{namedRef{r}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { - return WithTag(r, tagged.Tag()) + return XWithTag(r, tagged.Tag()) } return r, nil } -// WithName returns a named object representing the given string. If the input +// XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -func WithName(name string) (Named, error) { +func XWithName(name string) (XNamed, error) { name, err := normalize(name) if err != nil { return nil, err @@ -94,10 +95,11 @@ func WithName(name string) (Named, error) { return &namedRef{r}, nil } -// WithTag combines the name from "name" and the tag from "tag" to form a +// XWithTag 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) +func XWithTag(name XNamed, tag string) (XNamedTagged, error) { + // FIXME: depends on XWithName returning a *namedRef, and that this is only called on XNameOnly values. + r, err := distreference.WithTag(name.(*namedRef).Named, tag) if err != nil { return nil, err } @@ -114,54 +116,60 @@ type canonicalRef struct { namedRef } -func (r *namedRef) FullName() string { - hostname, remoteName := splitHostname(r.Name()) +func (r *namedRef) XName() string { + return r.Named.Name() +} +func (r *namedRef) XString() string { + return r.Named.String() +} +func (r *namedRef) XFullName() string { + hostname, remoteName := splitHostname(r.XName()) return hostname + "/" + remoteName } -func (r *namedRef) Hostname() string { - hostname, _ := splitHostname(r.Name()) +func (r *namedRef) XHostname() string { + hostname, _ := splitHostname(r.XName()) return hostname } -func (r *namedRef) RemoteName() string { - _, remoteName := splitHostname(r.Name()) +func (r *namedRef) XRemoteName() string { + _, remoteName := splitHostname(r.XName()) return remoteName } -func (r *taggedRef) Tag() string { +func (r *taggedRef) XTag() string { return r.namedRef.Named.(distreference.NamedTagged).Tag() } -func (r *canonicalRef) Digest() digest.Digest { +func (r *canonicalRef) XDigest() 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) +// XWithDefaultTag adds a default tag to a reference if it only has a repo name. +func XWithDefaultTag(ref XNamed) XNamed { + if XIsNameOnly(ref) { + ref, _ = XWithTag(ref, XDefaultTag) } return ref } -// IsNameOnly returns true if reference only contains a repo name. -func IsNameOnly(ref Named) bool { - if _, ok := ref.(NamedTagged); ok { +// XIsNameOnly returns true if reference only contains a repo name. +func XIsNameOnly(ref XNamed) bool { + if _, ok := ref.(XNamedTagged); ok { return false } - if _, ok := ref.(Canonical); ok { + if _, ok := ref.(XCanonical); ok { return false } return true } -// ParseIDOrReference parses string for an image ID or a reference. ID can be +// XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. -func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) { +func XParseIDOrReference(idOrRef string) (digest.Digest, XNamed, 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) + ref, err := XParseNamed(idOrRef) return "", ref, err } @@ -171,15 +179,15 @@ func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) { 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 + hostname, remoteName = XDefaultHostname, name } else { hostname, remoteName = name[:i], name[i+1:] } - if hostname == LegacyDefaultHostname { - hostname = DefaultHostname + if hostname == XLegacyDefaultHostname { + hostname = XDefaultHostname } - if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') { - remoteName = DefaultRepoPrefix + remoteName + if hostname == XDefaultHostname && !strings.ContainsRune(remoteName, '/') { + remoteName = XDefaultRepoPrefix + remoteName } return } @@ -191,9 +199,9 @@ func normalize(name string) (string, error) { 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 + if host == XDefaultHostname { + if strings.HasPrefix(remoteName, XDefaultRepoPrefix) { + return strings.TrimPrefix(remoteName, XDefaultRepoPrefix), nil } return remoteName, nil } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 79aa829fed..4d89fdbf13 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -36,14 +36,14 @@ func TestValidateReferenceName(t *testing.T) { } for _, name := range invalidRepoNames { - _, err := ParseNamed(name) + _, err := XParseNamed(name) if err == nil { t.Fatalf("Expected invalid repo name for %q", name) } } for _, name := range validRepoNames { - _, err := ParseNamed(name) + _, err := XParseNamed(name) if err != nil { t.Fatalf("Error parsing repo name %s, got: %q", name, err) } @@ -75,7 +75,7 @@ func TestValidateRemoteName(t *testing.T) { "dock__er/docker", } for _, repositoryName := range validRepositoryNames { - _, err := ParseNamed(repositoryName) + _, err := XParseNamed(repositoryName) if err != nil { t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) } @@ -113,7 +113,7 @@ func TestValidateRemoteName(t *testing.T) { "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 { + if _, err := XParseNamed(repositoryName); err == nil { t.Errorf("Repository name should be invalid: %v", repositoryName) } } @@ -210,14 +210,14 @@ func TestParseRepositoryInfo(t *testing.T) { refStrings = append(refStrings, tcase.AmbiguousName) } - var refs []Named + var refs []XNamed for _, r := range refStrings { - named, err := ParseNamed(r) + named, err := XParseNamed(r) if err != nil { t.Fatal(err) } refs = append(refs, named) - named, err = WithName(r) + named, err = XWithName(r) if err != nil { t.Fatal(err) } @@ -225,16 +225,16 @@ func TestParseRepositoryInfo(t *testing.T) { } for _, r := range refs { - if expected, actual := tcase.NormalizedName, r.Name(); expected != actual { + if expected, actual := tcase.NormalizedName, r.XName(); 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 { + if expected, actual := tcase.FullName, r.XFullName(); 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 { + if expected, actual := tcase.Hostname, r.XHostname(); expected != actual { t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) } - if expected, actual := tcase.RemoteName, r.RemoteName(); expected != actual { + if expected, actual := tcase.RemoteName, r.XRemoteName(); expected != actual { t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) } @@ -243,30 +243,30 @@ func TestParseRepositoryInfo(t *testing.T) { } func TestParseReferenceWithTagAndDigest(t *testing.T) { - ref, err := ParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") + ref, err := XParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") if err != nil { t.Fatal(err) } - if _, isTagged := ref.(NamedTagged); isTagged { + if _, isTagged := ref.(XNamedTagged); isTagged { t.Fatalf("Reference from %q should not support tag", ref) } - if _, isCanonical := ref.(Canonical); !isCanonical { + if _, isCanonical := ref.(XCanonical); !isCanonical { t.Fatalf("Reference from %q should not support digest", ref) } - if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected { + if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.XString(); actual != expected { t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) } } func TestInvalidReferenceComponents(t *testing.T) { - if _, err := WithName("-foo"); err == nil { + if _, err := XWithName("-foo"); err == nil { t.Fatal("Expected WithName to detect invalid name") } - ref, err := WithName("busybox") + ref, err := XWithName("busybox") if err != nil { t.Fatal(err) } - if _, err := WithTag(ref, "-foo"); err == nil { + if _, err := XWithTag(ref, "-foo"); err == nil { t.Fatal("Expected WithName to detect invalid tag") } } diff --git a/image/docker_schema1.go b/image/docker_schema1.go index dce81a14d1..4f7c28f41c 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -69,12 +69,12 @@ func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) { } // manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data. -func manifestSchema1FromComponents(ref reference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { +func manifestSchema1FromComponents(ref reference.XNamed, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { var name, tag string if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. - name = ref.RemoteName() - if tagged, ok := ref.(reference.NamedTagged); ok { - tag = tagged.Tag() + name = ref.XRemoteName() + if tagged, ok := ref.(reference.XNamedTagged); ok { + tag = tagged.XTag() } } return &manifestSchema1{ diff --git a/image/docker_schema2_test.go b/image/docker_schema2_test.go index 52ef308929..1e74d21364 100644 --- a/image/docker_schema2_test.go +++ b/image/docker_schema2_test.go @@ -284,7 +284,7 @@ func TestManifestSchema2UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // schema2ImageSource is plausible enough for schema conversions in manifestSchema2.UpdatedImage() to work. type schema2ImageSource struct { configBlobImageSource - ref reference.Named + ref reference.XNamed } func (s2is *schema2ImageSource) Reference() types.ImageReference { @@ -292,7 +292,7 @@ func (s2is *schema2ImageSource) Reference() types.ImageReference { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ reference.Named } +type refImageReferenceMock struct{ reference.XNamed } func (ref refImageReferenceMock) Transport() types.ImageTransport { panic("unexpected call to a mock function") @@ -300,8 +300,8 @@ func (ref refImageReferenceMock) Transport() types.ImageTransport { func (ref refImageReferenceMock) StringWithinTransport() string { panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() reference.Named { - return ref.Named +func (ref refImageReferenceMock) DockerReference() reference.XNamed { + return ref.XNamed } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { panic("unexpected call to a mock function") @@ -326,7 +326,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") require.NoError(t, err) - ref, err := reference.ParseNamed(dockerRef) + ref, err := reference.XParseNamed(dockerRef) require.NoError(t, err) return &schema2ImageSource{ @@ -340,7 +340,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { } type memoryImageDest struct { - ref reference.Named + ref reference.XNamed storedBlobs map[digest.Digest][]byte } diff --git a/image/oci_test.go b/image/oci_test.go index ac74fbec13..996bcec631 100644 --- a/image/oci_test.go +++ b/image/oci_test.go @@ -249,7 +249,7 @@ func TestManifestOCI1UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // oci1ImageSource is plausible enough for schema conversions in manifestOCI1.UpdatedImage() to work. type oci1ImageSource struct { configBlobImageSource - ref reference.Named + ref reference.XNamed } func (OCIis *oci1ImageSource) Reference() types.ImageReference { @@ -260,7 +260,7 @@ func newOCI1ImageSource(t *testing.T, dockerRef string) *oci1ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") require.NoError(t, err) - ref, err := reference.ParseNamed(dockerRef) + ref, err := reference.XParseNamed(dockerRef) require.NoError(t, err) return &oci1ImageSource{ diff --git a/image/unparsed.go b/image/unparsed.go index 1e1ee0b528..2a98330cc1 100644 --- a/image/unparsed.go +++ b/image/unparsed.go @@ -51,8 +51,8 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) { // this immediately protects also any user of types.Image. ref := i.Reference().DockerReference() if ref != nil { - if canonical, ok := ref.(reference.Canonical); ok { - digest := canonical.Digest() + if canonical, ok := ref.(reference.XCanonical); ok { + digest := canonical.XDigest() matches, err := manifest.MatchesDigest(m, digest) if err != nil { return nil, "", errors.Wrap(err, "Error computing manifest digest") diff --git a/oci/layout/oci_transport.go b/oci/layout/oci_transport.go index 734af87c0b..e4c5696853 100644 --- a/oci/layout/oci_transport.go +++ b/oci/layout/oci_transport.go @@ -128,9 +128,9 @@ func (ref ociReference) StringWithinTransport() string { } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref ociReference) DockerReference() reference.Named { +func (ref ociReference) DockerReference() reference.XNamed { return nil } diff --git a/openshift/openshift.go b/openshift/openshift.go index 1fc0e24c34..36dc964d8a 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -153,7 +153,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error if len(parts) != 2 { return "", errors.Errorf("Invalid format of docker reference %s: missing '/'", ref) } - return c.ref.dockerReference.Hostname() + "/" + parts[1], nil + return c.ref.dockerReference.XHostname() + "/" + parts[1], nil } type openshiftImageSource struct { @@ -258,7 +258,7 @@ func (s *openshiftImageSource) ensureImageIsResolved() error { } var te *tagEvent for _, tag := range is.Status.Tags { - if tag.Tag != s.client.ref.dockerReference.Tag() { + if tag.Tag != s.client.ref.dockerReference.XTag() { continue } if len(tag.Items) > 0 { @@ -305,7 +305,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type // FIXME: Should this always use a digest, not a tag? Uploading to Docker by tag requires the tag _inside_ the manifest to match, // i.e. a single signed image cannot be available under multiple tags. But with types.ImageDestination, we don't know // the manifest digest at this point. - dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.ref.dockerReference.Hostname(), client.ref.namespace, client.ref.stream, client.ref.dockerReference.Tag()) + dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.ref.dockerReference.XHostname(), client.ref.namespace, client.ref.stream, client.ref.dockerReference.XTag()) dockerRef, err := docker.ParseReference(dockerRefString) if err != nil { return nil, err diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index b37fb2c21d..9ebba41b5d 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -44,30 +44,30 @@ func (t openshiftTransport) ValidatePolicyConfigurationScope(scope string) error // openshiftReference is an ImageReference for OpenShift images. type openshiftReference struct { - dockerReference reference.NamedTagged + dockerReference reference.XNamedTagged namespace string // Computed from dockerReference in advance. stream string // Computed from dockerReference in advance. } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference. func ParseReference(ref string) (types.ImageReference, error) { - r, err := reference.ParseNamed(ref) + r, err := reference.XParseNamed(ref) if err != nil { return nil, errors.Wrapf(err, "failed to parse image reference %q", ref) } - tagged, ok := r.(reference.NamedTagged) + tagged, ok := r.(reference.XNamedTagged) if !ok { return nil, errors.Errorf("invalid image reference %s, expected format: 'hostname/namespace/stream:tag'", ref) } return NewReference(tagged) } -// NewReference returns an OpenShift reference for a reference.NamedTagged -func NewReference(dockerRef reference.NamedTagged) (types.ImageReference, error) { - r := strings.SplitN(dockerRef.RemoteName(), "/", 3) +// NewReference returns an OpenShift reference for a reference.XNamedTagged +func NewReference(dockerRef reference.XNamedTagged) (types.ImageReference, error) { + r := strings.SplitN(dockerRef.XRemoteName(), "/", 3) if len(r) != 2 { return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", - dockerRef.String()) + dockerRef.XString()) } return openshiftReference{ namespace: r[0], @@ -86,13 +86,13 @@ func (ref openshiftReference) 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 openshiftReference) StringWithinTransport() string { - return ref.dockerReference.String() + return ref.dockerReference.XString() } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref openshiftReference) DockerReference() reference.Named { +func (ref openshiftReference) DockerReference() reference.XNamed { return ref.dockerReference } diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 46904ff369..6edf33f049 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -40,16 +40,16 @@ func TestTransportValidatePolicyConfigurationScope(t *testing.T) { func TestNewReference(t *testing.T) { // too many ns - r, err := reference.ParseNamed("registry.example.com/ns1/ns2/ns3/stream:tag") + r, err := reference.XParseNamed("registry.example.com/ns1/ns2/ns3/stream:tag") require.NoError(t, err) - tagged, ok := r.(reference.NamedTagged) + tagged, ok := r.(reference.XNamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.Error(t, err) - r, err = reference.ParseNamed("registry.example.com/ns/stream:tag") + r, err = reference.XParseNamed("registry.example.com/ns/stream:tag") require.NoError(t, err) - tagged, ok = r.(reference.NamedTagged) + tagged, ok = r.(reference.XNamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.NoError(t, err) @@ -63,8 +63,8 @@ func TestParseReference(t *testing.T) { require.True(t, ok) assert.Equal(t, "ns", osRef.namespace) assert.Equal(t, "stream", osRef.stream) - assert.Equal(t, "notlatest", osRef.dockerReference.Tag()) - assert.Equal(t, "registry.example.com:8443", osRef.dockerReference.Hostname()) + assert.Equal(t, "notlatest", osRef.dockerReference.XTag()) + assert.Equal(t, "registry.example.com:8443", osRef.dockerReference.XHostname()) // Components creating an invalid Docker Reference name _, err = ParseReference("registry.example.com/ns/UPPERCASEISINVALID:notlatest") @@ -79,7 +79,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef) - assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", dockerRef.String()) + assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", dockerRef.XString()) } func TestReferenceTransport(t *testing.T) { diff --git a/signature/docker.go b/signature/docker.go index 901a225a29..ee1e82b101 100644 --- a/signature/docker.go +++ b/signature/docker.go @@ -25,7 +25,7 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, // using mech. func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte, expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) { - expectedRef, err := reference.ParseNamed(expectedDockerReference) + expectedRef, err := reference.XParseNamed(expectedDockerReference) if err != nil { return nil, err } @@ -37,11 +37,11 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt return nil }, validateSignedDockerReference: func(signedDockerReference string) error { - signedRef, err := reference.ParseNamed(signedDockerReference) + signedRef, err := reference.XParseNamed(signedDockerReference) if err != nil { return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)} } - if signedRef.String() != expectedRef.String() { + if signedRef.XString() != expectedRef.XString() { return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s", signedDockerReference, expectedDockerReference)} } diff --git a/signature/policy_config.go b/signature/policy_config.go index e4525795dc..94f9d4237a 100644 --- a/signature/policy_config.go +++ b/signature/policy_config.go @@ -634,11 +634,11 @@ func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error { // newPRMExactReference is NewPRMExactReference, except it resturns the private type. func newPRMExactReference(dockerReference string) (*prmExactReference, error) { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.XParseNamed(dockerReference) if err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error())) } - if reference.IsNameOnly(ref) { + if reference.XIsNameOnly(ref) { return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference)) } return &prmExactReference{ @@ -686,7 +686,7 @@ func (prm *prmExactReference) UnmarshalJSON(data []byte) error { // newPRMExactRepository is NewPRMExactRepository, except it resturns the private type. func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) { - if _, err := reference.ParseNamed(dockerRepository); err != nil { + if _, err := reference.XParseNamed(dockerRepository); err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error())) } return &prmExactRepository{ diff --git a/signature/policy_eval_signedby_test.go b/signature/policy_eval_signedby_test.go index d21ee9c17f..972e08172a 100644 --- a/signature/policy_eval_signedby_test.go +++ b/signature/policy_eval_signedby_test.go @@ -17,7 +17,7 @@ import ( // dirImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference. // The caller must call .Close() on the returned UnparsedImage. func dirImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.XParseNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, refImageReferenceMock{ref}) } diff --git a/signature/policy_eval_simple_test.go b/signature/policy_eval_simple_test.go index aae4b6a8b8..5d766a9c6b 100644 --- a/signature/policy_eval_simple_test.go +++ b/signature/policy_eval_simple_test.go @@ -25,7 +25,7 @@ func (ref nameOnlyImageReferenceMock) Transport() types.ImageTransport { func (ref nameOnlyImageReferenceMock) StringWithinTransport() string { return string(ref) } -func (ref nameOnlyImageReferenceMock) DockerReference() reference.Named { +func (ref nameOnlyImageReferenceMock) DockerReference() reference.XNamed { panic("unexpected call to a mock function") } func (ref nameOnlyImageReferenceMock) PolicyConfigurationIdentity() string { diff --git a/signature/policy_eval_test.go b/signature/policy_eval_test.go index c0bfe1a39a..487bf19306 100644 --- a/signature/policy_eval_test.go +++ b/signature/policy_eval_test.go @@ -64,7 +64,7 @@ func TestPolicyContextNewDestroy(t *testing.T) { // and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently. type pcImageReferenceMock struct { transportName string - ref reference.Named + ref reference.XNamed } func (ref pcImageReferenceMock) Transport() types.ImageTransport { @@ -74,7 +74,7 @@ func (ref pcImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. return "== StringWithinTransport mock" } -func (ref pcImageReferenceMock) DockerReference() reference.Named { +func (ref pcImageReferenceMock) DockerReference() reference.XNamed { return ref.ref } func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string { @@ -148,7 +148,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // No match within a matched transport which doesn't have a "" scope {"atomic", "this.doesnt/match:anything", "", ""}, // No configuration available for this transport at all - {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.Named. + {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.XNamed. } { var expected PolicyRequirements if c.matchedTransport != "" { @@ -159,7 +159,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { expected = policy.Default } - ref, err := reference.ParseNamed(c.input) + ref, err := reference.XParseNamed(c.input) require.NoError(t, err) reqs := pc.requirementsForImageRef(pcImageReferenceMock{c.inputTransport, ref}) comment := fmt.Sprintf("case %s:%s: %#v", c.inputTransport, c.input, reqs[0]) @@ -174,7 +174,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // pcImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference and implementing PolicyConfigurationIdentity/PolicyConfigurationNamespaces. // The caller must call .Close() on the returned Image. func pcImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.XParseNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, pcImageReferenceMock{"docker", ref}) } diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index ced51e6e07..3bdda21b89 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -11,13 +11,13 @@ import ( ) // parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images. -func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.Named, reference.Named, error) { +func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.XNamed, reference.XNamed, error) { r1 := image.Reference().DockerReference() if r1 == nil { return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity", transports.ImageName(image.Reference()))) } - r2, err := reference.ParseNamed(s2) + r2, err := reference.XParseNamed(s2) if err != nil { return nil, nil, err } @@ -30,10 +30,10 @@ func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, sign return false } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) { + if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { return false } - return signature.String() == intended.String() + return signature.XString() == intended.XString() } func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { @@ -43,18 +43,18 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if reference.IsNameOnly(signature) { + if reference.XIsNameOnly(signature) { return false } switch intended.(type) { - case reference.NamedTagged: // Includes the case when intended has both a tag and a digest. - return signature.String() == intended.String() - case reference.Canonical: + case reference.XNamedTagged: // Includes the case when intended has both a tag and a digest. + return signature.XString() == intended.XString() + case reference.XCanonical: // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, // we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms) - return signature.Name() == intended.Name() - default: // !reference.IsNameOnly(intended) + return signature.XName() == intended.XName() + default: // !reference.XIsNameOnly(intended) return false } } @@ -64,16 +64,16 @@ func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, if err != nil { return false } - return signature.Name() == intended.Name() + return signature.XName() == intended.XName() } // parseDockerReferences converts two reference strings into parsed entities, failing on any error -func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, error) { - r1, err := reference.ParseNamed(s1) +func parseDockerReferences(s1, s2 string) (reference.XNamed, reference.XNamed, error) { + r1, err := reference.XParseNamed(s1) if err != nil { return nil, nil, err } - r2, err := reference.ParseNamed(s2) + r2, err := reference.XParseNamed(s2) if err != nil { return nil, nil, err } @@ -86,10 +86,10 @@ func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, return false } // prm.DockerReference and signatureDockerReference should be exact; so, verify that now. - if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) { + if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { return false } - return signature.String() == intended.String() + return signature.XString() == intended.XString() } func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { @@ -97,5 +97,5 @@ func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, if err != nil { return false } - return signature.Name() == intended.Name() + return signature.XName() == intended.XName() } diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 2554182168..0284256822 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -26,12 +26,12 @@ func TestParseImageAndDockerReference(t *testing.T) { bad2 = "" ) // Success - ref, err := reference.ParseNamed(ok1) + ref, err := reference.XParseNamed(ok1) require.NoError(t, err) r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.String()) - assert.Equal(t, ok2, r2.String()) + assert.Equal(t, ok1, r1.XString()) + assert.Equal(t, ok2, r2.XString()) // Unidentified images are rejected. _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) @@ -44,7 +44,7 @@ func TestParseImageAndDockerReference(t *testing.T) { {ok1, bad2}, {bad1, bad2}, } { - ref, err := reference.ParseNamed(refs[0]) + ref, err := reference.XParseNamed(refs[0]) if err == nil { _, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1]) assert.Error(t, err) @@ -53,10 +53,10 @@ func TestParseImageAndDockerReference(t *testing.T) { } // refImageMock is a mock of types.UnparsedImage which returns itself in Reference().DockerReference. -type refImageMock struct{ reference.Named } +type refImageMock struct{ reference.XNamed } func (ref refImageMock) Reference() types.ImageReference { - return refImageReferenceMock{ref.Named} + return refImageReferenceMock{ref.XNamed} } func (ref refImageMock) Close() { panic("unexpected call to a mock function") @@ -69,24 +69,24 @@ func (ref refImageMock) Signatures() ([][]byte, error) { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ reference.Named } +type refImageReferenceMock struct{ reference.XNamed } func (ref refImageReferenceMock) Transport() types.ImageTransport { // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that. - if ref.Named == nil { + if ref.XNamed == nil { return nameImageTransportMock("== Transport mock") } panic("unexpected call to a mock function") } func (ref refImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that. - if ref.Named == nil { + if ref.XNamed == nil { return "== StringWithinTransport for an image with no Docker support" } panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() reference.Named { - return ref.Named +func (ref refImageReferenceMock) DockerReference() reference.XNamed { + return ref.XNamed } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { panic("unexpected call to a mock function") @@ -148,7 +148,7 @@ var prmExactMatchTestTable = []prmSymmetricTableTest{ {"busybox", "busybox:latest", false}, {"busybox", "busybox" + digestSuffix, false}, {"busybox", "busybox", false}, - // References with both tags and digests: `reference.WithName` essentially drops the tag. + // References with both tags and digests: `reference.XWithName` essentially drops the tag. // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, // even if the tag may reflect a different user intent. // NOTE: Again, this is not documented behavior; the recommendation is to sign tags, not digests, and then tag-and-digest references won’t match the signed identity. @@ -194,7 +194,7 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ {"hostname/library/busybox:latest", "busybox:notlatest", false}, {"busybox:latest", fullRHELRef, false}, {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, - // References with both tags and digests: `reference.WithName` essentially drops the tag, and we ignore both anyway. + // References with both tags and digests: `reference.XWithName` essentially drops the tag, and we ignore both anyway. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, @@ -208,9 +208,9 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ } func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) { - // This assumes that all ways to obtain a reference.Named perform equivalent validation, - // and therefore values refused by reference.ParseNamed can not happen in practice. - parsedImageRef, err := reference.ParseNamed(imageRef) + // This assumes that all ways to obtain a reference.XNamed perform equivalent validation, + // and therefore values refused by reference.XParseNamed can not happen in practice. + parsedImageRef, err := reference.XParseNamed(imageRef) if err != nil { return } @@ -272,7 +272,7 @@ func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) { // Digest references accept any signature with matching repository. {"busybox" + digestSuffix, "busybox:latest", true}, {"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - // References with both tags and digests: `reference.WithName` essentially drops the tag. + // References with both tags and digests: `reference.XWithName` essentially drops the tag. // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, // even if the tag may reflect a different user intent. {"busybox:latest" + digestSuffix, "busybox:latest", true}, @@ -307,8 +307,8 @@ func TestParseDockerReferences(t *testing.T) { // Success r1, r2, err := parseDockerReferences(ok1, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.String()) - assert.Equal(t, ok2, r2.String()) + assert.Equal(t, ok1, r1.XString()) + assert.Equal(t, ok2, r2.XString()) // Failures for _, refs := range [][]string{ diff --git a/storage/storage_reference.go b/storage/storage_reference.go index 13413df1bc..809693ec25 100644 --- a/storage/storage_reference.go +++ b/storage/storage_reference.go @@ -15,10 +15,10 @@ type storageReference struct { transport storageTransport reference string id string - name reference.Named + name reference.XNamed } -func newReference(transport storageTransport, reference, id string, name reference.Named) *storageReference { +func newReference(transport storageTransport, reference, id string, name reference.XNamed) *storageReference { // We take a copy of the transport, which contains a pointer to the // store that it used for resolving this reference, so that the // transport that we'll return from Transport() won't be affected by @@ -52,7 +52,7 @@ func (s storageReference) Transport() types.ImageTransport { } // Return a name with a tag, if we have a name to base them on. -func (s storageReference) DockerReference() reference.Named { +func (s storageReference) DockerReference() reference.XNamed { return s.name } @@ -87,7 +87,7 @@ func (s storageReference) PolicyConfigurationNamespaces() []string { // The reference without the ID is also a valid namespace. namespaces = append(namespaces, storeSpec+s.reference) } - components := strings.Split(s.name.FullName(), "/") + components := strings.Split(s.name.XFullName(), "/") for len(components) > 0 { namespaces = append(namespaces, storeSpec+strings.Join(components, "/")) components = components[:len(components)-1] diff --git a/storage/storage_reference_test.go b/storage/storage_reference_test.go index 687d1005dc..7c3c8f3c9b 100644 --- a/storage/storage_reference_test.go +++ b/storage/storage_reference_test.go @@ -23,7 +23,7 @@ func TestStorageReferenceDockerReference(t *testing.T) { require.NoError(t, err) dr := ref.DockerReference() require.NotNil(t, dr) - assert.Equal(t, "busybox:latest", dr.String()) + assert.Equal(t, "busybox:latest", dr.XString()) ref, err = Transport.ParseReference("@" + sha256digestHex) require.NoError(t, err) diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 661df1038a..24ef7465a2 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -66,7 +66,7 @@ func (s *storageTransport) SetStore(store storage.Store) { // ParseStoreReference takes a name or an ID, tries to figure out which it is // relative to the given store, and returns it in a reference object. func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) { - var name reference.Named + var name reference.XNamed var sum digest.Digest var err error if ref == "" { @@ -83,14 +83,14 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( refInfo := strings.SplitN(ref, "@", 2) if len(refInfo) == 1 { // A name. - name, err = reference.ParseNamed(refInfo[0]) + name, err = reference.XParseNamed(refInfo[0]) if err != nil { return nil, err } } else if len(refInfo) == 2 { // An ID, possibly preceded by a name. if refInfo[0] != "" { - name, err = reference.ParseNamed(refInfo[0]) + name, err = reference.XParseNamed(refInfo[0]) if err != nil { return nil, err } @@ -111,7 +111,7 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( } refname := "" if name != nil { - name = reference.WithDefaultTag(name) + name = reference.XWithDefaultTag(name) refname = verboseName(name) } if refname == "" { @@ -257,12 +257,12 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { // that are just bare IDs. scopeInfo := strings.SplitN(scope, "@", 2) if len(scopeInfo) == 1 && scopeInfo[0] != "" { - _, err := reference.ParseNamed(scopeInfo[0]) + _, err := reference.XParseNamed(scopeInfo[0]) if err != nil { return err } } else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" { - _, err := reference.ParseNamed(scopeInfo[0]) + _, err := reference.XParseNamed(scopeInfo[0]) if err != nil { return err } @@ -276,11 +276,11 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { return nil } -func verboseName(name reference.Named) string { - name = reference.WithDefaultTag(name) +func verboseName(name reference.XNamed) string { + name = reference.XWithDefaultTag(name) tag := "" - if tagged, ok := name.(reference.NamedTagged); ok { - tag = tagged.Tag() + if tagged, ok := name.(reference.XNamedTagged); ok { + tag = tagged.XTag() } - return name.FullName() + ":" + tag + return name.XFullName() + ":" + tag } diff --git a/storage/storage_transport_test.go b/storage/storage_transport_test.go index 2ca7a657e2..c1ee2fc4d6 100644 --- a/storage/storage_transport_test.go +++ b/storage/storage_transport_test.go @@ -54,10 +54,10 @@ func TestTransportParseStoreReference(t *testing.T) { if c.expectedRef == "" { assert.Nil(t, storageRef.name, c.input) } else { - dockerRef, err := reference.ParseNamed(c.expectedRef) + dockerRef, err := reference.XParseNamed(c.expectedRef) require.NoError(t, err) require.NotNil(t, storageRef.name, c.input) - assert.Equal(t, dockerRef.String(), storageRef.name.String()) + assert.Equal(t, dockerRef.XString(), storageRef.name.XString()) } } } diff --git a/types/types.go b/types/types.go index 517388a047..f834dc8ad7 100644 --- a/types/types.go +++ b/types/types.go @@ -55,7 +55,7 @@ type ImageReference interface { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. - DockerReference() reference.Named + DockerReference() reference.XNamed // PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup. // This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases; From 2db870f59860f832d84a16b5161c59bf7590599a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:30:14 +0100 Subject: [PATCH 02/38] Duplication: Have TWO distreference.Named values in namedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To start a transition to the upstream distreference.Named canonicalization semantics, first start computing the upstream value: In namedRef (and its subtypes), carry BOTH an "our" field (with existing semantics, canonical = minimal) and "upstream" field (with the upstream semantics, canonical = fully explicit). .upstream is currently essentially write-only: it is used _only_ to compute further .upstream values. Therefore, this does not change behavior (perhaps apart from a bit more error checking which now happens on the upstream value). To make this reasonably possible, some of the public methods return a *namedRef instead of a public type, which breaks golint. This is temporary. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 62 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index c521ae7c87..9b7826dc2b 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -65,12 +65,15 @@ func XParseNamed(s string) (XNamed, error) { return nil, err } if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - // FIXME: depends on XWithName returning a *namedRef. - r, err := distreference.WithDigest(r.(*namedRef).Named, canonical.Digest()) + upstreamR, err := distreference.WithDigest(r.upstream, canonical.Digest()) if err != nil { return nil, err } - return &canonicalRef{namedRef{r}}, nil + ourR, err := distreference.WithDigest(r.our, canonical.Digest()) + if err != nil { + return nil, err + } + return &canonicalRef{namedRef{upstream: upstreamR, our: ourR}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { return XWithTag(r, tagged.Tag()) @@ -80,34 +83,58 @@ func XParseNamed(s string) (XNamed, error) { // XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -func XWithName(name string) (XNamed, error) { - name, err := normalize(name) +// FIXME: returns *namedRef to expose the upstream/our fields. Should revert to XNamed/Named. +func XWithName(name string) (*namedRef, error) { + upstreamR, err := distreference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + name, err = normalize(name) if err != nil { return nil, err } if err := validateName(name); err != nil { return nil, err } - r, err := distreference.WithName(name) + ourR, err := distreference.WithName(name) if err != nil { return nil, err } - return &namedRef{r}, nil + return &namedRef{upstream: upstreamR, our: ourR}, nil } // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -func XWithTag(name XNamed, tag string) (XNamedTagged, error) { - // FIXME: depends on XWithName returning a *namedRef, and that this is only called on XNameOnly values. - r, err := distreference.WithTag(name.(*namedRef).Named, tag) +// FIXME: expects *namedRef to expose the upstream/our fields. Should revert to XNamed/Named. +func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { + upstreamR, err := distreference.WithTag(name.upstream, tag) + if err != nil { + return nil, err + } + ourR, err := distreference.WithTag(name.our, tag) if err != nil { return nil, err } - return &taggedRef{namedRef{r}}, nil + return &taggedRef{namedRef{upstream: upstreamR, our: ourR}}, nil } type namedRef struct { - distreference.Named + // TRANSITIONAL state: We want to transition from our semantics (Name(), String() return a minified form) + // to the upstream ones (Name(), String() return the fully expanded form). In the mean time we still + // want to call upstream distreference.* methods on the namedRef implementation. + // + // As it happens, distreference.WithTag and distreference.WithDigest can both accept + // minimized input and return minimized output, so we can keep using them even with the minimized + // values. + // + // For the transition, we keep an "upstream", fully expanded, value, and "our", which we have minimized. + // We start with "upstream" being essentially write-only, with no users in containers/image. + // Then we will, bit by bit, eliminate uses of "our". + // + // upstream uses the normalization from distreference.ParseNormalizedNamed + upstream distreference.Named + // our is what the existing code used to do, via normalize() + our distreference.Named } type taggedRef struct { namedRef @@ -117,10 +144,10 @@ type canonicalRef struct { } func (r *namedRef) XName() string { - return r.Named.Name() + return r.our.Name() } func (r *namedRef) XString() string { - return r.Named.String() + return r.our.String() } func (r *namedRef) XFullName() string { hostname, remoteName := splitHostname(r.XName()) @@ -135,16 +162,17 @@ func (r *namedRef) XRemoteName() string { return remoteName } func (r *taggedRef) XTag() string { - return r.namedRef.Named.(distreference.NamedTagged).Tag() + return r.namedRef.our.(distreference.NamedTagged).Tag() } func (r *canonicalRef) XDigest() digest.Digest { - return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) + return digest.Digest(r.namedRef.our.(distreference.Canonical).Digest()) } // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref XNamed) XNamed { if XIsNameOnly(ref) { - ref, _ = XWithTag(ref, XDefaultTag) + // FIXME: uses *namedRef to expose the upstream/our fields. Should use ref without a cast. + ref, _ = XWithTag(ref.(*namedRef), XDefaultTag) } return ref } From 2c1ea37d0fe84a0833872c7514d3e4c0f3a48f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:36:09 +0100 Subject: [PATCH 03/38] Transition to .upstream: Use in taggedRef and canonicalRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start transitioning from .our uses to .upstream. First in the simplest cases: taggedRef.Tag() and canonicalRef.Digest() are values in principle unaffected by the name canonicalization, so this should be an obviously correct change which does not change behavior. Starting with this one to demostrate the principle of moving step by step. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 9b7826dc2b..7cae40abcd 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -162,10 +162,10 @@ func (r *namedRef) XRemoteName() string { return remoteName } func (r *taggedRef) XTag() string { - return r.namedRef.our.(distreference.NamedTagged).Tag() + return r.namedRef.upstream.(distreference.NamedTagged).Tag() } func (r *canonicalRef) XDigest() digest.Digest { - return digest.Digest(r.namedRef.our.(distreference.Canonical).Digest()) + return digest.Digest(r.namedRef.upstream.(distreference.Canonical).Digest()) } // XWithDefaultTag adds a default tag to a reference if it only has a repo name. From 465998c6fa7ea00397cc13d72cb45db8cbae83a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:43:05 +0100 Subject: [PATCH 04/38] Transition to .upstream: Use instead of splitHostname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the “new” methods introduced in docker/reference.[X]Named, to return the fully expanded host/path/both, instead of using .our and expanding it in splitHostname, rely on the fully-expanded .upstream and its fully-expanded .Name(), and the newly introduced distreference.Domain() and distreference.Path() helpers. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 7cae40abcd..975d6615b0 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -150,16 +150,13 @@ func (r *namedRef) XString() string { return r.our.String() } func (r *namedRef) XFullName() string { - hostname, remoteName := splitHostname(r.XName()) - return hostname + "/" + remoteName + return r.upstream.Name() } func (r *namedRef) XHostname() string { - hostname, _ := splitHostname(r.XName()) - return hostname + return distreference.Domain(r.upstream) } func (r *namedRef) XRemoteName() string { - _, remoteName := splitHostname(r.XName()) - return remoteName + return distreference.Path(r.upstream) } func (r *taggedRef) XTag() string { return r.namedRef.upstream.(distreference.NamedTagged).Tag() From e8533be03a20c8b339bc39c50d8f82520e9f58fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:48:19 +0100 Subject: [PATCH 05/38] Transition to .upstream: Use distreference.Familiar* instead of our normalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call the newly provided distreference.FamilarName and distreference.FamiliarString instead of using our minimal canonical version. This removes the last “externally-visible” uses of .our. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 975d6615b0..686e82872e 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -144,10 +144,10 @@ type canonicalRef struct { } func (r *namedRef) XName() string { - return r.our.Name() + return distreference.FamiliarName(r.upstream) } func (r *namedRef) XString() string { - return r.our.String() + return distreference.FamiliarString(r.upstream) } func (r *namedRef) XFullName() string { return r.upstream.Name() From 5edbd7f016d275db17d3ed19a8a01ef4e0354be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 03:58:31 +0100 Subject: [PATCH 06/38] Simplification: drop namedRef.our MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that namedRef.our values are only used for computing other namedRef.our values, drop the struct member and all code computing it, including the entirety of our normalization code. We still keep .upstream as a private member instead of using distreference.Named directly, or making namedRef an implementation of distreference.Named. BEHAVIOR CHANGE: We used to minimize the input and then check whether it is a 64-char hex string, now distreference.ParseNormalizedNamed first checks for a 64-char hext string and then normalized (and by expanding, not minimizing). Hence, things like docker.io/$64hexchars are now accepted, which is a behavior change (noticed by the tests). Though, there is really no risk of confusing such a value with a digest reference, so this behavior change seems quite acceptable. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 94 +++--------------------------- docker/reference/reference_test.go | 2 +- 2 files changed, 10 insertions(+), 86 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 686e82872e..e59b96912f 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -2,7 +2,6 @@ package reference import ( "regexp" - "strings" // "opencontainers/go-digest" requires us to load the algorithms that we // want to use into the binary (it calls .Available). @@ -65,15 +64,11 @@ func XParseNamed(s string) (XNamed, error) { return nil, err } if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - upstreamR, err := distreference.WithDigest(r.upstream, canonical.Digest()) + r, err := distreference.WithDigest(r.upstream, canonical.Digest()) if err != nil { return nil, err } - ourR, err := distreference.WithDigest(r.our, canonical.Digest()) - if err != nil { - return nil, err - } - return &canonicalRef{namedRef{upstream: upstreamR, our: ourR}}, nil + return &canonicalRef{namedRef{upstream: r}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { return XWithTag(r, tagged.Tag()) @@ -83,58 +78,29 @@ func XParseNamed(s string) (XNamed, error) { // XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -// FIXME: returns *namedRef to expose the upstream/our fields. Should revert to XNamed/Named. +// FIXME: returns *namedRef to expose the upstream field. Should revert to XNamed/Named. func XWithName(name string) (*namedRef, error) { - upstreamR, err := distreference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - name, err = normalize(name) - if err != nil { - return nil, err - } - if err := validateName(name); err != nil { - return nil, err - } - ourR, err := distreference.WithName(name) + r, err := distreference.ParseNormalizedNamed(name) if err != nil { return nil, err } - return &namedRef{upstream: upstreamR, our: ourR}, nil + return &namedRef{upstream: r}, nil } // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -// FIXME: expects *namedRef to expose the upstream/our fields. Should revert to XNamed/Named. +// FIXME: expects *namedRef to expose the upstream field. Should revert to XNamed/Named. func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { - upstreamR, err := distreference.WithTag(name.upstream, tag) - if err != nil { - return nil, err - } - ourR, err := distreference.WithTag(name.our, tag) + r, err := distreference.WithTag(name.upstream, tag) if err != nil { return nil, err } - return &taggedRef{namedRef{upstream: upstreamR, our: ourR}}, nil + return &taggedRef{namedRef{upstream: r}}, nil } type namedRef struct { - // TRANSITIONAL state: We want to transition from our semantics (Name(), String() return a minified form) - // to the upstream ones (Name(), String() return the fully expanded form). In the mean time we still - // want to call upstream distreference.* methods on the namedRef implementation. - // - // As it happens, distreference.WithTag and distreference.WithDigest can both accept - // minimized input and return minimized output, so we can keep using them even with the minimized - // values. - // - // For the transition, we keep an "upstream", fully expanded, value, and "our", which we have minimized. - // We start with "upstream" being essentially write-only, with no users in containers/image. - // Then we will, bit by bit, eliminate uses of "our". - // // upstream uses the normalization from distreference.ParseNormalizedNamed upstream distreference.Named - // our is what the existing code used to do, via normalize() - our distreference.Named } type taggedRef struct { namedRef @@ -168,7 +134,7 @@ func (r *canonicalRef) XDigest() digest.Digest { // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref XNamed) XNamed { if XIsNameOnly(ref) { - // FIXME: uses *namedRef to expose the upstream/our fields. Should use ref without a cast. + // FIXME: uses *namedRef to expose the upstream fields. Should use ref without a cast. ref, _ = XWithTag(ref.(*namedRef), XDefaultTag) } return ref @@ -198,41 +164,6 @@ func XParseIDOrReference(idOrRef string) (digest.Digest, XNamed, error) { return "", ref, err } -// 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 = XDefaultHostname, name - } else { - hostname, remoteName = name[:i], name[i+1:] - } - if hostname == XLegacyDefaultHostname { - hostname = XDefaultHostname - } - if hostname == XDefaultHostname && !strings.ContainsRune(remoteName, '/') { - remoteName = XDefaultRepoPrefix + remoteName - } - return -} - -// 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 == XDefaultHostname { - if strings.HasPrefix(remoteName, XDefaultRepoPrefix) { - return strings.TrimPrefix(remoteName, XDefaultRepoPrefix), nil - } - return remoteName, nil - } - return name, nil -} - var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) func validateID(id string) error { @@ -241,10 +172,3 @@ func validateID(id string) error { } return nil } - -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 -} diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 4d89fdbf13..7572660060 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -21,6 +21,7 @@ func TestValidateReferenceName(t *testing.T) { "127.0.0.1:5000/library/debian", "127.0.0.1:5000/debian", "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", } invalidRepoNames := []string{ "https://github.com/docker/docker", @@ -32,7 +33,6 @@ func TestValidateReferenceName(t *testing.T) { "docker.io/docker/Docker", "docker.io/docker///docker", "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", } for _, name := range invalidRepoNames { From 5240b7c4bb3b0fc5f8547dd7600b7e55bfac69e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 04:29:20 +0100 Subject: [PATCH 07/38] Simplification: Make namedRef implement distreference.Named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of embedding a distreference.Named as a private field, embed it as an anonymous field, making namedRef a valid distreference.Named implementation. This is EXTREMELY ugly. In theory, docker/distribution/reference should be able to work with any valid input implementing distreference.Named() equally, based on only what the public method implementations return. In practice, the code expects specific implementations of internal interfaces, and merely embeding a distreference.Named into our struct makes our struct _not_ implement these internal interfaces. We are forced to explicitly define forwarding methods, using an undocumented knowledge that the returned distreference.Named implements them. Soon enough we will completely eiliminate namedRef and use a distreference.Named directly, and then distreference can keep playing these ugly games without us having to care. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 59 +++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index e59b96912f..6cf311088b 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -25,6 +25,7 @@ const ( // XNamed is an object with a full name type XNamed interface { + distreference.Named // XName returns normalized repository name, like "ubuntu". XName() string // XString returns full reference, like "ubuntu@sha256:abcdef..." @@ -64,11 +65,11 @@ func XParseNamed(s string) (XNamed, error) { return nil, err } if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - r, err := distreference.WithDigest(r.upstream, canonical.Digest()) + r, err := distreference.WithDigest(r, canonical.Digest()) if err != nil { return nil, err } - return &canonicalRef{namedRef{upstream: r}}, nil + return &canonicalRef{namedRef{r}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { return XWithTag(r, tagged.Tag()) @@ -78,29 +79,28 @@ func XParseNamed(s string) (XNamed, error) { // XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -// FIXME: returns *namedRef to expose the upstream field. Should revert to XNamed/Named. +// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to XNamed/Named. func XWithName(name string) (*namedRef, error) { r, err := distreference.ParseNormalizedNamed(name) if err != nil { return nil, err } - return &namedRef{upstream: r}, nil + return &namedRef{r}, nil } // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -// FIXME: expects *namedRef to expose the upstream field. Should revert to XNamed/Named. +// FIXME: expects *namedRef to expose the distreference.Named implementation. Should revert to XNamed/Named. func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { - r, err := distreference.WithTag(name.upstream, tag) + r, err := distreference.WithTag(name, tag) if err != nil { return nil, err } - return &taggedRef{namedRef{upstream: r}}, nil + return &taggedRef{namedRef{r}}, nil } type namedRef struct { - // upstream uses the normalization from distreference.ParseNormalizedNamed - upstream distreference.Named + distreference.Named // FIXME: must implement private distreference.NamedRepository } type taggedRef struct { namedRef @@ -109,32 +109,57 @@ type canonicalRef struct { namedRef } +// TEMPORARY: distreference.WithDigest and distreference.WithTag can work with any distreference.Named, +// but if so, they break the values of distreference.Domain() and distreference.Path(), +// and hence also distreference.FamiliarName()/distreference.FamiliarString(). To preserve this, +// we need to implement a PRIVATE distreference.namedRepository. +// Similarly, we need to implement a PRIVATE distreference.normalizedNamed so that distreference.Familiar*() +// knows how to compute the minimal form. +// Right now that happens by these REALLY UGLY methods; eventually we will eliminate namedRef entirely in favor of +// distreference.Named, and distreference can keep its implementation games to itself. +type drPRIVATEInterfaces interface { + distreference.Named + Domain() string + Path() string + Familiar() distreference.Named +} + +func (r *namedRef) Domain() string { + return r.Named.(drPRIVATEInterfaces).Domain() +} +func (r *namedRef) Path() string { + return r.Named.(drPRIVATEInterfaces).Path() +} +func (r *namedRef) Familiar() distreference.Named { + return r.Named.(drPRIVATEInterfaces).Familiar() +} + func (r *namedRef) XName() string { - return distreference.FamiliarName(r.upstream) + return distreference.FamiliarName(r) } func (r *namedRef) XString() string { - return distreference.FamiliarString(r.upstream) + return distreference.FamiliarString(r) } func (r *namedRef) XFullName() string { - return r.upstream.Name() + return r.Name() } func (r *namedRef) XHostname() string { - return distreference.Domain(r.upstream) + return distreference.Domain(r) } func (r *namedRef) XRemoteName() string { - return distreference.Path(r.upstream) + return distreference.Path(r) } func (r *taggedRef) XTag() string { - return r.namedRef.upstream.(distreference.NamedTagged).Tag() + return r.namedRef.Named.(distreference.NamedTagged).Tag() } func (r *canonicalRef) XDigest() digest.Digest { - return digest.Digest(r.namedRef.upstream.(distreference.Canonical).Digest()) + return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) } // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref XNamed) XNamed { if XIsNameOnly(ref) { - // FIXME: uses *namedRef to expose the upstream fields. Should use ref without a cast. + // FIXME: uses *namedRef to expose the distreference.Named implementations. Should use ref without a cast. ref, _ = XWithTag(ref.(*namedRef), XDefaultTag) } return ref From df65181c7085e9918ff6138082fc703394855352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 04:45:47 +0100 Subject: [PATCH 08/38] API transition: Drop XNamed.XFullName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.Named.Name() in all users. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_dest.go | 2 +- docker/docker_image.go | 2 +- docker/lookaside.go | 2 +- docker/policyconfiguration/naming.go | 4 ++-- docker/reference/reference.go | 5 ----- docker/reference/reference_test.go | 2 +- storage/storage_reference.go | 2 +- storage/storage_transport.go | 2 +- 8 files changed, 8 insertions(+), 13 deletions(-) diff --git a/docker/daemon/daemon_dest.go b/docker/daemon/daemon_dest.go index a18c0a6e88..2707167ecc 100644 --- a/docker/daemon/daemon_dest.go +++ b/docker/daemon/daemon_dest.go @@ -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.XFullName(), d.namedTaggedRef.XTag()) + refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.Name(), d.namedTaggedRef.XTag()) items := []manifestItem{{ Config: man.Config.Digest.String(), diff --git a/docker/docker_image.go b/docker/docker_image.go index 98137e9f84..f96f561783 100644 --- a/docker/docker_image.go +++ b/docker/docker_image.go @@ -34,7 +34,7 @@ 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.XFullName() + 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. diff --git a/docker/lookaside.go b/docker/lookaside.go index 0b4334db10..0b3d4a3c1b 100644 --- a/docker/lookaside.go +++ b/docker/lookaside.go @@ -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.XFullName() // 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.XString()) } diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index af2c73e7a4..89ed9d7bcc 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -12,7 +12,7 @@ import ( // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.XIsNameOnly(). func DockerReferenceIdentity(ref reference.XNamed) (string, error) { - res := ref.XFullName() + res := ref.Name() tagged, isTagged := ref.(reference.XNamedTagged) digested, isDigested := ref.(reference.XCanonical) switch { @@ -43,7 +43,7 @@ func DockerReferenceNamespaces(ref reference.XNamed) []string { // ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last // iteration matches the host name (for any namespace). res := []string{} - name := ref.XFullName() + name := ref.Name() for { res = append(res, name) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 6cf311088b..f6afff38ad 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -30,8 +30,6 @@ type XNamed interface { XName() string // XString returns full reference, like "ubuntu@sha256:abcdef..." XString() string - // XFullName returns full repository name with hostname, like "docker.io/library/ubuntu" - XFullName() string // XHostname returns hostname for the reference, like "docker.io" XHostname() string // XRemoteName returns the repository component of the full name, like "library/ubuntu" @@ -140,9 +138,6 @@ func (r *namedRef) XName() string { func (r *namedRef) XString() string { return distreference.FamiliarString(r) } -func (r *namedRef) XFullName() string { - return r.Name() -} func (r *namedRef) XHostname() string { return distreference.Domain(r) } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 7572660060..9b5cd2e68d 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -228,7 +228,7 @@ func TestParseRepositoryInfo(t *testing.T) { if expected, actual := tcase.NormalizedName, r.XName(); expected != actual { t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) } - if expected, actual := tcase.FullName, r.XFullName(); expected != actual { + if expected, actual := tcase.FullName, r.Name(); expected != actual { t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) } if expected, actual := tcase.Hostname, r.XHostname(); expected != actual { diff --git a/storage/storage_reference.go b/storage/storage_reference.go index 809693ec25..c550f3da2e 100644 --- a/storage/storage_reference.go +++ b/storage/storage_reference.go @@ -87,7 +87,7 @@ func (s storageReference) PolicyConfigurationNamespaces() []string { // The reference without the ID is also a valid namespace. namespaces = append(namespaces, storeSpec+s.reference) } - components := strings.Split(s.name.XFullName(), "/") + components := strings.Split(s.name.Name(), "/") for len(components) > 0 { namespaces = append(namespaces, storeSpec+strings.Join(components, "/")) components = components[:len(components)-1] diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 24ef7465a2..3eeb9665f4 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -282,5 +282,5 @@ func verboseName(name reference.XNamed) string { if tagged, ok := name.(reference.XNamedTagged); ok { tag = tagged.XTag() } - return name.XFullName() + ":" + tag + return name.Name() + ":" + tag } From 2f8c5951995addb09d6edca085ef45b0e02b3b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 04:53:10 +0100 Subject: [PATCH 09/38] API transition: Drop XNamed.XHostname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.Domain() in all users. Signed-off-by: Miloslav Trmač --- docker/docker_client.go | 5 +++-- docker/reference/reference.go | 5 ----- docker/reference/reference_test.go | 4 +++- openshift/openshift.go | 5 +++-- openshift/openshift_transport_test.go | 3 ++- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docker/docker_client.go b/docker/docker_client.go index 9937208c19..263e95533e 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -17,6 +17,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/types" "github.com/containers/storage/pkg/homedir" + "github.com/docker/distribution/reference" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" @@ -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.XHostname() + registry := reference.Domain(ref.ref) if registry == dockerHostname { registry = dockerRegistry } - username, password, err := getAuth(ctx, ref.ref.XHostname()) + username, password, err := getAuth(ctx, reference.Domain(ref.ref)) if err != nil { return nil, err } diff --git a/docker/reference/reference.go b/docker/reference/reference.go index f6afff38ad..101742a85b 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -30,8 +30,6 @@ type XNamed interface { XName() string // XString returns full reference, like "ubuntu@sha256:abcdef..." XString() string - // XHostname returns hostname for the reference, like "docker.io" - XHostname() string // XRemoteName returns the repository component of the full name, like "library/ubuntu" XRemoteName() string } @@ -138,9 +136,6 @@ func (r *namedRef) XName() string { func (r *namedRef) XString() string { return distreference.FamiliarString(r) } -func (r *namedRef) XHostname() string { - return distreference.Domain(r) -} func (r *namedRef) XRemoteName() string { return distreference.Path(r) } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 9b5cd2e68d..caad977637 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -4,6 +4,8 @@ import ( "testing" _ "crypto/sha256" + + distreference "github.com/docker/distribution/reference" ) func TestValidateReferenceName(t *testing.T) { @@ -231,7 +233,7 @@ func TestParseRepositoryInfo(t *testing.T) { if expected, actual := tcase.FullName, r.Name(); expected != actual { t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) } - if expected, actual := tcase.Hostname, r.XHostname(); expected != actual { + if expected, actual := tcase.Hostname, distreference.Domain(r); expected != actual { t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) } if expected, actual := tcase.RemoteName, r.XRemoteName(); expected != actual { diff --git a/openshift/openshift.go b/openshift/openshift.go index 36dc964d8a..1da80e8fb6 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -16,6 +16,7 @@ import ( "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/containers/image/version" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -153,7 +154,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error if len(parts) != 2 { return "", errors.Errorf("Invalid format of docker reference %s: missing '/'", ref) } - return c.ref.dockerReference.XHostname() + "/" + parts[1], nil + return reference.Domain(c.ref.dockerReference) + "/" + parts[1], nil } type openshiftImageSource struct { @@ -305,7 +306,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type // FIXME: Should this always use a digest, not a tag? Uploading to Docker by tag requires the tag _inside_ the manifest to match, // i.e. a single signed image cannot be available under multiple tags. But with types.ImageDestination, we don't know // the manifest digest at this point. - dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.ref.dockerReference.XHostname(), client.ref.namespace, client.ref.stream, client.ref.dockerReference.XTag()) + dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", reference.Domain(client.ref.dockerReference), client.ref.namespace, client.ref.stream, client.ref.dockerReference.XTag()) dockerRef, err := docker.ParseReference(dockerRefString) if err != nil { return nil, err diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 6edf33f049..47b0295361 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/containers/image/docker/reference" + distreference "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -64,7 +65,7 @@ func TestParseReference(t *testing.T) { assert.Equal(t, "ns", osRef.namespace) assert.Equal(t, "stream", osRef.stream) assert.Equal(t, "notlatest", osRef.dockerReference.XTag()) - assert.Equal(t, "registry.example.com:8443", osRef.dockerReference.XHostname()) + assert.Equal(t, "registry.example.com:8443", distreference.Domain(osRef.dockerReference)) // Components creating an invalid Docker Reference name _, err = ParseReference("registry.example.com/ns/UPPERCASEISINVALID:notlatest") From 8cde5543ddc41a4d9515d4aa59562e061c2c39eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 05:06:09 +0100 Subject: [PATCH 10/38] API transition: Drop XNamed.XRemoteName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.Path() in all users. Signed-off-by: Miloslav Trmač --- docker/docker_client.go | 2 +- docker/docker_image.go | 3 ++- docker/docker_image_dest.go | 19 ++++++++++--------- docker/docker_image_src.go | 11 ++++++----- docker/reference/reference.go | 5 ----- docker/reference/reference_test.go | 2 +- image/docker_schema1.go | 3 ++- openshift/openshift_transport.go | 3 ++- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docker/docker_client.go b/docker/docker_client.go index 263e95533e..33ff88df50 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -203,7 +203,7 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, signatureBase: sigBase, scope: authScope{ actions: actions, - remoteName: ref.ref.XRemoteName(), + remoteName: reference.Path(ref.ref), }, }, nil } diff --git a/docker/docker_image.go b/docker/docker_image.go index f96f561783..141a7294a7 100644 --- a/docker/docker_image.go +++ b/docker/docker_image.go @@ -7,6 +7,7 @@ import ( "github.com/containers/image/image" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -39,7 +40,7 @@ func (i *Image) SourceRefFullName() string { // 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.XRemoteName()) + 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 diff --git a/docker/docker_image_dest.go b/docker/docker_image_dest.go index cd2d0f857c..bfa47d3c8c 100644 --- a/docker/docker_image_dest.go +++ b/docker/docker_image_dest.go @@ -13,6 +13,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -98,7 +99,7 @@ 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.XRemoteName(), inputInfo.Digest.String()) + checkURL := fmt.Sprintf(blobsURL, reference.Path(d.ref.ref), inputInfo.Digest.String()) logrus.Debugf("Checking %s", checkURL) res, err := d.c.makeRequest("HEAD", checkURL, nil, nil) @@ -112,17 +113,17 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI 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.XRemoteName()) + return types.BlobInfo{}, errors.Errorf("not authorized to read from destination repository %s", reference.Path(d.ref.ref)) case http.StatusNotFound: // noop default: - return types.BlobInfo{}, errors.Errorf("failed to read from destination repository %s: %v", d.ref.ref.XRemoteName(), http.StatusText(res.StatusCode)) + return types.BlobInfo{}, errors.Errorf("failed to read from destination repository %s: %v", reference.Path(d.ref.ref), http.StatusText(res.StatusCode)) } logrus.Debugf("... failed, status %d", res.StatusCode) } // FIXME? Chunked upload, progress reporting, etc. - uploadURL := fmt.Sprintf(blobUploadURL, d.ref.ref.XRemoteName()) + 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 +179,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.XRemoteName(), 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,12 +193,12 @@ 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.XRemoteName()) + 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.XRemoteName(), http.StatusText(res.StatusCode)) + logrus.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 @@ -214,11 +215,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.XRemoteName(), reference) + url := fmt.Sprintf(manifestURL, reference.Path(d.ref.ref), refTail) headers := map[string][]string{} mimeType := manifest.GuessMIMEType(m) diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index f92ab6fd8e..fda1313abd 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -13,6 +13,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/docker/distribution/registry/client" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -91,7 +92,7 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) { } func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) { - url := fmt.Sprintf(manifestURL, s.ref.ref.XRemoteName(), 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 +178,7 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, return s.getExternalBlob(info.URLs) } - url := fmt.Sprintf(blobsURL, s.ref.ref.XRemoteName(), 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 +272,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.XRemoteName(), 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 +295,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error { } digest := get.Header.Get("Docker-Content-Digest") - deleteURL := fmt.Sprintf(manifestURL, ref.ref.XRemoteName(), 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" diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 101742a85b..afe17f64c5 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -30,8 +30,6 @@ type XNamed interface { XName() string // XString returns full reference, like "ubuntu@sha256:abcdef..." XString() string - // XRemoteName returns the repository component of the full name, like "library/ubuntu" - XRemoteName() string } // XNamedTagged is an object including a name and tag. @@ -136,9 +134,6 @@ func (r *namedRef) XName() string { func (r *namedRef) XString() string { return distreference.FamiliarString(r) } -func (r *namedRef) XRemoteName() string { - return distreference.Path(r) -} func (r *taggedRef) XTag() string { return r.namedRef.Named.(distreference.NamedTagged).Tag() } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index caad977637..6fb1c5a555 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -236,7 +236,7 @@ func TestParseRepositoryInfo(t *testing.T) { if expected, actual := tcase.Hostname, distreference.Domain(r); expected != actual { t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) } - if expected, actual := tcase.RemoteName, r.XRemoteName(); expected != actual { + if expected, actual := tcase.RemoteName, distreference.Path(r); expected != actual { t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) } diff --git a/image/docker_schema1.go b/image/docker_schema1.go index 4f7c28f41c..34a0820e4a 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -9,6 +9,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -72,7 +73,7 @@ func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) { func manifestSchema1FromComponents(ref reference.XNamed, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { var name, tag string if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. - name = ref.XRemoteName() + name = distreference.Path(ref) if tagged, ok := ref.(reference.XNamedTagged); ok { tag = tagged.XTag() } diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index 9ebba41b5d..77a9aa2237 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -9,6 +9,7 @@ import ( "github.com/containers/image/docker/reference" genericImage "github.com/containers/image/image" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -64,7 +65,7 @@ func ParseReference(ref string) (types.ImageReference, error) { // NewReference returns an OpenShift reference for a reference.XNamedTagged func NewReference(dockerRef reference.XNamedTagged) (types.ImageReference, error) { - r := strings.SplitN(dockerRef.XRemoteName(), "/", 3) + r := strings.SplitN(distreference.Path(dockerRef), "/", 3) if len(r) != 2 { return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", dockerRef.XString()) From 2b4f09f141946e27397411d006cd7b01b05298a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 05:23:13 +0100 Subject: [PATCH 11/38] API transition: Drop XNamed.XName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.FamiliarName() in SOME uses. In signature/policy_reference_match.go, where we care about equality but not exactly about the kind of normalization, call XNamed.Name() instead. That compares the fully-explicit instaed of the fully-minimized name forms. If both canonicalizations are consistent, this should not matter—and if they weren’t, using the fully explicit form should be safer. (Also, .Name() is likely to be a bit faster, but that really doesn’t matter all that much.) Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 3 ++- docker/reference/reference.go | 5 ----- docker/reference/reference_test.go | 2 +- signature/policy_reference_match.go | 6 +++--- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 1d0ac42116..98501e8caa 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -6,6 +6,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) @@ -64,7 +65,7 @@ func ParseReference(refString string) (types.ImageReference, error) { if err != nil { return nil, err } - if ref.XName() == digest.Canonical.String() { + if distreference.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) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index afe17f64c5..05c81c0849 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -26,8 +26,6 @@ const ( // XNamed is an object with a full name type XNamed interface { distreference.Named - // XName returns normalized repository name, like "ubuntu". - XName() string // XString returns full reference, like "ubuntu@sha256:abcdef..." XString() string } @@ -128,9 +126,6 @@ func (r *namedRef) Familiar() distreference.Named { return r.Named.(drPRIVATEInterfaces).Familiar() } -func (r *namedRef) XName() string { - return distreference.FamiliarName(r) -} func (r *namedRef) XString() string { return distreference.FamiliarString(r) } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 6fb1c5a555..286d8a0fb8 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -227,7 +227,7 @@ func TestParseRepositoryInfo(t *testing.T) { } for _, r := range refs { - if expected, actual := tcase.NormalizedName, r.XName(); expected != actual { + if expected, actual := tcase.NormalizedName, distreference.FamiliarName(r); expected != actual { t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) } if expected, actual := tcase.FullName, r.Name(); expected != actual { diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index 3bdda21b89..b38fd10639 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -53,7 +53,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, // we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms) - return signature.XName() == intended.XName() + return signature.Name() == intended.Name() default: // !reference.XIsNameOnly(intended) return false } @@ -64,7 +64,7 @@ func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, if err != nil { return false } - return signature.XName() == intended.XName() + return signature.Name() == intended.Name() } // parseDockerReferences converts two reference strings into parsed entities, failing on any error @@ -97,5 +97,5 @@ func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, if err != nil { return false } - return signature.XName() == intended.XName() + return signature.Name() == intended.Name() } From 7abfa9812ffec6dc6476fe39b53a6263e997adf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 05:42:56 +0100 Subject: [PATCH 12/38] API transition: Drop XNamed.XString MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.FamiliarString() for SOME uses, generally for error messages and StringWithinTransport(). In signature/policy_reference_match.go and signature/docker.go, where we care about equality but not exactly about the kind of normalization, call XNamed.String() instead, with the same rationale as the earlier Name/FamiliarName choice. In copy.Image, when creating a singature, use .String() (i.e. the fully explicit form), for that extra bit of safety. In tests, generally use the simpler .String() and modify expected results, instead of calling FamilarString(). XNamed is now equivalent to distreference.Named, all the extra methods have went away. Signed-off-by: Miloslav Trmač --- copy/copy.go | 2 +- docker/daemon/daemon_transport.go | 4 ++-- docker/daemon/daemon_transport_test.go | 24 ++++++++++---------- docker/docker_transport.go | 7 +++--- docker/docker_transport_test.go | 28 ++++++++++++------------ docker/lookaside.go | 2 +- docker/policyconfiguration/naming.go | 5 +++-- docker/reference/reference.go | 5 ----- docker/reference/reference_test.go | 2 +- openshift/openshift_transport.go | 4 ++-- openshift/openshift_transport_test.go | 2 +- signature/docker.go | 2 +- signature/policy_reference_match.go | 6 ++--- signature/policy_reference_match_test.go | 10 ++++----- storage/storage_reference_test.go | 2 +- storage/storage_transport_test.go | 2 +- 16 files changed, 52 insertions(+), 55 deletions(-) diff --git a/copy/copy.go b/copy/copy.go index 214ed45f76..d27e634ce5 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -210,7 +210,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe } writeReport("Signing manifest\n") - newSig, err := signature.SignDockerManifest(manifest, dockerReference.XString(), mech, options.SignBy) + newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, options.SignBy) if err != nil { return errors.Wrap(err, "Error creating signature") } diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 98501e8caa..c6043929eb 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -78,7 +78,7 @@ func NewReference(id digest.Digest, ref reference.XNamed) (types.ImageReference, } if ref != nil { if reference.XIsNameOnly(ref) { - return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", ref.XString()) + return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", distreference.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. @@ -109,7 +109,7 @@ func (ref daemonReference) StringWithinTransport() string { case ref.id != "": return ref.id.String() case ref.ref != nil: - return ref.ref.XString() + return distreference.FamiliarString(ref.ref) default: // Coverage: Should never happen, NewReference above should refuse such values. panic("Internal inconsistency: daemonReference has empty id and nil ref") } diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 5a5bd97727..2c6eaae860 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -50,15 +50,15 @@ 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 + {"busybox:latest@" + sha256digest, "", "docker.io/library/busybox@" + 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 == "" { @@ -80,11 +80,11 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } else { assert.Equal(t, "", daemonRef.id.String(), c.input) require.NotNil(t, daemonRef.ref, c.input) - assert.Equal(t, c.expectedRef, daemonRef.ref.XString(), 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) - assert.Equal(t, c.expectedRef, dockerRef.XString(), c.input) + assert.Equal(t, c.expectedRef, dockerRef.String(), c.input) } } } @@ -100,10 +100,10 @@ func (ref refWithTagAndDigest) XTag() string { // 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) { @@ -127,7 +127,7 @@ func TestNewReference(t *testing.T) { require.True(t, ok, c.input) assert.Equal(t, "", daemonRef.id.String()) require.NotNil(t, daemonRef.ref) - assert.Equal(t, c.dockerRef, daemonRef.ref.XString(), c.input) + assert.Equal(t, c.dockerRef, daemonRef.ref.String(), c.input) } // Both an ID and a named reference provided @@ -190,7 +190,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err, c.input) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef, c.input) - assert.Equal(t, c.dockerRef, dockerRef.XString(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) } } diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 14fe9d191a..78a3d76fc6 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -7,6 +7,7 @@ import ( "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -56,7 +57,7 @@ func ParseReference(refString string) (types.ImageReference, error) { // NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.XIsNameOnly(). func NewReference(ref reference.XNamed) (types.ImageReference, error) { if reference.XIsNameOnly(ref) { - return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", ref.XString()) + return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", distreference.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. @@ -82,7 +83,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.XString() + return "//" + distreference.FamiliarString(ref.ref) } // DockerReference returns a Docker reference associated with this reference @@ -152,5 +153,5 @@ func (ref dockerReference) tagOrDigest() (string, error) { return ref.XTag(), nil } // This should not happen, NewReference above refuses reference.XIsNameOnly values. - return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", ref.ref.XString()) + return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", distreference.FamiliarString(ref.ref)) } diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 8d8d27af10..3872bc2889 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -42,18 +42,18 @@ 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 + {"//busybox:latest" + sha256digest, "docker.io/library/busybox" + 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 == "" { @@ -62,7 +62,7 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) - assert.Equal(t, c.expected, dockerRef.ref.XString(), c.input) + assert.Equal(t, c.expected, dockerRef.ref.String(), c.input) } } } @@ -76,10 +76,10 @@ func (ref refWithTagAndDigest) XTag() string { // 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) { @@ -90,7 +90,7 @@ func TestNewReference(t *testing.T) { require.NoError(t, err, c.input) dockerRef, ok := ref.(dockerReference) require.True(t, ok, c.input) - assert.Equal(t, c.dockerRef, dockerRef.ref.XString(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.ref.String(), c.input) } // Neither a tag nor digest @@ -135,7 +135,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err, c.input) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef, c.input) - assert.Equal(t, c.dockerRef, dockerRef.XString(), c.input) + assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) } } diff --git a/docker/lookaside.go b/docker/lookaside.go index 0b3d4a3c1b..8896b758e0 100644 --- a/docker/lookaside.go +++ b/docker/lookaside.go @@ -66,7 +66,7 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc // FIXME? Restrict to explicitly supported schemes? 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.XString()) + return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String()) } url.Path = url.Path + "/" + repo return url, nil diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 89ed9d7bcc..80ea02d4b8 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/containers/image/docker/reference" + distreference "github.com/docker/distribution/reference" ) // DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup, @@ -17,9 +18,9 @@ func DockerReferenceIdentity(ref reference.XNamed) (string, error) { digested, isDigested := ref.(reference.XCanonical) switch { case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. - return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", ref.XString()) + return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", distreference.FamiliarString(ref)) case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.XIsNameOnly() - return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", ref.XString()) + return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", distreference.FamiliarString(ref)) case isTagged: res = res + ":" + tagged.XTag() case isDigested: diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 05c81c0849..e0f12c0d17 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -26,8 +26,6 @@ const ( // XNamed is an object with a full name type XNamed interface { distreference.Named - // XString returns full reference, like "ubuntu@sha256:abcdef..." - XString() string } // XNamedTagged is an object including a name and tag. @@ -126,9 +124,6 @@ func (r *namedRef) Familiar() distreference.Named { return r.Named.(drPRIVATEInterfaces).Familiar() } -func (r *namedRef) XString() string { - return distreference.FamiliarString(r) -} func (r *taggedRef) XTag() string { return r.namedRef.Named.(distreference.NamedTagged).Tag() } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 286d8a0fb8..c34e2d38ca 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -255,7 +255,7 @@ func TestParseReferenceWithTagAndDigest(t *testing.T) { if _, isCanonical := ref.(XCanonical); !isCanonical { t.Fatalf("Reference from %q should not support digest", ref) } - if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.XString(); actual != expected { + if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", distreference.FamiliarString(ref); actual != expected { t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) } } diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index 77a9aa2237..fc1baf5f8e 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -68,7 +68,7 @@ func NewReference(dockerRef reference.XNamedTagged) (types.ImageReference, error r := strings.SplitN(distreference.Path(dockerRef), "/", 3) if len(r) != 2 { return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", - dockerRef.XString()) + distreference.FamiliarString(dockerRef)) } return openshiftReference{ namespace: r[0], @@ -87,7 +87,7 @@ func (ref openshiftReference) 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 openshiftReference) StringWithinTransport() string { - return ref.dockerReference.XString() + return distreference.FamiliarString(ref.dockerReference) } // DockerReference returns a Docker reference associated with this reference diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 47b0295361..3aeac57f3a 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -80,7 +80,7 @@ func TestReferenceDockerReference(t *testing.T) { require.NoError(t, err) dockerRef := ref.DockerReference() require.NotNil(t, dockerRef) - assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", dockerRef.XString()) + assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", dockerRef.String()) } func TestReferenceTransport(t *testing.T) { diff --git a/signature/docker.go b/signature/docker.go index ee1e82b101..e135a14886 100644 --- a/signature/docker.go +++ b/signature/docker.go @@ -41,7 +41,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt if err != nil { return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)} } - if signedRef.XString() != expectedRef.XString() { + if signedRef.String() != expectedRef.String() { return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s", signedDockerReference, expectedDockerReference)} } diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index b38fd10639..74492a7b85 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -33,7 +33,7 @@ func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, sign if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { return false } - return signature.XString() == intended.XString() + return signature.String() == intended.String() } func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { @@ -48,7 +48,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse } switch intended.(type) { case reference.XNamedTagged: // Includes the case when intended has both a tag and a digest. - return signature.XString() == intended.XString() + return signature.String() == intended.String() case reference.XCanonical: // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, @@ -89,7 +89,7 @@ func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { return false } - return signature.XString() == intended.XString() + return signature.String() == intended.String() } func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool { diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 0284256822..391a7183ad 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -6,7 +6,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/types" - + distreference "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,8 +30,8 @@ func TestParseImageAndDockerReference(t *testing.T) { require.NoError(t, err) r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.XString()) - assert.Equal(t, ok2, r2.XString()) + assert.Equal(t, ok1, distreference.FamiliarString(r1)) + assert.Equal(t, ok2, distreference.FamiliarString(r2)) // Unidentified images are rejected. _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) @@ -307,8 +307,8 @@ func TestParseDockerReferences(t *testing.T) { // Success r1, r2, err := parseDockerReferences(ok1, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.XString()) - assert.Equal(t, ok2, r2.XString()) + assert.Equal(t, ok1, distreference.FamiliarString(r1)) + assert.Equal(t, ok2, distreference.FamiliarString(r2)) // Failures for _, refs := range [][]string{ diff --git a/storage/storage_reference_test.go b/storage/storage_reference_test.go index 7c3c8f3c9b..ee4613414d 100644 --- a/storage/storage_reference_test.go +++ b/storage/storage_reference_test.go @@ -23,7 +23,7 @@ func TestStorageReferenceDockerReference(t *testing.T) { require.NoError(t, err) dr := ref.DockerReference() require.NotNil(t, dr) - assert.Equal(t, "busybox:latest", dr.XString()) + assert.Equal(t, "docker.io/library/busybox:latest", dr.String()) ref, err = Transport.ParseReference("@" + sha256digestHex) require.NoError(t, err) diff --git a/storage/storage_transport_test.go b/storage/storage_transport_test.go index c1ee2fc4d6..271bba3e87 100644 --- a/storage/storage_transport_test.go +++ b/storage/storage_transport_test.go @@ -57,7 +57,7 @@ func TestTransportParseStoreReference(t *testing.T) { dockerRef, err := reference.XParseNamed(c.expectedRef) require.NoError(t, err) require.NotNil(t, storageRef.name, c.input) - assert.Equal(t, dockerRef.XString(), storageRef.name.XString()) + assert.Equal(t, dockerRef.String(), storageRef.name.String()) } } } From dfe2fafaa2d702e5a932721aed55b996024051b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:14:12 +0100 Subject: [PATCH 13/38] API transition: Drop reference.XNamed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use the distreference.Named interface directly. Signed-off-by: Miloslav Trmač --- directory/directory_transport.go | 4 ++-- docker/daemon/daemon_transport.go | 6 +++--- docker/docker_transport.go | 6 +++--- docker/policyconfiguration/naming.go | 4 ++-- docker/reference/reference.go | 21 ++++++++------------- docker/reference/reference_test.go | 2 +- image/docker_schema1.go | 2 +- image/docker_schema2_test.go | 14 +++++++------- image/oci_test.go | 6 +++--- oci/layout/oci_transport.go | 4 ++-- openshift/openshift_transport.go | 2 +- signature/policy_eval_simple_test.go | 4 ++-- signature/policy_eval_test.go | 7 ++++--- signature/policy_reference_match.go | 5 +++-- signature/policy_reference_match_test.go | 18 +++++++++--------- storage/storage_reference.go | 8 ++++---- storage/storage_transport.go | 5 +++-- types/types.go | 7 +++---- 18 files changed, 61 insertions(+), 64 deletions(-) diff --git a/directory/directory_transport.go b/directory/directory_transport.go index 4f294c92e8..526ef3d0ff 100644 --- a/directory/directory_transport.go +++ b/directory/directory_transport.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "github.com/containers/image/directory/explicitfilepath" - "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) @@ -93,7 +93,7 @@ func (ref dirReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref dirReference) DockerReference() reference.XNamed { +func (ref dirReference) DockerReference() reference.Named { return nil } diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index c6043929eb..cbe2a2bb1a 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -42,7 +42,7 @@ func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error { // Using the config digest requires the caller to parse the manifest themselves, which is very cumbersome; so, for now, we don’t bother.) type daemonReference struct { id digest.Digest - ref reference.XNamed // !reference.XIsNameOnly + ref distreference.Named // !reference.XIsNameOnly } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. @@ -72,7 +72,7 @@ func ParseReference(refString string) (types.ImageReference, error) { } // NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.XIsNameOnly) -func NewReference(id digest.Digest, ref reference.XNamed) (types.ImageReference, error) { +func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReference, error) { if id != "" && ref != nil { return nil, errors.New("docker-daemon: reference must not have an image ID and a reference string specified at the same time") } @@ -118,7 +118,7 @@ func (ref daemonReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref daemonReference) DockerReference() reference.XNamed { +func (ref daemonReference) DockerReference() distreference.Named { return ref.ref // May be nil } diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 78a3d76fc6..235c59828d 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -38,7 +38,7 @@ func (t dockerTransport) ValidatePolicyConfigurationScope(scope string) error { // dockerReference is an ImageReference for Docker images. type dockerReference struct { - ref reference.XNamed // By construction we know that !reference.XIsNameOnly(ref) + ref distreference.Named // By construction we know that !reference.XIsNameOnly(ref) } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference. @@ -55,7 +55,7 @@ func ParseReference(refString string) (types.ImageReference, error) { } // NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.XIsNameOnly(). -func NewReference(ref reference.XNamed) (types.ImageReference, error) { +func NewReference(ref distreference.Named) (types.ImageReference, error) { if reference.XIsNameOnly(ref) { return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", distreference.FamiliarString(ref)) } @@ -89,7 +89,7 @@ func (ref dockerReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref dockerReference) DockerReference() reference.XNamed { +func (ref dockerReference) DockerReference() distreference.Named { return ref.ref } diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 80ea02d4b8..fc6871852a 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -12,7 +12,7 @@ import ( // DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup, // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.XIsNameOnly(). -func DockerReferenceIdentity(ref reference.XNamed) (string, error) { +func DockerReferenceIdentity(ref distreference.Named) (string, error) { res := ref.Name() tagged, isTagged := ref.(reference.XNamedTagged) digested, isDigested := ref.(reference.XCanonical) @@ -34,7 +34,7 @@ func DockerReferenceIdentity(ref reference.XNamed) (string, error) { // DockerReferenceNamespaces returns a list of other policy configuration namespaces to search, // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.XIsNameOnly(). -func DockerReferenceNamespaces(ref reference.XNamed) []string { +func DockerReferenceNamespaces(ref distreference.Named) []string { // Look for a match of the repository, and then of the possible parent // namespaces. Note that this only happens on the expanded host names // and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox", diff --git a/docker/reference/reference.go b/docker/reference/reference.go index e0f12c0d17..ea351c1382 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -23,21 +23,16 @@ const ( XDefaultRepoPrefix = "library/" ) -// XNamed is an object with a full name -type XNamed interface { - distreference.Named -} - // XNamedTagged is an object including a name and tag. type XNamedTagged interface { - XNamed + distreference.Named XTag() string } // XCanonical reference is an object with a fully unique // name including a name with hostname and digest type XCanonical interface { - XNamed + distreference.Named XDigest() digest.Digest } @@ -45,7 +40,7 @@ type XCanonical interface { // the Named interface. The reference must have a name, otherwise an error is // returned. // If an error was encountered it is returned, along with a nil Reference. -func XParseNamed(s string) (XNamed, error) { +func XParseNamed(s string) (distreference.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) @@ -69,7 +64,7 @@ func XParseNamed(s string) (XNamed, error) { // XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to XNamed/Named. +// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named. func XWithName(name string) (*namedRef, error) { r, err := distreference.ParseNormalizedNamed(name) if err != nil { @@ -80,7 +75,7 @@ func XWithName(name string) (*namedRef, error) { // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -// FIXME: expects *namedRef to expose the distreference.Named implementation. Should revert to XNamed/Named. +// FIXME: expects *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named. func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { r, err := distreference.WithTag(name, tag) if err != nil { @@ -132,7 +127,7 @@ func (r *canonicalRef) XDigest() digest.Digest { } // XWithDefaultTag adds a default tag to a reference if it only has a repo name. -func XWithDefaultTag(ref XNamed) XNamed { +func XWithDefaultTag(ref distreference.Named) distreference.Named { if XIsNameOnly(ref) { // FIXME: uses *namedRef to expose the distreference.Named implementations. Should use ref without a cast. ref, _ = XWithTag(ref.(*namedRef), XDefaultTag) @@ -141,7 +136,7 @@ func XWithDefaultTag(ref XNamed) XNamed { } // XIsNameOnly returns true if reference only contains a repo name. -func XIsNameOnly(ref XNamed) bool { +func XIsNameOnly(ref distreference.Named) bool { if _, ok := ref.(XNamedTagged); ok { return false } @@ -153,7 +148,7 @@ func XIsNameOnly(ref XNamed) bool { // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. -func XParseIDOrReference(idOrRef string) (digest.Digest, XNamed, error) { +func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { if err := validateID(idOrRef); err == nil { idOrRef = "sha256:" + idOrRef } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index c34e2d38ca..a612aa9d01 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -212,7 +212,7 @@ func TestParseRepositoryInfo(t *testing.T) { refStrings = append(refStrings, tcase.AmbiguousName) } - var refs []XNamed + var refs []distreference.Named for _, r := range refStrings { named, err := XParseNamed(r) if err != nil { diff --git a/image/docker_schema1.go b/image/docker_schema1.go index 34a0820e4a..e9c294d923 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -70,7 +70,7 @@ func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) { } // manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data. -func manifestSchema1FromComponents(ref reference.XNamed, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { +func manifestSchema1FromComponents(ref distreference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { var name, tag string if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. name = distreference.Path(ref) diff --git a/image/docker_schema2_test.go b/image/docker_schema2_test.go index 1e74d21364..3f4c8d5016 100644 --- a/image/docker_schema2_test.go +++ b/image/docker_schema2_test.go @@ -9,13 +9,13 @@ import ( "testing" "time" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -284,7 +284,7 @@ func TestManifestSchema2UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // schema2ImageSource is plausible enough for schema conversions in manifestSchema2.UpdatedImage() to work. type schema2ImageSource struct { configBlobImageSource - ref reference.XNamed + ref distreference.Named } func (s2is *schema2ImageSource) Reference() types.ImageReference { @@ -292,7 +292,7 @@ func (s2is *schema2ImageSource) Reference() types.ImageReference { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ reference.XNamed } +type refImageReferenceMock struct{ distreference.Named } func (ref refImageReferenceMock) Transport() types.ImageTransport { panic("unexpected call to a mock function") @@ -300,8 +300,8 @@ func (ref refImageReferenceMock) Transport() types.ImageTransport { func (ref refImageReferenceMock) StringWithinTransport() string { panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() reference.XNamed { - return ref.XNamed +func (ref refImageReferenceMock) DockerReference() distreference.Named { + return ref.Named } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { panic("unexpected call to a mock function") @@ -340,7 +340,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { } type memoryImageDest struct { - ref reference.XNamed + ref distreference.Named storedBlobs map[digest.Digest][]byte } diff --git a/image/oci_test.go b/image/oci_test.go index 996bcec631..c7ff622ddf 100644 --- a/image/oci_test.go +++ b/image/oci_test.go @@ -9,13 +9,13 @@ import ( "testing" "time" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -249,7 +249,7 @@ func TestManifestOCI1UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // oci1ImageSource is plausible enough for schema conversions in manifestOCI1.UpdatedImage() to work. type oci1ImageSource struct { configBlobImageSource - ref reference.XNamed + ref distreference.Named } func (OCIis *oci1ImageSource) Reference() types.ImageReference { diff --git a/oci/layout/oci_transport.go b/oci/layout/oci_transport.go index e4c5696853..434789d912 100644 --- a/oci/layout/oci_transport.go +++ b/oci/layout/oci_transport.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/containers/image/directory/explicitfilepath" - "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -130,7 +130,7 @@ func (ref ociReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref ociReference) DockerReference() reference.XNamed { +func (ref ociReference) DockerReference() reference.Named { return nil } diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index fc1baf5f8e..9e14fd6ad3 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -93,7 +93,7 @@ func (ref openshiftReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref openshiftReference) DockerReference() reference.XNamed { +func (ref openshiftReference) DockerReference() distreference.Named { return ref.dockerReference } diff --git a/signature/policy_eval_simple_test.go b/signature/policy_eval_simple_test.go index 5d766a9c6b..f24f6533b4 100644 --- a/signature/policy_eval_simple_test.go +++ b/signature/policy_eval_simple_test.go @@ -3,8 +3,8 @@ package signature import ( "testing" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" + "github.com/docker/distribution/reference" ) // nameOnlyImageMock is a mock of types.UnparsedImage which only allows transports.ImageName to work @@ -25,7 +25,7 @@ func (ref nameOnlyImageReferenceMock) Transport() types.ImageTransport { func (ref nameOnlyImageReferenceMock) StringWithinTransport() string { return string(ref) } -func (ref nameOnlyImageReferenceMock) DockerReference() reference.XNamed { +func (ref nameOnlyImageReferenceMock) DockerReference() reference.Named { panic("unexpected call to a mock function") } func (ref nameOnlyImageReferenceMock) PolicyConfigurationIdentity() string { diff --git a/signature/policy_eval_test.go b/signature/policy_eval_test.go index 487bf19306..1e0a42c043 100644 --- a/signature/policy_eval_test.go +++ b/signature/policy_eval_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -64,7 +65,7 @@ func TestPolicyContextNewDestroy(t *testing.T) { // and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently. type pcImageReferenceMock struct { transportName string - ref reference.XNamed + ref distreference.Named } func (ref pcImageReferenceMock) Transport() types.ImageTransport { @@ -74,7 +75,7 @@ func (ref pcImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. return "== StringWithinTransport mock" } -func (ref pcImageReferenceMock) DockerReference() reference.XNamed { +func (ref pcImageReferenceMock) DockerReference() distreference.Named { return ref.ref } func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string { @@ -148,7 +149,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // No match within a matched transport which doesn't have a "" scope {"atomic", "this.doesnt/match:anything", "", ""}, // No configuration available for this transport at all - {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.XNamed. + {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid distreference.Named. } { var expected PolicyRequirements if c.matchedTransport != "" { diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index 74492a7b85..2e1450e8a4 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -8,10 +8,11 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" ) // parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images. -func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.XNamed, reference.XNamed, error) { +func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (distreference.Named, distreference.Named, error) { r1 := image.Reference().DockerReference() if r1 == nil { return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity", @@ -68,7 +69,7 @@ func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, } // parseDockerReferences converts two reference strings into parsed entities, failing on any error -func parseDockerReferences(s1, s2 string) (reference.XNamed, reference.XNamed, error) { +func parseDockerReferences(s1, s2 string) (distreference.Named, distreference.Named, error) { r1, err := reference.XParseNamed(s1) if err != nil { return nil, nil, err diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 391a7183ad..4f438965f1 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -53,10 +53,10 @@ func TestParseImageAndDockerReference(t *testing.T) { } // refImageMock is a mock of types.UnparsedImage which returns itself in Reference().DockerReference. -type refImageMock struct{ reference.XNamed } +type refImageMock struct{ distreference.Named } func (ref refImageMock) Reference() types.ImageReference { - return refImageReferenceMock{ref.XNamed} + return refImageReferenceMock{ref.Named} } func (ref refImageMock) Close() { panic("unexpected call to a mock function") @@ -69,24 +69,24 @@ func (ref refImageMock) Signatures() ([][]byte, error) { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ reference.XNamed } +type refImageReferenceMock struct{ distreference.Named } func (ref refImageReferenceMock) Transport() types.ImageTransport { - // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that. - if ref.XNamed == nil { + // We use this in error messages, so sady we must return something. But right now we do so only when DockerReference is nil, so restrict to that. + if ref.Named == nil { return nameImageTransportMock("== Transport mock") } panic("unexpected call to a mock function") } func (ref refImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that. - if ref.XNamed == nil { + if ref.Named == nil { return "== StringWithinTransport for an image with no Docker support" } panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() reference.XNamed { - return ref.XNamed +func (ref refImageReferenceMock) DockerReference() distreference.Named { + return ref.Named } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { panic("unexpected call to a mock function") @@ -208,7 +208,7 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ } func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) { - // This assumes that all ways to obtain a reference.XNamed perform equivalent validation, + // This assumes that all ways to obtain a distreference.Named perform equivalent validation, // and therefore values refused by reference.XParseNamed can not happen in practice. parsedImageRef, err := reference.XParseNamed(imageRef) if err != nil { diff --git a/storage/storage_reference.go b/storage/storage_reference.go index c550f3da2e..0689e15b01 100644 --- a/storage/storage_reference.go +++ b/storage/storage_reference.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" + "github.com/docker/distribution/reference" ) // A storageReference holds an arbitrary name and/or an ID, which is a 32-byte @@ -15,10 +15,10 @@ type storageReference struct { transport storageTransport reference string id string - name reference.XNamed + name reference.Named } -func newReference(transport storageTransport, reference, id string, name reference.XNamed) *storageReference { +func newReference(transport storageTransport, reference, id string, name reference.Named) *storageReference { // We take a copy of the transport, which contains a pointer to the // store that it used for resolving this reference, so that the // transport that we'll return from Transport() won't be affected by @@ -52,7 +52,7 @@ func (s storageReference) Transport() types.ImageTransport { } // Return a name with a tag, if we have a name to base them on. -func (s storageReference) DockerReference() reference.XNamed { +func (s storageReference) DockerReference() reference.Named { return s.name } diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 3eeb9665f4..e22c5c7334 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -11,6 +11,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/storage/storage" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ddigest "github.com/opencontainers/go-digest" ) @@ -66,7 +67,7 @@ func (s *storageTransport) SetStore(store storage.Store) { // ParseStoreReference takes a name or an ID, tries to figure out which it is // relative to the given store, and returns it in a reference object. func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) { - var name reference.XNamed + var name distreference.Named var sum digest.Digest var err error if ref == "" { @@ -276,7 +277,7 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { return nil } -func verboseName(name reference.XNamed) string { +func verboseName(name distreference.Named) string { name = reference.XWithDefaultTag(name) tag := "" if tagged, ok := name.(reference.XNamedTagged); ok { diff --git a/types/types.go b/types/types.go index f834dc8ad7..b34b966f37 100644 --- a/types/types.go +++ b/types/types.go @@ -4,10 +4,9 @@ import ( "io" "time" - "github.com/pkg/errors" - - "github.com/containers/image/docker/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" + "github.com/pkg/errors" ) // ImageTransport is a top-level namespace for ways to to store/load an image. @@ -55,7 +54,7 @@ type ImageReference interface { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. - DockerReference() reference.XNamed + DockerReference() reference.Named // PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup. // This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases; From c2360fcb87fc660a05abb04f348862631ef1518f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:37:17 +0100 Subject: [PATCH 14/38] Duplication: Have both a namedRef and NamedTagged in taggedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to the .our/.upstream dance in namedRef, add a second, write-only implementation in taggedRef. Except now we go a bit faster and skip the .our/.upstream member names. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport_test.go | 3 +++ docker/docker_transport_test.go | 3 +++ docker/policyconfiguration/naming_test.go | 3 +++ docker/reference/reference.go | 5 +++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 2c6eaae860..e04359a324 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -96,6 +96,9 @@ type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) XTag() string { return "notLatest" } +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) diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 3872bc2889..7717da66bd 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -73,6 +73,9 @@ type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) XTag() string { return "notLatest" } +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 }{ diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index 4425cb9ba9..fb1eb2c5f4 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -68,6 +68,9 @@ type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) XTag() string { return "notLatest" } +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. diff --git a/docker/reference/reference.go b/docker/reference/reference.go index ea351c1382..ca34880035 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -25,7 +25,7 @@ const ( // XNamedTagged is an object including a name and tag. type XNamedTagged interface { - distreference.Named + distreference.NamedTagged XTag() string } @@ -81,13 +81,14 @@ func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { if err != nil { return nil, err } - return &taggedRef{namedRef{r}}, nil + return &taggedRef{NamedTagged: r, namedRef: namedRef{r}}, nil } type namedRef struct { distreference.Named // FIXME: must implement private distreference.NamedRepository } type taggedRef struct { + distreference.NamedTagged namedRef } type canonicalRef struct { From 09779a3d7332587af1238fbdbea8ff4b7bfe7ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:40:23 +0100 Subject: [PATCH 15/38] Implementation transition: Use the NamedTagged in XTag instead of namedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rely on the recently added member instead of the previous implementation. Does not change behavior. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index ca34880035..978fa69d1c 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -121,7 +121,7 @@ func (r *namedRef) Familiar() distreference.Named { } func (r *taggedRef) XTag() string { - return r.namedRef.Named.(distreference.NamedTagged).Tag() + return r.NamedTagged.Tag() } func (r *canonicalRef) XDigest() digest.Digest { return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) From 0268d9077d7b28b4efee91388acd01660cb8657d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:46:03 +0100 Subject: [PATCH 16/38] API transition: Drop XNamedTagged.XTag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, call NamedTagged.Tag in all users. XNamedTagged is now equivalent to distreference.NamedTagged. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_dest.go | 2 +- docker/daemon/daemon_transport_test.go | 3 --- docker/docker_transport.go | 2 +- docker/docker_transport_test.go | 3 --- docker/policyconfiguration/naming.go | 2 +- docker/policyconfiguration/naming_test.go | 3 --- docker/reference/reference.go | 4 ---- image/docker_schema1.go | 2 +- openshift/openshift.go | 4 ++-- openshift/openshift_transport_test.go | 2 +- storage/storage_transport.go | 2 +- 11 files changed, 8 insertions(+), 21 deletions(-) diff --git a/docker/daemon/daemon_dest.go b/docker/daemon/daemon_dest.go index 2707167ecc..a9113a274a 100644 --- a/docker/daemon/daemon_dest.go +++ b/docker/daemon/daemon_dest.go @@ -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.Name(), d.namedTaggedRef.XTag()) + refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.Name(), d.namedTaggedRef.Tag()) items := []manifestItem{{ Config: man.Config.Digest.String(), diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index e04359a324..0696deec27 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -93,9 +93,6 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err // refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) XTag() string { - return "notLatest" -} func (ref refWithTagAndDigest) Tag() string { return "notLatest" } diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 235c59828d..2fcfc3ec0b 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -150,7 +150,7 @@ func (ref dockerReference) tagOrDigest() (string, error) { return ref.XDigest().String(), nil } if ref, ok := ref.ref.(reference.XNamedTagged); ok { - return ref.XTag(), nil + return ref.Tag(), nil } // This should not happen, NewReference above refuses reference.XIsNameOnly values. return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", distreference.FamiliarString(ref.ref)) diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 7717da66bd..1ff88e13ec 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -70,9 +70,6 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err // refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) XTag() string { - return "notLatest" -} func (ref refWithTagAndDigest) Tag() string { return "notLatest" } diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index fc6871852a..438f2cc66f 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -22,7 +22,7 @@ func DockerReferenceIdentity(ref distreference.Named) (string, error) { case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.XIsNameOnly() return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", distreference.FamiliarString(ref)) case isTagged: - res = res + ":" + tagged.XTag() + res = res + ":" + tagged.Tag() case isDigested: res = res + "@" + digested.XDigest().String() default: // Coverage: The above was supposed to be exhaustive. diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index fb1eb2c5f4..5ed9d40be0 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -65,9 +65,6 @@ func TestDockerReference(t *testing.T) { // refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } -func (ref refWithTagAndDigest) XTag() string { - return "notLatest" -} func (ref refWithTagAndDigest) Tag() string { return "notLatest" } diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 978fa69d1c..cf61d31baa 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -26,7 +26,6 @@ const ( // XNamedTagged is an object including a name and tag. type XNamedTagged interface { distreference.NamedTagged - XTag() string } // XCanonical reference is an object with a fully unique @@ -120,9 +119,6 @@ func (r *namedRef) Familiar() distreference.Named { return r.Named.(drPRIVATEInterfaces).Familiar() } -func (r *taggedRef) XTag() string { - return r.NamedTagged.Tag() -} func (r *canonicalRef) XDigest() digest.Digest { return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) } diff --git a/image/docker_schema1.go b/image/docker_schema1.go index e9c294d923..5da9bbbc49 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -75,7 +75,7 @@ func manifestSchema1FromComponents(ref distreference.Named, fsLayers []fsLayersS if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. name = distreference.Path(ref) if tagged, ok := ref.(reference.XNamedTagged); ok { - tag = tagged.XTag() + tag = tagged.Tag() } } return &manifestSchema1{ diff --git a/openshift/openshift.go b/openshift/openshift.go index 1da80e8fb6..81671e7937 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -259,7 +259,7 @@ func (s *openshiftImageSource) ensureImageIsResolved() error { } var te *tagEvent for _, tag := range is.Status.Tags { - if tag.Tag != s.client.ref.dockerReference.XTag() { + if tag.Tag != s.client.ref.dockerReference.Tag() { continue } if len(tag.Items) > 0 { @@ -306,7 +306,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type // FIXME: Should this always use a digest, not a tag? Uploading to Docker by tag requires the tag _inside_ the manifest to match, // i.e. a single signed image cannot be available under multiple tags. But with types.ImageDestination, we don't know // the manifest digest at this point. - dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", reference.Domain(client.ref.dockerReference), client.ref.namespace, client.ref.stream, client.ref.dockerReference.XTag()) + dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", reference.Domain(client.ref.dockerReference), client.ref.namespace, client.ref.stream, client.ref.dockerReference.Tag()) dockerRef, err := docker.ParseReference(dockerRefString) if err != nil { return nil, err diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 3aeac57f3a..85db7d0466 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -64,7 +64,7 @@ func TestParseReference(t *testing.T) { require.True(t, ok) assert.Equal(t, "ns", osRef.namespace) assert.Equal(t, "stream", osRef.stream) - assert.Equal(t, "notlatest", osRef.dockerReference.XTag()) + assert.Equal(t, "notlatest", osRef.dockerReference.Tag()) assert.Equal(t, "registry.example.com:8443", distreference.Domain(osRef.dockerReference)) // Components creating an invalid Docker Reference name diff --git a/storage/storage_transport.go b/storage/storage_transport.go index e22c5c7334..f71bbb736c 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -281,7 +281,7 @@ func verboseName(name distreference.Named) string { name = reference.XWithDefaultTag(name) tag := "" if tagged, ok := name.(reference.XNamedTagged); ok { - tag = tagged.XTag() + tag = tagged.Tag() } return name.Name() + ":" + tag } From 751e8a5a2ee5f26715e32610acdbf1b77ee948db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:49:19 +0100 Subject: [PATCH 17/38] Implementation collapse: Drop taggedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that taggedRef merely wraps a distreference.NamedTagged, adding no functionality, just use a distreference.NamedTagged directly. This also simplifies XWithTag to merely call distreference.WithTag. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index cf61d31baa..973090a270 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -74,22 +74,13 @@ func XWithName(name string) (*namedRef, error) { // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -// FIXME: expects *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named. -func XWithTag(name *namedRef, tag string) (XNamedTagged, error) { - r, err := distreference.WithTag(name, tag) - if err != nil { - return nil, err - } - return &taggedRef{NamedTagged: r, namedRef: namedRef{r}}, nil +func XWithTag(name distreference.Named, tag string) (XNamedTagged, error) { + return distreference.WithTag(name, tag) } type namedRef struct { distreference.Named // FIXME: must implement private distreference.NamedRepository } -type taggedRef struct { - distreference.NamedTagged - namedRef -} type canonicalRef struct { namedRef } From 777b2151772516b7160a1c8a4b4ba7aee104400c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 06:57:22 +0100 Subject: [PATCH 18/38] API transition: Drop reference.XNamedTagged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use distreference.NamedTagged directly. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_dest.go | 6 +++--- docker/daemon/daemon_transport.go | 2 +- docker/daemon/daemon_transport_test.go | 2 +- docker/docker_transport.go | 4 ++-- docker/docker_transport_test.go | 2 +- docker/policyconfiguration/naming.go | 2 +- docker/policyconfiguration/naming_test.go | 2 +- docker/reference/reference.go | 9 ++------- docker/reference/reference_test.go | 2 +- image/docker_schema1.go | 9 ++++----- openshift/openshift_transport.go | 8 ++++---- openshift/openshift_transport_test.go | 4 ++-- signature/policy_reference_match.go | 2 +- storage/storage_transport.go | 2 +- 14 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docker/daemon/daemon_dest.go b/docker/daemon/daemon_dest.go index a9113a274a..dcbace94ef 100644 --- a/docker/daemon/daemon_dest.go +++ b/docker/daemon/daemon_dest.go @@ -11,9 +11,9 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/docker/docker/client" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -22,7 +22,7 @@ import ( type daemonImageDestination struct { ref daemonReference - namedTaggedRef reference.XNamedTagged // Strictly speaking redundant with ref above; having the field makes it structurally impossible for later users to fail. + namedTaggedRef reference.NamedTagged // Strictly speaking redundant with ref above; having the field makes it structurally impossible for later users to fail. // For talking to imageLoadGoroutine goroutineCancel context.CancelFunc statusChannel <-chan error @@ -38,7 +38,7 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t if ref.ref == nil { return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport()) } - namedTaggedRef, ok := ref.ref.(reference.XNamedTagged) + namedTaggedRef, ok := ref.ref.(reference.NamedTagged) if !ok { return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport()) } diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index cbe2a2bb1a..62ba156c48 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -82,7 +82,7 @@ func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReferen } // 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. - _, isTagged := ref.(reference.XNamedTagged) + _, isTagged := ref.(distreference.NamedTagged) _, isDigested := ref.(reference.XCanonical) if isTagged && isDigested { return nil, errors.Errorf("docker-daemon: references with both a tag and digest are currently not supported") diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 0696deec27..3a4dfd2657 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -90,7 +90,7 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } } -// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) Tag() string { diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 2fcfc3ec0b..cdbb0285ed 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -63,7 +63,7 @@ func NewReference(ref distreference.Named) (types.ImageReference, error) { // docker/reference does not handle that, so fail. // (Even if it were supported, the semantics of policy namespaces are unclear - should we drop // the tag or the digest first?) - _, isTagged := ref.(reference.XNamedTagged) + _, isTagged := ref.(distreference.NamedTagged) _, isDigested := ref.(reference.XCanonical) if isTagged && isDigested { return nil, errors.Errorf("Docker references with both a tag and digest are currently not supported") @@ -149,7 +149,7 @@ func (ref dockerReference) tagOrDigest() (string, error) { if ref, ok := ref.ref.(reference.XCanonical); ok { return ref.XDigest().String(), nil } - if ref, ok := ref.ref.(reference.XNamedTagged); ok { + if ref, ok := ref.ref.(distreference.NamedTagged); ok { return ref.Tag(), nil } // This should not happen, NewReference above refuses reference.XIsNameOnly values. diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 1ff88e13ec..d48456e25f 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -67,7 +67,7 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } } -// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) Tag() string { diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 438f2cc66f..60d26430c7 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -14,7 +14,7 @@ import ( // The reference must satisfy !reference.XIsNameOnly(). func DockerReferenceIdentity(ref distreference.Named) (string, error) { res := ref.Name() - tagged, isTagged := ref.(reference.XNamedTagged) + tagged, isTagged := ref.(distreference.NamedTagged) digested, isDigested := ref.(reference.XCanonical) switch { case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index 5ed9d40be0..e10a6b2f67 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -62,7 +62,7 @@ func TestDockerReference(t *testing.T) { } } -// refWithTagAndDigest is a reference.XNamedTagged and reference.XCanonical at the same time. +// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. type refWithTagAndDigest struct{ reference.XCanonical } func (ref refWithTagAndDigest) Tag() string { diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 973090a270..bf959a92e9 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -23,11 +23,6 @@ const ( XDefaultRepoPrefix = "library/" ) -// XNamedTagged is an object including a name and tag. -type XNamedTagged interface { - distreference.NamedTagged -} - // XCanonical reference is an object with a fully unique // name including a name with hostname and digest type XCanonical interface { @@ -74,7 +69,7 @@ func XWithName(name string) (*namedRef, error) { // XWithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. -func XWithTag(name distreference.Named, tag string) (XNamedTagged, error) { +func XWithTag(name distreference.Named, tag string) (distreference.NamedTagged, error) { return distreference.WithTag(name, tag) } @@ -125,7 +120,7 @@ func XWithDefaultTag(ref distreference.Named) distreference.Named { // XIsNameOnly returns true if reference only contains a repo name. func XIsNameOnly(ref distreference.Named) bool { - if _, ok := ref.(XNamedTagged); ok { + if _, ok := ref.(distreference.NamedTagged); ok { return false } if _, ok := ref.(XCanonical); ok { diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index a612aa9d01..7346df372c 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -249,7 +249,7 @@ func TestParseReferenceWithTagAndDigest(t *testing.T) { if err != nil { t.Fatal(err) } - if _, isTagged := ref.(XNamedTagged); isTagged { + if _, isTagged := ref.(distreference.NamedTagged); isTagged { t.Fatalf("Reference from %q should not support tag", ref) } if _, isCanonical := ref.(XCanonical); !isCanonical { diff --git a/image/docker_schema1.go b/image/docker_schema1.go index 5da9bbbc49..4afb17b9a4 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -6,10 +6,9 @@ import ( "strings" "time" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -70,11 +69,11 @@ func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) { } // manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data. -func manifestSchema1FromComponents(ref distreference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { +func manifestSchema1FromComponents(ref reference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { var name, tag string if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. - name = distreference.Path(ref) - if tagged, ok := ref.(reference.XNamedTagged); ok { + name = reference.Path(ref) + if tagged, ok := ref.(reference.NamedTagged); ok { tag = tagged.Tag() } } diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index 9e14fd6ad3..58a2a1876e 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -45,7 +45,7 @@ func (t openshiftTransport) ValidatePolicyConfigurationScope(scope string) error // openshiftReference is an ImageReference for OpenShift images. type openshiftReference struct { - dockerReference reference.XNamedTagged + dockerReference distreference.NamedTagged namespace string // Computed from dockerReference in advance. stream string // Computed from dockerReference in advance. } @@ -56,15 +56,15 @@ func ParseReference(ref string) (types.ImageReference, error) { if err != nil { return nil, errors.Wrapf(err, "failed to parse image reference %q", ref) } - tagged, ok := r.(reference.XNamedTagged) + tagged, ok := r.(distreference.NamedTagged) if !ok { return nil, errors.Errorf("invalid image reference %s, expected format: 'hostname/namespace/stream:tag'", ref) } return NewReference(tagged) } -// NewReference returns an OpenShift reference for a reference.XNamedTagged -func NewReference(dockerRef reference.XNamedTagged) (types.ImageReference, error) { +// NewReference returns an OpenShift reference for a distreference.NamedTagged +func NewReference(dockerRef distreference.NamedTagged) (types.ImageReference, error) { r := strings.SplitN(distreference.Path(dockerRef), "/", 3) if len(r) != 2 { return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 85db7d0466..44059b5ec8 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -43,14 +43,14 @@ func TestNewReference(t *testing.T) { // too many ns r, err := reference.XParseNamed("registry.example.com/ns1/ns2/ns3/stream:tag") require.NoError(t, err) - tagged, ok := r.(reference.XNamedTagged) + tagged, ok := r.(distreference.NamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.Error(t, err) r, err = reference.XParseNamed("registry.example.com/ns/stream:tag") require.NoError(t, err) - tagged, ok = r.(reference.XNamedTagged) + tagged, ok = r.(distreference.NamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.NoError(t, err) diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index 2e1450e8a4..fa38dfb4f2 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -48,7 +48,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse return false } switch intended.(type) { - case reference.XNamedTagged: // Includes the case when intended has both a tag and a digest. + case distreference.NamedTagged: // Includes the case when intended has both a tag and a digest. return signature.String() == intended.String() case reference.XCanonical: // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. diff --git a/storage/storage_transport.go b/storage/storage_transport.go index f71bbb736c..785ca9dcd1 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -280,7 +280,7 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { func verboseName(name distreference.Named) string { name = reference.XWithDefaultTag(name) tag := "" - if tagged, ok := name.(reference.XNamedTagged); ok { + if tagged, ok := name.(distreference.NamedTagged); ok { tag = tagged.Tag() } return name.Name() + ":" + tag From 49dea97ee5f455b9ee5b418f89e55f52df058c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:00:42 +0100 Subject: [PATCH 19/38] API transition: Drop XWithTag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use distreference.WithTag directly. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 11 ++--------- docker/reference/reference_test.go | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index bf959a92e9..aeda7cda10 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -51,7 +51,7 @@ func XParseNamed(s string) (distreference.Named, error) { return &canonicalRef{namedRef{r}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { - return XWithTag(r, tagged.Tag()) + return distreference.WithTag(r, tagged.Tag()) } return r, nil } @@ -67,12 +67,6 @@ func XWithName(name string) (*namedRef, error) { return &namedRef{r}, nil } -// XWithTag combines the name from "name" and the tag from "tag" to form a -// reference incorporating both the name and the tag. -func XWithTag(name distreference.Named, tag string) (distreference.NamedTagged, error) { - return distreference.WithTag(name, tag) -} - type namedRef struct { distreference.Named // FIXME: must implement private distreference.NamedRepository } @@ -112,8 +106,7 @@ func (r *canonicalRef) XDigest() digest.Digest { // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref distreference.Named) distreference.Named { if XIsNameOnly(ref) { - // FIXME: uses *namedRef to expose the distreference.Named implementations. Should use ref without a cast. - ref, _ = XWithTag(ref.(*namedRef), XDefaultTag) + ref, _ = distreference.WithTag(ref, XDefaultTag) } return ref } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 7346df372c..7dc047bea5 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -268,7 +268,7 @@ func TestInvalidReferenceComponents(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := XWithTag(ref, "-foo"); err == nil { + if _, err := distreference.WithTag(ref, "-foo"); err == nil { t.Fatal("Expected WithName to detect invalid tag") } } From 11e43ced2433ad57286f8165812cf719fffe377a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:04:20 +0100 Subject: [PATCH 20/38] Duplication: Have both a namedRef and Canonical in canonicalRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to the .our/.upstream dance in namedRef, add a second, write-only implementation in canonicalRef. Except now we go a bit faster and skip the .our/.upstream member names. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index aeda7cda10..b27a117062 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -26,7 +26,7 @@ const ( // XCanonical reference is an object with a fully unique // name including a name with hostname and digest type XCanonical interface { - distreference.Named + distreference.Canonical XDigest() digest.Digest } @@ -48,7 +48,7 @@ func XParseNamed(s string) (distreference.Named, error) { if err != nil { return nil, err } - return &canonicalRef{namedRef{r}}, nil + return &canonicalRef{Canonical: r, namedRef: namedRef{r}}, nil } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { return distreference.WithTag(r, tagged.Tag()) @@ -71,6 +71,7 @@ type namedRef struct { distreference.Named // FIXME: must implement private distreference.NamedRepository } type canonicalRef struct { + distreference.Canonical namedRef } From 76aef09512a9560bca260d646d7790e78b03a086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:06:57 +0100 Subject: [PATCH 21/38] Implementation transition: Use the Canonical in XDigest instead of namedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rely on the recently added member instead of the previous implementation. Does not change behavior. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index b27a117062..0985e56464 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -101,7 +101,7 @@ func (r *namedRef) Familiar() distreference.Named { } func (r *canonicalRef) XDigest() digest.Digest { - return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) + return digest.Digest(r.Canonical.Digest()) } // XWithDefaultTag adds a default tag to a reference if it only has a repo name. From 00b598c166593767218097fc6e34bb923bf75ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:10:54 +0100 Subject: [PATCH 22/38] API transition: Drop XCanonical.XDigest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, call Canonical.Digest in all users. XCanonical is now equivalent to distreference.Canonical. Signed-off-by: Miloslav Trmač --- docker/docker_transport.go | 2 +- docker/policyconfiguration/naming.go | 2 +- docker/reference/reference.go | 5 ----- image/unparsed.go | 3 ++- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docker/docker_transport.go b/docker/docker_transport.go index cdbb0285ed..157f94a596 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -147,7 +147,7 @@ func (ref dockerReference) DeleteImage(ctx *types.SystemContext) error { // tagOrDigest returns a tag or digest from the reference. func (ref dockerReference) tagOrDigest() (string, error) { if ref, ok := ref.ref.(reference.XCanonical); ok { - return ref.XDigest().String(), nil + return ref.Digest().String(), nil } if ref, ok := ref.ref.(distreference.NamedTagged); ok { return ref.Tag(), nil diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 60d26430c7..bc4b10c163 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -24,7 +24,7 @@ func DockerReferenceIdentity(ref distreference.Named) (string, error) { case isTagged: res = res + ":" + tagged.Tag() case isDigested: - res = res + "@" + digested.XDigest().String() + res = res + "@" + digested.Digest().String() default: // Coverage: The above was supposed to be exhaustive. return "", errors.New("Internal inconsistency, unexpected default branch") } diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 0985e56464..ce048b8484 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -27,7 +27,6 @@ const ( // name including a name with hostname and digest type XCanonical interface { distreference.Canonical - XDigest() digest.Digest } // XParseNamed parses s and returns a syntactically valid reference implementing @@ -100,10 +99,6 @@ func (r *namedRef) Familiar() distreference.Named { return r.Named.(drPRIVATEInterfaces).Familiar() } -func (r *canonicalRef) XDigest() digest.Digest { - return digest.Digest(r.Canonical.Digest()) -} - // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref distreference.Named) distreference.Named { if XIsNameOnly(ref) { diff --git a/image/unparsed.go b/image/unparsed.go index 2a98330cc1..67ef33f73d 100644 --- a/image/unparsed.go +++ b/image/unparsed.go @@ -4,6 +4,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -52,7 +53,7 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) { ref := i.Reference().DockerReference() if ref != nil { if canonical, ok := ref.(reference.XCanonical); ok { - digest := canonical.XDigest() + digest := digest.Digest(canonical.Digest()) matches, err := manifest.MatchesDigest(m, digest) if err != nil { return nil, "", errors.Wrap(err, "Error computing manifest digest") From c91c7c2ebb009dc892fdebec8f784f2b038a5ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:13:47 +0100 Subject: [PATCH 23/38] Implementation collapse: Drop canonicalRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that canonicalRef merely wraps a distreference.Canonical, adding no functionality, just use a distreference.Canonical directly. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index ce048b8484..9f4090a0d8 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -43,11 +43,7 @@ func XParseNamed(s string) (distreference.Named, error) { return nil, err } if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - r, err := distreference.WithDigest(r, canonical.Digest()) - if err != nil { - return nil, err - } - return &canonicalRef{Canonical: r, namedRef: namedRef{r}}, nil + return distreference.WithDigest(r, canonical.Digest()) } if tagged, isTagged := named.(distreference.NamedTagged); isTagged { return distreference.WithTag(r, tagged.Tag()) @@ -69,10 +65,6 @@ func XWithName(name string) (*namedRef, error) { type namedRef struct { distreference.Named // FIXME: must implement private distreference.NamedRepository } -type canonicalRef struct { - distreference.Canonical - namedRef -} // TEMPORARY: distreference.WithDigest and distreference.WithTag can work with any distreference.Named, // but if so, they break the values of distreference.Domain() and distreference.Path(), From cc0f48aa0389d854040db4a7bde765bd9786638f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:22:38 +0100 Subject: [PATCH 24/38] API transition: Drop reference.XCanonical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use distreference.Canonical directly. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 2 +- docker/daemon/daemon_transport_test.go | 7 ++++--- docker/docker_transport.go | 4 ++-- docker/docker_transport_test.go | 7 ++++--- docker/policyconfiguration/naming.go | 16 +++++++--------- docker/policyconfiguration/naming_test.go | 7 ++++--- docker/reference/reference.go | 8 +------- docker/reference/reference_test.go | 2 +- image/unparsed.go | 4 ++-- signature/policy_reference_match.go | 2 +- 10 files changed, 27 insertions(+), 32 deletions(-) diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 62ba156c48..33b1830fee 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -83,7 +83,7 @@ func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReferen // 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. _, isTagged := ref.(distreference.NamedTagged) - _, isDigested := ref.(reference.XCanonical) + _, isDigested := ref.(distreference.Canonical) if isTagged && isDigested { return nil, errors.Errorf("docker-daemon: references with both a tag and digest are currently not supported") } diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 3a4dfd2657..09f94aba01 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -5,6 +5,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -90,8 +91,8 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } } -// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. -type refWithTagAndDigest struct{ reference.XCanonical } +// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. +type refWithTagAndDigest struct{ distreference.Canonical } func (ref refWithTagAndDigest) Tag() string { return "notLatest" @@ -145,7 +146,7 @@ func TestNewReference(t *testing.T) { // A github.com/distribution/reference value can have a tag and a digest at the same time! parsed, err = reference.XParseNamed("busybox@" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.XCanonical) + refDigested, ok := parsed.(distreference.Canonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} _, err = NewReference("", tagDigestRef) diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 157f94a596..e9f9ba6618 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -64,7 +64,7 @@ func NewReference(ref distreference.Named) (types.ImageReference, error) { // (Even if it were supported, the semantics of policy namespaces are unclear - should we drop // the tag or the digest first?) _, isTagged := ref.(distreference.NamedTagged) - _, isDigested := ref.(reference.XCanonical) + _, isDigested := ref.(distreference.Canonical) if isTagged && isDigested { return nil, errors.Errorf("Docker references with both a tag and digest are currently not supported") } @@ -146,7 +146,7 @@ func (ref dockerReference) DeleteImage(ctx *types.SystemContext) error { // tagOrDigest returns a tag or digest from the reference. func (ref dockerReference) tagOrDigest() (string, error) { - if ref, ok := ref.ref.(reference.XCanonical); ok { + if ref, ok := ref.ref.(distreference.Canonical); ok { return ref.Digest().String(), nil } if ref, ok := ref.ref.(distreference.NamedTagged); ok { diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index d48456e25f..e391642178 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -5,6 +5,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -67,8 +68,8 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } } -// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. -type refWithTagAndDigest struct{ reference.XCanonical } +// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. +type refWithTagAndDigest struct{ distreference.Canonical } func (ref refWithTagAndDigest) Tag() string { return "notLatest" @@ -102,7 +103,7 @@ func TestNewReference(t *testing.T) { // A github.com/distribution/reference value can have a tag and a digest at the same time! parsed, err = reference.XParseNamed("busybox" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.XCanonical) + refDigested, ok := parsed.(distreference.Canonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} _, err = NewReference(tagDigestRef) diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index bc4b10c163..3d55a366f6 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -3,24 +3,22 @@ package policyconfiguration import ( "strings" + "github.com/docker/distribution/reference" "github.com/pkg/errors" - - "github.com/containers/image/docker/reference" - distreference "github.com/docker/distribution/reference" ) // DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup, // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.XIsNameOnly(). -func DockerReferenceIdentity(ref distreference.Named) (string, error) { +func DockerReferenceIdentity(ref reference.Named) (string, error) { res := ref.Name() - tagged, isTagged := ref.(distreference.NamedTagged) - digested, isDigested := ref.(reference.XCanonical) + tagged, isTagged := ref.(reference.NamedTagged) + digested, isDigested := ref.(reference.Canonical) switch { case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. - return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", distreference.FamiliarString(ref)) + 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.XIsNameOnly() - return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", distreference.FamiliarString(ref)) + 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: @@ -34,7 +32,7 @@ func DockerReferenceIdentity(ref distreference.Named) (string, error) { // DockerReferenceNamespaces returns a list of other policy configuration namespaces to search, // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.XIsNameOnly(). -func DockerReferenceNamespaces(ref distreference.Named) []string { +func DockerReferenceNamespaces(ref reference.Named) []string { // Look for a match of the repository, and then of the possible parent // namespaces. Note that this only happens on the expanded host names // and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox", diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index e10a6b2f67..71290d6c45 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/containers/image/docker/reference" + distreference "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -62,8 +63,8 @@ func TestDockerReference(t *testing.T) { } } -// refWithTagAndDigest is a reference.NamedTagged and reference.XCanonical at the same time. -type refWithTagAndDigest struct{ reference.XCanonical } +// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. +type refWithTagAndDigest struct{ distreference.Canonical } func (ref refWithTagAndDigest) Tag() string { return "notLatest" @@ -82,7 +83,7 @@ func TestDockerReferenceIdentity(t *testing.T) { // A github.com/distribution/reference value can have a tag and a digest at the same time! parsed, err = reference.XParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") require.NoError(t, err) - refDigested, ok := parsed.(reference.XCanonical) + refDigested, ok := parsed.(distreference.Canonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} id, err = DockerReferenceIdentity(tagDigestRef) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 9f4090a0d8..fcc66fa8e2 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -23,12 +23,6 @@ const ( XDefaultRepoPrefix = "library/" ) -// XCanonical reference is an object with a fully unique -// name including a name with hostname and digest -type XCanonical interface { - distreference.Canonical -} - // XParseNamed parses s and returns a syntactically valid reference implementing // the Named interface. The reference must have a name, otherwise an error is // returned. @@ -104,7 +98,7 @@ func XIsNameOnly(ref distreference.Named) bool { if _, ok := ref.(distreference.NamedTagged); ok { return false } - if _, ok := ref.(XCanonical); ok { + if _, ok := ref.(distreference.Canonical); ok { return false } return true diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 7dc047bea5..617f3b6b8b 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -252,7 +252,7 @@ func TestParseReferenceWithTagAndDigest(t *testing.T) { if _, isTagged := ref.(distreference.NamedTagged); isTagged { t.Fatalf("Reference from %q should not support tag", ref) } - if _, isCanonical := ref.(XCanonical); !isCanonical { + if _, isCanonical := ref.(distreference.Canonical); !isCanonical { t.Fatalf("Reference from %q should not support digest", ref) } if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", distreference.FamiliarString(ref); actual != expected { diff --git a/image/unparsed.go b/image/unparsed.go index 67ef33f73d..e11978643e 100644 --- a/image/unparsed.go +++ b/image/unparsed.go @@ -1,9 +1,9 @@ package image import ( - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -52,7 +52,7 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) { // this immediately protects also any user of types.Image. ref := i.Reference().DockerReference() if ref != nil { - if canonical, ok := ref.(reference.XCanonical); ok { + if canonical, ok := ref.(reference.Canonical); ok { digest := digest.Digest(canonical.Digest()) matches, err := manifest.MatchesDigest(m, digest) if err != nil { diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index fa38dfb4f2..cbb97f1c6f 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -50,7 +50,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse switch intended.(type) { case distreference.NamedTagged: // Includes the case when intended has both a tag and a digest. return signature.String() == intended.String() - case reference.XCanonical: + case distreference.Canonical: // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, // we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms) From b55e51d49628277a93014e55eb745e1342e66fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:25:41 +0100 Subject: [PATCH 25/38] Implementation transition: Use distreference.IsNameOnly in XIsNameOnly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two functions are line-by-line identical now. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index fcc66fa8e2..e382b6dba6 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -95,13 +95,7 @@ func XWithDefaultTag(ref distreference.Named) distreference.Named { // XIsNameOnly returns true if reference only contains a repo name. func XIsNameOnly(ref distreference.Named) bool { - if _, ok := ref.(distreference.NamedTagged); ok { - return false - } - if _, ok := ref.(distreference.Canonical); ok { - return false - } - return true + return distreference.IsNameOnly(ref) } // XParseIDOrReference parses string for an image ID or a reference. ID can be From c2d8ac2b3f7c556daeb525141f3c9cce8e310cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:31:57 +0100 Subject: [PATCH 26/38] API transition: Drop reference.XIsNameOnly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.IsNameOnly directly. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 8 ++++---- docker/docker_transport.go | 10 +++++----- docker/policyconfiguration/naming.go | 6 +++--- docker/reference/reference.go | 7 +------ oci/layout/oci_transport.go | 2 +- openshift/openshift_transport.go | 2 +- signature/policy_config.go | 6 +++--- signature/policy_reference_match.go | 8 ++++---- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 33b1830fee..a80225ad85 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -42,7 +42,7 @@ func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error { // Using the config digest requires the caller to parse the manifest themselves, which is very cumbersome; so, for now, we don’t bother.) type daemonReference struct { id digest.Digest - ref distreference.Named // !reference.XIsNameOnly + ref distreference.Named // !reference.IsNameOnly } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. @@ -71,13 +71,13 @@ func ParseReference(refString string) (types.ImageReference, error) { return NewReference("", ref) } -// NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.XIsNameOnly) +// NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.IsNameOnly) func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReference, error) { if id != "" && ref != nil { return nil, errors.New("docker-daemon: reference must not have an image ID and a reference string specified at the same time") } if ref != nil { - if reference.XIsNameOnly(ref) { + if distreference.IsNameOnly(ref) { return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", distreference.FamiliarString(ref)) } // A github.com/distribution/reference value can have a tag and a digest at the same time! @@ -116,7 +116,7 @@ func (ref daemonReference) StringWithinTransport() string { } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. func (ref daemonReference) DockerReference() distreference.Named { return ref.ref // May be nil diff --git a/docker/docker_transport.go b/docker/docker_transport.go index e9f9ba6618..ed35a7ed76 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -38,7 +38,7 @@ func (t dockerTransport) ValidatePolicyConfigurationScope(scope string) error { // dockerReference is an ImageReference for Docker images. type dockerReference struct { - ref distreference.Named // By construction we know that !reference.XIsNameOnly(ref) + ref distreference.Named // By construction we know that !reference.IsNameOnly(ref) } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference. @@ -54,9 +54,9 @@ func ParseReference(refString string) (types.ImageReference, error) { return NewReference(ref) } -// NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.XIsNameOnly(). +// NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.IsNameOnly(). func NewReference(ref distreference.Named) (types.ImageReference, error) { - if reference.XIsNameOnly(ref) { + if distreference.IsNameOnly(ref) { return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", distreference.FamiliarString(ref)) } // A github.com/distribution/reference value can have a tag and a digest at the same time! @@ -87,7 +87,7 @@ func (ref dockerReference) StringWithinTransport() string { } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. func (ref dockerReference) DockerReference() distreference.Named { return ref.ref @@ -152,6 +152,6 @@ func (ref dockerReference) tagOrDigest() (string, error) { if ref, ok := ref.ref.(distreference.NamedTagged); ok { return ref.Tag(), nil } - // This should not happen, NewReference above refuses reference.XIsNameOnly values. + // 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", distreference.FamiliarString(ref.ref)) } diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 3d55a366f6..c8909134e2 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -9,7 +9,7 @@ import ( // DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup, // as a backend for ImageReference.PolicyConfigurationIdentity. -// The reference must satisfy !reference.XIsNameOnly(). +// The reference must satisfy !reference.IsNameOnly(). func DockerReferenceIdentity(ref reference.Named) (string, error) { res := ref.Name() tagged, isTagged := ref.(reference.NamedTagged) @@ -17,7 +17,7 @@ func DockerReferenceIdentity(ref reference.Named) (string, error) { switch { case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. 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.XIsNameOnly() + 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", reference.FamiliarString(ref)) case isTagged: res = res + ":" + tagged.Tag() @@ -31,7 +31,7 @@ func DockerReferenceIdentity(ref reference.Named) (string, error) { // DockerReferenceNamespaces returns a list of other policy configuration namespaces to search, // as a backend for ImageReference.PolicyConfigurationIdentity. -// The reference must satisfy !reference.XIsNameOnly(). +// The reference must satisfy !reference.IsNameOnly(). func DockerReferenceNamespaces(ref reference.Named) []string { // Look for a match of the repository, and then of the possible parent // namespaces. Note that this only happens on the expanded host names diff --git a/docker/reference/reference.go b/docker/reference/reference.go index e382b6dba6..d20eb47d84 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -87,17 +87,12 @@ func (r *namedRef) Familiar() distreference.Named { // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref distreference.Named) distreference.Named { - if XIsNameOnly(ref) { + if distreference.IsNameOnly(ref) { ref, _ = distreference.WithTag(ref, XDefaultTag) } return ref } -// XIsNameOnly returns true if reference only contains a repo name. -func XIsNameOnly(ref distreference.Named) bool { - return distreference.IsNameOnly(ref) -} - // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { diff --git a/oci/layout/oci_transport.go b/oci/layout/oci_transport.go index 434789d912..cd4500a7b4 100644 --- a/oci/layout/oci_transport.go +++ b/oci/layout/oci_transport.go @@ -128,7 +128,7 @@ func (ref ociReference) StringWithinTransport() string { } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. func (ref ociReference) DockerReference() reference.Named { return nil diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index 58a2a1876e..df0756c2f9 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -91,7 +91,7 @@ func (ref openshiftReference) StringWithinTransport() string { } // DockerReference returns a Docker reference associated with this reference -// (fully explicit, i.e. !reference.XIsNameOnly, but reflecting user intent, +// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. func (ref openshiftReference) DockerReference() distreference.Named { return ref.dockerReference diff --git a/signature/policy_config.go b/signature/policy_config.go index 94f9d4237a..4330cad0fa 100644 --- a/signature/policy_config.go +++ b/signature/policy_config.go @@ -19,11 +19,11 @@ import ( "io/ioutil" "path/filepath" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" + distreference "github.com/docker/distribution/reference" + "github.com/pkg/errors" ) // systemDefaultPolicyPath is the policy path used for DefaultPolicy(). @@ -638,7 +638,7 @@ func newPRMExactReference(dockerReference string) (*prmExactReference, error) { if err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error())) } - if reference.XIsNameOnly(ref) { + if distreference.IsNameOnly(ref) { return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference)) } return &prmExactReference{ diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index cbb97f1c6f..2e0b9b839d 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -31,7 +31,7 @@ func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, sign return false } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { + if distreference.IsNameOnly(intended) || distreference.IsNameOnly(signature) { return false } return signature.String() == intended.String() @@ -44,7 +44,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if reference.XIsNameOnly(signature) { + if distreference.IsNameOnly(signature) { return false } switch intended.(type) { @@ -55,7 +55,7 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, // we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms) return signature.Name() == intended.Name() - default: // !reference.XIsNameOnly(intended) + default: // !reference.IsNameOnly(intended) return false } } @@ -87,7 +87,7 @@ func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, return false } // prm.DockerReference and signatureDockerReference should be exact; so, verify that now. - if reference.XIsNameOnly(intended) || reference.XIsNameOnly(signature) { + if distreference.IsNameOnly(intended) || distreference.IsNameOnly(signature) { return false } return signature.String() == intended.String() From b330f50f373c4e94ab4e06c4baaa6a66933a51fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:36:22 +0100 Subject: [PATCH 27/38] Implementation transition: Use distreference.TagNameOnly in XWithDefaultTag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index d20eb47d84..b6998c2ff7 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -87,10 +87,7 @@ func (r *namedRef) Familiar() distreference.Named { // XWithDefaultTag adds a default tag to a reference if it only has a repo name. func XWithDefaultTag(ref distreference.Named) distreference.Named { - if distreference.IsNameOnly(ref) { - ref, _ = distreference.WithTag(ref, XDefaultTag) - } - return ref + return distreference.TagNameOnly(ref) } // XParseIDOrReference parses string for an image ID or a reference. ID can be From 05f35b9f4ca45d5cface3e81f591e2ab13024953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 1 Feb 2017 22:38:44 +0100 Subject: [PATCH 28/38] API transition: Drop reference.XWithDefaultTag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use distreference.TagNameOnly directly. Signed-off-by: Miloslav Trmač --- docker/docker_transport.go | 2 +- docker/reference/reference.go | 5 ----- storage/storage_transport.go | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docker/docker_transport.go b/docker/docker_transport.go index ed35a7ed76..9fe352d27e 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -50,7 +50,7 @@ func ParseReference(refString string) (types.ImageReference, error) { if err != nil { return nil, err } - ref = reference.XWithDefaultTag(ref) + ref = distreference.TagNameOnly(ref) return NewReference(ref) } diff --git a/docker/reference/reference.go b/docker/reference/reference.go index b6998c2ff7..928c32e23b 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -85,11 +85,6 @@ func (r *namedRef) Familiar() distreference.Named { return r.Named.(drPRIVATEInterfaces).Familiar() } -// XWithDefaultTag adds a default tag to a reference if it only has a repo name. -func XWithDefaultTag(ref distreference.Named) distreference.Named { - return distreference.TagNameOnly(ref) -} - // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 785ca9dcd1..901f5fb0ea 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -112,7 +112,7 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( } refname := "" if name != nil { - name = reference.XWithDefaultTag(name) + name = distreference.TagNameOnly(name) refname = verboseName(name) } if refname == "" { @@ -278,7 +278,7 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { } func verboseName(name distreference.Named) string { - name = reference.XWithDefaultTag(name) + name = distreference.TagNameOnly(name) tag := "" if tagged, ok := name.(distreference.NamedTagged); ok { tag = tagged.Tag() From 7c038c265b70e739cf97e3dc3f54fdc86b236b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:43:54 +0100 Subject: [PATCH 29/38] Implementation collapse: Drop namedRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (This could have been done a few commits ago.) Now that namedRef merely wraps a distreference.Named, adding no functionality, just use a distreference.Named directly. Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 38 ++--------------------------------- 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 928c32e23b..c02f353ac2 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -47,42 +47,8 @@ func XParseNamed(s string) (distreference.Named, error) { // XWithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. -// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named. -func XWithName(name string) (*namedRef, error) { - r, err := distreference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - return &namedRef{r}, nil -} - -type namedRef struct { - distreference.Named // FIXME: must implement private distreference.NamedRepository -} - -// TEMPORARY: distreference.WithDigest and distreference.WithTag can work with any distreference.Named, -// but if so, they break the values of distreference.Domain() and distreference.Path(), -// and hence also distreference.FamiliarName()/distreference.FamiliarString(). To preserve this, -// we need to implement a PRIVATE distreference.namedRepository. -// Similarly, we need to implement a PRIVATE distreference.normalizedNamed so that distreference.Familiar*() -// knows how to compute the minimal form. -// Right now that happens by these REALLY UGLY methods; eventually we will eliminate namedRef entirely in favor of -// distreference.Named, and distreference can keep its implementation games to itself. -type drPRIVATEInterfaces interface { - distreference.Named - Domain() string - Path() string - Familiar() distreference.Named -} - -func (r *namedRef) Domain() string { - return r.Named.(drPRIVATEInterfaces).Domain() -} -func (r *namedRef) Path() string { - return r.Named.(drPRIVATEInterfaces).Path() -} -func (r *namedRef) Familiar() distreference.Named { - return r.Named.(drPRIVATEInterfaces).Familiar() +func XWithName(name string) (distreference.Named, error) { + return distreference.ParseNormalizedNamed(name) } // XParseIDOrReference parses string for an image ID or a reference. ID can be From 32d33ac0f4d6b6f99863ab93bffa3327646f0157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:49:11 +0100 Subject: [PATCH 30/38] API transition: Drop reference.XWithName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead use distreference.ParseNormalizedNamedDirectly (and update obsolete comments). Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 8 +------- docker/reference/reference_test.go | 15 +-------------- signature/policy_reference_match_test.go | 6 +++--- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index c02f353ac2..a3ed84fac0 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -32,7 +32,7 @@ func XParseNamed(s string) (distreference.Named, error) { if err != nil { return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s) } - r, err := XWithName(named.Name()) + r, err := distreference.ParseNormalizedNamed(named.Name()) if err != nil { return nil, err } @@ -45,12 +45,6 @@ func XParseNamed(s string) (distreference.Named, error) { return r, nil } -// XWithName returns a named object representing the given string. If the input -// is invalid ErrReferenceInvalidFormat will be returned. -func XWithName(name string) (distreference.Named, error) { - return distreference.ParseNormalizedNamed(name) -} - // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 617f3b6b8b..0db49b7ffe 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -219,7 +219,7 @@ func TestParseRepositoryInfo(t *testing.T) { t.Fatal(err) } refs = append(refs, named) - named, err = XWithName(r) + named, err = distreference.ParseNormalizedNamed(r) if err != nil { t.Fatal(err) } @@ -259,16 +259,3 @@ func TestParseReferenceWithTagAndDigest(t *testing.T) { t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) } } - -func TestInvalidReferenceComponents(t *testing.T) { - if _, err := XWithName("-foo"); err == nil { - t.Fatal("Expected WithName to detect invalid name") - } - ref, err := XWithName("busybox") - if err != nil { - t.Fatal(err) - } - if _, err := distreference.WithTag(ref, "-foo"); err == nil { - t.Fatal("Expected WithName to detect invalid tag") - } -} diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 4f438965f1..b7e4194c41 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -148,7 +148,7 @@ var prmExactMatchTestTable = []prmSymmetricTableTest{ {"busybox", "busybox:latest", false}, {"busybox", "busybox" + digestSuffix, false}, {"busybox", "busybox", false}, - // References with both tags and digests: `reference.XWithName` essentially drops the tag. + // References with both tags and digests: `reference.XParseNamed` essentially drops the tag. // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, // even if the tag may reflect a different user intent. // NOTE: Again, this is not documented behavior; the recommendation is to sign tags, not digests, and then tag-and-digest references won’t match the signed identity. @@ -194,7 +194,7 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ {"hostname/library/busybox:latest", "busybox:notlatest", false}, {"busybox:latest", fullRHELRef, false}, {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, - // References with both tags and digests: `reference.XWithName` essentially drops the tag, and we ignore both anyway. + // References with both tags and digests: `reference.XParseNamed` essentially drops the tag, and we ignore both anyway. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, @@ -272,7 +272,7 @@ func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) { // Digest references accept any signature with matching repository. {"busybox" + digestSuffix, "busybox:latest", true}, {"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - // References with both tags and digests: `reference.XWithName` essentially drops the tag. + // References with both tags and digests: `reference.XParseNamed` essentially drops the tag. // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, // even if the tag may reflect a different user intent. {"busybox:latest" + digestSuffix, "busybox:latest", true}, From 5a9a27a334aa7f6a69744619ce4a42a2d09e574a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:51:24 +0100 Subject: [PATCH 31/38] Do not normalize the input string twice in reference.XParseNamed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have _just_ normalized it, no need to do it again. (distreference.WithName does no checking; we could also call distreference.ParseNamed which does, but that does the checking by calling ParseNormalizedNamed anyway, again. We will eliminate this soon anyway…) Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index a3ed84fac0..05751d7f64 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -32,7 +32,7 @@ func XParseNamed(s string) (distreference.Named, error) { if err != nil { return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s) } - r, err := distreference.ParseNormalizedNamed(named.Name()) + r, err := distreference.WithName(named.Name()) if err != nil { return nil, err } From e928b402a5ab75b84b3f315effe65df8fa1b3774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 07:54:47 +0100 Subject: [PATCH 32/38] Drop unused constants from docker/reference. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 05751d7f64..f8547b8794 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -12,17 +12,6 @@ import ( "github.com/pkg/errors" ) -const ( - // XDefaultTag defines the default tag used when performing images related actions and no tag or digest is specified - XDefaultTag = "latest" - // XDefaultHostname is the default built-in hostname - XDefaultHostname = "docker.io" - // XLegacyDefaultHostname is automatically converted to DefaultHostname - XLegacyDefaultHostname = "index.docker.io" - // XDefaultRepoPrefix is the prefix used for default repositories in default host - XDefaultRepoPrefix = "library/" -) - // XParseNamed parses s and returns a syntactically valid reference implementing // the Named interface. The reference must have a name, otherwise an error is // returned. From 184b810c0581a9c835e22a61a1d49a3dbee37d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 08:22:59 +0100 Subject: [PATCH 33/38] BEHAVIOR CHANGE: Do not re-construct the reference in XParseNamed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of rebuilding it as name/name+digest/name+tag, just use the return value from distreference.ParseNormalizedName without modification. THIS CHANGES BEHAVIOR: before, name@tag:digest inputs were silently trated as name:digest, dropping the tag; now the semantics is correctly preserved. We already anticipate such strings as references in docker: and docker-daemon: (where they are now rejected) and in signature verification (where, unless we check repository names only, they must match exactly). Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 3 ++- docker/daemon/daemon_transport_test.go | 9 +++------ docker/docker_transport.go | 3 ++- docker/docker_transport_test.go | 12 +++++------- docker/policyconfiguration/naming_test.go | 14 +++++--------- docker/reference/reference.go | 16 +--------------- docker/reference/reference_test.go | 16 ---------------- signature/policy_reference_match_test.go | 22 +++++++++------------- 8 files changed, 27 insertions(+), 68 deletions(-) diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index a80225ad85..5b62934717 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -81,7 +81,8 @@ func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReferen return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", distreference.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.(distreference.NamedTagged) _, isDigested := ref.(distreference.Canonical) if isTagged && isDigested { diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 09f94aba01..d6b4186e23 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -54,12 +54,9 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err {"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, "", "docker.io/library/busybox@" + sha256digest}, // Both tag and digest - {"docker.io/library/busybox:latest", "", "docker.io/library/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 == "" { diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 9fe352d27e..2fd6f00844 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -60,7 +60,8 @@ func NewReference(ref distreference.Named) (types.ImageReference, error) { return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", distreference.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.(distreference.NamedTagged) diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index e391642178..91ca1488aa 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -48,13 +48,11 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err {"//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, "docker.io/library/busybox" + sha256digest}, // Both tag and digest - {"//docker.io/library/busybox:latest", "docker.io/library/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 == "" { diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index 71290d6c45..db5c6ecd12 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -36,11 +36,6 @@ 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.XParseNamed(fullInput) @@ -81,12 +76,13 @@ func TestDockerReferenceIdentity(t *testing.T) { assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! - parsed, err = reference.XParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + parsed, err = reference.XParseNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") require.NoError(t, err) - refDigested, ok := parsed.(distreference.Canonical) + _, ok := parsed.(distreference.Canonical) require.True(t, ok) - tagDigestRef := refWithTagAndDigest{refDigested} - id, err = DockerReferenceIdentity(tagDigestRef) + _, ok = parsed.(distreference.NamedTagged) + require.True(t, ok) + id, err = DockerReferenceIdentity(parsed) assert.Equal(t, "", id) assert.Error(t, err) } diff --git a/docker/reference/reference.go b/docker/reference/reference.go index f8547b8794..340f2f9878 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -17,21 +17,7 @@ import ( // returned. // If an error was encountered it is returned, along with a nil Reference. func XParseNamed(s string) (distreference.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) - } - r, err := distreference.WithName(named.Name()) - if err != nil { - return nil, err - } - if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - return distreference.WithDigest(r, canonical.Digest()) - } - if tagged, isTagged := named.(distreference.NamedTagged); isTagged { - return distreference.WithTag(r, tagged.Tag()) - } - return r, nil + return distreference.ParseNormalizedNamed(s) } // XParseIDOrReference parses string for an image ID or a reference. ID can be diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go index 0db49b7ffe..9153b4c65e 100644 --- a/docker/reference/reference_test.go +++ b/docker/reference/reference_test.go @@ -243,19 +243,3 @@ func TestParseRepositoryInfo(t *testing.T) { } } } - -func TestParseReferenceWithTagAndDigest(t *testing.T) { - ref, err := XParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") - if err != nil { - t.Fatal(err) - } - if _, isTagged := ref.(distreference.NamedTagged); isTagged { - t.Fatalf("Reference from %q should not support tag", ref) - } - if _, isCanonical := ref.(distreference.Canonical); !isCanonical { - t.Fatalf("Reference from %q should not support digest", ref) - } - if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", distreference.FamiliarString(ref); actual != expected { - t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) - } -} diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index b7e4194c41..413b11a506 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -148,14 +148,12 @@ var prmExactMatchTestTable = []prmSymmetricTableTest{ {"busybox", "busybox:latest", false}, {"busybox", "busybox" + digestSuffix, false}, {"busybox", "busybox", false}, - // References with both tags and digests: `reference.XParseNamed` essentially drops the tag. - // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, - // even if the tag may reflect a different user intent. + // References with both tags and digests: We match them exactly (requiring BOTH to match) // NOTE: Again, this is not documented behavior; the recommendation is to sign tags, not digests, and then tag-and-digest references won’t match the signed identity. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, false}, - {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, // Ugly. Do not rely on this. - {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, true}, // Ugly. Do not rely on this. + {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, false}, + {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, false}, {"busybox:latest" + digestSuffix, "busybox:latest", false}, // Invalid format {"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false}, @@ -194,7 +192,7 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ {"hostname/library/busybox:latest", "busybox:notlatest", false}, {"busybox:latest", fullRHELRef, false}, {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, - // References with both tags and digests: `reference.XParseNamed` essentially drops the tag, and we ignore both anyway. + // References with both tags and digests: We ignore both anyway. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, @@ -272,14 +270,12 @@ func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) { // Digest references accept any signature with matching repository. {"busybox" + digestSuffix, "busybox:latest", true}, {"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - // References with both tags and digests: `reference.XParseNamed` essentially drops the tag. - // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, - // even if the tag may reflect a different user intent. - {"busybox:latest" + digestSuffix, "busybox:latest", true}, - {"busybox:latest" + digestSuffix, "busybox:notlatest", true}, + // References with both tags and digests: We match them exactly (requiring BOTH to match). + {"busybox:latest" + digestSuffix, "busybox:latest", false}, + {"busybox:latest" + digestSuffix, "busybox:notlatest", false}, {"busybox:latest", "busybox:latest" + digestSuffix, false}, - {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, true}, // Ugly. Do not rely on this. + {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, false}, + {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, false}, } { testImageAndSig(t, prm, test.imageRef, test.sigRef, test.result) } From a81649c9c741542c7c31fafe96a896996788bf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 08:53:15 +0100 Subject: [PATCH 34/38] API transition: Drop reference.XParseNamed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead call distreference.ParseNormalizedNamed directly. (This looks bigger than it really is because so many files now don't need c/i/docker/reference, so they are dropping the “distreference” qualifier for docker/distribution/reference.) Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 23 +- docker/daemon/daemon_transport_test.go | 22 +- docker/docker_transport.go | 29 ++- docker/docker_transport_test.go | 25 +-- docker/policyconfiguration/naming.go | 2 +- docker/policyconfiguration/naming_test.go | 23 +- docker/reference/reference.go | 10 +- docker/reference/reference_test.go | 245 ---------------------- image/docker_schema2_test.go | 13 +- image/oci_test.go | 7 +- openshift/openshift_transport.go | 21 +- openshift/openshift_transport_test.go | 13 +- signature/docker.go | 6 +- signature/policy_config.go | 9 +- signature/policy_eval_signedby_test.go | 4 +- signature/policy_eval_test.go | 13 +- signature/policy_reference_match.go | 23 +- signature/policy_reference_match_test.go | 27 ++- storage/storage_transport.go | 21 +- storage/storage_transport_test.go | 4 +- 20 files changed, 127 insertions(+), 413 deletions(-) delete mode 100644 docker/reference/reference_test.go diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 5b62934717..00777605e4 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -3,10 +3,9 @@ package daemon import ( "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) @@ -42,7 +41,7 @@ func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error { // Using the config digest requires the caller to parse the manifest themselves, which is very cumbersome; so, for now, we don’t bother.) type daemonReference struct { id digest.Digest - ref distreference.Named // !reference.IsNameOnly + ref reference.Named // !reference.IsNameOnly } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. @@ -61,30 +60,30 @@ func ParseReference(refString string) (types.ImageReference, error) { return NewReference(dgst, nil) } - ref, err := reference.XParseNamed(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 distreference.FamiliarName(ref) == 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) } // NewReference returns a docker-daemon reference for either the supplied image ID (config digest) or the supplied reference (which must satisfy !reference.IsNameOnly) -func NewReference(id digest.Digest, ref distreference.Named) (types.ImageReference, error) { +func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference, error) { if id != "" && ref != nil { return nil, errors.New("docker-daemon: reference must not have an image ID and a reference string specified at the same time") } if ref != nil { - if distreference.IsNameOnly(ref) { - return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", distreference.FamiliarString(ref)) + if reference.IsNameOnly(ref) { + 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! // 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.(distreference.NamedTagged) - _, isDigested := ref.(distreference.Canonical) + _, isTagged := ref.(reference.NamedTagged) + _, isDigested := ref.(reference.Canonical) if isTagged && isDigested { return nil, errors.Errorf("docker-daemon: references with both a tag and digest are currently not supported") } @@ -110,7 +109,7 @@ func (ref daemonReference) StringWithinTransport() string { case ref.id != "": return ref.id.String() case ref.ref != nil: - return distreference.FamiliarString(ref.ref) + 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") } @@ -119,7 +118,7 @@ func (ref daemonReference) StringWithinTransport() string { // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref daemonReference) DockerReference() distreference.Named { +func (ref daemonReference) DockerReference() reference.Named { return ref.ref // May be nil } diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index d6b4186e23..200e5e0ecc 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -88,13 +88,6 @@ 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{ distreference.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 }{ @@ -117,7 +110,7 @@ func TestNewReference(t *testing.T) { // Named references for _, c := range validNamedReferenceTestCases { - parsed, err := reference.XParseNamed(c.input) + parsed, err := distreference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference("", parsed) require.NoError(t, err, c.input) @@ -129,24 +122,25 @@ func TestNewReference(t *testing.T) { } // Both an ID and a named reference provided - parsed, err := reference.XParseNamed("busybox:latest") + parsed, err := distreference.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.XParseNamed("busybox") + parsed, err = distreference.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.XParseNamed("busybox@" + sha256digest) + parsed, err = distreference.ParseNormalizedNamed("busybox:notlatest@" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(distreference.Canonical) + _, ok = parsed.(distreference.Canonical) require.True(t, ok) - tagDigestRef := refWithTagAndDigest{refDigested} - _, err = NewReference("", tagDigestRef) + _, ok = parsed.(distreference.NamedTagged) + require.True(t, ok) + _, err = NewReference("", parsed) assert.Error(t, err) } diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 2fd6f00844..6bea694c42 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -5,9 +5,8 @@ import ( "strings" "github.com/containers/image/docker/policyconfiguration" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -38,7 +37,7 @@ func (t dockerTransport) ValidatePolicyConfigurationScope(scope string) error { // dockerReference is an ImageReference for Docker images. type dockerReference struct { - ref distreference.Named // By construction we know that !reference.IsNameOnly(ref) + ref reference.Named // By construction we know that !reference.IsNameOnly(ref) } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference. @@ -46,26 +45,26 @@ 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.XParseNamed(strings.TrimPrefix(refString, "//")) + ref, err := reference.ParseNormalizedNamed(strings.TrimPrefix(refString, "//")) if err != nil { return nil, err } - ref = distreference.TagNameOnly(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 distreference.Named) (types.ImageReference, error) { - if distreference.IsNameOnly(ref) { - return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", distreference.FamiliarString(ref)) +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", reference.FamiliarString(ref)) } // A github.com/distribution/reference value can have a tag and a digest at the same time! // 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.(distreference.NamedTagged) - _, isDigested := ref.(distreference.Canonical) + _, isTagged := ref.(reference.NamedTagged) + _, isDigested := ref.(reference.Canonical) if isTagged && isDigested { return nil, errors.Errorf("Docker references with both a tag and digest are currently not supported") } @@ -84,13 +83,13 @@ 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 "//" + distreference.FamiliarString(ref.ref) + return "//" + reference.FamiliarString(ref.ref) } // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref dockerReference) DockerReference() distreference.Named { +func (ref dockerReference) DockerReference() reference.Named { return ref.ref } @@ -147,12 +146,12 @@ func (ref dockerReference) DeleteImage(ctx *types.SystemContext) error { // tagOrDigest returns a tag or digest from the reference. func (ref dockerReference) tagOrDigest() (string, error) { - if ref, ok := ref.ref.(distreference.Canonical); ok { + if ref, ok := ref.ref.(reference.Canonical); ok { return ref.Digest().String(), nil } - if ref, ok := ref.ref.(distreference.NamedTagged); ok { + if ref, ok := ref.ref.(reference.NamedTagged); ok { 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", distreference.FamiliarString(ref.ref)) + return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", reference.FamiliarString(ref.ref)) } diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 91ca1488aa..61be0b91bb 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -3,9 +3,8 @@ package docker import ( "testing" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,13 +65,6 @@ 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{ distreference.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", "docker.io/library/busybox:notlatest", "//busybox:notlatest"}, // Explicit tag @@ -83,7 +75,7 @@ var validReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport func TestNewReference(t *testing.T) { for _, c := range validReferenceTestCases { - parsed, err := reference.XParseNamed(c.input) + parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference(parsed) require.NoError(t, err, c.input) @@ -93,18 +85,19 @@ func TestNewReference(t *testing.T) { } // Neither a tag nor digest - parsed, err := reference.XParseNamed("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.XParseNamed("busybox" + sha256digest) + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(distreference.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) } @@ -195,7 +188,7 @@ func TestReferenceTagOrDigest(t *testing.T) { } // Invalid input - ref, err := reference.XParseNamed("busybox") + ref, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) dockerRef := dockerReference{ref: ref} _, err = dockerRef.tagOrDigest() diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index c8909134e2..49170fc858 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -15,7 +15,7 @@ func DockerReferenceIdentity(ref reference.Named) (string, error) { tagged, isTagged := ref.(reference.NamedTagged) digested, isDigested := ref.(reference.Canonical) switch { - case isTagged && isDigested: // This should not happen, docker/reference.XParseNamed drops the tag. + 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", reference.FamiliarString(ref)) diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index db5c6ecd12..6ddd004367 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -1,13 +1,11 @@ package policyconfiguration import ( + "fmt" "strings" "testing" - "fmt" - - "github.com/containers/image/docker/reference" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +36,7 @@ func TestDockerReference(t *testing.T) { sha256Digest: sha256Digest, } { fullInput := inputName + inputSuffix - ref, err := reference.XParseNamed(fullInput) + ref, err := reference.ParseNormalizedNamed(fullInput) require.NoError(t, err, fullInput) identity, err := DockerReferenceIdentity(ref) @@ -58,29 +56,22 @@ func TestDockerReference(t *testing.T) { } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ distreference.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.XParseNamed("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.XParseNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") require.NoError(t, err) - _, ok := parsed.(distreference.Canonical) + _, ok := parsed.(reference.Canonical) require.True(t, ok) - _, ok = parsed.(distreference.NamedTagged) + _, ok = parsed.(reference.NamedTagged) require.True(t, ok) id, err = DockerReferenceIdentity(parsed) assert.Equal(t, "", id) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index 340f2f9878..c818087cc1 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -12,14 +12,6 @@ import ( "github.com/pkg/errors" ) -// XParseNamed parses s and returns a syntactically valid reference implementing -// the Named interface. The reference must have a name, otherwise an error is -// returned. -// If an error was encountered it is returned, along with a nil Reference. -func XParseNamed(s string) (distreference.Named, error) { - return distreference.ParseNormalizedNamed(s) -} - // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { @@ -29,7 +21,7 @@ func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, er if dgst, err := digest.Parse(idOrRef); err == nil { return dgst, nil, nil } - ref, err := XParseNamed(idOrRef) + ref, err := distreference.ParseNormalizedNamed(idOrRef) return "", ref, err } diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go deleted file mode 100644 index 9153b4c65e..0000000000 --- a/docker/reference/reference_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package reference - -import ( - "testing" - - _ "crypto/sha256" - - distreference "github.com/docker/distribution/reference" -) - -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", - "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 := XParseNamed(name) - if err == nil { - t.Fatalf("Expected invalid repo name for %q", name) - } - } - - for _, name := range validRepoNames { - _, err := XParseNamed(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 := XParseNamed(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 := XParseNamed(repositoryName); err == nil { - t.Errorf("Repository name should be invalid: %v", repositoryName) - } - } -} - -func TestParseRepositoryInfo(t *testing.T) { - type tcase struct { - RemoteName, NormalizedName, FullName, AmbiguousName, Hostname string - } - - tcases := []tcase{ - { - RemoteName: "fooo/bar", - NormalizedName: "fooo/bar", - FullName: "docker.io/fooo/bar", - AmbiguousName: "index.docker.io/fooo/bar", - Hostname: "docker.io", - }, - { - RemoteName: "library/ubuntu", - NormalizedName: "ubuntu", - FullName: "docker.io/library/ubuntu", - AmbiguousName: "library/ubuntu", - Hostname: "docker.io", - }, - { - RemoteName: "nonlibrary/ubuntu", - NormalizedName: "nonlibrary/ubuntu", - FullName: "docker.io/nonlibrary/ubuntu", - AmbiguousName: "", - Hostname: "docker.io", - }, - { - RemoteName: "other/library", - NormalizedName: "other/library", - FullName: "docker.io/other/library", - AmbiguousName: "", - Hostname: "docker.io", - }, - { - 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", - }, - } - - for _, tcase := range tcases { - refStrings := []string{tcase.NormalizedName, tcase.FullName} - if tcase.AmbiguousName != "" { - refStrings = append(refStrings, tcase.AmbiguousName) - } - - var refs []distreference.Named - for _, r := range refStrings { - named, err := XParseNamed(r) - if err != nil { - t.Fatal(err) - } - refs = append(refs, named) - named, err = distreference.ParseNormalizedNamed(r) - if err != nil { - t.Fatal(err) - } - refs = append(refs, named) - } - - for _, r := range refs { - if expected, actual := tcase.NormalizedName, distreference.FamiliarName(r); expected != actual { - t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.FullName, r.Name(); expected != actual { - t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.Hostname, distreference.Domain(r); expected != actual { - t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.RemoteName, distreference.Path(r); expected != actual { - t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) - } - - } - } -} diff --git a/image/docker_schema2_test.go b/image/docker_schema2_test.go index 3f4c8d5016..cf3dcb7a2d 100644 --- a/image/docker_schema2_test.go +++ b/image/docker_schema2_test.go @@ -9,10 +9,9 @@ import ( "testing" "time" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -284,7 +283,7 @@ func TestManifestSchema2UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // schema2ImageSource is plausible enough for schema conversions in manifestSchema2.UpdatedImage() to work. type schema2ImageSource struct { configBlobImageSource - ref distreference.Named + ref reference.Named } func (s2is *schema2ImageSource) Reference() types.ImageReference { @@ -292,7 +291,7 @@ func (s2is *schema2ImageSource) Reference() types.ImageReference { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ distreference.Named } +type refImageReferenceMock struct{ reference.Named } func (ref refImageReferenceMock) Transport() types.ImageTransport { panic("unexpected call to a mock function") @@ -300,7 +299,7 @@ func (ref refImageReferenceMock) Transport() types.ImageTransport { func (ref refImageReferenceMock) StringWithinTransport() string { panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() distreference.Named { +func (ref refImageReferenceMock) DockerReference() reference.Named { return ref.Named } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { @@ -326,7 +325,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") require.NoError(t, err) - ref, err := reference.XParseNamed(dockerRef) + ref, err := reference.ParseNormalizedNamed(dockerRef) require.NoError(t, err) return &schema2ImageSource{ @@ -340,7 +339,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { } type memoryImageDest struct { - ref distreference.Named + ref reference.Named storedBlobs map[digest.Digest][]byte } diff --git a/image/oci_test.go b/image/oci_test.go index c7ff622ddf..6a23d6a701 100644 --- a/image/oci_test.go +++ b/image/oci_test.go @@ -9,10 +9,9 @@ import ( "testing" "time" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -249,7 +248,7 @@ func TestManifestOCI1UpdatedImageNeedsLayerDiffIDs(t *testing.T) { // oci1ImageSource is plausible enough for schema conversions in manifestOCI1.UpdatedImage() to work. type oci1ImageSource struct { configBlobImageSource - ref distreference.Named + ref reference.Named } func (OCIis *oci1ImageSource) Reference() types.ImageReference { @@ -260,7 +259,7 @@ func newOCI1ImageSource(t *testing.T, dockerRef string) *oci1ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") require.NoError(t, err) - ref, err := reference.XParseNamed(dockerRef) + ref, err := reference.ParseNormalizedNamed(dockerRef) require.NoError(t, err) return &oci1ImageSource{ diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index df0756c2f9..84403dfc10 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -6,10 +6,9 @@ import ( "strings" "github.com/containers/image/docker/policyconfiguration" - "github.com/containers/image/docker/reference" genericImage "github.com/containers/image/image" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -45,30 +44,30 @@ func (t openshiftTransport) ValidatePolicyConfigurationScope(scope string) error // openshiftReference is an ImageReference for OpenShift images. type openshiftReference struct { - dockerReference distreference.NamedTagged + dockerReference reference.NamedTagged namespace string // Computed from dockerReference in advance. stream string // Computed from dockerReference in advance. } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference. func ParseReference(ref string) (types.ImageReference, error) { - r, err := reference.XParseNamed(ref) + r, err := reference.ParseNormalizedNamed(ref) if err != nil { return nil, errors.Wrapf(err, "failed to parse image reference %q", ref) } - tagged, ok := r.(distreference.NamedTagged) + tagged, ok := r.(reference.NamedTagged) if !ok { return nil, errors.Errorf("invalid image reference %s, expected format: 'hostname/namespace/stream:tag'", ref) } return NewReference(tagged) } -// NewReference returns an OpenShift reference for a distreference.NamedTagged -func NewReference(dockerRef distreference.NamedTagged) (types.ImageReference, error) { - r := strings.SplitN(distreference.Path(dockerRef), "/", 3) +// NewReference returns an OpenShift reference for a reference.NamedTagged +func NewReference(dockerRef reference.NamedTagged) (types.ImageReference, error) { + r := strings.SplitN(reference.Path(dockerRef), "/", 3) if len(r) != 2 { return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", - distreference.FamiliarString(dockerRef)) + reference.FamiliarString(dockerRef)) } return openshiftReference{ namespace: r[0], @@ -87,13 +86,13 @@ func (ref openshiftReference) 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 openshiftReference) StringWithinTransport() string { - return distreference.FamiliarString(ref.dockerReference) + return reference.FamiliarString(ref.dockerReference) } // DockerReference returns a Docker reference associated with this reference // (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent, // not e.g. after redirect or alias processing), or nil if unknown/not applicable. -func (ref openshiftReference) DockerReference() distreference.Named { +func (ref openshiftReference) DockerReference() reference.Named { return ref.dockerReference } diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 44059b5ec8..9f8522ded4 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -3,8 +3,7 @@ package openshift import ( "testing" - "github.com/containers/image/docker/reference" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -41,16 +40,16 @@ func TestTransportValidatePolicyConfigurationScope(t *testing.T) { func TestNewReference(t *testing.T) { // too many ns - r, err := reference.XParseNamed("registry.example.com/ns1/ns2/ns3/stream:tag") + r, err := reference.ParseNormalizedNamed("registry.example.com/ns1/ns2/ns3/stream:tag") require.NoError(t, err) - tagged, ok := r.(distreference.NamedTagged) + tagged, ok := r.(reference.NamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.Error(t, err) - r, err = reference.XParseNamed("registry.example.com/ns/stream:tag") + r, err = reference.ParseNormalizedNamed("registry.example.com/ns/stream:tag") require.NoError(t, err) - tagged, ok = r.(distreference.NamedTagged) + tagged, ok = r.(reference.NamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.NoError(t, err) @@ -65,7 +64,7 @@ func TestParseReference(t *testing.T) { assert.Equal(t, "ns", osRef.namespace) assert.Equal(t, "stream", osRef.stream) assert.Equal(t, "notlatest", osRef.dockerReference.Tag()) - assert.Equal(t, "registry.example.com:8443", distreference.Domain(osRef.dockerReference)) + assert.Equal(t, "registry.example.com:8443", reference.Domain(osRef.dockerReference)) // Components creating an invalid Docker Reference name _, err = ParseReference("registry.example.com/ns/UPPERCASEISINVALID:notlatest") diff --git a/signature/docker.go b/signature/docker.go index e135a14886..7a4973110e 100644 --- a/signature/docker.go +++ b/signature/docker.go @@ -5,8 +5,8 @@ package signature import ( "fmt" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) @@ -25,7 +25,7 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, // using mech. func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte, expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) { - expectedRef, err := reference.XParseNamed(expectedDockerReference) + expectedRef, err := reference.ParseNormalizedNamed(expectedDockerReference) if err != nil { return nil, err } @@ -37,7 +37,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt return nil }, validateSignedDockerReference: func(signedDockerReference string) error { - signedRef, err := reference.XParseNamed(signedDockerReference) + signedRef, err := reference.ParseNormalizedNamed(signedDockerReference) if err != nil { return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)} } diff --git a/signature/policy_config.go b/signature/policy_config.go index 4330cad0fa..a060b8ab61 100644 --- a/signature/policy_config.go +++ b/signature/policy_config.go @@ -19,10 +19,9 @@ import ( "io/ioutil" "path/filepath" - "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/pkg/errors" ) @@ -634,11 +633,11 @@ func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error { // newPRMExactReference is NewPRMExactReference, except it resturns the private type. func newPRMExactReference(dockerReference string) (*prmExactReference, error) { - ref, err := reference.XParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) if err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error())) } - if distreference.IsNameOnly(ref) { + if reference.IsNameOnly(ref) { return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference)) } return &prmExactReference{ @@ -686,7 +685,7 @@ func (prm *prmExactReference) UnmarshalJSON(data []byte) error { // newPRMExactRepository is NewPRMExactRepository, except it resturns the private type. func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) { - if _, err := reference.XParseNamed(dockerRepository); err != nil { + if _, err := reference.ParseNormalizedNamed(dockerRepository); err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error())) } return &prmExactRepository{ diff --git a/signature/policy_eval_signedby_test.go b/signature/policy_eval_signedby_test.go index 972e08172a..a4e02892b4 100644 --- a/signature/policy_eval_signedby_test.go +++ b/signature/policy_eval_signedby_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/containers/image/directory" - "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ import ( // dirImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference. // The caller must call .Close() on the returned UnparsedImage. func dirImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.XParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, refImageReferenceMock{ref}) } diff --git a/signature/policy_eval_test.go b/signature/policy_eval_test.go index 1e0a42c043..d180b5df52 100644 --- a/signature/policy_eval_test.go +++ b/signature/policy_eval_test.go @@ -6,9 +6,8 @@ import ( "testing" "github.com/containers/image/docker/policyconfiguration" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -65,7 +64,7 @@ func TestPolicyContextNewDestroy(t *testing.T) { // and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently. type pcImageReferenceMock struct { transportName string - ref distreference.Named + ref reference.Named } func (ref pcImageReferenceMock) Transport() types.ImageTransport { @@ -75,7 +74,7 @@ func (ref pcImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. return "== StringWithinTransport mock" } -func (ref pcImageReferenceMock) DockerReference() distreference.Named { +func (ref pcImageReferenceMock) DockerReference() reference.Named { return ref.ref } func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string { @@ -149,7 +148,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // No match within a matched transport which doesn't have a "" scope {"atomic", "this.doesnt/match:anything", "", ""}, // No configuration available for this transport at all - {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid distreference.Named. + {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.Named. } { var expected PolicyRequirements if c.matchedTransport != "" { @@ -160,7 +159,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { expected = policy.Default } - ref, err := reference.XParseNamed(c.input) + ref, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) reqs := pc.requirementsForImageRef(pcImageReferenceMock{c.inputTransport, ref}) comment := fmt.Sprintf("case %s:%s: %#v", c.inputTransport, c.input, reqs[0]) @@ -175,7 +174,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // pcImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference and implementing PolicyConfigurationIdentity/PolicyConfigurationNamespaces. // The caller must call .Close() on the returned Image. func pcImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.XParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, pcImageReferenceMock{"docker", ref}) } diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index 2e0b9b839d..b7947c4698 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -5,20 +5,19 @@ package signature import ( "fmt" - "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" ) // parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images. -func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (distreference.Named, distreference.Named, error) { +func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.Named, reference.Named, error) { r1 := image.Reference().DockerReference() if r1 == nil { return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity", transports.ImageName(image.Reference()))) } - r2, err := reference.XParseNamed(s2) + r2, err := reference.ParseNormalizedNamed(s2) if err != nil { return nil, nil, err } @@ -31,7 +30,7 @@ func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, sign return false } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if distreference.IsNameOnly(intended) || distreference.IsNameOnly(signature) { + if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) { return false } return signature.String() == intended.String() @@ -44,13 +43,13 @@ func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.Unparse } // Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now. - if distreference.IsNameOnly(signature) { + if reference.IsNameOnly(signature) { return false } switch intended.(type) { - case distreference.NamedTagged: // Includes the case when intended has both a tag and a digest. + case reference.NamedTagged: // Includes the case when intended has both a tag and a digest. return signature.String() == intended.String() - case distreference.Canonical: + case reference.Canonical: // We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest. // Becase UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest, // we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms) @@ -69,12 +68,12 @@ func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, } // parseDockerReferences converts two reference strings into parsed entities, failing on any error -func parseDockerReferences(s1, s2 string) (distreference.Named, distreference.Named, error) { - r1, err := reference.XParseNamed(s1) +func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, error) { + r1, err := reference.ParseNormalizedNamed(s1) if err != nil { return nil, nil, err } - r2, err := reference.XParseNamed(s2) + r2, err := reference.ParseNormalizedNamed(s2) if err != nil { return nil, nil, err } @@ -87,7 +86,7 @@ func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, return false } // prm.DockerReference and signatureDockerReference should be exact; so, verify that now. - if distreference.IsNameOnly(intended) || distreference.IsNameOnly(signature) { + if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) { return false } return signature.String() == intended.String() diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 413b11a506..2b142c962c 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -4,9 +4,8 @@ import ( "fmt" "testing" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,12 +25,12 @@ func TestParseImageAndDockerReference(t *testing.T) { bad2 = "" ) // Success - ref, err := reference.XParseNamed(ok1) + ref, err := reference.ParseNormalizedNamed(ok1) require.NoError(t, err) r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) require.NoError(t, err) - assert.Equal(t, ok1, distreference.FamiliarString(r1)) - assert.Equal(t, ok2, distreference.FamiliarString(r2)) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) // Unidentified images are rejected. _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) @@ -44,7 +43,7 @@ func TestParseImageAndDockerReference(t *testing.T) { {ok1, bad2}, {bad1, bad2}, } { - ref, err := reference.XParseNamed(refs[0]) + ref, err := reference.ParseNormalizedNamed(refs[0]) if err == nil { _, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1]) assert.Error(t, err) @@ -53,7 +52,7 @@ func TestParseImageAndDockerReference(t *testing.T) { } // refImageMock is a mock of types.UnparsedImage which returns itself in Reference().DockerReference. -type refImageMock struct{ distreference.Named } +type refImageMock struct{ reference.Named } func (ref refImageMock) Reference() types.ImageReference { return refImageReferenceMock{ref.Named} @@ -69,7 +68,7 @@ func (ref refImageMock) Signatures() ([][]byte, error) { } // refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. -type refImageReferenceMock struct{ distreference.Named } +type refImageReferenceMock struct{ reference.Named } func (ref refImageReferenceMock) Transport() types.ImageTransport { // We use this in error messages, so sady we must return something. But right now we do so only when DockerReference is nil, so restrict to that. @@ -85,7 +84,7 @@ func (ref refImageReferenceMock) StringWithinTransport() string { } panic("unexpected call to a mock function") } -func (ref refImageReferenceMock) DockerReference() distreference.Named { +func (ref refImageReferenceMock) DockerReference() reference.Named { return ref.Named } func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { @@ -206,9 +205,9 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ } func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) { - // This assumes that all ways to obtain a distreference.Named perform equivalent validation, - // and therefore values refused by reference.XParseNamed can not happen in practice. - parsedImageRef, err := reference.XParseNamed(imageRef) + // This assumes that all ways to obtain a reference.Named perform equivalent validation, + // and therefore values refused by reference.ParseNormalizedNamed can not happen in practice. + parsedImageRef, err := reference.ParseNormalizedNamed(imageRef) if err != nil { return } @@ -303,8 +302,8 @@ func TestParseDockerReferences(t *testing.T) { // Success r1, r2, err := parseDockerReferences(ok1, ok2) require.NoError(t, err) - assert.Equal(t, ok1, distreference.FamiliarString(r1)) - assert.Equal(t, ok2, distreference.FamiliarString(r2)) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) // Failures for _, refs := range [][]string{ diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 901f5fb0ea..6d4cb7b621 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -8,10 +8,9 @@ import ( "github.com/pkg/errors" "github.com/Sirupsen/logrus" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/storage/storage" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ddigest "github.com/opencontainers/go-digest" ) @@ -67,7 +66,7 @@ func (s *storageTransport) SetStore(store storage.Store) { // ParseStoreReference takes a name or an ID, tries to figure out which it is // relative to the given store, and returns it in a reference object. func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) { - var name distreference.Named + var name reference.Named var sum digest.Digest var err error if ref == "" { @@ -84,14 +83,14 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( refInfo := strings.SplitN(ref, "@", 2) if len(refInfo) == 1 { // A name. - name, err = reference.XParseNamed(refInfo[0]) + name, err = reference.ParseNormalizedNamed(refInfo[0]) if err != nil { return nil, err } } else if len(refInfo) == 2 { // An ID, possibly preceded by a name. if refInfo[0] != "" { - name, err = reference.XParseNamed(refInfo[0]) + name, err = reference.ParseNormalizedNamed(refInfo[0]) if err != nil { return nil, err } @@ -112,7 +111,7 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( } refname := "" if name != nil { - name = distreference.TagNameOnly(name) + name = reference.TagNameOnly(name) refname = verboseName(name) } if refname == "" { @@ -258,12 +257,12 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { // that are just bare IDs. scopeInfo := strings.SplitN(scope, "@", 2) if len(scopeInfo) == 1 && scopeInfo[0] != "" { - _, err := reference.XParseNamed(scopeInfo[0]) + _, err := reference.ParseNormalizedNamed(scopeInfo[0]) if err != nil { return err } } else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" { - _, err := reference.XParseNamed(scopeInfo[0]) + _, err := reference.ParseNormalizedNamed(scopeInfo[0]) if err != nil { return err } @@ -277,10 +276,10 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { return nil } -func verboseName(name distreference.Named) string { - name = distreference.TagNameOnly(name) +func verboseName(name reference.Named) string { + name = reference.TagNameOnly(name) tag := "" - if tagged, ok := name.(distreference.NamedTagged); ok { + if tagged, ok := name.(reference.NamedTagged); ok { tag = tagged.Tag() } return name.Name() + ":" + tag diff --git a/storage/storage_transport_test.go b/storage/storage_transport_test.go index 271bba3e87..ed1ebd936f 100644 --- a/storage/storage_transport_test.go +++ b/storage/storage_transport_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/containers/image/docker/reference" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -54,7 +54,7 @@ func TestTransportParseStoreReference(t *testing.T) { if c.expectedRef == "" { assert.Nil(t, storageRef.name, c.input) } else { - dockerRef, err := reference.XParseNamed(c.expectedRef) + dockerRef, err := reference.ParseNormalizedNamed(c.expectedRef) require.NoError(t, err) require.NotNil(t, storageRef.name, c.input) assert.Equal(t, dockerRef.String(), storageRef.name.String()) From 2f1d5df82a305bf474eef496023dc2ed64cc0467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 09:01:46 +0100 Subject: [PATCH 35/38] Implementation transition: Base XParseIDOrReference on distreference.ParseAnyReference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- docker/reference/reference.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/docker/reference/reference.go b/docker/reference/reference.go index c818087cc1..3606ae9e91 100644 --- a/docker/reference/reference.go +++ b/docker/reference/reference.go @@ -1,7 +1,6 @@ package reference import ( - "regexp" // "opencontainers/go-digest" requires us to load the algorithms that we // want to use into the binary (it calls .Available). @@ -15,21 +14,15 @@ import ( // XParseIDOrReference parses string for an image ID or a reference. ID can be // without a default prefix. func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { - if err := validateID(idOrRef); err == nil { - idOrRef = "sha256:" + idOrRef + ref, err := distreference.ParseAnyReference(idOrRef) + if err != nil { + return "", nil, err } - if dgst, err := digest.Parse(idOrRef); err == nil { - return dgst, nil, nil + if named, ok := ref.(distreference.Named); ok { + return "", named, nil } - ref, err := distreference.ParseNormalizedNamed(idOrRef) - return "", ref, err -} - -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) + if digested, ok := ref.(distreference.Digested); ok { + return digest.Digest(digested.Digest()), nil, nil } - return nil + return "", nil, errors.New("Unexpected inconsistency: %#v, the result of ParseAnyReference is neither a Named or nor a Digested") } From 6e4f15a094d0d4fa29e5d382f833b188b99d902f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 09:22:08 +0100 Subject: [PATCH 36/38] API transition: Drop reference.XParseIDOrReference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use distreference.ParseAnyReference directly. Signed-off-by: Miloslav Trmač --- docker/daemon/daemon_transport.go | 4 ++-- docker/daemon/daemon_transport_test.go | 28 +++++++++++++------------- docker/reference/reference.go | 28 -------------------------- 3 files changed, 16 insertions(+), 44 deletions(-) delete mode 100644 docker/reference/reference.go diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 00777605e4..0b41bd03ff 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -46,11 +46,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.XParseIDOrReference, 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.XParseIDOrReference 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. diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 200e5e0ecc..24cb0a51dd 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -3,9 +3,8 @@ package daemon import ( "testing" - "github.com/containers/image/docker/reference" "github.com/containers/image/types" - distreference "github.com/docker/distribution/reference" + "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -65,23 +64,24 @@ 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.XParseIDOrReference - dockerID, dockerRef, err := reference.XParseIDOrReference(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) } } @@ -110,7 +110,7 @@ func TestNewReference(t *testing.T) { // Named references for _, c := range validNamedReferenceTestCases { - parsed, err := distreference.ParseNormalizedNamed(c.input) + parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference("", parsed) require.NoError(t, err, c.input) @@ -122,23 +122,23 @@ func TestNewReference(t *testing.T) { } // Both an ID and a named reference provided - parsed, err := distreference.ParseNormalizedNamed("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 = distreference.ParseNormalizedNamed("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 = distreference.ParseNormalizedNamed("busybox:notlatest@" + sha256digest) + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@" + sha256digest) require.NoError(t, err) - _, ok = parsed.(distreference.Canonical) + _, ok = parsed.(reference.Canonical) require.True(t, ok) - _, ok = parsed.(distreference.NamedTagged) + _, ok = parsed.(reference.NamedTagged) require.True(t, ok) _, err = NewReference("", parsed) assert.Error(t, err) diff --git a/docker/reference/reference.go b/docker/reference/reference.go deleted file mode 100644 index 3606ae9e91..0000000000 --- a/docker/reference/reference.go +++ /dev/null @@ -1,28 +0,0 @@ -package reference - -import ( - - // "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" -) - -// XParseIDOrReference parses string for an image ID or a reference. ID can be -// without a default prefix. -func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) { - ref, err := distreference.ParseAnyReference(idOrRef) - if err != nil { - return "", nil, err - } - if named, ok := ref.(distreference.Named); ok { - return "", named, nil - } - if digested, ok := ref.(distreference.Digested); ok { - return digest.Digest(digested.Digest()), nil, nil - } - return "", nil, errors.New("Unexpected inconsistency: %#v, the result of ParseAnyReference is neither a Named or nor a Digested") -} From 74c24ed75a6943f8d159eeb520562530d848469e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 20 Jan 2017 09:24:45 +0100 Subject: [PATCH 37/38] Finally, get rid of the last remains of c/i/docker/reference. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- docker/reference/doc.go | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 docker/reference/doc.go diff --git a/docker/reference/doc.go b/docker/reference/doc.go deleted file mode 100644 index a75ea749e5..0000000000 --- a/docker/reference/doc.go +++ /dev/null @@ -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 From ecdd233c842aefc26e342c144eef66e6b93b5f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 1 Feb 2017 22:52:45 +0100 Subject: [PATCH 38/38] Copy github.com/docker/distribution/reference to docker/reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This replaces the copy of github.com/docker/docker/reference in the same place, which we have just gotten rid of, and allows using this package even in consumers which insist on an incompatible version of docker/distribution. The copy has been edited to drop a reference to github.com/docker/distribution/digestset . Signed-off-by: Miloslav Trmač --- directory/directory_transport.go | 2 +- docker/daemon/daemon_dest.go | 2 +- docker/daemon/daemon_transport.go | 2 +- docker/daemon/daemon_transport_test.go | 2 +- docker/docker_client.go | 2 +- docker/docker_image.go | 2 +- docker/docker_image_dest.go | 2 +- docker/docker_image_src.go | 2 +- docker/docker_transport.go | 2 +- docker/docker_transport_test.go | 2 +- docker/policyconfiguration/naming.go | 2 +- docker/policyconfiguration/naming_test.go | 2 +- docker/reference/README.md | 2 + docker/reference/helpers.go | 42 ++ docker/reference/normalize.go | 152 +++++ docker/reference/normalize_test.go | 573 +++++++++++++++++++ docker/reference/reference.go | 433 ++++++++++++++ docker/reference/reference_test.go | 659 ++++++++++++++++++++++ docker/reference/regexp.go | 143 +++++ docker/reference/regexp_test.go | 553 ++++++++++++++++++ image/docker_schema1.go | 2 +- image/docker_schema2_test.go | 2 +- image/oci_test.go | 2 +- image/unparsed.go | 2 +- oci/layout/oci_transport.go | 2 +- openshift/openshift.go | 2 +- openshift/openshift_transport.go | 2 +- openshift/openshift_transport_test.go | 2 +- signature/docker.go | 2 +- signature/policy_config.go | 2 +- signature/policy_eval_signedby_test.go | 2 +- signature/policy_eval_simple_test.go | 2 +- signature/policy_eval_test.go | 2 +- signature/policy_reference_match.go | 2 +- signature/policy_reference_match_test.go | 2 +- storage/storage_reference.go | 2 +- storage/storage_transport.go | 2 +- storage/storage_transport_test.go | 2 +- types/types.go | 2 +- 39 files changed, 2588 insertions(+), 31 deletions(-) create mode 100644 docker/reference/README.md create mode 100644 docker/reference/helpers.go create mode 100644 docker/reference/normalize.go create mode 100644 docker/reference/normalize_test.go create mode 100644 docker/reference/reference.go create mode 100644 docker/reference/reference_test.go create mode 100644 docker/reference/regexp.go create mode 100644 docker/reference/regexp_test.go diff --git a/directory/directory_transport.go b/directory/directory_transport.go index 526ef3d0ff..89d2565aac 100644 --- a/directory/directory_transport.go +++ b/directory/directory_transport.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "github.com/containers/image/directory/explicitfilepath" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) diff --git a/docker/daemon/daemon_dest.go b/docker/daemon/daemon_dest.go index dcbace94ef..dd2d725bca 100644 --- a/docker/daemon/daemon_dest.go +++ b/docker/daemon/daemon_dest.go @@ -11,9 +11,9 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/docker/docker/client" "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/docker/daemon/daemon_transport.go b/docker/daemon/daemon_transport.go index 0b41bd03ff..d64f088f00 100644 --- a/docker/daemon/daemon_transport.go +++ b/docker/daemon/daemon_transport.go @@ -3,9 +3,9 @@ package daemon import ( "github.com/pkg/errors" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) diff --git a/docker/daemon/daemon_transport_test.go b/docker/daemon/daemon_transport_test.go index 24cb0a51dd..2a60c6b29c 100644 --- a/docker/daemon/daemon_transport_test.go +++ b/docker/daemon/daemon_transport_test.go @@ -3,8 +3,8 @@ package daemon import ( "testing" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/docker/docker_client.go b/docker/docker_client.go index 33ff88df50..4e9fe5756f 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -15,9 +15,9 @@ 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/distribution/reference" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" diff --git a/docker/docker_image.go b/docker/docker_image.go index 141a7294a7..2bea7eb643 100644 --- a/docker/docker_image.go +++ b/docker/docker_image.go @@ -5,9 +5,9 @@ import ( "fmt" "net/http" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/pkg/errors" ) diff --git a/docker/docker_image_dest.go b/docker/docker_image_dest.go index bfa47d3c8c..4c5ee1436c 100644 --- a/docker/docker_image_dest.go +++ b/docker/docker_image_dest.go @@ -11,9 +11,9 @@ 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/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index fda1313abd..e26d0f0b53 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -11,9 +11,9 @@ 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/reference" "github.com/docker/distribution/registry/client" "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/docker/docker_transport.go b/docker/docker_transport.go index 6bea694c42..a8f511a0a5 100644 --- a/docker/docker_transport.go +++ b/docker/docker_transport.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/containers/image/docker/policyconfiguration" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/pkg/errors" ) diff --git a/docker/docker_transport_test.go b/docker/docker_transport_test.go index 61be0b91bb..98dc7f5861 100644 --- a/docker/docker_transport_test.go +++ b/docker/docker_transport_test.go @@ -3,8 +3,8 @@ package docker import ( "testing" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/docker/policyconfiguration/naming.go b/docker/policyconfiguration/naming.go index 49170fc858..31bbb544c6 100644 --- a/docker/policyconfiguration/naming.go +++ b/docker/policyconfiguration/naming.go @@ -3,7 +3,7 @@ package policyconfiguration import ( "strings" - "github.com/docker/distribution/reference" + "github.com/containers/image/docker/reference" "github.com/pkg/errors" ) diff --git a/docker/policyconfiguration/naming_test.go b/docker/policyconfiguration/naming_test.go index 6ddd004367..5998faa81f 100644 --- a/docker/policyconfiguration/naming_test.go +++ b/docker/policyconfiguration/naming_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/docker/distribution/reference" + "github.com/containers/image/docker/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/docker/reference/README.md b/docker/reference/README.md new file mode 100644 index 0000000000..53a88de826 --- /dev/null +++ b/docker/reference/README.md @@ -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. \ No newline at end of file diff --git a/docker/reference/helpers.go b/docker/reference/helpers.go new file mode 100644 index 0000000000..978df7eabb --- /dev/null +++ b/docker/reference/helpers.go @@ -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 +} diff --git a/docker/reference/normalize.go b/docker/reference/normalize.go new file mode 100644 index 0000000000..fcc436a395 --- /dev/null +++ b/docker/reference/normalize.go @@ -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/" + 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) +} diff --git a/docker/reference/normalize_test.go b/docker/reference/normalize_test.go new file mode 100644 index 0000000000..064ee749c0 --- /dev/null +++ b/docker/reference/normalize_test.go @@ -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) + } + } +} diff --git a/docker/reference/reference.go b/docker/reference/reference.go new file mode 100644 index 0000000000..fd3510e9ee --- /dev/null +++ b/docker/reference/reference.go @@ -0,0 +1,433 @@ +// 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 ( + "errors" + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // 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 { + Reference + Name() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string +} + +// NamedTagged is an object including a name and tag. +type NamedTagged interface { + Named + 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 domain and digest +type Canonical interface { + Named + Digest() digest.Digest +} + +// 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. +// 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 + } + + if len(matches[1]) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + 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 + } + } + + 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) { + if len(name) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat + } + 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) { + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat + } + 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 +} + +type reference struct { + namedRepository + tag string + digest digest.Digest +} + +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() +} + +func (r reference) Tag() string { + return r.tag +} + +func (r reference) Digest() digest.Digest { + return r.digest +} + +type repository struct { + domain string + path string +} + +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 +} diff --git a/docker/reference/reference_test.go b/docker/reference/reference_test.go new file mode 100644 index 0000000000..16b871f987 --- /dev/null +++ b/docker/reference/reference_test.go @@ -0,0 +1,659 @@ +package reference + +import ( + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/json" + "strconv" + "strings" + "testing" + + "github.com/opencontainers/go-digest" +) + +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() + } + + 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 { + failf("no error parsing name. expected: %s", testcase.err) + } + } +} + +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 { + 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) + } + } +} + +type serializationType struct { + Description string + Field Field +} + +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 _, 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 { + failf("error marshalling: %v", err) + } + t := serializationType{} + + 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) + } + + 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 TestWithTag(t *testing.T) { + testcases := []struct { + name string + digest digest.Digest + tag string + combined string + }{ + { + name: "test.com/foo", + tag: "tag", + combined: "test.com/foo:tag", + }, + { + name: "foo", + tag: "tag2", + combined: "foo:tag2", + }, + { + name: "test.com:8000/foo", + tag: "tag4", + combined: "test.com:8000/foo:tag4", + }, + { + name: "test.com:8000/foo", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5", + }, + { + name: "test.com:8000/foo", + digest: "sha256:1234567890098765432112345667890098765", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5@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.digest != "" { + canonical, err := WithDigest(named, testcase.digest) + if err != nil { + failf("error adding digest") + } + 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 { + failf("error adding tag") + } + named = tagged + } + 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 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, + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + 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) + } + } +} diff --git a/docker/reference/regexp.go b/docker/reference/regexp.go new file mode 100644 index 0000000000..405e995db9 --- /dev/null +++ b/docker/reference/regexp.go @@ -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() + `$`) +} diff --git a/docker/reference/regexp_test.go b/docker/reference/regexp_test.go new file mode 100644 index 0000000000..c21263992f --- /dev/null +++ b/docker/reference/regexp_test.go @@ -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]) + } +} diff --git a/image/docker_schema1.go b/image/docker_schema1.go index 4afb17b9a4..7d6de1a608 100644 --- a/image/docker_schema1.go +++ b/image/docker_schema1.go @@ -6,9 +6,9 @@ import ( "strings" "time" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/image/docker_schema2_test.go b/image/docker_schema2_test.go index cf3dcb7a2d..473e51e246 100644 --- a/image/docker_schema2_test.go +++ b/image/docker_schema2_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/image/oci_test.go b/image/oci_test.go index 6a23d6a701..c51b3ae774 100644 --- a/image/oci_test.go +++ b/image/oci_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/image/unparsed.go b/image/unparsed.go index e11978643e..0feb1101c5 100644 --- a/image/unparsed.go +++ b/image/unparsed.go @@ -1,9 +1,9 @@ package image import ( + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/oci/layout/oci_transport.go b/oci/layout/oci_transport.go index cd4500a7b4..734af87c0b 100644 --- a/oci/layout/oci_transport.go +++ b/oci/layout/oci_transport.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/containers/image/directory/explicitfilepath" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/openshift/openshift.go b/openshift/openshift.go index 81671e7937..c1de090d63 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -13,10 +13,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/docker" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/containers/image/version" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/openshift/openshift_transport.go b/openshift/openshift_transport.go index 84403dfc10..119385bbc3 100644 --- a/openshift/openshift_transport.go +++ b/openshift/openshift_transport.go @@ -6,9 +6,9 @@ import ( "strings" "github.com/containers/image/docker/policyconfiguration" + "github.com/containers/image/docker/reference" genericImage "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/pkg/errors" ) diff --git a/openshift/openshift_transport_test.go b/openshift/openshift_transport_test.go index 9f8522ded4..5c589c1923 100644 --- a/openshift/openshift_transport_test.go +++ b/openshift/openshift_transport_test.go @@ -3,7 +3,7 @@ package openshift import ( "testing" - "github.com/docker/distribution/reference" + "github.com/containers/image/docker/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/signature/docker.go b/signature/docker.go index 7a4973110e..16eb3f7993 100644 --- a/signature/docker.go +++ b/signature/docker.go @@ -5,8 +5,8 @@ package signature import ( "fmt" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ) diff --git a/signature/policy_config.go b/signature/policy_config.go index a060b8ab61..ace24fec88 100644 --- a/signature/policy_config.go +++ b/signature/policy_config.go @@ -19,9 +19,9 @@ import ( "io/ioutil" "path/filepath" + "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/pkg/errors" ) diff --git a/signature/policy_eval_signedby_test.go b/signature/policy_eval_signedby_test.go index a4e02892b4..19086fcf5b 100644 --- a/signature/policy_eval_signedby_test.go +++ b/signature/policy_eval_signedby_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/containers/image/directory" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/signature/policy_eval_simple_test.go b/signature/policy_eval_simple_test.go index f24f6533b4..aae4b6a8b8 100644 --- a/signature/policy_eval_simple_test.go +++ b/signature/policy_eval_simple_test.go @@ -3,8 +3,8 @@ package signature import ( "testing" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" ) // nameOnlyImageMock is a mock of types.UnparsedImage which only allows transports.ImageName to work diff --git a/signature/policy_eval_test.go b/signature/policy_eval_test.go index d180b5df52..785d7900b5 100644 --- a/signature/policy_eval_test.go +++ b/signature/policy_eval_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/containers/image/docker/policyconfiguration" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/signature/policy_reference_match.go b/signature/policy_reference_match.go index b7947c4698..a8dad67701 100644 --- a/signature/policy_reference_match.go +++ b/signature/policy_reference_match.go @@ -5,9 +5,9 @@ package signature import ( "fmt" + "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" - "github.com/docker/distribution/reference" ) // parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images. diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 2b142c962c..295e8339a0 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/storage/storage_reference.go b/storage/storage_reference.go index 0689e15b01..bee753f45f 100644 --- a/storage/storage_reference.go +++ b/storage/storage_reference.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/docker/distribution/reference" ) // A storageReference holds an arbitrary name and/or an ID, which is a 32-byte diff --git a/storage/storage_transport.go b/storage/storage_transport.go index 6d4cb7b621..78c7ef651a 100644 --- a/storage/storage_transport.go +++ b/storage/storage_transport.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/storage/storage" - "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" ddigest "github.com/opencontainers/go-digest" ) diff --git a/storage/storage_transport_test.go b/storage/storage_transport_test.go index ed1ebd936f..3fddff5ccc 100644 --- a/storage/storage_transport_test.go +++ b/storage/storage_transport_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/docker/distribution/reference" + "github.com/containers/image/docker/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/types/types.go b/types/types.go index b34b966f37..2154f306f7 100644 --- a/types/types.go +++ b/types/types.go @@ -4,7 +4,7 @@ import ( "io" "time" - "github.com/docker/distribution/reference" + "github.com/containers/image/docker/reference" "github.com/opencontainers/go-digest" "github.com/pkg/errors" )