Move names regular expressions to api/v2 packages

Because the repository name definitions are part of the v2 specification, they
have been moved out of the common package. This is part of the effort to break
up the common package into more sensible components.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-01-05 16:44:03 -08:00
parent 8be20212f1
commit d88884c51c
3 changed files with 9 additions and 10 deletions

View file

@ -4,7 +4,6 @@ import (
"net/http"
"regexp"
"github.com/docker/distribution/common"
"github.com/docker/distribution/digest"
)
@ -12,7 +11,7 @@ var (
nameParameterDescriptor = ParameterDescriptor{
Name: "name",
Type: "string",
Format: common.RepositoryNameRegexp.String(),
Format: RepositoryNameRegexp.String(),
Required: true,
Description: `Name of the target repository.`,
}
@ -20,7 +19,7 @@ var (
tagParameterDescriptor = ParameterDescriptor{
Name: "tag",
Type: "string",
Format: common.TagNameRegexp.String(),
Format: TagNameRegexp.String(),
Required: true,
Description: `Tag of the target manifiest.`,
}
@ -307,7 +306,7 @@ var routeDescriptors = []RouteDescriptor{
},
{
Name: RouteNameTags,
Path: "/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list",
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list",
Entity: "Tags",
Description: "Retrieve information about tags.",
Methods: []MethodDescriptor{
@ -352,7 +351,7 @@ var routeDescriptors = []RouteDescriptor{
},
{
Name: RouteNameManifest,
Path: "/v2/{name:" + common.RepositoryNameRegexp.String() + "}/manifests/{tag:" + common.TagNameRegexp.String() + "}",
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{tag:" + TagNameRegexp.String() + "}",
Entity: "Manifest",
Description: "Create, update and retrieve manifests.",
Methods: []MethodDescriptor{
@ -514,7 +513,7 @@ var routeDescriptors = []RouteDescriptor{
{
Name: RouteNameBlob,
Path: "/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}",
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}",
Entity: "Blob",
Description: "Fetch the blob identified by `name` and `digest`. Used to fetch layers by tarsum digest.",
Methods: []MethodDescriptor{
@ -592,7 +591,7 @@ var routeDescriptors = []RouteDescriptor{
{
Name: RouteNameBlobUpload,
Path: "/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/",
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/",
Entity: "Intiate Blob Upload",
Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.",
Methods: []MethodDescriptor{
@ -700,7 +699,7 @@ var routeDescriptors = []RouteDescriptor{
{
Name: RouteNameBlobUploadChunk,
Path: "/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}",
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}",
Entity: "Blob Upload",
Description: "Interact with blob uploads. Clients should never assemble URLs for this endpoint and should only take it through the `Location` header on related API requests.",
Methods: []MethodDescriptor{

115
api/v2/names.go Normal file
View file

@ -0,0 +1,115 @@
package v2
import (
"fmt"
"regexp"
"strings"
)
const (
// RepositoryNameComponentMinLength is the minimum number of characters in a
// single repository name slash-delimited component
RepositoryNameComponentMinLength = 2
// RepositoryNameComponentMaxLength is the maximum number of characters in a
// single repository name slash-delimited component
RepositoryNameComponentMaxLength = 30
// RepositoryNameMinComponents is the minimum number of slash-delimited
// components that a repository name must have
RepositoryNameMinComponents = 2
// RepositoryNameMaxComponents is the maximum number of slash-delimited
// components that a repository name must have
RepositoryNameMaxComponents = 5
// RepositoryNameTotalLengthMax is the maximum total number of characters in
// a repository name
RepositoryNameTotalLengthMax = 255
)
// RepositoryNameComponentRegexp restricts registtry path components names to
// start with at least two letters or numbers, with following parts able to
// separated by one period, dash or underscore.
var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`)
// RepositoryNameComponentAnchoredRegexp is the version of
// RepositoryNameComponentRegexp which must completely match the content
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
// TODO(stevvooe): RepositoryName needs to be limited to some fixed length.
// Looking path prefixes and s3 limitation of 1024, this should likely be
// around 512 bytes. 256 bytes might be more manageable.
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 2 to
// 5 path components, separated by a forward slash.
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){1,4}` + RepositoryNameComponentRegexp.String())
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
// TODO(stevvooe): Contribute these exports back to core, so they are shared.
var (
// ErrRepositoryNameComponentShort is returned when a repository name
// contains a component which is shorter than
// RepositoryNameComponentMinLength
ErrRepositoryNameComponentShort = fmt.Errorf("respository name component must be %v or more characters", RepositoryNameComponentMinLength)
// ErrRepositoryNameComponentLong is returned when a repository name
// contains a component which is longer than
// RepositoryNameComponentMaxLength
ErrRepositoryNameComponentLong = fmt.Errorf("respository name component must be %v characters or less", RepositoryNameComponentMaxLength)
// ErrRepositoryNameMissingComponents is returned when a repository name
// contains fewer than RepositoryNameMinComponents components
ErrRepositoryNameMissingComponents = fmt.Errorf("repository name must have at least %v components", RepositoryNameMinComponents)
// ErrRepositoryNameTooManyComponents is returned when a repository name
// contains more than RepositoryNameMaxComponents components
ErrRepositoryNameTooManyComponents = fmt.Errorf("repository name %v or less components", RepositoryNameMaxComponents)
// ErrRepositoryNameLong is returned when a repository name is longer than
// RepositoryNameTotalLengthMax
ErrRepositoryNameLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax)
// ErrRepositoryNameComponentInvalid is returned when a repository name does
// not match RepositoryNameComponentRegexp
ErrRepositoryNameComponentInvalid = fmt.Errorf("repository name component must match %q", RepositoryNameComponentRegexp.String())
)
// ValidateRespositoryName ensures the repository name is valid for use in the
// registry. This function accepts a superset of what might be accepted by
// docker core or docker hub. If the name does not pass validation, an error,
// describing the conditions, is returned.
func ValidateRespositoryName(name string) error {
if len(name) > RepositoryNameTotalLengthMax {
return ErrRepositoryNameLong
}
components := strings.Split(name, "/")
if len(components) < RepositoryNameMinComponents {
return ErrRepositoryNameMissingComponents
}
if len(components) > RepositoryNameMaxComponents {
return ErrRepositoryNameTooManyComponents
}
for _, component := range components {
if len(component) < RepositoryNameComponentMinLength {
return ErrRepositoryNameComponentShort
}
if len(component) > RepositoryNameComponentMaxLength {
return ErrRepositoryNameComponentLong
}
if !RepositoryNameComponentAnchoredRegexp.MatchString(component) {
return ErrRepositoryNameComponentInvalid
}
}
return nil
}

91
api/v2/names_test.go Normal file
View file

@ -0,0 +1,91 @@
package v2
import (
"testing"
)
func TestRepositoryNameRegexp(t *testing.T) {
for _, testcase := range []struct {
input string
err error
}{
{
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",
err: ErrRepositoryNameTooManyComponents,
},
{
input: "aa/aa/bb/bb/bb",
},
{
input: "a/a/a/b/b",
err: ErrRepositoryNameComponentShort,
},
{
input: "a/a/a/a/",
err: ErrRepositoryNameComponentShort,
},
{
input: "foo.com/bar/baz",
},
{
input: "blog.foo.com/bar/baz",
},
{
input: "asdf",
err: ErrRepositoryNameMissingComponents,
},
{
input: "asdf$$^/aa",
err: ErrRepositoryNameComponentInvalid,
},
{
input: "aa-a/aa",
},
{
input: "aa/aa",
},
{
input: "a-a/a-a",
},
{
input: "a",
err: ErrRepositoryNameMissingComponents,
},
{
input: "a-/a/a/a",
err: ErrRepositoryNameComponentInvalid,
},
} {
failf := func(format string, v ...interface{}) {
t.Logf(testcase.input+": "+format, v...)
t.Fail()
}
if err := ValidateRespositoryName(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)
}
}
}
}
}