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)
This commit is contained in:
		
							parent
							
								
									21db8e8597
								
							
						
					
					
						commit
						042fe9bf46
					
				
					 2 changed files with 547 additions and 2 deletions
				
			
		|  | @ -1,9 +1,89 @@ | |||
| package reference | ||||
| 
 | ||||
| var ( | ||||
| 	defaultTag = "latest" | ||||
| import ( | ||||
| 	"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 | ||||
| // a repo name. | ||||
| func EnsureTagged(ref Named) NamedTagged { | ||||
|  | @ -20,3 +100,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.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) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										435
									
								
								reference/normalize_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										435
									
								
								reference/normalize_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue