Move docker reference functionality to reference package
Add normalization functions and Docker specific domain splitting to reference package. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)master
							parent
							
								
									21db8e8597
								
							
						
					
					
						commit
						042fe9bf46
					
				|  | @ -1,9 +1,89 @@ | ||||||
| package reference | package reference | ||||||
| 
 | 
 | ||||||
| var ( | import ( | ||||||
| 	defaultTag = "latest" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/docker/distribution/digest" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	legacyDefaultDomain = "index.docker.io" | ||||||
|  | 	defaultDomain       = "docker.io" | ||||||
|  | 	defaultRepoPrefix   = "library/" | ||||||
|  | 	defaultTag          = "latest" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NormalizedName 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) { | ||||||
|  | 	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") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ParseNamed(domain + "/" + remainder) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FamiliarName 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()) | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // EnsureTagged adds the default tag "latest" to a reference if it only has
 | // EnsureTagged adds the default tag "latest" to a reference if it only has
 | ||||||
| // a repo name.
 | // a repo name.
 | ||||||
| func EnsureTagged(ref Named) NamedTagged { | func EnsureTagged(ref Named) NamedTagged { | ||||||
|  | @ -20,3 +100,33 @@ func EnsureTagged(ref Named) NamedTagged { | ||||||
| 	} | 	} | ||||||
| 	return 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.ParseDigest(ref); err == nil { | ||||||
|  | 		return digestReference(dgst), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return NormalizedName(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 *digest.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.ParseDigest(ref); err == nil { | ||||||
|  | 			return digestReference(dgst), nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return NormalizedName(ref) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,435 @@ | ||||||
|  | package reference | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/docker/distribution/digest" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestValidateReferenceName(t *testing.T) { | ||||||
|  | 	validRepoNames := []string{ | ||||||
|  | 		"docker/docker", | ||||||
|  | 		"library/debian", | ||||||
|  | 		"debian", | ||||||
|  | 		"docker.io/docker/docker", | ||||||
|  | 		"docker.io/library/debian", | ||||||
|  | 		"docker.io/debian", | ||||||
|  | 		"index.docker.io/docker/docker", | ||||||
|  | 		"index.docker.io/library/debian", | ||||||
|  | 		"index.docker.io/debian", | ||||||
|  | 		"127.0.0.1:5000/docker/docker", | ||||||
|  | 		"127.0.0.1:5000/library/debian", | ||||||
|  | 		"127.0.0.1:5000/debian", | ||||||
|  | 		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", | ||||||
|  | 
 | ||||||
|  | 		// This test case was moved from invalid to valid since it is valid input
 | ||||||
|  | 		// when specified with a hostname, it removes the ambiguity from about
 | ||||||
|  | 		// whether the value is an identifier or repository name
 | ||||||
|  | 		"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", | ||||||
|  | 	} | ||||||
|  | 	invalidRepoNames := []string{ | ||||||
|  | 		"https://github.com/docker/docker", | ||||||
|  | 		"docker/Docker", | ||||||
|  | 		"-docker", | ||||||
|  | 		"-docker/docker", | ||||||
|  | 		"-docker.io/docker/docker", | ||||||
|  | 		"docker///docker", | ||||||
|  | 		"docker.io/docker/Docker", | ||||||
|  | 		"docker.io/docker///docker", | ||||||
|  | 		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, name := range invalidRepoNames { | ||||||
|  | 		_, err := NormalizedName(name) | ||||||
|  | 		if err == nil { | ||||||
|  | 			t.Fatalf("Expected invalid repo name for %q", name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, name := range validRepoNames { | ||||||
|  | 		_, err := NormalizedName(name) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("Error parsing repo name %s, got: %q", name, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateRemoteName(t *testing.T) { | ||||||
|  | 	validRepositoryNames := []string{ | ||||||
|  | 		// Sanity check.
 | ||||||
|  | 		"docker/docker", | ||||||
|  | 
 | ||||||
|  | 		// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
 | ||||||
|  | 		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", | ||||||
|  | 
 | ||||||
|  | 		// Allow embedded hyphens.
 | ||||||
|  | 		"docker-rules/docker", | ||||||
|  | 
 | ||||||
|  | 		// Allow multiple hyphens as well.
 | ||||||
|  | 		"docker---rules/docker", | ||||||
|  | 
 | ||||||
|  | 		//Username doc and image name docker being tested.
 | ||||||
|  | 		"doc/docker", | ||||||
|  | 
 | ||||||
|  | 		// single character names are now allowed.
 | ||||||
|  | 		"d/docker", | ||||||
|  | 		"jess/t", | ||||||
|  | 
 | ||||||
|  | 		// Consecutive underscores.
 | ||||||
|  | 		"dock__er/docker", | ||||||
|  | 	} | ||||||
|  | 	for _, repositoryName := range validRepositoryNames { | ||||||
|  | 		_, err := NormalizedName(repositoryName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	invalidRepositoryNames := []string{ | ||||||
|  | 		// Disallow capital letters.
 | ||||||
|  | 		"docker/Docker", | ||||||
|  | 
 | ||||||
|  | 		// Only allow one slash.
 | ||||||
|  | 		"docker///docker", | ||||||
|  | 
 | ||||||
|  | 		// Disallow 64-character hexadecimal.
 | ||||||
|  | 		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", | ||||||
|  | 
 | ||||||
|  | 		// Disallow leading and trailing hyphens in namespace.
 | ||||||
|  | 		"-docker/docker", | ||||||
|  | 		"docker-/docker", | ||||||
|  | 		"-docker-/docker", | ||||||
|  | 
 | ||||||
|  | 		// Don't allow underscores everywhere (as opposed to hyphens).
 | ||||||
|  | 		"____/____", | ||||||
|  | 
 | ||||||
|  | 		"_docker/_docker", | ||||||
|  | 
 | ||||||
|  | 		// Disallow consecutive periods.
 | ||||||
|  | 		"dock..er/docker", | ||||||
|  | 		"dock_.er/docker", | ||||||
|  | 		"dock-.er/docker", | ||||||
|  | 
 | ||||||
|  | 		// No repository.
 | ||||||
|  | 		"docker/", | ||||||
|  | 
 | ||||||
|  | 		//namespace too long
 | ||||||
|  | 		"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", | ||||||
|  | 	} | ||||||
|  | 	for _, repositoryName := range invalidRepositoryNames { | ||||||
|  | 		if _, err := NormalizedName(repositoryName); err == nil { | ||||||
|  | 			t.Errorf("Repository name should be invalid: %v", repositoryName) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestParseRepositoryInfo(t *testing.T) { | ||||||
|  | 	type tcase struct { | ||||||
|  | 		RemoteName, FamiliarName, FullName, AmbiguousName, Domain string | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tcases := []tcase{ | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "fooo/bar", | ||||||
|  | 			FamiliarName:  "fooo/bar", | ||||||
|  | 			FullName:      "docker.io/fooo/bar", | ||||||
|  | 			AmbiguousName: "index.docker.io/fooo/bar", | ||||||
|  | 			Domain:        "docker.io", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "library/ubuntu", | ||||||
|  | 			FamiliarName:  "ubuntu", | ||||||
|  | 			FullName:      "docker.io/library/ubuntu", | ||||||
|  | 			AmbiguousName: "library/ubuntu", | ||||||
|  | 			Domain:        "docker.io", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "nonlibrary/ubuntu", | ||||||
|  | 			FamiliarName:  "nonlibrary/ubuntu", | ||||||
|  | 			FullName:      "docker.io/nonlibrary/ubuntu", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "docker.io", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "other/library", | ||||||
|  | 			FamiliarName:  "other/library", | ||||||
|  | 			FullName:      "docker.io/other/library", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "docker.io", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "private/moonbase", | ||||||
|  | 			FamiliarName:  "127.0.0.1:8000/private/moonbase", | ||||||
|  | 			FullName:      "127.0.0.1:8000/private/moonbase", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "127.0.0.1:8000", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "privatebase", | ||||||
|  | 			FamiliarName:  "127.0.0.1:8000/privatebase", | ||||||
|  | 			FullName:      "127.0.0.1:8000/privatebase", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "127.0.0.1:8000", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "private/moonbase", | ||||||
|  | 			FamiliarName:  "example.com/private/moonbase", | ||||||
|  | 			FullName:      "example.com/private/moonbase", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "example.com", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "privatebase", | ||||||
|  | 			FamiliarName:  "example.com/privatebase", | ||||||
|  | 			FullName:      "example.com/privatebase", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "example.com", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "private/moonbase", | ||||||
|  | 			FamiliarName:  "example.com:8000/private/moonbase", | ||||||
|  | 			FullName:      "example.com:8000/private/moonbase", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "example.com:8000", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "privatebasee", | ||||||
|  | 			FamiliarName:  "example.com:8000/privatebasee", | ||||||
|  | 			FullName:      "example.com:8000/privatebasee", | ||||||
|  | 			AmbiguousName: "", | ||||||
|  | 			Domain:        "example.com:8000", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			RemoteName:    "library/ubuntu-12.04-base", | ||||||
|  | 			FamiliarName:  "ubuntu-12.04-base", | ||||||
|  | 			FullName:      "docker.io/library/ubuntu-12.04-base", | ||||||
|  | 			AmbiguousName: "index.docker.io/library/ubuntu-12.04-base", | ||||||
|  | 			Domain:        "docker.io", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tcase := range tcases { | ||||||
|  | 		refStrings := []string{tcase.FamiliarName, tcase.FullName} | ||||||
|  | 		if tcase.AmbiguousName != "" { | ||||||
|  | 			refStrings = append(refStrings, tcase.AmbiguousName) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var refs []Named | ||||||
|  | 		for _, r := range refStrings { | ||||||
|  | 			named, err := NormalizedName(r) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  | 			refs = append(refs, named) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, r := range refs { | ||||||
|  | 			if expected, actual := tcase.FamiliarName, FamiliarName(r).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 { | ||||||
|  | 				t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual) | ||||||
|  | 			} | ||||||
|  | 			if expected, actual := tcase.Domain, Domain(r); expected != actual { | ||||||
|  | 				t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual) | ||||||
|  | 			} | ||||||
|  | 			if expected, actual := tcase.RemoteName, Path(r); expected != actual { | ||||||
|  | 				t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestParseReferenceWithTagAndDigest(t *testing.T) { | ||||||
|  | 	ref, err := NormalizedName("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") | ||||||
|  | 	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) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ref = FamiliarName(ref) | ||||||
|  | 	if _, isTagged := ref.(NamedTagged); isTagged { | ||||||
|  | 		t.Fatalf("Reference from %q should not support tag", ref) | ||||||
|  | 	} | ||||||
|  | 	if _, isCanonical := ref.(Canonical); !isCanonical { | ||||||
|  | 		t.Fatalf("Reference from %q should not support digest", ref) | ||||||
|  | 	} | ||||||
|  | 	if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", FamiliarName(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 { | ||||||
|  | 		t.Fatal("Expected WithName to detect invalid name") | ||||||
|  | 	} | ||||||
|  | 	ref, err := NormalizedName("busybox") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := WithTag(ref, "-foo"); err == nil { | ||||||
|  | 		t.Fatal("Expected WithName to detect invalid tag") | ||||||
|  | 	} | ||||||
|  | 	if _, err := WithDigest(ref, digest.Digest("foo")); err == nil { | ||||||
|  | 		t.Fatal("Expected WithDigest to detect invalid digest") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func equalReference(r1, r2 Reference) bool { | ||||||
|  | 	switch v1 := r1.(type) { | ||||||
|  | 	case digestReference: | ||||||
|  | 		if v2, ok := r2.(digestReference); ok { | ||||||
|  | 			return v1 == v2 | ||||||
|  | 		} | ||||||
|  | 	case repository: | ||||||
|  | 		if v2, ok := r2.(repository); ok { | ||||||
|  | 			return v1 == v2 | ||||||
|  | 		} | ||||||
|  | 	case taggedReference: | ||||||
|  | 		if v2, ok := r2.(taggedReference); ok { | ||||||
|  | 			return v1 == v2 | ||||||
|  | 		} | ||||||
|  | 	case canonicalReference: | ||||||
|  | 		if v2, ok := r2.(canonicalReference); ok { | ||||||
|  | 			return v1 == v2 | ||||||
|  | 		} | ||||||
|  | 	case reference: | ||||||
|  | 		if v2, ok := r2.(reference); ok { | ||||||
|  | 			return v1 == v2 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestParseAnyReference(t *testing.T) { | ||||||
|  | 	tcases := []struct { | ||||||
|  | 		Reference  string | ||||||
|  | 		Equivalent string | ||||||
|  | 		Expected   Reference | ||||||
|  | 		Digests    []digest.Digest | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "redis", | ||||||
|  | 			Equivalent: "docker.io/library/redis", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "redis:latest", | ||||||
|  | 			Equivalent: "docker.io/library/redis:latest", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "docker.io/library/redis:latest", | ||||||
|  | 			Equivalent: "docker.io/library/redis:latest", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dmcgowan/myapp", | ||||||
|  | 			Equivalent: "docker.io/dmcgowan/myapp", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dmcgowan/myapp:latest", | ||||||
|  | 			Equivalent: "docker.io/dmcgowan/myapp:latest", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "docker.io/mcgowan/myapp:latest", | ||||||
|  | 			Equivalent: "docker.io/mcgowan/myapp:latest", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Expected:  digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", | ||||||
|  | 			Expected:  digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", | ||||||
|  | 			Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", | ||||||
|  | 			Expected:  digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			Digests: []digest.Digest{ | ||||||
|  | 				digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 				digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", | ||||||
|  | 			Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", | ||||||
|  | 			Digests: []digest.Digest{ | ||||||
|  | 				digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference: "dbcc1c", | ||||||
|  | 			Expected:  digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			Digests: []digest.Digest{ | ||||||
|  | 				digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 				digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dbcc1", | ||||||
|  | 			Equivalent: "docker.io/library/dbcc1", | ||||||
|  | 			Digests: []digest.Digest{ | ||||||
|  | 				digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 				digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Reference:  "dbcc1c", | ||||||
|  | 			Equivalent: "docker.io/library/dbcc1c", | ||||||
|  | 			Digests: []digest.Digest{ | ||||||
|  | 				digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tcase := range tcases { | ||||||
|  | 		var ref Reference | ||||||
|  | 		var err error | ||||||
|  | 		if len(tcase.Digests) == 0 { | ||||||
|  | 			ref, err = ParseAnyReference(tcase.Reference) | ||||||
|  | 		} else { | ||||||
|  | 			ds := digest.NewSet() | ||||||
|  | 			for _, dgst := range tcase.Digests { | ||||||
|  | 				if err := ds.Add(dgst); err != nil { | ||||||
|  | 					t.Fatalf("Error adding digest %s: %v", dgst.String(), err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			ref, err = ParseAnyReferenceWithSet(tcase.Reference, ds) | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		expected := tcase.Expected | ||||||
|  | 		if expected == nil { | ||||||
|  | 			expected, err = Parse(tcase.Equivalent) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !equalReference(ref, expected) { | ||||||
|  | 			t.Errorf("Unexpected reference %#v, expected %#v", ref, expected) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue