reference: use named capturing groups
Rewrite the regular expressions to use named capturing groups. This simplifies handling the resulting matches, but does require some additional handling to associate matches with their names. Also making some changes to the matching to match how domains are _actually_ matched; some of that was already handled in code parsing the results of the regex, but now this is handled by the regex itself. Before: BenchmarkParse BenchmarkParse-10 12696 93805 ns/op 9311 B/op 185 allocs/op PASS After: BenchmarkParse BenchmarkParse-10 12486 94774 ns/op 18617 B/op 178 allocs/op PASS Benchstat: go test -run='^$' -bench=. -count=10 ./reference/ > old.txt go test -run='^$' -bench=. -count=10 ./reference/ > new.txt benchstat old.txt new.txt name old time/op new time/op delta Parse-10 91.7µs ± 0% 97.0µs ±11% +5.82% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Parse-10 9.32kB ± 0% 18.63kB ± 0% +99.93% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Parse-10 185 ± 0% 178 ± 0% -3.78% (p=0.000 n=10+10) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
8e29e870a4
commit
365c73379f
6 changed files with 145 additions and 117 deletions
|
@ -123,20 +123,23 @@ func ParseDockerRef(ref string) (Named, error) {
|
|||
// splitDockerDomain splits a repository name to domain and remote-name.
|
||||
// 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 && strings.ToLower(name[:i]) == name[:i]) {
|
||||
domain, remainder = defaultDomain, name
|
||||
} else {
|
||||
domain, remainder = name[:i], name[i+1:]
|
||||
func splitDockerDomain(name string) (domainName, remoteName string) {
|
||||
domainName, remoteName, ok := strings.Cut(name, "/")
|
||||
switch domainName {
|
||||
case legacyDefaultDomain:
|
||||
domainName = defaultDomain
|
||||
case defaultDomain, localhost:
|
||||
// done
|
||||
default:
|
||||
if !ok || (!strings.ContainsAny(domainName, ".:") && strings.ToLower(domainName) == domainName) {
|
||||
domainName = defaultDomain
|
||||
remoteName = name
|
||||
}
|
||||
}
|
||||
if domain == legacyDefaultDomain {
|
||||
domain = defaultDomain
|
||||
if domainName == defaultDomain && !strings.ContainsRune(remoteName, '/') {
|
||||
remoteName = officialRepoPrefix + remoteName
|
||||
}
|
||||
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
||||
remainder = officialRepoPrefix + remainder
|
||||
}
|
||||
return
|
||||
return domainName, remoteName
|
||||
}
|
||||
|
||||
// familiarizeName returns a shortened version of the name familiar
|
||||
|
|
|
@ -35,8 +35,6 @@ func TestValidateReferenceName(t *testing.T) {
|
|||
// 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",
|
||||
|
@ -53,6 +51,8 @@ func TestValidateReferenceName(t *testing.T) {
|
|||
"[fe80::1%eth0]:5000/debian",
|
||||
"[2001:db8:3:4::192.0.2.33]:5000/debian",
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
"Docker/docker",
|
||||
"DOCKER/docker",
|
||||
}
|
||||
|
||||
for _, name := range invalidRepoNames {
|
||||
|
@ -247,17 +247,17 @@ func TestParseRepositoryInfo(t *testing.T) {
|
|||
},
|
||||
{
|
||||
RemoteName: "bar",
|
||||
FamiliarName: "Foo/bar",
|
||||
FullName: "Foo/bar",
|
||||
FamiliarName: "Foo.com/bar",
|
||||
FullName: "Foo.com/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "Foo",
|
||||
Domain: "Foo.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "bar",
|
||||
FamiliarName: "FOO/bar",
|
||||
FullName: "FOO/bar",
|
||||
FamiliarName: "FOO.COM/bar",
|
||||
FullName: "FOO.COM/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "FOO",
|
||||
Domain: "FOO.COM",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -166,11 +166,8 @@ func Path(named Named) (name string) {
|
|||
}
|
||||
|
||||
func splitDomain(name string) (string, string) {
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
named, _ := getNamedMatches(anchoredNameRegexp, name)
|
||||
return named["domain"], named["repository"]
|
||||
}
|
||||
|
||||
// SplitHostname splits a named reference into a
|
||||
|
@ -189,8 +186,8 @@ func SplitHostname(named Named) (string, string) {
|
|||
// Parse parses s and returns a syntactically valid Reference.
|
||||
// If an error was encountered it is returned, along with a nil Reference.
|
||||
func Parse(s string) (Reference, error) {
|
||||
matches := ReferenceRegexp.FindStringSubmatch(s)
|
||||
if matches == nil {
|
||||
namedMatches, ok := getNamedMatches(ReferenceRegexp, s)
|
||||
if !ok {
|
||||
if s == "" {
|
||||
return nil, ErrNameEmpty
|
||||
}
|
||||
|
@ -200,28 +197,20 @@ func Parse(s string) (Reference, error) {
|
|||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
|
||||
if len(matches[1]) > NameTotalLengthMax {
|
||||
if len(namedMatches["name"]) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
|
||||
var repo repository
|
||||
|
||||
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
|
||||
if len(nameMatch) == 3 {
|
||||
repo.domain = nameMatch[1]
|
||||
repo.path = nameMatch[2]
|
||||
} else {
|
||||
repo.domain = ""
|
||||
repo.path = matches[1]
|
||||
}
|
||||
|
||||
ref := reference{
|
||||
namedRepository: repo,
|
||||
tag: matches[2],
|
||||
namedRepository: repository{
|
||||
domain: namedMatches["domain"],
|
||||
path: namedMatches["repository"],
|
||||
},
|
||||
tag: namedMatches["tag"],
|
||||
}
|
||||
if matches[3] != "" {
|
||||
if namedMatches["digest"] != "" {
|
||||
var err error
|
||||
ref.digest, err = digest.Parse(matches[3])
|
||||
ref.digest, err = digest.Parse(namedMatches["digest"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -101,12 +101,10 @@ func TestReferenceParse(t *testing.T) {
|
|||
input: "Uppercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
|
||||
// See https://github.com/distribution/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
|
||||
// {
|
||||
// input: "Uppercase/lowercase:tag",
|
||||
// err: ErrNameContainsUppercase,
|
||||
// },
|
||||
{
|
||||
input: "Uppercase/lowercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
{
|
||||
input: "test:5000/Uppercase/lowercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
|
@ -122,7 +120,6 @@ func TestReferenceParse(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
|
||||
domain: "a",
|
||||
repository: strings.Repeat("a/", 127) + "a",
|
||||
tag: "tag-puts-this-over-max",
|
||||
},
|
||||
|
@ -167,7 +164,6 @@ func TestReferenceParse(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "foo/foo_bar.com:8080",
|
||||
domain: "foo",
|
||||
repository: "foo/foo_bar.com",
|
||||
tag: "8080",
|
||||
},
|
||||
|
@ -541,7 +537,7 @@ func TestSerialization(t *testing.T) {
|
|||
|
||||
b2, err := json.Marshal(st)
|
||||
if err != nil {
|
||||
t.Errorf("error marshing serialization type: %v", err)
|
||||
t.Errorf("error marshaling serialization type: %v", err)
|
||||
}
|
||||
|
||||
if string(b) != string(b2) {
|
||||
|
|
|
@ -29,8 +29,8 @@ var IdentifierRegexp = regexp.MustCompile(identifier)
|
|||
var NameRegexp = regexp.MustCompile(namePat)
|
||||
|
||||
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||
// is anchored and has capturing groups for name, tag, and digest
|
||||
// components.
|
||||
// is anchored and has capturing groups for domain, name, repository, tag,
|
||||
// and digest components.
|
||||
var ReferenceRegexp = regexp.MustCompile(referencePat)
|
||||
|
||||
// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
|
||||
|
@ -60,9 +60,12 @@ const (
|
|||
// repository name to start with a component as defined by DomainRegexp.
|
||||
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
|
||||
|
||||
// port matches a port-number including the port separator (e.g. ":80").
|
||||
port = `(?::[0-9]+)`
|
||||
|
||||
// optionalPort matches an optional port-number including the port separator
|
||||
// (e.g. ":80").
|
||||
optionalPort = `(?::[0-9]+)?`
|
||||
optionalPort = port + `?`
|
||||
|
||||
// tag matches valid tag names. From docker/docker:graph/tags.go.
|
||||
tag = `[\w][\w.-]{0,127}`
|
||||
|
@ -96,14 +99,23 @@ var (
|
|||
// that may be part of image names. This is purposely a subset of what is
|
||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||
// names. This includes IPv4 addresses on decimal format.
|
||||
domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent)
|
||||
//
|
||||
// TODO(thaJeztah): disambiguate: docker requires domain-name to be either;
|
||||
// - localhost (special case)
|
||||
// - at least one "."
|
||||
// - or a ":port"
|
||||
//
|
||||
// Any other domain is considered a path-element.
|
||||
domainName = domainNameComponent + oneOrMore(`\.`+domainNameComponent)
|
||||
|
||||
domainWithPort = domainNameComponent + anyTimes(`\.`+domainNameComponent) + port
|
||||
|
||||
// host defines the structure of potential domains based on the URI
|
||||
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
|
||||
// or an IPv4 address in decimal format, or an IPv6 address between square
|
||||
// brackets (excluding zone identifiers as defined by rfc6874 or special
|
||||
// addresses such as IPv4-Mapped).
|
||||
host = `(?:` + domainName + `|` + ipv6address + `)`
|
||||
host = `(?:` + localhost + `|` + domainName + `|` + domainWithPort + `|` + ipv6address + optionalPort + `)`
|
||||
|
||||
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
|
||||
// compatibility with Docker image names.
|
||||
|
@ -127,13 +139,15 @@ var (
|
|||
//
|
||||
// pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu"
|
||||
remoteName = pathComponent + anyTimes(`/`+pathComponent)
|
||||
namePat = optional(domainAndPort+`/`) + remoteName
|
||||
|
||||
domainAndRepo = optional(capture("domain", domainAndPort), `/`) + capture("repository", remoteName)
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// domain and trailing components.
|
||||
anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName)))
|
||||
anchoredNameRegexp = regexp.MustCompile(anchored(domainAndRepo))
|
||||
|
||||
referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat)))
|
||||
namePat = optional(domainAndPort+`/`) + remoteName
|
||||
referencePat = anchored(capture("name", domainAndRepo), optional(`:`, capture("tag", tag)), optional(`@`, capture("digest", digestPat)))
|
||||
|
||||
// anchoredIdentifierRegexp is used to check or match an
|
||||
// identifier value, anchored at start and end of string.
|
||||
|
@ -152,12 +166,41 @@ func anyTimes(res ...string) string {
|
|||
return `(?:` + strings.Join(res, "") + `)*`
|
||||
}
|
||||
|
||||
// oneOrMore wraps the expression in a non-capturing group that can occur
|
||||
// one or more times.
|
||||
func oneOrMore(res ...string) string {
|
||||
return `(?:` + strings.Join(res, "") + `)+`
|
||||
}
|
||||
|
||||
// capture wraps the expression in a capturing group.
|
||||
func capture(res ...string) string {
|
||||
return `(` + strings.Join(res, "") + `)`
|
||||
func capture(name string, res ...string) string {
|
||||
return `(?P<` + name + `>` + strings.Join(res, "") + `)`
|
||||
}
|
||||
|
||||
// anchored anchors the regular expression by adding start and end delimiters.
|
||||
func anchored(res ...string) string {
|
||||
return `^` + strings.Join(res, "") + `$`
|
||||
}
|
||||
|
||||
// getNamedMatches returns a map containing matches for each named subexpression.
|
||||
// It panics if the given regular expression does not contain named subexpressions.
|
||||
func getNamedMatches(r *regexp.Regexp, input string) (namedMatches map[string]string, ok bool) {
|
||||
if len(r.SubexpNames()) == 0 {
|
||||
panic("regex does not have named subexpressions: " + r.String())
|
||||
}
|
||||
|
||||
matches := r.FindStringSubmatch(input)
|
||||
if ok = len(matches) > 0; !ok {
|
||||
return nil, false
|
||||
}
|
||||
namedMatches = make(map[string]string, len(matches))
|
||||
// We loop through matches here, in case there's optional named match-groups.
|
||||
for i, match := range matches {
|
||||
if i == 0 {
|
||||
// first entry is always the full string that was matched
|
||||
continue
|
||||
}
|
||||
namedMatches[r.SubexpNames()[i]] = match
|
||||
}
|
||||
return namedMatches, true
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -9,27 +10,24 @@ import (
|
|||
type regexpMatch struct {
|
||||
input string
|
||||
match bool
|
||||
subs []string
|
||||
named map[string]string
|
||||
}
|
||||
|
||||
func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
|
||||
t.Helper()
|
||||
matches := r.FindStringSubmatch(m.input)
|
||||
if m.match && matches != nil {
|
||||
if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
|
||||
t.Fatalf("Bad match result %#v for %q", matches, m.input)
|
||||
var matched bool
|
||||
if len(m.named) > 0 {
|
||||
var namedMatches map[string]string
|
||||
namedMatches, matched = getNamedMatches(r, m.input)
|
||||
if !reflect.DeepEqual(m.named, namedMatches) {
|
||||
t.Errorf("Named matches differ:\nExpected: %+v\nGot: %+v", m.named, namedMatches)
|
||||
}
|
||||
if len(matches) < (len(m.subs) + 1) {
|
||||
t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
|
||||
}
|
||||
for i := range m.subs {
|
||||
if m.subs[i] != matches[i+1] {
|
||||
t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
|
||||
}
|
||||
}
|
||||
} else if m.match {
|
||||
} else {
|
||||
matched = len(r.FindStringSubmatch(m.input)) > 0
|
||||
}
|
||||
if m.match && !matched {
|
||||
t.Errorf("Expected match for %q", m.input)
|
||||
} else if matches != nil {
|
||||
} else if !m.match && matched {
|
||||
t.Errorf("Unexpected match for %q", m.input)
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +60,7 @@ func TestDomainRegexp(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "a.b",
|
||||
|
@ -189,37 +187,37 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "short",
|
||||
match: true,
|
||||
subs: []string{"", "short"},
|
||||
named: map[string]string{"domain": "", "repository": "short"},
|
||||
},
|
||||
{
|
||||
input: "simple/name",
|
||||
match: true,
|
||||
subs: []string{"simple", "name"},
|
||||
named: map[string]string{"domain": "", "repository": "simple/name"},
|
||||
},
|
||||
{
|
||||
input: "library/ubuntu",
|
||||
match: true,
|
||||
subs: []string{"library", "ubuntu"},
|
||||
named: map[string]string{"domain": "", "repository": "library/ubuntu"},
|
||||
},
|
||||
{
|
||||
input: "docker/stevvooe/app",
|
||||
match: true,
|
||||
subs: []string{"docker", "stevvooe/app"},
|
||||
named: map[string]string{"domain": "", "repository": "docker/stevvooe/app"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
|
||||
named: map[string]string{"domain": "", "repository": "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/bb/bb/bb"},
|
||||
named: map[string]string{"domain": "", "repository": "aa/aa/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a",
|
||||
match: true,
|
||||
subs: []string{"a", "a/a/a"},
|
||||
named: map[string]string{"domain": "", "repository": "a/a/a/a"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a/",
|
||||
|
@ -232,22 +230,22 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
subs: []string{"", "a"},
|
||||
named: map[string]string{"domain": "", "repository": "a"},
|
||||
},
|
||||
{
|
||||
input: "a/aa",
|
||||
match: true,
|
||||
subs: []string{"a", "aa"},
|
||||
named: map[string]string{"domain": "", "repository": "a/aa"},
|
||||
},
|
||||
{
|
||||
input: "a/aa/a",
|
||||
match: true,
|
||||
subs: []string{"a", "aa/a"},
|
||||
named: map[string]string{"domain": "", "repository": "a/aa/a"},
|
||||
},
|
||||
{
|
||||
input: "foo.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo.com"},
|
||||
named: map[string]string{"domain": "", "repository": "foo.com"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/",
|
||||
|
@ -256,7 +254,7 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "foo.com:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com:8080", "bar"},
|
||||
named: map[string]string{"domain": "foo.com:8080", "repository": "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com:http/bar",
|
||||
|
@ -265,27 +263,27 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "foo.com/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar"},
|
||||
named: map[string]string{"domain": "foo.com", "repository": "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar/baz"},
|
||||
named: map[string]string{"domain": "foo.com", "repository": "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080", "bar"},
|
||||
named: map[string]string{"domain": "localhost:8080", "repository": "bar"},
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
||||
match: true,
|
||||
subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
|
||||
named: map[string]string{"domain": "sub-dom1.foo.com", "repository": "bar/baz/quux"},
|
||||
},
|
||||
{
|
||||
input: "blog.foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"blog.foo.com", "bar/baz"},
|
||||
named: map[string]string{"domain": "blog.foo.com", "repository": "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "a^a",
|
||||
|
@ -302,12 +300,12 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "aa-a/a",
|
||||
match: true,
|
||||
subs: []string{"aa-a", "a"},
|
||||
named: map[string]string{"domain": "", "repository": "aa-a/a"},
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a",
|
||||
match: true,
|
||||
subs: []string{"a", strings.Repeat("a/", 127) + "a"},
|
||||
named: map[string]string{"domain": "", "repository": strings.Repeat("a/", 128) + "a"},
|
||||
},
|
||||
{
|
||||
input: "a-/a/a/a",
|
||||
|
@ -340,12 +338,12 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "foo_bar",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar"},
|
||||
named: map[string]string{"domain": "", "repository": "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar.com"},
|
||||
named: map[string]string{"domain": "", "repository": "foo_bar.com"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080",
|
||||
|
@ -358,7 +356,7 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "foo.com/foo_bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "foo_bar"},
|
||||
named: map[string]string{"domain": "foo.com", "repository": "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "____/____",
|
||||
|
@ -375,27 +373,27 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
named: map[string]string{"domain": "b.gcr.io", "repository": "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/myimage", // ☃.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--n3h.com", "myimage"},
|
||||
named: map[string]string{"domain": "xn--n3h.com", "repository": "myimage"},
|
||||
},
|
||||
{
|
||||
input: "xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--7o8h.com", "myimage"},
|
||||
named: map[string]string{"domain": "xn--7o8h.com", "repository": "myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"example.com", "xn--7o8h.com/myimage"},
|
||||
named: map[string]string{"domain": "example.com", "repository": "xn--7o8h.com/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/some_separator__underscore/myimage",
|
||||
match: true,
|
||||
subs: []string{"example.com", "some_separator__underscore/myimage"},
|
||||
named: map[string]string{"domain": "example.com", "repository": "some_separator__underscore/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/__underscore/myimage",
|
||||
|
@ -444,17 +442,17 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
{
|
||||
input: "do__cker/docker",
|
||||
match: true,
|
||||
subs: []string{"", "do__cker/docker"},
|
||||
named: map[string]string{"domain": "", "repository": "do__cker/docker"},
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
named: map[string]string{"domain": "b.gcr.io", "repository": "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "registry.io/foo/project--id.module--name.ver---sion--name",
|
||||
match: true,
|
||||
subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
|
||||
named: map[string]string{"domain": "registry.io", "repository": "foo/project--id.module--name.ver---sion--name"},
|
||||
},
|
||||
{
|
||||
input: "Asdf.com/foo/bar", // uppercase character in hostname
|
||||
|
@ -476,26 +474,25 @@ func TestFullNameRegexp(t *testing.T) {
|
|||
|
||||
func TestReferenceRegexp(t *testing.T) {
|
||||
t.Parallel()
|
||||
if ReferenceRegexp.NumSubexp() != 3 {
|
||||
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
|
||||
ReferenceRegexp, ReferenceRegexp.NumSubexp())
|
||||
if ReferenceRegexp.NumSubexp() != 5 {
|
||||
t.Fatalf("anchored name regexp should have five submatches: %v, %v != 5", ReferenceRegexp, ReferenceRegexp.NumSubexp())
|
||||
}
|
||||
|
||||
tests := []regexpMatch{
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag", ""},
|
||||
named: map[string]string{"domain": "registry.com:8080", "name": "registry.com:8080/myapp", "repository": "myapp", "tag": "tag", "digest": ""},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
named: map[string]string{"domain": "registry.com:8080", "name": "registry.com:8080/myapp", "repository": "myapp", "tag": "", "digest": "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
named: map[string]string{"domain": "registry.com:8080", "name": "registry.com:8080/myapp", "repository": "myapp", "tag": "tag2", "digest": "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:badbadbadbad",
|
||||
|
@ -513,12 +510,12 @@ func TestReferenceRegexp(t *testing.T) {
|
|||
input:// localhost treated as name, missing tag with 8080 as tag
|
||||
"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
named: map[string]string{"domain": "", "name": "localhost", "repository": "localhost", "tag": "8080", "digest": "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
named: map[string]string{"domain": "localhost:8080", "name": "localhost:8080/name", "repository": "name", "tag": "", "digest": "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
|
@ -528,7 +525,7 @@ func TestReferenceRegexp(t *testing.T) {
|
|||
// localhost will be treated as an image name without a host
|
||||
input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
named: map[string]string{"domain": "", "name": "localhost", "repository": "localhost", "tag": "", "digest": "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@bad",
|
||||
|
|
Loading…
Reference in a new issue