diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 787d09b3f6..cfd672e77a 100644 --- a/cli/command/container/create.go +++ b/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 } } diff --git a/cli/command/formatter/image.go b/cli/command/formatter/image.go index 3dbb1b9642..06319b9355 100644 --- a/cli/command/formatter/image.go +++ b/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()) } } diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 3c92ba20b9..67753bea14 100644 --- a/cli/command/image/build.go +++ b/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, diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index e840671c62..967beca86f 100644 --- a/cli/command/image/pull.go +++ b/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") { diff --git a/cli/command/image/push.go b/cli/command/image/push.go index a5ba7d794e..3879d849dd 100644 --- a/cli/command/image/push.go +++ b/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 } diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index 58e0574396..2ff9b463d5 100644 --- a/cli/command/image/trust.go +++ b/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) - return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String()) + fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef) + + return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef) } diff --git a/cli/command/plugin/create.go b/cli/command/plugin/create.go index 82d17af48c..e1e6f74ee3 100644 --- a/cli/command/plugin/create.go +++ b/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 } diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index fd30600370..ebfe1f1eec 100644 --- a/cli/command/plugin/install.go +++ b/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, diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 1a9c592a93..c5c906b821 100644 --- a/cli/command/plugin/push.go +++ b/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 } diff --git a/cli/command/registry.go b/cli/command/registry.go index 65f6b3309e..411310fa34 100644 --- a/cli/command/registry.go +++ b/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 } diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index 15f8a708f0..d466f3b648 100644 --- a/cli/command/service/trust.go +++ b/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) } diff --git a/distribution/push_v2_test.go b/distribution/push_v2_test.go index 6aa3722758..7e9f4166f1 100644 --- a/distribution/push_v2_test.go +++ b/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"}, diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index c9d51b51b3..b3b215a8b7 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/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", }) } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 09c6e96127..a40244ff46 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/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) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 245419eb9a..9749af4cda 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/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) } } diff --git a/plugin/manager.go b/plugin/manager.go index a81c3edb47..3126097250 100644 --- a/plugin/manager.go +++ b/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" diff --git a/reference/reference.go b/reference/reference.go index 620ee09340..e6e070cb8c 100644 --- a/reference/reference.go +++ b/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) diff --git a/registry/config.go b/registry/config.go index cc4111efaf..ce98e85c7e 100644 --- a/registry/config.go +++ b/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 diff --git a/registry/registry_test.go b/registry/registry_test.go index a4e5312142..7ab10721e0 100644 --- a/registry/registry_test.go +++ b/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) } diff --git a/registry/service.go b/registry/service.go index af74cbad26..56dabab754 100644 --- a/registry/service.go +++ b/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 ( diff --git a/vendor.conf b/vendor.conf index 1734bbd84e..6a7e82ceb4 100644 --- a/vendor.conf +++ b/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 diff --git a/vendor/github.com/docker/distribution/reference/helpers.go b/vendor/github.com/docker/distribution/reference/helpers.go index dd7ee0ea65..a8f46cedda 100644 --- a/vendor/github.com/docker/distribution/reference/helpers.go +++ b/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() +} diff --git a/vendor/github.com/docker/distribution/reference/normalize.go b/vendor/github.com/docker/distribution/reference/normalize.go index b19a34e3bf..da797b705b 100644 --- a/vendor/github.com/docker/distribution/reference/normalize.go +++ b/vendor/github.com/docker/distribution/reference/normalize.go @@ -1,9 +1,125 @@ package reference -var ( - defaultTag = "latest" +import ( + "errors" + "fmt" + "strings" + + "github.com/docker/distribution/digestset" + "github.com/opencontainers/go-digest" ) +var ( + 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) +} diff --git a/vendor/github.com/docker/distribution/reference/reference.go b/vendor/github.com/docker/distribution/reference/reference.go index 52da523706..888e9b6d32 100644 --- a/vendor/github.com/docker/distribution/reference/reference.go +++ b/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 { diff --git a/vendor/github.com/docker/distribution/reference/regexp.go b/vendor/github.com/docker/distribution/reference/regexp.go index 9a7d366bc8..405e995db9 100644 --- a/vendor/github.com/docker/distribution/reference/regexp.go +++ b/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.