Allow single character repository names

The main goal of this changeset is to allow repository name components to
consist of a single character. The number of components allowed and the slash
separation requirements have also been clarified.

To go along with this simplification, errant constants and unneeded error types
have been removed.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-07-10 12:00:06 -06:00
parent bdcae0597f
commit b54cc5ed47
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()
} }
} }