Procházet zdrojové kódy

Merge pull request #30043 from dmcgowan/distribution-reference-update-1

Distribution reference update
Aaron Lehmann před 8 roky
rodič
revize
e9c0de0de6

+ 22 - 15
cli/command/container/create.go

@@ -5,6 +5,7 @@ import (
 	"io"
 	"os"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	networktypes "github.com/docker/docker/api/types/network"
@@ -13,8 +14,6 @@ import (
 	"github.com/docker/docker/cli/command/image"
 	apiclient "github.com/docker/docker/client"
 	"github.com/docker/docker/pkg/jsonmessage"
-	// FIXME migrate to docker/distribution/reference
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
@@ -72,7 +71,7 @@ func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createO
 }
 
 func pullImage(ctx context.Context, dockerCli *command.DockerCli, image string, out io.Writer) error {
-	ref, err := reference.ParseNamed(image)
+	ref, err := reference.ParseNormalizedNamed(image)
 	if err != nil {
 		return err
 	}
@@ -150,7 +149,12 @@ func newCIDFile(path string) (*cidFile, error) {
 func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*container.ContainerCreateCreatedBody, error) {
 	stderr := dockerCli.Err()
 
-	var containerIDFile *cidFile
+	var (
+		containerIDFile *cidFile
+		trustedRef      reference.Canonical
+		namedRef        reference.Named
+	)
+
 	if cidfile != "" {
 		var err error
 		if containerIDFile, err = newCIDFile(cidfile); err != nil {
@@ -159,21 +163,24 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *
 		defer containerIDFile.Close()
 	}
 
-	var trustedRef reference.Canonical
-	_, ref, err := reference.ParseIDOrReference(config.Image)
+	ref, err := reference.ParseAnyReference(config.Image)
 	if err != nil {
 		return nil, err
 	}
-	if ref != nil {
-		ref = reference.WithDefaultTag(ref)
+	if named, ok := ref.(reference.Named); ok {
+		if reference.IsNameOnly(named) {
+			namedRef = reference.EnsureTagged(named)
+		} else {
+			namedRef = named
+		}
 
-		if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
+		if taggedRef, ok := namedRef.(reference.NamedTagged); ok && command.IsTrusted() {
 			var err error
-			trustedRef, err = image.TrustedReference(ctx, dockerCli, ref, nil)
+			trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil)
 			if err != nil {
 				return nil, err
 			}
-			config.Image = trustedRef.String()
+			config.Image = reference.FamiliarString(trustedRef)
 		}
 	}
 
@@ -182,15 +189,15 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *
 
 	//if image not found try to pull it
 	if err != nil {
-		if apiclient.IsErrImageNotFound(err) && ref != nil {
-			fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String())
+		if apiclient.IsErrImageNotFound(err) && namedRef != nil {
+			fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
 
 			// we don't want to write to stdout anything apart from container.ID
 			if err = pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
 				return nil, err
 			}
-			if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
-				if err := image.TagTrusted(ctx, dockerCli, trustedRef, ref); err != nil {
+			if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
+				if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
 					return nil, err
 				}
 			}

+ 7 - 5
cli/command/formatter/image.go

@@ -4,9 +4,9 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/stringid"
-	"github.com/docker/docker/reference"
 	units "github.com/docker/go-units"
 )
 
@@ -95,21 +95,23 @@ func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subC
 			repoDigests := map[string][]string{}
 
 			for _, refString := range append(image.RepoTags) {
-				ref, err := reference.ParseNamed(refString)
+				ref, err := reference.ParseNormalizedNamed(refString)
 				if err != nil {
 					continue
 				}
 				if nt, ok := ref.(reference.NamedTagged); ok {
-					repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
+					familiarRef := reference.FamiliarName(ref)
+					repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag())
 				}
 			}
 			for _, refString := range append(image.RepoDigests) {
-				ref, err := reference.ParseNamed(refString)
+				ref, err := reference.ParseNormalizedNamed(refString)
 				if err != nil {
 					continue
 				}
 				if c, ok := ref.(reference.Canonical); ok {
-					repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
+					familiarRef := reference.FamiliarName(ref)
+					repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String())
 				}
 			}
 

+ 8 - 5
cli/command/image/build.go

@@ -11,6 +11,7 @@ import (
 	"regexp"
 	"runtime"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
@@ -25,7 +26,6 @@ import (
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/urlutil"
-	"github.com/docker/docker/reference"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	units "github.com/docker/go-units"
 	"github.com/spf13/cobra"
@@ -360,7 +360,7 @@ type translatorFunc func(context.Context, reference.NamedTagged) (reference.Cano
 
 // validateTag checks if the given image name can be resolved.
 func validateTag(rawRepo string) (string, error) {
-	_, err := reference.ParseNamed(rawRepo)
+	_, err := reference.ParseNormalizedNamed(rawRepo)
 	if err != nil {
 		return "", err
 	}
@@ -392,18 +392,21 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator
 		matches := dockerfileFromLinePattern.FindStringSubmatch(line)
 		if matches != nil && matches[1] != api.NoBaseImageSpecifier {
 			// Replace the line with a resolved "FROM repo@digest"
-			ref, err := reference.ParseNamed(matches[1])
+			var ref reference.Named
+			ref, err = reference.ParseNormalizedNamed(matches[1])
 			if err != nil {
 				return nil, nil, err
 			}
-			ref = reference.WithDefaultTag(ref)
+			if reference.IsNameOnly(ref) {
+				ref = reference.EnsureTagged(ref)
+			}
 			if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
 				trustedRef, err := translator(ctx, ref)
 				if err != nil {
 					return nil, nil, err
 				}
 
-				line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
+				line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
 				resolvedTags = append(resolvedTags, &resolvedTag{
 					digestRef: trustedRef,
 					tagRef:    ref,

+ 7 - 5
cli/command/image/pull.go

@@ -7,9 +7,9 @@ import (
 
 	"golang.org/x/net/context"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/spf13/cobra"
 )
@@ -42,7 +42,8 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
 }
 
 func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
-	distributionRef, err := reference.ParseNamed(opts.remote)
+	var distributionRef reference.Named
+	distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
 	if err != nil {
 		return err
 	}
@@ -51,8 +52,9 @@ func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
 	}
 
 	if !opts.all && reference.IsNameOnly(distributionRef) {
-		distributionRef = reference.WithDefaultTag(distributionRef)
-		fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", reference.DefaultTag)
+		taggedRef := reference.EnsureTagged(distributionRef)
+		fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", taggedRef.Tag())
+		distributionRef = taggedRef
 	}
 
 	// Resolve the Repository name from fqn to RepositoryInfo
@@ -71,7 +73,7 @@ func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
 	if command.IsTrusted() && !isCanonical {
 		err = trustedPull(ctx, dockerCli, repoInfo, distributionRef, authConfig, requestPrivilege)
 	} else {
-		err = imagePullPrivileged(ctx, dockerCli, authConfig, distributionRef.String(), requestPrivilege, opts.all)
+		err = imagePullPrivileged(ctx, dockerCli, authConfig, reference.FamiliarString(distributionRef), requestPrivilege, opts.all)
 	}
 	if err != nil {
 		if strings.Contains(err.Error(), "target is plugin") {

+ 3 - 3
cli/command/image/push.go

@@ -3,10 +3,10 @@ package image
 import (
 	"golang.org/x/net/context"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/pkg/jsonmessage"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/spf13/cobra"
 )
@@ -30,7 +30,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
 }
 
 func runPush(dockerCli *command.DockerCli, remote string) error {
-	ref, err := reference.ParseNamed(remote)
+	ref, err := reference.ParseNormalizedNamed(remote)
 	if err != nil {
 		return err
 	}
@@ -51,7 +51,7 @@ func runPush(dockerCli *command.DockerCli, remote string) error {
 		return trustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
 	}
 
-	responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref.String(), requestPrivilege)
+	responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege)
 	if err != nil {
 		return err
 	}

+ 20 - 19
cli/command/image/trust.go

@@ -10,11 +10,11 @@ import (
 	"sort"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/trust"
 	"github.com/docker/docker/pkg/jsonmessage"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/notary/client"
 	"github.com/docker/notary/tuf/data"
@@ -30,7 +30,7 @@ type target struct {
 
 // trustedPush handles content trust pushing of an image
 func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
-	responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref.String(), requestPrivilege)
+	responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
 	if err != nil {
 		return err
 	}
@@ -202,7 +202,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T
 }
 
 // imagePushPrivileged push the image
-func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
+func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
 	encodedAuth, err := command.EncodeAuthToBase64(authConfig)
 	if err != nil {
 		return nil, err
@@ -212,7 +212,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 		PrivilegeFunc: requestPrivilege,
 	}
 
-	return cli.Client().ImagePush(ctx, ref, options)
+	return cli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
 }
 
 // trustedPull handles content trust pulling of an image
@@ -229,12 +229,12 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 		// List all targets
 		targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
 		if err != nil {
-			return trust.NotaryError(repoInfo.FullName(), err)
+			return trust.NotaryError(ref.Name(), err)
 		}
 		for _, tgt := range targets {
 			t, err := convertTarget(tgt.Target)
 			if err != nil {
-				fmt.Fprintf(cli.Out(), "Skipping target for %q\n", repoInfo.Name())
+				fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref))
 				continue
 			}
 			// Only list tags in the top level targets role or the releases delegation role - ignore
@@ -245,17 +245,17 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 			refs = append(refs, t)
 		}
 		if len(refs) == 0 {
-			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
+			return trust.NotaryError(ref.Name(), fmt.Errorf("No trusted tags for %s", ref.Name()))
 		}
 	} else {
 		t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 		if err != nil {
-			return trust.NotaryError(repoInfo.FullName(), err)
+			return trust.NotaryError(ref.Name(), err)
 		}
 		// Only get the tag if it's in the top level targets role or the releases delegation role
 		// ignore it if it's in any other delegation roles
 		if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
-			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
+			return trust.NotaryError(ref.Name(), fmt.Errorf("No trust data for %s", tagged.Tag()))
 		}
 
 		logrus.Debugf("retrieving target for %s role\n", t.Role)
@@ -272,24 +272,21 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 		if displayTag != "" {
 			displayTag = ":" + displayTag
 		}
-		fmt.Fprintf(cli.Out(), "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest)
+		fmt.Fprintf(cli.Out(), "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), reference.FamiliarName(ref), displayTag, r.digest)
 
-		ref, err := reference.WithDigest(reference.TrimNamed(repoInfo), r.digest)
+		trustedRef, err := reference.WithDigest(reference.TrimNamed(ref), r.digest)
 		if err != nil {
 			return err
 		}
-		if err := imagePullPrivileged(ctx, cli, authConfig, ref.String(), requestPrivilege, false); err != nil {
+		if err := imagePullPrivileged(ctx, cli, authConfig, reference.FamiliarString(trustedRef), requestPrivilege, false); err != nil {
 			return err
 		}
 
-		tagged, err := reference.WithTag(repoInfo, r.name)
-		if err != nil {
-			return err
-		}
-		trustedRef, err := reference.WithDigest(reference.TrimNamed(repoInfo), r.digest)
+		tagged, err := reference.WithTag(reference.TrimNamed(ref), r.name)
 		if err != nil {
 			return err
 		}
+
 		if err := TagTrusted(ctx, cli, trustedRef, tagged); err != nil {
 			return err
 		}
@@ -375,7 +372,11 @@ func convertTarget(t client.Target) (target, error) {
 
 // TagTrusted tags a trusted ref
 func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef reference.Canonical, ref reference.NamedTagged) error {
-	fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedRef.String(), ref.String())
+	// Use familiar references when interacting with client and output
+	familiarRef := reference.FamiliarString(ref)
+	trustedFamiliarRef := reference.FamiliarString(trustedRef)
+
+	fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
 
-	return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String())
+	return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef)
 }

+ 2 - 2
cli/command/plugin/create.go

@@ -8,18 +8,18 @@ import (
 	"path/filepath"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/pkg/archive"
-	"github.com/docker/docker/reference"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
 )
 
 // validateTag checks if the given repoName can be resolved.
 func validateTag(rawRepo string) error {
-	_, err := reference.ParseNamed(rawRepo)
+	_, err := reference.ParseNormalizedNamed(rawRepo)
 
 	return err
 }

+ 20 - 32
cli/command/plugin/install.go

@@ -6,14 +6,13 @@ import (
 	"fmt"
 	"strings"
 
-	distreference "github.com/docker/distribution/reference"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/image"
 	"github.com/docker/docker/pkg/jsonmessage"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -52,8 +51,8 @@ func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command {
 	return cmd
 }
 
-func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.IndexInfo, error) {
-	named, err := reference.ParseNamed(ref.Name())
+func getRepoIndexFromUnnormalizedRef(ref reference.Named) (*registrytypes.IndexInfo, error) {
+	named, err := reference.ParseNormalizedNamed(ref.Name())
 	if err != nil {
 		return nil, err
 	}
@@ -85,71 +84,60 @@ func newRegistryService() registry.Service {
 }
 
 func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
-	// Parse name using distribution reference package to support name
-	// containing both tag and digest. Names with both tag and digest
-	// will be treated by the daemon as a pull by digest with
-	// an alias for the tag (if no alias is provided).
-	ref, err := distreference.ParseNamed(opts.name)
+	// Names with both tag and digest will be treated by the daemon
+	// as a pull by digest with an alias for the tag
+	// (if no alias is provided).
+	ref, err := reference.ParseNormalizedNamed(opts.name)
 	if err != nil {
 		return err
 	}
 
 	alias := ""
 	if opts.alias != "" {
-		aref, err := reference.ParseNamed(opts.alias)
+		aref, err := reference.ParseNormalizedNamed(opts.alias)
 		if err != nil {
 			return err
 		}
-		aref = reference.WithDefaultTag(aref)
-		if _, ok := aref.(reference.NamedTagged); !ok {
+		if _, ok := aref.(reference.Canonical); ok {
 			return fmt.Errorf("invalid name: %s", opts.alias)
 		}
-		alias = aref.String()
+		alias = reference.FamiliarString(reference.EnsureTagged(aref))
 	}
 	ctx := context.Background()
 
-	index, err := getRepoIndexFromUnnormalizedRef(ref)
+	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	if err != nil {
 		return err
 	}
 
 	remote := ref.String()
 
-	_, isCanonical := ref.(distreference.Canonical)
+	_, isCanonical := ref.(reference.Canonical)
 	if command.IsTrusted() && !isCanonical {
 		if alias == "" {
-			alias = ref.String()
+			alias = reference.FamiliarString(ref)
 		}
-		var nt reference.NamedTagged
-		named, err := reference.ParseNamed(ref.Name())
-		if err != nil {
-			return err
-		}
-		if tagged, ok := ref.(distreference.Tagged); ok {
-			nt, err = reference.WithTag(named, tagged.Tag())
-			if err != nil {
-				return err
-			}
-		} else {
-			named = reference.WithDefaultTag(named)
-			nt = named.(reference.NamedTagged)
+
+		nt, ok := ref.(reference.NamedTagged)
+		if !ok {
+			nt = reference.EnsureTagged(ref)
 		}
 
 		trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
 		if err != nil {
 			return err
 		}
-		remote = trusted.String()
+		remote = reference.FamiliarString(trusted)
 	}
 
-	authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
+	authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
 
 	encodedAuth, err := command.EncodeAuthToBase64(authConfig)
 	if err != nil {
 		return err
 	}
 
-	registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, index, "plugin install")
+	registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "plugin install")
 
 	options := types.PluginInstallOptions{
 		RegistryAuth:          encodedAuth,

+ 9 - 7
cli/command/plugin/push.go

@@ -5,11 +5,11 @@ import (
 
 	"golang.org/x/net/context"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/image"
 	"github.com/docker/docker/pkg/jsonmessage"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/spf13/cobra"
 )
@@ -32,16 +32,17 @@ func newPushCommand(dockerCli *command.DockerCli) *cobra.Command {
 }
 
 func runPush(dockerCli *command.DockerCli, name string) error {
-	named, err := reference.ParseNamed(name) // FIXME: validate
+	named, err := reference.ParseNormalizedNamed(name)
 	if err != nil {
 		return err
 	}
-	if reference.IsNameOnly(named) {
-		named = reference.WithDefaultTag(named)
+	if _, ok := named.(reference.Canonical); ok {
+		return fmt.Errorf("invalid name: %s", name)
 	}
-	ref, ok := named.(reference.NamedTagged)
+
+	taggedRef, ok := named.(reference.NamedTagged)
 	if !ok {
-		return fmt.Errorf("invalid name: %s", named.String())
+		taggedRef = reference.EnsureTagged(named)
 	}
 
 	ctx := context.Background()
@@ -56,7 +57,8 @@ func runPush(dockerCli *command.DockerCli, name string) error {
 	if err != nil {
 		return err
 	}
-	responseBody, err := dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth)
+
+	responseBody, err := dockerCli.Client().PluginPush(ctx, reference.FamiliarString(taggedRef), encodedAuth)
 	if err != nil {
 		return err
 	}

+ 2 - 2
cli/command/registry.go

@@ -12,10 +12,10 @@ import (
 
 	"golang.org/x/net/context"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/pkg/term"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 )
 
@@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin
 
 // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
 func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) {
-	registryRef, err := reference.ParseNamed(image)
+	registryRef, err := reference.ParseNormalizedNamed(image)
 	if err != nil {
 		return types.AuthConfig{}, err
 	}

+ 19 - 29
cli/command/service/trust.go

@@ -5,11 +5,10 @@ import (
 	"fmt"
 
 	"github.com/Sirupsen/logrus"
-	distreference "github.com/docker/distribution/reference"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/trust"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/notary/tuf/data"
 	"github.com/opencontainers/go-digest"
@@ -24,41 +23,34 @@ func resolveServiceImageDigest(dockerCli *command.DockerCli, service *swarm.Serv
 		return nil
 	}
 
-	image := service.TaskTemplate.ContainerSpec.Image
-
-	// We only attempt to resolve the digest if the reference
-	// could be parsed as a digest reference. Specifying an image ID
-	// is valid but not resolvable. There is no warning message for
-	// an image ID because it's valid to use one.
-	if _, err := digest.Parse(image); err == nil {
-		return nil
-	}
-
-	ref, err := reference.ParseNamed(image)
+	ref, err := reference.ParseAnyReference(service.TaskTemplate.ContainerSpec.Image)
 	if err != nil {
-		return fmt.Errorf("Could not parse image reference %s", service.TaskTemplate.ContainerSpec.Image)
+		return errors.Wrapf(err, "invalid reference %s", service.TaskTemplate.ContainerSpec.Image)
 	}
-	if _, ok := ref.(reference.Canonical); !ok {
-		ref = reference.WithDefaultTag(ref)
 
-		taggedRef, ok := ref.(reference.NamedTagged)
+	// If reference does not have digest (is not canonical nor image id)
+	if _, ok := ref.(reference.Digested); !ok {
+		namedRef, ok := ref.(reference.Named)
 		if !ok {
-			// This should never happen because a reference either
-			// has a digest, or WithDefaultTag would give it a tag.
-			return errors.New("Failed to resolve image digest using content trust: reference is missing a tag")
+			return errors.New("failed to resolve image digest using content trust: reference is not named")
+
 		}
 
+		taggedRef := reference.EnsureTagged(namedRef)
+
 		resolvedImage, err := trustedResolveDigest(context.Background(), dockerCli, taggedRef)
 		if err != nil {
-			return fmt.Errorf("Failed to resolve image digest using content trust: %v", err)
+			return errors.Wrap(err, "failed to resolve image digest using content trust")
 		}
-		logrus.Debugf("resolved image tag to %s using content trust", resolvedImage.String())
-		service.TaskTemplate.ContainerSpec.Image = resolvedImage.String()
+		resolvedFamiliar := reference.FamiliarString(resolvedImage)
+		logrus.Debugf("resolved image tag to %s using content trust", resolvedFamiliar)
+		service.TaskTemplate.ContainerSpec.Image = resolvedFamiliar
 	}
+
 	return nil
 }
 
-func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (distreference.Canonical, error) {
+func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	if err != nil {
 		return nil, err
@@ -78,7 +70,7 @@ func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref refer
 	// Only get the tag if it's in the top level targets role or the releases delegation role
 	// ignore it if it's in any other delegation roles
 	if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
-		return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String()))
+		return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", reference.FamiliarString(ref)))
 	}
 
 	logrus.Debugf("retrieving target for %s role\n", t.Role)
@@ -89,8 +81,6 @@ func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref refer
 
 	dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
 
-	// Using distribution reference package to make sure that adding a
-	// digest does not erase the tag. When the two reference packages
-	// are unified, this will no longer be an issue.
-	return distreference.WithDigest(ref, dgst)
+	// Allow returning canonical reference with tag
+	return reference.WithDigest(ref, dgst)
 }

+ 1 - 1
distribution/push_v2_test.go

@@ -202,7 +202,7 @@ func TestLayerAlreadyExists(t *testing.T) {
 			checkOtherRepositories: true,
 			metadata: []metadata.V2Metadata{
 				{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"},
-				{Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"},
+				{Digest: digest.Digest("orange"), SourceRepository: "docker.io/busybox/subapp"},
 				{Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"},
 				{Digest: digest.Digest("plum"), SourceRepository: "busybox"},
 				{Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"},

+ 1 - 1
integration-cli/docker_cli_build_test.go

@@ -3082,7 +3082,7 @@ func (s *DockerSuite) TestBuildInvalidTag(c *check.C) {
 	name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200)
 	buildImage(name, withDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{
 		ExitCode: 125,
-		Err:      "Error parsing reference",
+		Err:      "invalid reference format",
 	})
 }
 

+ 1 - 1
integration-cli/docker_cli_create_test.go

@@ -274,7 +274,7 @@ func (s *DockerSuite) TestCreateByImageID(c *check.C) {
 		c.Fatalf("expected non-zero exit code; received %d", exit)
 	}
 
-	if expected := "Error parsing reference"; !strings.Contains(out, expected) {
+	if expected := "invalid reference format"; !strings.Contains(out, expected) {
 		c.Fatalf(`Expected %q in output; got: %s`, expected, out)
 	}
 

+ 2 - 2
integration-cli/docker_cli_run_test.go

@@ -3862,8 +3862,8 @@ func (s *DockerSuite) TestRunInvalidReference(c *check.C) {
 		c.Fatalf("expected non-zero exist code; received %d", exit)
 	}
 
-	if !strings.Contains(out, "Error parsing reference") {
-		c.Fatalf(`Expected "Error parsing reference" in output; got: %s`, out)
+	if !strings.Contains(out, "invalid reference format") {
+		c.Fatalf(`Expected "invalid reference format" in output; got: %s`, out)
 	}
 }
 

+ 1 - 1
plugin/manager.go

@@ -12,6 +12,7 @@ import (
 	"sync"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
@@ -19,7 +20,6 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/plugin/v2"
-	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/pkg/errors"

+ 33 - 55
reference/reference.go

@@ -1,13 +1,12 @@
 package reference
 
 import (
-	"errors"
 	"fmt"
-	"strings"
 
 	distreference "github.com/docker/distribution/reference"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
 )
 
 const (
@@ -53,21 +52,31 @@ type Canonical interface {
 // returned.
 // If an error was encountered it is returned, along with a nil Reference.
 func ParseNamed(s string) (Named, error) {
-	named, err := distreference.ParseNamed(s)
+	named, err := distreference.ParseNormalizedNamed(s)
 	if err != nil {
-		return nil, fmt.Errorf("Error parsing reference: %q is not a valid repository/tag: %s", s, err)
+		return nil, errors.Wrapf(err, "failed to parse reference %q", s)
 	}
-	r, err := WithName(named.Name())
-	if err != nil {
+	if err := validateName(distreference.FamiliarName(named)); err != nil {
 		return nil, err
 	}
+
+	// Ensure returned reference cannot have tag and digest
 	if canonical, isCanonical := named.(distreference.Canonical); isCanonical {
-		return WithDigest(r, canonical.Digest())
+		r, err := distreference.WithDigest(distreference.TrimNamed(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())
+		r, err := distreference.WithTag(distreference.TrimNamed(named), tagged.Tag())
+		if err != nil {
+			return nil, err
+		}
+		return &taggedRef{namedRef{r}}, nil
 	}
-	return r, nil
+
+	return &namedRef{named}, nil
 }
 
 // TrimNamed removes any tag or digest from the named reference
@@ -78,16 +87,15 @@ func TrimNamed(ref Named) Named {
 // WithName returns a named object representing the given string. If the input
 // is invalid ErrReferenceInvalidFormat will be returned.
 func WithName(name string) (Named, error) {
-	name, err := normalize(name)
+	r, err := distreference.ParseNormalizedNamed(name)
 	if err != nil {
 		return nil, err
 	}
-	if err := validateName(name); err != nil {
+	if err := validateName(distreference.FamiliarName(r)); err != nil {
 		return nil, err
 	}
-	r, err := distreference.WithName(name)
-	if err != nil {
-		return nil, err
+	if !distreference.IsNameOnly(r) {
+		return nil, distreference.ErrReferenceInvalidFormat
 	}
 	return &namedRef{r}, nil
 }
@@ -122,17 +130,22 @@ type canonicalRef struct {
 	namedRef
 }
 
+func (r *namedRef) Name() string {
+	return distreference.FamiliarName(r.Named)
+}
+
+func (r *namedRef) String() string {
+	return distreference.FamiliarString(r.Named)
+}
+
 func (r *namedRef) FullName() string {
-	hostname, remoteName := splitHostname(r.Name())
-	return hostname + "/" + remoteName
+	return r.Named.Name()
 }
 func (r *namedRef) Hostname() string {
-	hostname, _ := splitHostname(r.Name())
-	return hostname
+	return distreference.Domain(r.Named)
 }
 func (r *namedRef) RemoteName() string {
-	_, remoteName := splitHostname(r.Name())
-	return remoteName
+	return distreference.Path(r.Named)
 }
 func (r *taggedRef) Tag() string {
 	return r.namedRef.Named.(distreference.NamedTagged).Tag()
@@ -173,41 +186,6 @@ func ParseIDOrReference(idOrRef string) (digest.Digest, Named, 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 = DefaultHostname, name
-	} else {
-		hostname, remoteName = name[:i], name[i+1:]
-	}
-	if hostname == LegacyDefaultHostname {
-		hostname = DefaultHostname
-	}
-	if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') {
-		remoteName = DefaultRepoPrefix + remoteName
-	}
-	return
-}
-
-// normalize returns a repository name in its normalized form, meaning it
-// will not contain default hostname nor library/ prefix for official images.
-func normalize(name string) (string, error) {
-	host, remoteName := splitHostname(name)
-	if strings.ToLower(remoteName) != remoteName {
-		return "", errors.New("invalid reference format: repository name must be lowercase")
-	}
-	if host == DefaultHostname {
-		if strings.HasPrefix(remoteName, DefaultRepoPrefix) {
-			return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil
-		}
-		return remoteName, nil
-	}
-	return name, nil
-}
-
 func validateName(name string) error {
 	if err := stringid.ValidateID(name); err == nil {
 		return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)

+ 14 - 7
registry/config.go

@@ -1,16 +1,17 @@
 package registry
 
 import (
-	"errors"
 	"fmt"
 	"net"
 	"net/url"
 	"strings"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/reference"
 	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/opts"
-	"github.com/docker/docker/reference"
+	forkedref "github.com/docker/docker/reference"
+	"github.com/pkg/errors"
 	"github.com/spf13/pflag"
 )
 
@@ -270,8 +271,8 @@ func ValidateMirror(val string) (string, error) {
 
 // ValidateIndexName validates an index name.
 func ValidateIndexName(val string) (string, error) {
-	if val == reference.LegacyDefaultHostname {
-		val = reference.DefaultHostname
+	if val == forkedref.LegacyDefaultHostname {
+		val = forkedref.DefaultHostname
 	}
 	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
 		return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
@@ -321,13 +322,19 @@ func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
 
 // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
 func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
-	index, err := newIndexInfo(config, name.Hostname())
+	index, err := newIndexInfo(config, reference.Domain(name))
+	if err != nil {
+		return nil, err
+	}
+	official := !strings.ContainsRune(reference.FamiliarName(name), '/')
+
+	// TODO: remove used of forked reference package
+	nameref, err := forkedref.ParseNamed(name.String())
 	if err != nil {
 		return nil, err
 	}
-	official := !strings.ContainsRune(name.Name(), '/')
 	return &RepositoryInfo{
-		Named:    name,
+		Named:    nameref,
 		Index:    index,
 		Official: official,
 	}, nil

+ 12 - 11
registry/registry_test.go

@@ -10,10 +10,11 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types"
 	registrytypes "github.com/docker/docker/api/types/registry"
-	"github.com/docker/docker/reference"
+	forkedref "github.com/docker/docker/reference"
 )
 
 var (
@@ -201,7 +202,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
 
 func TestGetRemoteTag(t *testing.T) {
 	r := spawnTestRegistrySession(t)
-	repoRef, err := reference.ParseNamed(REPO)
+	repoRef, err := forkedref.ParseNamed(REPO)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -211,7 +212,7 @@ func TestGetRemoteTag(t *testing.T) {
 	}
 	assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID)
 
-	bazRef, err := reference.ParseNamed("foo42/baz")
+	bazRef, err := forkedref.ParseNamed("foo42/baz")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -223,7 +224,7 @@ func TestGetRemoteTag(t *testing.T) {
 
 func TestGetRemoteTags(t *testing.T) {
 	r := spawnTestRegistrySession(t)
-	repoRef, err := reference.ParseNamed(REPO)
+	repoRef, err := forkedref.ParseNamed(REPO)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -235,7 +236,7 @@ func TestGetRemoteTags(t *testing.T) {
 	assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
 	assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID)
 
-	bazRef, err := reference.ParseNamed("foo42/baz")
+	bazRef, err := forkedref.ParseNamed("foo42/baz")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -252,7 +253,7 @@ func TestGetRepositoryData(t *testing.T) {
 		t.Fatal(err)
 	}
 	host := "http://" + parsedURL.Host + "/v1/"
-	repoRef, err := reference.ParseNamed(REPO)
+	repoRef, err := forkedref.ParseNamed(REPO)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -505,7 +506,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 	}
 
 	for reposName, expectedRepoInfo := range expectedRepoInfos {
-		named, err := reference.WithName(reposName)
+		named, err := reference.ParseNormalizedNamed(reposName)
 		if err != nil {
 			t.Error(err)
 		}
@@ -669,7 +670,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 	if err != nil {
 		t.Error(err)
 	}
-	pushAPIEndpoints, err := s.LookupPushEndpoints(imageName.Hostname())
+	pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -677,7 +678,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 		t.Fatal("Push endpoint should not contain mirror")
 	}
 
-	pullAPIEndpoints, err := s.LookupPullEndpoints(imageName.Hostname())
+	pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -688,7 +689,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 
 func TestPushRegistryTag(t *testing.T) {
 	r := spawnTestRegistrySession(t)
-	repoRef, err := reference.ParseNamed(REPO)
+	repoRef, err := forkedref.ParseNamed(REPO)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -710,7 +711,7 @@ func TestPushImageJSONIndex(t *testing.T) {
 			Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
 		},
 	}
-	repoRef, err := reference.ParseNamed(REPO)
+	repoRef, err := forkedref.ParseNamed(REPO)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 1
registry/service.go

@@ -11,10 +11,10 @@ import (
 	"golang.org/x/net/context"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/api/types"
 	registrytypes "github.com/docker/docker/api/types/registry"
-	"github.com/docker/docker/reference"
 )
 
 const (

+ 1 - 1
vendor.conf

@@ -43,7 +43,7 @@ github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904
 github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
 
 # get graph and distribution packages
-github.com/docker/distribution 7dba427612198a11b161a27f9d40bb2dca1ccd20
+github.com/docker/distribution 129ad8ea0c3760d878b34cffdb9c3be874a7b2f7
 github.com/vbatts/tar-split v0.10.1
 github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
 

+ 18 - 0
vendor/github.com/docker/distribution/reference/helpers.go

@@ -10,3 +10,21 @@ func IsNameOnly(ref Named) bool {
 	}
 	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()
+}

+ 147 - 1
vendor/github.com/docker/distribution/reference/normalize.go

@@ -1,9 +1,125 @@
 package reference
 
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/docker/distribution/digestset"
+	"github.com/opencontainers/go-digest"
+)
+
 var (
-	defaultTag = "latest"
+	legacyDefaultDomain = "index.docker.io"
+	defaultDomain       = "docker.io"
+	defaultRepoPrefix   = "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) (NormalizedNamed, 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.(NormalizedNamed)
+	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 = defaultRepoPrefix + 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 = ""
+		repo.path = strings.TrimPrefix(repo.path, defaultRepoPrefix)
+	}
+	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,
+	}
+}
+
 // EnsureTagged adds the default tag "latest" to a reference if it only has
 // a repo name.
 func EnsureTagged(ref Named) NamedTagged {
@@ -20,3 +136,33 @@ func EnsureTagged(ref Named) NamedTagged {
 	}
 	return namedTagged
 }
+
+// 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)
+}
+
+// ParseAnyReferenceWithSet parses a reference string as a possible short
+// identifier to be matched in a digest set, a full digest, or familiar name.
+func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
+	if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
+		dgst, err := ds.Lookup(ref)
+		if err == nil {
+			return digestReference(dgst), nil
+		}
+	} else {
+		if dgst, err := digest.Parse(ref); err == nil {
+			return digestReference(dgst), nil
+		}
+	}
+
+	return ParseNormalizedNamed(ref)
+}

+ 125 - 53
vendor/github.com/docker/distribution/reference/reference.go

@@ -4,11 +4,11 @@
 // Grammar
 //
 // 	reference                       := name [ ":" tag ] [ "@" digest ]
-//	name                            := [hostname '/'] component ['/' component]*
-//	hostname                        := hostcomponent ['.' hostcomponent]* [':' port-number]
-//	hostcomponent                   := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
+//	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]+/
-//	component                       := alpha-numeric [separator alpha-numeric]*
+//	path-component                  := alpha-numeric [separator alpha-numeric]*
 // 	alpha-numeric                   := /[a-z0-9]+/
 //	separator                       := /[_.]|__|[-]*/
 //
@@ -19,6 +19,9 @@
 //	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 (
@@ -126,23 +129,56 @@ type Digested interface {
 }
 
 // Canonical reference is an object with a fully unique
-// name including a name with hostname and digest
+// name including a name with domain and digest
 type Canonical interface {
 	Named
 	Digest() digest.Digest
 }
 
+// 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) {
-	name := named.Name()
-	match := anchoredNameRegexp.FindStringSubmatch(name)
-	if len(match) != 3 {
-		return "", name
+	if r, ok := named.(namedRepository); ok {
+		return r.Domain(), r.Path()
 	}
-	return match[1], match[2]
+	return splitDomain(named.Name())
 }
 
 // Parse parses s and returns a syntactically valid Reference.
@@ -164,9 +200,20 @@ func Parse(s string) (Reference, error) {
 		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{
-		name: matches[1],
-		tag:  matches[2],
+		namedRepository: repo,
+		tag:             matches[2],
 	}
 	if matches[3] != "" {
 		var err error
@@ -207,10 +254,15 @@ func WithName(name string) (Named, error) {
 	if len(name) > NameTotalLengthMax {
 		return nil, ErrNameTooLong
 	}
-	if !anchoredNameRegexp.MatchString(name) {
+
+	match := anchoredNameRegexp.FindStringSubmatch(name)
+	if match == nil || len(match) != 3 {
 		return nil, ErrReferenceInvalidFormat
 	}
-	return repository(name), nil
+	return repository{
+		domain: match[1],
+		path:   match[2],
+	}, nil
 }
 
 // WithTag combines the name from "name" and the tag from "tag" to form a
@@ -219,16 +271,23 @@ 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{
-			name:   name.Name(),
-			tag:    tag,
-			digest: canonical.Digest(),
+			namedRepository: repo,
+			tag:             tag,
+			digest:          canonical.Digest(),
 		}, nil
 	}
 	return taggedReference{
-		name: name.Name(),
-		tag:  tag,
+		namedRepository: repo,
+		tag:             tag,
 	}, nil
 }
 
@@ -238,16 +297,23 @@ 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{
-			name:   name.Name(),
-			tag:    tagged.Tag(),
-			digest: digest,
+			namedRepository: repo,
+			tag:             tagged.Tag(),
+			digest:          digest,
 		}, nil
 	}
 	return canonicalReference{
-		name:   name.Name(),
-		digest: digest,
+		namedRepository: repo,
+		digest:          digest,
 	}, nil
 }
 
@@ -263,11 +329,15 @@ func Match(pattern string, ref Reference) (bool, error) {
 
 // TrimNamed removes any tag or digest from the named reference.
 func TrimNamed(ref Named) Named {
-	return repository(ref.Name())
+	domain, path := SplitHostname(ref)
+	return repository{
+		domain: domain,
+		path:   path,
+	}
 }
 
 func getBestReferenceType(ref reference) Reference {
-	if ref.name == "" {
+	if ref.Name() == "" {
 		// Allow digest only references
 		if ref.digest != "" {
 			return digestReference(ref.digest)
@@ -277,16 +347,16 @@ func getBestReferenceType(ref reference) Reference {
 	if ref.tag == "" {
 		if ref.digest != "" {
 			return canonicalReference{
-				name:   ref.name,
-				digest: ref.digest,
+				namedRepository: ref.namedRepository,
+				digest:          ref.digest,
 			}
 		}
-		return repository(ref.name)
+		return ref.namedRepository
 	}
 	if ref.digest == "" {
 		return taggedReference{
-			name: ref.name,
-			tag:  ref.tag,
+			namedRepository: ref.namedRepository,
+			tag:             ref.tag,
 		}
 	}
 
@@ -294,17 +364,13 @@ func getBestReferenceType(ref reference) Reference {
 }
 
 type reference struct {
-	name   string
+	namedRepository
 	tag    string
 	digest digest.Digest
 }
 
 func (r reference) String() string {
-	return r.name + ":" + r.tag + "@" + r.digest.String()
-}
-
-func (r reference) Name() string {
-	return r.name
+	return r.Name() + ":" + r.tag + "@" + r.digest.String()
 }
 
 func (r reference) Tag() string {
@@ -315,14 +381,28 @@ func (r reference) Digest() digest.Digest {
 	return r.digest
 }
 
-type repository string
+type repository struct {
+	domain string
+	path   string
+}
 
 func (r repository) String() string {
-	return string(r)
+	return r.Name()
 }
 
 func (r repository) Name() string {
-	return string(r)
+	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
@@ -336,16 +416,12 @@ func (d digestReference) Digest() digest.Digest {
 }
 
 type taggedReference struct {
-	name string
-	tag  string
+	namedRepository
+	tag string
 }
 
 func (t taggedReference) String() string {
-	return t.name + ":" + t.tag
-}
-
-func (t taggedReference) Name() string {
-	return t.name
+	return t.Name() + ":" + t.tag
 }
 
 func (t taggedReference) Tag() string {
@@ -353,16 +429,12 @@ func (t taggedReference) Tag() string {
 }
 
 type canonicalReference struct {
-	name   string
+	namedRepository
 	digest digest.Digest
 }
 
 func (c canonicalReference) String() string {
-	return c.name + "@" + c.digest.String()
-}
-
-func (c canonicalReference) Name() string {
-	return c.name
+	return c.Name() + "@" + c.digest.String()
 }
 
 func (c canonicalReference) Digest() digest.Digest {

+ 30 - 11
vendor/github.com/docker/distribution/reference/regexp.go

@@ -19,18 +19,18 @@ var (
 		alphaNumericRegexp,
 		optional(repeated(separatorRegexp, alphaNumericRegexp)))
 
-	// hostnameComponentRegexp restricts the registry hostname component of a
-	// repository name to start with a component as defined by hostnameRegexp
+	// 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.
-	hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
+	domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
 
-	// hostnameRegexp defines the structure of potential hostname components
+	// 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.
-	hostnameRegexp = expression(
-		hostnameComponentRegexp,
-		optional(repeated(literal(`.`), hostnameComponentRegexp)),
+	domainRegexp = expression(
+		domainComponentRegexp,
+		optional(repeated(literal(`.`), domainComponentRegexp)),
 		optional(literal(`:`), match(`[0-9]+`)))
 
 	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
@@ -48,17 +48,17 @@ var (
 	anchoredDigestRegexp = anchored(DigestRegexp)
 
 	// NameRegexp is the format for the name component of references. The
-	// regexp has capturing groups for the hostname and name part omitting
+	// regexp has capturing groups for the domain and name part omitting
 	// the separating forward slash from either.
 	NameRegexp = expression(
-		optional(hostnameRegexp, literal(`/`)),
+		optional(domainRegexp, literal(`/`)),
 		nameComponentRegexp,
 		optional(repeated(literal(`/`), nameComponentRegexp)))
 
 	// anchoredNameRegexp is used to parse a name value, capturing the
-	// hostname and trailing components.
+	// domain and trailing components.
 	anchoredNameRegexp = anchored(
-		optional(capture(hostnameRegexp), literal(`/`)),
+		optional(capture(domainRegexp), literal(`/`)),
 		capture(nameComponentRegexp,
 			optional(repeated(literal(`/`), nameComponentRegexp))))
 
@@ -68,6 +68,25 @@ var (
 	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.