Add a new reference package abstracting repositories, tags and digests
There seems to be a need for a type that represents a way of pointing to an image, irrespective of the implementation. This patch defines a Reference interface and provides 3 implementations: - TagReference: when only a tag is provided - DigestReference: when a digest (according to the digest package) is provided, can include optional tag as well Validation of references are purely syntactic. There is also a strong type for tags, analogous to digests, as well as a strong type for Repository from which clients can access the hostname alone, or the repository name without the hostname, or both together via the String() method. For Repository, the files names.go and names_test.go were moved from the v2 package. Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
parent
d5ca577ad1
commit
b72f1fd2e3
7 changed files with 28 additions and 378 deletions
|
@ -5,6 +5,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ var (
|
||||||
nameParameterDescriptor = ParameterDescriptor{
|
nameParameterDescriptor = ParameterDescriptor{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Format: RepositoryNameRegexp.String(),
|
Format: reference.RepositoryNameRegexp.String(),
|
||||||
Required: true,
|
Required: true,
|
||||||
Description: `Name of the target repository.`,
|
Description: `Name of the target repository.`,
|
||||||
}
|
}
|
||||||
|
@ -20,7 +21,7 @@ var (
|
||||||
referenceParameterDescriptor = ParameterDescriptor{
|
referenceParameterDescriptor = ParameterDescriptor{
|
||||||
Name: "reference",
|
Name: "reference",
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Format: TagNameRegexp.String(),
|
Format: reference.TagRegexp.String(),
|
||||||
Required: true,
|
Required: true,
|
||||||
Description: `Tag or digest of the target manifest.`,
|
Description: `Tag or digest of the target manifest.`,
|
||||||
}
|
}
|
||||||
|
@ -389,7 +390,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: RouteNameTags,
|
Name: RouteNameTags,
|
||||||
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list",
|
Path: "/v2/{name:" + reference.RepositoryNameRegexp.String() + "}/tags/list",
|
||||||
Entity: "Tags",
|
Entity: "Tags",
|
||||||
Description: "Retrieve information about tags.",
|
Description: "Retrieve information about tags.",
|
||||||
Methods: []MethodDescriptor{
|
Methods: []MethodDescriptor{
|
||||||
|
@ -517,7 +518,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: RouteNameManifest,
|
Name: RouteNameManifest,
|
||||||
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + digest.DigestRegexp.String() + "}",
|
Path: "/v2/{name:" + reference.RepositoryNameRegexp.String() + "}/manifests/{reference:" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + "}",
|
||||||
Entity: "Manifest",
|
Entity: "Manifest",
|
||||||
Description: "Create, update, delete and retrieve manifests.",
|
Description: "Create, update, delete and retrieve manifests.",
|
||||||
Methods: []MethodDescriptor{
|
Methods: []MethodDescriptor{
|
||||||
|
@ -782,7 +783,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
|
|
||||||
{
|
{
|
||||||
Name: RouteNameBlob,
|
Name: RouteNameBlob,
|
||||||
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}",
|
Path: "/v2/{name:" + reference.RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}",
|
||||||
Entity: "Blob",
|
Entity: "Blob",
|
||||||
Description: "Operations on blobs identified by `name` and `digest`. Used to fetch or delete layers by digest.",
|
Description: "Operations on blobs identified by `name` and `digest`. Used to fetch or delete layers by digest.",
|
||||||
Methods: []MethodDescriptor{
|
Methods: []MethodDescriptor{
|
||||||
|
@ -1006,7 +1007,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
|
|
||||||
{
|
{
|
||||||
Name: RouteNameBlobUpload,
|
Name: RouteNameBlobUpload,
|
||||||
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/",
|
Path: "/v2/{name:" + reference.RepositoryNameRegexp.String() + "}/blobs/uploads/",
|
||||||
Entity: "Initiate Blob Upload",
|
Entity: "Initiate Blob Upload",
|
||||||
Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.",
|
Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.",
|
||||||
Methods: []MethodDescriptor{
|
Methods: []MethodDescriptor{
|
||||||
|
@ -1128,7 +1129,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
|
|
||||||
{
|
{
|
||||||
Name: RouteNameBlobUploadChunk,
|
Name: RouteNameBlobUploadChunk,
|
||||||
Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid:[a-zA-Z0-9-_.=]+}",
|
Path: "/v2/{name:" + reference.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid:[a-zA-Z0-9-_.=]+}",
|
||||||
Entity: "Blob Upload",
|
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. The `Location` header and its parameters should be preserved by clients, using the latest value returned via upload related API calls.",
|
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. The `Location` header and its parameters should be preserved by clients, using the latest value returned via upload related API calls.",
|
||||||
Methods: []MethodDescriptor{
|
Methods: []MethodDescriptor{
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(stevvooe): Move these definitions to the future "reference" package.
|
|
||||||
// While they are used with v2 definitions, their relevance expands beyond.
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RepositoryNameTotalLengthMax is the maximum total number of characters in
|
|
||||||
// a repository name
|
|
||||||
RepositoryNameTotalLengthMax = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
// domainLabelRegexp represents the following RFC-2396 BNF construct:
|
|
||||||
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
|
|
||||||
var domainLabelRegexp = regexp.MustCompile(`[a-z0-9](?:-*[a-z0-9])*`)
|
|
||||||
|
|
||||||
// RepositoryNameComponentRegexp restricts registry path component names to
|
|
||||||
// the allow valid hostnames according to: https://www.ietf.org/rfc/rfc2396.txt
|
|
||||||
// with the following differences:
|
|
||||||
// 1) It DOES NOT allow for fully-qualified domain names, which include a
|
|
||||||
// trailing '.', e.g. "google.com."
|
|
||||||
// 2) It DOES NOT restrict 'top-level' domain labels to start with just alpha
|
|
||||||
// characters.
|
|
||||||
// 3) It DOES allow for underscores to appear in the same situations as dots.
|
|
||||||
//
|
|
||||||
// RFC-2396 uses the BNF construct:
|
|
||||||
// hostname = *( domainlabel "." ) toplabel [ "." ]
|
|
||||||
var RepositoryNameComponentRegexp = regexp.MustCompile(
|
|
||||||
domainLabelRegexp.String() + `(?:[._]` + domainLabelRegexp.String() + `)*`)
|
|
||||||
|
|
||||||
// RepositoryNameComponentAnchoredRegexp is the version of
|
|
||||||
// RepositoryNameComponentRegexp which must completely match the content
|
|
||||||
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
|
|
||||||
|
|
||||||
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow
|
|
||||||
// multiple path components, separated by a forward slash.
|
|
||||||
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/)*` + RepositoryNameComponentRegexp.String())
|
|
||||||
|
|
||||||
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
|
||||||
var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
|
|
||||||
|
|
||||||
// TagNameAnchoredRegexp matches valid tag names, anchored at the start and
|
|
||||||
// end of the matched string.
|
|
||||||
var TagNameAnchoredRegexp = regexp.MustCompile("^" + TagNameRegexp.String() + "$")
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrRepositoryNameEmpty is returned for empty, invalid repository names.
|
|
||||||
ErrRepositoryNameEmpty = fmt.Errorf("repository name must have at least one component")
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateRepositoryName 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.
|
|
||||||
//
|
|
||||||
// Effectively, the name should comply with the following grammar:
|
|
||||||
//
|
|
||||||
// alpha-numeric := /[a-z0-9]+/
|
|
||||||
// separator := /[._-]/
|
|
||||||
// component := alpha-numeric [separator alpha-numeric]*
|
|
||||||
// namespace := component ['/' component]*
|
|
||||||
//
|
|
||||||
// The result of the production, known as the "namespace", should be limited
|
|
||||||
// to 255 characters.
|
|
||||||
func ValidateRepositoryName(name string) error {
|
|
||||||
if name == "" {
|
|
||||||
return ErrRepositoryNameEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(name) > RepositoryNameTotalLengthMax {
|
|
||||||
return ErrRepositoryNameLong
|
|
||||||
}
|
|
||||||
|
|
||||||
components := strings.Split(name, "/")
|
|
||||||
|
|
||||||
for _, component := range components {
|
|
||||||
if !RepositoryNameComponentAnchoredRegexp.MatchString(component) {
|
|
||||||
return ErrRepositoryNameComponentInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,256 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// regexpTestcases is a unified set of testcases for
|
|
||||||
// TestValidateRepositoryName and TestRepositoryNameRegexp.
|
|
||||||
// Some of them are valid inputs for one and not the other.
|
|
||||||
regexpTestcases = []struct {
|
|
||||||
// input is the repository name or name component testcase
|
|
||||||
input string
|
|
||||||
// err is the error expected from ValidateRepositoryName, or nil
|
|
||||||
err error
|
|
||||||
// invalid should be true if the testcase is *not* expected to
|
|
||||||
// match RepositoryNameRegexp
|
|
||||||
invalid bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
err: ErrRepositoryNameEmpty,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "short",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/aa/bb/bb/bb",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/a/a/b/b",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/a/a/a/",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a//a/a",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/aa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a/aa/a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// TODO: this testcase should be valid once we switch to
|
|
||||||
// the reference package.
|
|
||||||
input: "foo.com:8080/bar",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/bar/baz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo.com/bar/baz/quux",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "blog.foo.com/bar/baz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "asdf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "asdf$$^/aa",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa-a/aa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "aa/aa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a-a/a-a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "a-/a/a/a",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a", 255),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: strings.Repeat("a", 256),
|
|
||||||
err: ErrRepositoryNameLong,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "-foo/bar",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/bar-",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo-/bar",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/-bar",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "_foo/bar",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo/bar_",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "____/____",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "_docker/_docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker_/docker_",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "do__cker/docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker./docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: ".docker/docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "do..cker/docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "docker-/docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "-docker/docker",
|
|
||||||
err: ErrRepositoryNameComponentInvalid,
|
|
||||||
invalid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "b.gcr.io/test.example.com/my-app", // embedded domain component
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--n3h.com/myimage", // http://☃.com in punycode
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "xn--7o8h.com/myimage", // http://🐳.com in punycode
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "registry.io/foo/project--id.module--name.ver---sion--name", // image with hostname
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestValidateRepositoryName tests the ValidateRepositoryName function,
|
|
||||||
// which uses RepositoryNameComponentAnchoredRegexp for validation
|
|
||||||
func TestValidateRepositoryName(t *testing.T) {
|
|
||||||
for _, testcase := range regexpTestcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ValidateRepositoryName(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepositoryNameRegexp(t *testing.T) {
|
|
||||||
for _, testcase := range regexpTestcases {
|
|
||||||
failf := func(format string, v ...interface{}) {
|
|
||||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := RepositoryNameRegexp.FindString(testcase.input) == testcase.input
|
|
||||||
if matches == testcase.invalid {
|
|
||||||
if testcase.invalid {
|
|
||||||
failf("expected invalid repository name %s", testcase.input)
|
|
||||||
} else {
|
|
||||||
failf("expected valid repository name %s", testcase.input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
|
@ -96,9 +97,9 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
|
||||||
return numFilled, returnErr
|
return numFilled, returnErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepository creates a new Repository for the given repository name and base URL
|
// NewRepository creates a new Repository for the given canonical repository name and base URL.
|
||||||
func NewRepository(ctx context.Context, name, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
func NewRepository(ctx context.Context, canonicalName, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
||||||
if err := v2.ValidateRepositoryName(name); err != nil {
|
if _, err := reference.NewRepository(canonicalName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +116,7 @@ func NewRepository(ctx context.Context, name, baseURL string, transport http.Rou
|
||||||
return &repository{
|
return &repository{
|
||||||
client: client,
|
client: client,
|
||||||
ub: ub,
|
ub: ub,
|
||||||
name: name,
|
name: canonicalName,
|
||||||
context: ctx,
|
context: ctx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
10
docs/storage/cache/memory/memory.go
vendored
10
docs/storage/cache/memory/memory.go
vendored
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ func NewInMemoryBlobDescriptorCacheProvider() cache.BlobDescriptorCacheProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imbdcp *inMemoryBlobDescriptorCacheProvider) RepositoryScoped(repo string) (distribution.BlobDescriptorService, error) {
|
func (imbdcp *inMemoryBlobDescriptorCacheProvider) RepositoryScoped(canonicalName string) (distribution.BlobDescriptorService, error) {
|
||||||
if err := v2.ValidateRepositoryName(repo); err != nil {
|
if _, err := reference.NewRepository(canonicalName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ func (imbdcp *inMemoryBlobDescriptorCacheProvider) RepositoryScoped(repo string)
|
||||||
defer imbdcp.mu.RUnlock()
|
defer imbdcp.mu.RUnlock()
|
||||||
|
|
||||||
return &repositoryScopedInMemoryBlobDescriptorCache{
|
return &repositoryScopedInMemoryBlobDescriptorCache{
|
||||||
repo: repo,
|
repo: canonicalName,
|
||||||
parent: imbdcp,
|
parent: imbdcp,
|
||||||
repository: imbdcp.repositories[repo],
|
repository: imbdcp.repositories[canonicalName],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
docs/storage/cache/redis/redis.go
vendored
8
docs/storage/cache/redis/redis.go
vendored
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
)
|
)
|
||||||
|
@ -40,13 +40,13 @@ func NewRedisBlobDescriptorCacheProvider(pool *redis.Pool) cache.BlobDescriptorC
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepositoryScoped returns the scoped cache.
|
// RepositoryScoped returns the scoped cache.
|
||||||
func (rbds *redisBlobDescriptorService) RepositoryScoped(repo string) (distribution.BlobDescriptorService, error) {
|
func (rbds *redisBlobDescriptorService) RepositoryScoped(canonicalName string) (distribution.BlobDescriptorService, error) {
|
||||||
if err := v2.ValidateRepositoryName(repo); err != nil {
|
if _, err := reference.NewRepository(canonicalName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &repositoryScopedRedisBlobDescriptorService{
|
return &repositoryScopedRedisBlobDescriptorService{
|
||||||
repo: repo,
|
repo: canonicalName,
|
||||||
upstream: rbds,
|
upstream: rbds,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/storage/cache"
|
"github.com/docker/distribution/registry/storage/cache"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
@ -107,10 +107,10 @@ func (reg *registry) Scope() distribution.Scope {
|
||||||
// Repository returns an instance of the repository tied to the registry.
|
// Repository returns an instance of the repository tied to the registry.
|
||||||
// Instances should not be shared between goroutines but are cheap to
|
// Instances should not be shared between goroutines but are cheap to
|
||||||
// allocate. In general, they should be request scoped.
|
// allocate. In general, they should be request scoped.
|
||||||
func (reg *registry) Repository(ctx context.Context, name string) (distribution.Repository, error) {
|
func (reg *registry) Repository(ctx context.Context, canonicalName string) (distribution.Repository, error) {
|
||||||
if err := v2.ValidateRepositoryName(name); err != nil {
|
if _, err := reference.NewRepository(canonicalName); err != nil {
|
||||||
return nil, distribution.ErrRepositoryNameInvalid{
|
return nil, distribution.ErrRepositoryNameInvalid{
|
||||||
Name: name,
|
Name: canonicalName,
|
||||||
Reason: err,
|
Reason: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ func (reg *registry) Repository(ctx context.Context, name string) (distribution.
|
||||||
var descriptorCache distribution.BlobDescriptorService
|
var descriptorCache distribution.BlobDescriptorService
|
||||||
if reg.blobDescriptorCacheProvider != nil {
|
if reg.blobDescriptorCacheProvider != nil {
|
||||||
var err error
|
var err error
|
||||||
descriptorCache, err = reg.blobDescriptorCacheProvider.RepositoryScoped(name)
|
descriptorCache, err = reg.blobDescriptorCacheProvider.RepositoryScoped(canonicalName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func (reg *registry) Repository(ctx context.Context, name string) (distribution.
|
||||||
return &repository{
|
return &repository{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
registry: reg,
|
registry: reg,
|
||||||
name: name,
|
name: canonicalName,
|
||||||
descriptorCache: descriptorCache,
|
descriptorCache: descriptorCache,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue