From 429c75faf07af01f426f0b9ec8347eef44919d60 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Mon, 27 Jun 2016 16:41:28 -0700 Subject: [PATCH] Add NormalizedName interface Add interface for for a normalized name and corresponding parser for that type. New normalized versions of all interfaces are not added since all type information is preserved on calls to Familiar. Signed-off-by: Derek McGowan (github: dmcgowan) --- reference/helpers.go | 18 +++++++++ reference/normalize.go | 77 +++++++++++++++++++++++++++---------- reference/normalize_test.go | 35 +++++++++-------- reference/reference.go | 48 ++++++++++++----------- 4 files changed, 118 insertions(+), 60 deletions(-) diff --git a/reference/helpers.go b/reference/helpers.go index dd7ee0ea..a8f46ced 100644 --- a/reference/helpers.go +++ b/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/reference/normalize.go b/reference/normalize.go index 2f6df14c..7fc6382b 100644 --- a/reference/normalize.go +++ b/reference/normalize.go @@ -15,11 +15,21 @@ var ( defaultTag = "latest" ) -// NormalizedName parses a string into a named reference +// 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 NormalizedName(s string) (Named, error) { +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) } @@ -34,7 +44,15 @@ func NormalizedName(s string) (Named, error) { return nil, errors.New("invalid reference format: repository name must be lowercase") } - return ParseNamed(domain + "/" + remainder) + 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. @@ -56,34 +74,51 @@ func splitDockerDomain(name string) (domain, remainder string) { return } -// FamiliarName returns a shortened version of the name familiar +// 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". -func FamiliarName(named Named) Named { - var repo repository - repo.domain, repo.path = splitDomain(named.Name()) +// 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) } - if digested, ok := named.(Digested); ok { - return canonicalReference{ - repository: repo, - digest: digested.Digest(), - } - } - if tagged, ok := named.(Tagged); ok { - return taggedReference{ - repository: repo, - tag: tagged.Tag(), - } - } 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 { @@ -111,7 +146,7 @@ func ParseAnyReference(ref string) (Reference, error) { return digestReference(dgst), nil } - return NormalizedName(ref) + return ParseNormalizedNamed(ref) } // ParseAnyReferenceWithSet parses a reference string as a possible short @@ -128,5 +163,5 @@ func ParseAnyReferenceWithSet(ref string, ds *digest.Set) (Reference, error) { } } - return NormalizedName(ref) + return ParseNormalizedNamed(ref) } diff --git a/reference/normalize_test.go b/reference/normalize_test.go index 84926df5..f4e09b04 100644 --- a/reference/normalize_test.go +++ b/reference/normalize_test.go @@ -40,14 +40,14 @@ func TestValidateReferenceName(t *testing.T) { } for _, name := range invalidRepoNames { - _, err := NormalizedName(name) + _, err := ParseNormalizedNamed(name) if err == nil { t.Fatalf("Expected invalid repo name for %q", name) } } for _, name := range validRepoNames { - _, err := NormalizedName(name) + _, err := ParseNormalizedNamed(name) if err != nil { t.Fatalf("Error parsing repo name %s, got: %q", name, err) } @@ -79,7 +79,7 @@ func TestValidateRemoteName(t *testing.T) { "dock__er/docker", } for _, repositoryName := range validRepositoryNames { - _, err := NormalizedName(repositoryName) + _, err := ParseNormalizedNamed(repositoryName) if err != nil { t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) } @@ -117,7 +117,7 @@ func TestValidateRemoteName(t *testing.T) { "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", } for _, repositoryName := range invalidRepositoryNames { - if _, err := NormalizedName(repositoryName); err == nil { + if _, err := ParseNormalizedNamed(repositoryName); err == nil { t.Errorf("Repository name should be invalid: %v", repositoryName) } } @@ -214,9 +214,9 @@ func TestParseRepositoryInfo(t *testing.T) { refStrings = append(refStrings, tcase.AmbiguousName) } - var refs []Named + var refs []NormalizedNamed for _, r := range refStrings { - named, err := NormalizedName(r) + named, err := ParseNormalizedNamed(r) if err != nil { t.Fatal(err) } @@ -224,7 +224,7 @@ func TestParseRepositoryInfo(t *testing.T) { } for _, r := range refs { - if expected, actual := tcase.FamiliarName, FamiliarName(r).Name(); expected != actual { + if expected, actual := tcase.FamiliarName, r.Familiar().Name(); expected != actual { t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) } if expected, actual := tcase.FullName, r.String(); expected != actual { @@ -242,31 +242,32 @@ func TestParseRepositoryInfo(t *testing.T) { } func TestParseReferenceWithTagAndDigest(t *testing.T) { - ref, err := NormalizedName("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") + shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa" + nref, err := ParseNormalizedNamed(shortRef) if err != nil { t.Fatal(err) } - if expected, actual := "docker.io/library/busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected { - t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) + if expected, actual := "docker.io/library/"+shortRef, nref.String(); actual != expected { + t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", nref, expected, actual) } - ref = FamiliarName(ref) - if _, isTagged := ref.(NamedTagged); isTagged { - t.Fatalf("Reference from %q should not support tag", ref) + ref := nref.Familiar() + if _, isTagged := ref.(NamedTagged); !isTagged { + t.Fatalf("Reference from %q should support tag", ref) } if _, isCanonical := ref.(Canonical); !isCanonical { - t.Fatalf("Reference from %q should not support digest", ref) + t.Fatalf("Reference from %q should support digest", ref) } - if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", FamiliarName(ref).String(); actual != expected { + if expected, actual := shortRef, ref.String(); actual != expected { t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) } } func TestInvalidReferenceComponents(t *testing.T) { - if _, err := NormalizedName("-foo"); err == nil { + if _, err := ParseNormalizedNamed("-foo"); err == nil { t.Fatal("Expected WithName to detect invalid name") } - ref, err := NormalizedName("busybox") + ref, err := ParseNormalizedNamed("busybox") if err != nil { t.Fatal(err) } diff --git a/reference/reference.go b/reference/reference.go index ab15f1e6..4e38766c 100644 --- a/reference/reference.go +++ b/reference/reference.go @@ -212,8 +212,8 @@ func Parse(s string) (Reference, error) { } ref := reference{ - repository: repo, - tag: matches[2], + NamedRepository: repo, + tag: matches[2], } if matches[3] != "" { var err error @@ -280,14 +280,14 @@ func WithTag(name Named, tag string) (NamedTagged, error) { } if canonical, ok := name.(Canonical); ok { return reference{ - repository: repo, - tag: tag, - digest: canonical.Digest(), + NamedRepository: repo, + tag: tag, + digest: canonical.Digest(), }, nil } return taggedReference{ - repository: repo, - tag: tag, + NamedRepository: repo, + tag: tag, }, nil } @@ -306,14 +306,14 @@ func WithDigest(name Named, digest digest.Digest) (Canonical, error) { } if tagged, ok := name.(Tagged); ok { return reference{ - repository: repo, - tag: tagged.Tag(), - digest: digest, + NamedRepository: repo, + tag: tagged.Tag(), + digest: digest, }, nil } return canonicalReference{ - repository: repo, - digest: digest, + NamedRepository: repo, + digest: digest, }, nil } @@ -329,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.repository.path == "" { + if ref.Name() == "" { // Allow digest only references if ref.digest != "" { return digestReference(ref.digest) @@ -343,16 +347,16 @@ func getBestReferenceType(ref reference) Reference { if ref.tag == "" { if ref.digest != "" { return canonicalReference{ - repository: ref.repository, - digest: ref.digest, + NamedRepository: ref.NamedRepository, + digest: ref.digest, } } - return ref.repository + return ref.NamedRepository } if ref.digest == "" { return taggedReference{ - repository: ref.repository, - tag: ref.tag, + NamedRepository: ref.NamedRepository, + tag: ref.tag, } } @@ -360,7 +364,7 @@ func getBestReferenceType(ref reference) Reference { } type reference struct { - repository + NamedRepository tag string digest digest.Digest } @@ -412,7 +416,7 @@ func (d digestReference) Digest() digest.Digest { } type taggedReference struct { - repository + NamedRepository tag string } @@ -425,7 +429,7 @@ func (t taggedReference) Tag() string { } type canonicalReference struct { - repository + NamedRepository digest digest.Digest }