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 <derek@mcgstyle.net> (github: dmcgowan)master
							parent
							
								
									042fe9bf46
								
							
						
					
					
						commit
						429c75faf0
					
				|  | @ -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() | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 	} | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue