Merge pull request #689 from stevvooe/remove-repository-min-component-length

Allow single character repository names
This commit is contained in:
Stephen Day 2015-07-10 12:27:13 -06:00
commit 419bbc2da6
5 changed files with 57 additions and 46 deletions

View file

@ -120,6 +120,12 @@ indicating what is different. Optionally, we may start marking parts of the
specification to correspond with the versions enumerated here. specification to correspond with the versions enumerated here.
<dl> <dl>
<dt>2.0.3</dt>
<dd>
<li>Allow repository name components to be one character.</li>
<li>Clarified that single component names are allowed.</li>
</dd>
<dt>2.0.2</dt> <dt>2.0.2</dt>
<dd> <dd>
<li>Added section covering digest format.</li> <li>Added section covering digest format.</li>
@ -174,12 +180,11 @@ path component is less than 30 characters. The V2 registry API does not
enforce this. The rules for a repository name are as follows: enforce this. The rules for a repository name are as follows:
1. A repository name is broken up into _path components_. A component of a 1. A repository name is broken up into _path components_. A component of a
repository name must be at least two lowercase, alpha-numeric characters, repository name must be at least one lowercase, alpha-numeric characters,
optionally separated by periods, dashes or underscores. More strictly, it optionally separated by periods, dashes or underscores. More strictly, it
must match the regular expression `[a-z0-9]+(?:[._-][a-z0-9]+)*` and the must match the regular expression `[a-z0-9]+(?:[._-][a-z0-9]+)*`.
matched result must be 2 or more characters in length. 2. If a repository name has two or more path components, they must be
2. The name of a repository must have at least two path components, separated separated by a forward slash ("/").
by a forward slash.
3. The total length of a repository name, including slashes, must be less the 3. The total length of a repository name, including slashes, must be less the
256 characters. 256 characters.

View file

@ -120,6 +120,12 @@ indicating what is different. Optionally, we may start marking parts of the
specification to correspond with the versions enumerated here. specification to correspond with the versions enumerated here.
<dl> <dl>
<dt>2.0.3</dt>
<dd>
<li>Allow repository name components to be one character.</li>
<li>Clarified that single component names are allowed.</li>
</dd>
<dt>2.0.2</dt> <dt>2.0.2</dt>
<dd> <dd>
<li>Added section covering digest format.</li> <li>Added section covering digest format.</li>
@ -174,12 +180,11 @@ path component is less than 30 characters. The V2 registry API does not
enforce this. The rules for a repository name are as follows: enforce this. The rules for a repository name are as follows:
1. A repository name is broken up into _path components_. A component of a 1. A repository name is broken up into _path components_. A component of a
repository name must be at least two lowercase, alpha-numeric characters, repository name must be at least one lowercase, alpha-numeric characters,
optionally separated by periods, dashes or underscores. More strictly, it optionally separated by periods, dashes or underscores. More strictly, it
must match the regular expression `[a-z0-9]+(?:[._-][a-z0-9]+)*` and the must match the regular expression `[a-z0-9]+(?:[._-][a-z0-9]+)*`.
matched result must be 2 or more characters in length. 2. If a repository name has two or more path components, they must be
2. The name of a repository must have at least two path components, separated separated by a forward slash ("/").
by a forward slash.
3. The total length of a repository name, including slashes, must be less the 3. The total length of a repository name, including slashes, must be less the
256 characters. 256 characters.

View file

@ -6,19 +6,10 @@ import (
"strings" "strings"
) )
// TODO(stevvooe): Move these definitions back to an exported package. While // TODO(stevvooe): Move these definitions to the future "reference" package.
// they are used with v2 definitions, their relevance expands beyond. // While they are used with v2 definitions, their relevance expands beyond.
// "distribution/names" is a candidate package.
const ( const (
// RepositoryNameComponentMinLength is the minimum number of characters in a
// single repository name slash-delimited component
RepositoryNameComponentMinLength = 2
// RepositoryNameMinComponents is the minimum number of slash-delimited
// components that a repository name must have
RepositoryNameMinComponents = 1
// RepositoryNameTotalLengthMax is the maximum total number of characters in // RepositoryNameTotalLengthMax is the maximum total number of characters in
// a repository name // a repository name
RepositoryNameTotalLengthMax = 255 RepositoryNameTotalLengthMax = 255
@ -40,17 +31,13 @@ var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentReg
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. // TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
// TODO(stevvooe): Contribute these exports back to core, so they are shared. // TagNameAnchoredRegexp matches valid tag names, anchored at the start and
// end of the matched string.
var TagNameAnchoredRegexp = regexp.MustCompile("^" + TagNameRegexp.String() + "$")
var ( var (
// ErrRepositoryNameComponentShort is returned when a repository name // ErrRepositoryNameEmpty is returned for empty, invalid repository names.
// contains a component which is shorter than ErrRepositoryNameEmpty = fmt.Errorf("repository name must have at least one component")
// RepositoryNameComponentMinLength
ErrRepositoryNameComponentShort = fmt.Errorf("repository name component must be %v or more characters", RepositoryNameComponentMinLength)
// ErrRepositoryNameMissingComponents is returned when a repository name
// contains fewer than RepositoryNameMinComponents components
ErrRepositoryNameMissingComponents = fmt.Errorf("repository name must have at least %v components", RepositoryNameMinComponents)
// ErrRepositoryNameLong is returned when a repository name is longer than // ErrRepositoryNameLong is returned when a repository name is longer than
// RepositoryNameTotalLengthMax // RepositoryNameTotalLengthMax
@ -76,21 +63,17 @@ var (
// The result of the production, known as the "namespace", should be limited // The result of the production, known as the "namespace", should be limited
// to 255 characters. // to 255 characters.
func ValidateRepositoryName(name string) error { func ValidateRepositoryName(name string) error {
if name == "" {
return ErrRepositoryNameEmpty
}
if len(name) > RepositoryNameTotalLengthMax { if len(name) > RepositoryNameTotalLengthMax {
return ErrRepositoryNameLong return ErrRepositoryNameLong
} }
components := strings.Split(name, "/") components := strings.Split(name, "/")
if len(components) < RepositoryNameMinComponents {
return ErrRepositoryNameMissingComponents
}
for _, component := range components { for _, component := range components {
if len(component) < RepositoryNameComponentMinLength {
return ErrRepositoryNameComponentShort
}
if !RepositoryNameComponentAnchoredRegexp.MatchString(component) { if !RepositoryNameComponentAnchoredRegexp.MatchString(component) {
return ErrRepositoryNameComponentInvalid return ErrRepositoryNameComponentInvalid
} }

View file

@ -1,6 +1,7 @@
package v2 package v2
import ( import (
"strconv"
"strings" "strings"
"testing" "testing"
) )
@ -10,6 +11,10 @@ func TestRepositoryNameRegexp(t *testing.T) {
input string input string
err error err error
}{ }{
{
input: "",
err: ErrRepositoryNameEmpty,
},
{ {
input: "short", input: "short",
}, },
@ -30,11 +35,26 @@ func TestRepositoryNameRegexp(t *testing.T) {
}, },
{ {
input: "a/a/a/b/b", input: "a/a/a/b/b",
err: ErrRepositoryNameComponentShort,
}, },
{ {
input: "a/a/a/a/", input: "a/a/a/a/",
err: ErrRepositoryNameComponentShort, err: ErrRepositoryNameComponentInvalid,
},
{
input: "a//a/a",
err: ErrRepositoryNameComponentInvalid,
},
{
input: "a",
},
{
input: "a/aa",
},
{
input: "aa/a",
},
{
input: "a/aa/a",
}, },
{ {
input: "foo.com/bar/baz", input: "foo.com/bar/baz",
@ -58,10 +78,6 @@ func TestRepositoryNameRegexp(t *testing.T) {
{ {
input: "a-a/a-a", input: "a-a/a-a",
}, },
{
input: "a",
err: ErrRepositoryNameComponentShort,
},
{ {
input: "a-/a/a/a", input: "a-/a/a/a",
err: ErrRepositoryNameComponentInvalid, err: ErrRepositoryNameComponentInvalid,
@ -110,9 +126,8 @@ func TestRepositoryNameRegexp(t *testing.T) {
err: ErrRepositoryNameComponentInvalid, err: ErrRepositoryNameComponentInvalid,
}, },
} { } {
failf := func(format string, v ...interface{}) { failf := func(format string, v ...interface{}) {
t.Logf(testcase.input+": "+format, v...) t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail() t.Fail()
} }

View file

@ -263,6 +263,7 @@ func checkTestRouter(t *testing.T, testCases []routeTestCase, prefix string, dee
} }
if testcase.StatusCode != http.StatusOK { if testcase.StatusCode != http.StatusOK {
resp.Body.Close()
// We don't care about json response. // We don't care about json response.
continue continue
} }
@ -291,6 +292,8 @@ func checkTestRouter(t *testing.T, testCases []routeTestCase, prefix string, dee
if deeplyEqual && !reflect.DeepEqual(actualRouteInfo, testcase) { if deeplyEqual && !reflect.DeepEqual(actualRouteInfo, testcase) {
t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase) t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase)
} }
resp.Body.Close()
} }
} }