forked from TrueCloudLab/distribution
f8c09b6a7d
There seems to be a need for a type that represents a way of pointing to an image, irrespective of the implementation. This patch defines a Reference interface and provides 3 implementations: - TagReference: when only a tag is provided - DigestReference: when a digest (according to the digest package) is provided, can include optional tag as well Validation of references are purely syntactic. There is also a strong type for tags, analogous to digests, as well as a strong type for Repository from which clients can access the hostname alone, or the repository name without the hostname, or both together via the String() method. For Repository, the files names.go and names_test.go were moved from the v2 package. Signed-off-by: Tibor Vass <tibor@docker.com>
286 lines
5.9 KiB
Go
286 lines
5.9 KiB
Go
package reference
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var (
|
|
// regexpTestcases is a unified set of testcases for
|
|
// TestValidateRepositoryName and TestRepositoryNameRegexp.
|
|
// Some of them are valid inputs for one and not the other.
|
|
regexpTestcases = []struct {
|
|
// input is the repository name or name component testcase
|
|
input string
|
|
// err is the error expected from ValidateRepositoryName, or nil
|
|
err error
|
|
// invalid should be true if the testcase is *not* expected to
|
|
// match RepositoryNameRegexp
|
|
invalid bool
|
|
}{
|
|
{
|
|
input: "",
|
|
err: ErrRepositoryNameEmpty,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "short",
|
|
err: ErrRepositoryNameMissingHostname,
|
|
},
|
|
{
|
|
input: "simple/name",
|
|
},
|
|
{
|
|
input: "library/ubuntu",
|
|
},
|
|
{
|
|
input: "docker/stevvooe/app",
|
|
},
|
|
{
|
|
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
|
|
},
|
|
{
|
|
input: "aa/aa/bb/bb/bb",
|
|
},
|
|
{
|
|
input: "a/a/a/b/b",
|
|
},
|
|
{
|
|
input: "a/a/a/a/",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "a//a/a",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "a",
|
|
err: ErrRepositoryNameMissingHostname,
|
|
},
|
|
{
|
|
input: "a/aa",
|
|
},
|
|
{
|
|
input: "aa/a",
|
|
},
|
|
{
|
|
input: "a/aa/a",
|
|
},
|
|
{
|
|
input: "foo.com/",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "foo.com:8080/bar",
|
|
},
|
|
{
|
|
input: "foo.com/bar",
|
|
},
|
|
{
|
|
input: "foo.com/bar/baz",
|
|
},
|
|
{
|
|
input: "foo.com/bar/baz/quux",
|
|
},
|
|
{
|
|
input: "blog.foo.com/bar/baz",
|
|
},
|
|
{
|
|
input: "asdf",
|
|
err: ErrRepositoryNameMissingHostname,
|
|
},
|
|
{
|
|
input: "aa/asdf$$^/aa",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "asdf$$^/aa",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "aa-a/aa",
|
|
},
|
|
{
|
|
input: "aa/aa",
|
|
},
|
|
{
|
|
input: "a-a/a-a",
|
|
},
|
|
{
|
|
input: "a",
|
|
err: ErrRepositoryNameMissingHostname,
|
|
},
|
|
{
|
|
input: "a/image",
|
|
},
|
|
{
|
|
input: "a-/a/a/a",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "a/a-/a/a/a",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
// total length = 255
|
|
input: "a/" + strings.Repeat("a", 253),
|
|
},
|
|
{
|
|
// total length = 256
|
|
input: "b/" + strings.Repeat("a", 254),
|
|
err: ErrRepositoryNameLong,
|
|
},
|
|
{
|
|
input: "-foo/bar",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "foo/bar-",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "foo-/bar",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "foo/-bar",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "_foo/bar",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "foo/bar_",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "____/____",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "_docker/_docker",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "docker_/docker_",
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "do__cker/docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "docker./docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: ".docker/docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "do..cker/docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "docker-/docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "-docker/docker",
|
|
err: ErrRepositoryNameComponentInvalid,
|
|
},
|
|
{
|
|
input: "xn--n3h.com/myimage", // http://☃.com in punycode
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "xn--7o8h.com/myimage", // http://🐳.com in punycode
|
|
err: ErrRepositoryNameHostnameInvalid,
|
|
invalid: true,
|
|
},
|
|
{
|
|
input: "b.gcr.io/test.example.com/my-app", // embedded domain component
|
|
},
|
|
{
|
|
input: "xn--n3h.com/myimage", // http://☃.com in punycode
|
|
},
|
|
{
|
|
input: "xn--7o8h.com/myimage", // http://🐳.com in punycode
|
|
},
|
|
{
|
|
input: "registry.io/foo/project--id.module--name.ver---sion--name", // image with hostname
|
|
},
|
|
}
|
|
)
|
|
|
|
// TestValidateRepositoryName tests the ValidateRepositoryName function,
|
|
// which uses RepositoryNameComponentAnchoredRegexp for validation
|
|
func TestValidateRepositoryName(t *testing.T) {
|
|
for _, testcase := range regexpTestcases {
|
|
failf := func(format string, v ...interface{}) {
|
|
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
t.Fail()
|
|
}
|
|
|
|
if _, err := NewRepository(testcase.input); err != testcase.err {
|
|
if testcase.err != nil {
|
|
if err != nil {
|
|
failf("unexpected error for invalid repository: got %v, expected %v", err, testcase.err)
|
|
} else {
|
|
failf("expected invalid repository: %v", testcase.err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
// Wrong error returned.
|
|
failf("unexpected error validating repository name: %v, expected %v", err, testcase.err)
|
|
} else {
|
|
failf("unexpected error validating repository name: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRepositoryNameRegexp(t *testing.T) {
|
|
AnchoredRepositoryNameRegexp := regexp.MustCompile(`^` + RepositoryNameRegexp.String() + `$`)
|
|
for _, testcase := range regexpTestcases {
|
|
failf := func(format string, v ...interface{}) {
|
|
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
t.Fail()
|
|
}
|
|
|
|
matches := AnchoredRepositoryNameRegexp.MatchString(testcase.input)
|
|
if matches == testcase.invalid {
|
|
if testcase.invalid {
|
|
failf("expected invalid repository name %s", testcase.input)
|
|
} else {
|
|
failf("expected valid repository name %s", testcase.input)
|
|
}
|
|
}
|
|
}
|
|
}
|