53a6f7d7aa
Current registry reference use a subset of dns and IPv4 addresses to represent a registry domain. Since registries are mostly compatible with rfc3986, that defines the URI generic syntax, this adds support for IPv6 enclosed in squared brackets based on the mentioned rfc. The regexp is only expanded to match on IPv6 addreses enclosed between square brackets, considering only regular IPv6 addresses represented as compressed or uncompressed, excluding special IPv6 address representations. Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
733 lines
20 KiB
Go
733 lines
20 KiB
Go
package reference
|
|
|
|
import (
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3/digestset"
|
|
"github.com/opencontainers/go-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",
|
|
"192.168.0.1",
|
|
"192.168.0.1:80",
|
|
"192.168.0.1:8/debian",
|
|
"192.168.0.2:25000/debian",
|
|
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
|
"[fc00::1]:5000/docker",
|
|
"[fc00::1]:5000/docker/docker",
|
|
"[fc00:1:2:3:4:5:6:7]:5000/library/debian",
|
|
|
|
// 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",
|
|
"Docker/docker",
|
|
"DOCKER/docker",
|
|
}
|
|
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",
|
|
"[fc00::1]",
|
|
"[fc00::1]:5000",
|
|
"fc00::1:5000/debian",
|
|
"[fe80::1%eth0]:5000/debian",
|
|
"[2001:db8:3:4::192.0.2.33]:5000/debian",
|
|
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
|
}
|
|
|
|
for _, name := range invalidRepoNames {
|
|
_, err := ParseNormalizedNamed(name)
|
|
if err == nil {
|
|
t.Fatalf("Expected invalid repo name for %q", name)
|
|
}
|
|
}
|
|
|
|
for _, name := range validRepoNames {
|
|
_, err := ParseNormalizedNamed(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 := ParseNormalizedNamed(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 := ParseNormalizedNamed(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",
|
|
},
|
|
{
|
|
RemoteName: "library/foo",
|
|
FamiliarName: "foo",
|
|
FullName: "docker.io/library/foo",
|
|
AmbiguousName: "docker.io/foo",
|
|
Domain: "docker.io",
|
|
},
|
|
{
|
|
RemoteName: "library/foo/bar",
|
|
FamiliarName: "library/foo/bar",
|
|
FullName: "docker.io/library/foo/bar",
|
|
AmbiguousName: "",
|
|
Domain: "docker.io",
|
|
},
|
|
{
|
|
RemoteName: "store/foo/bar",
|
|
FamiliarName: "store/foo/bar",
|
|
FullName: "docker.io/store/foo/bar",
|
|
AmbiguousName: "",
|
|
Domain: "docker.io",
|
|
},
|
|
{
|
|
RemoteName: "bar",
|
|
FamiliarName: "Foo/bar",
|
|
FullName: "Foo/bar",
|
|
AmbiguousName: "",
|
|
Domain: "Foo",
|
|
},
|
|
{
|
|
RemoteName: "bar",
|
|
FamiliarName: "FOO/bar",
|
|
FullName: "FOO/bar",
|
|
AmbiguousName: "",
|
|
Domain: "FOO",
|
|
},
|
|
}
|
|
|
|
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 := ParseNormalizedNamed(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
refs = append(refs, named)
|
|
}
|
|
|
|
for _, r := range refs {
|
|
if expected, actual := tcase.FamiliarName, FamiliarName(r); 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) {
|
|
shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
|
|
ref, err := ParseNormalizedNamed(shortRef)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
|
|
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
|
}
|
|
|
|
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 support digest", ref)
|
|
}
|
|
if expected, actual := shortRef, FamiliarString(ref); actual != expected {
|
|
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestInvalidReferenceComponents(t *testing.T) {
|
|
if _, err := ParseNormalizedNamed("-foo"); err == nil {
|
|
t.Fatal("Expected WithName to detect invalid name")
|
|
}
|
|
ref, err := ParseNormalizedNamed("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"),
|
|
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
},
|
|
{
|
|
Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
|
},
|
|
{
|
|
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
},
|
|
{
|
|
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
|
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
|
Equivalent: "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"),
|
|
Equivalent: "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 := digestset.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)
|
|
}
|
|
if ref.String() != tcase.Equivalent {
|
|
t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNormalizedSplitHostname(t *testing.T) {
|
|
testcases := []struct {
|
|
input string
|
|
domain string
|
|
name string
|
|
}{
|
|
{
|
|
input: "test.com/foo",
|
|
domain: "test.com",
|
|
name: "foo",
|
|
},
|
|
{
|
|
input: "test_com/foo",
|
|
domain: "docker.io",
|
|
name: "test_com/foo",
|
|
},
|
|
{
|
|
input: "docker/migrator",
|
|
domain: "docker.io",
|
|
name: "docker/migrator",
|
|
},
|
|
{
|
|
input: "test.com:8080/foo",
|
|
domain: "test.com:8080",
|
|
name: "foo",
|
|
},
|
|
{
|
|
input: "test-com:8080/foo",
|
|
domain: "test-com:8080",
|
|
name: "foo",
|
|
},
|
|
{
|
|
input: "foo",
|
|
domain: "docker.io",
|
|
name: "library/foo",
|
|
},
|
|
{
|
|
input: "xn--n3h.com/foo",
|
|
domain: "xn--n3h.com",
|
|
name: "foo",
|
|
},
|
|
{
|
|
input: "xn--n3h.com:18080/foo",
|
|
domain: "xn--n3h.com:18080",
|
|
name: "foo",
|
|
},
|
|
{
|
|
input: "docker.io/foo",
|
|
domain: "docker.io",
|
|
name: "library/foo",
|
|
},
|
|
{
|
|
input: "docker.io/library/foo",
|
|
domain: "docker.io",
|
|
name: "library/foo",
|
|
},
|
|
{
|
|
input: "docker.io/library/foo/bar",
|
|
domain: "docker.io",
|
|
name: "library/foo/bar",
|
|
},
|
|
}
|
|
for _, testcase := range testcases {
|
|
failf := func(format string, v ...interface{}) {
|
|
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
t.Fail()
|
|
}
|
|
|
|
named, err := ParseNormalizedNamed(testcase.input)
|
|
if err != nil {
|
|
failf("error parsing name: %s", err)
|
|
}
|
|
domain, name := SplitHostname(named)
|
|
if domain != testcase.domain {
|
|
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
|
}
|
|
if name != testcase.name {
|
|
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMatchError(t *testing.T) {
|
|
named, err := ParseAnyReference("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = FamiliarMatch("[-x]", named)
|
|
if err == nil {
|
|
t.Fatalf("expected an error, got nothing")
|
|
}
|
|
}
|
|
|
|
func TestMatch(t *testing.T) {
|
|
matchCases := []struct {
|
|
reference string
|
|
pattern string
|
|
expected bool
|
|
}{
|
|
{
|
|
reference: "foo",
|
|
pattern: "foo/**/ba[rz]",
|
|
expected: false,
|
|
},
|
|
{
|
|
reference: "foo/any/bat",
|
|
pattern: "foo/**/ba[rz]",
|
|
expected: false,
|
|
},
|
|
{
|
|
reference: "foo/a/bar",
|
|
pattern: "foo/**/ba[rz]",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "foo/b/baz",
|
|
pattern: "foo/**/ba[rz]",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "foo/c/baz:tag",
|
|
pattern: "foo/**/ba[rz]",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "foo/c/baz:tag",
|
|
pattern: "foo/*/baz:tag",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "foo/c/baz:tag",
|
|
pattern: "foo/c/baz:tag",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "example.com/foo/c/baz:tag",
|
|
pattern: "*/foo/c/baz",
|
|
expected: true,
|
|
},
|
|
{
|
|
reference: "example.com/foo/c/baz:tag",
|
|
pattern: "example.com/foo/c/baz",
|
|
expected: true,
|
|
},
|
|
}
|
|
for _, c := range matchCases {
|
|
named, err := ParseAnyReference(c.reference)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual, err := FamiliarMatch(c.pattern, named)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if actual != c.expected {
|
|
t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDockerRef(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "nothing",
|
|
input: "busybox",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "tag only",
|
|
input: "busybox:latest",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "digest only",
|
|
input: "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
expected: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
},
|
|
{
|
|
name: "path only",
|
|
input: "library/busybox",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "hostname only",
|
|
input: "docker.io/busybox",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "no tag",
|
|
input: "docker.io/library/busybox",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "no path",
|
|
input: "docker.io/busybox:latest",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "no hostname",
|
|
input: "library/busybox:latest",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "full reference with tag",
|
|
input: "docker.io/library/busybox:latest",
|
|
expected: "docker.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "gcr reference without tag",
|
|
input: "gcr.io/library/busybox",
|
|
expected: "gcr.io/library/busybox:latest",
|
|
},
|
|
{
|
|
name: "both tag and digest",
|
|
input: "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
expected: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
},
|
|
}
|
|
for _, test := range testcases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
normalized, err := ParseDockerRef(test.input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
output := normalized.String()
|
|
if output != test.expected {
|
|
t.Fatalf("expected %q to be parsed as %v, got %v", test.input, test.expected, output)
|
|
}
|
|
_, err = Parse(output)
|
|
if err != nil {
|
|
t.Fatalf("%q should be a valid reference, but got an error: %v", output, err)
|
|
}
|
|
})
|
|
}
|
|
}
|