Merge pull request #689 from stevvooe/remove-repository-min-component-length
Allow single character repository namesmaster
						commit
						419bbc2da6
					
				| 
						 | 
					@ -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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue