Merge pull request #846 from stevvooe/next-generation
Consolidate routes and error codes into v2 package
This commit is contained in:
commit
d726630ad0
21 changed files with 422 additions and 312 deletions
|
@ -1,7 +1,10 @@
|
||||||
package errors
|
package v2
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
// TODO(stevvooe): Add route descriptors for each named route, along with
|
||||||
|
// accepted methods, parameters, returned status codes and error codes.
|
||||||
|
|
||||||
// ErrorDescriptor provides relevant information about a given error code.
|
// ErrorDescriptor provides relevant information about a given error code.
|
||||||
type ErrorDescriptor struct {
|
type ErrorDescriptor struct {
|
||||||
// Code is the error code that this descriptor describes.
|
// Code is the error code that this descriptor describes.
|
||||||
|
@ -26,9 +29,9 @@ type ErrorDescriptor struct {
|
||||||
HTTPStatusCodes []int
|
HTTPStatusCodes []int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Descriptors provides a list of HTTP API Error codes that may be encountered
|
// ErrorDescriptors provides a list of HTTP API Error codes that may be
|
||||||
// when interacting with the registry API.
|
// encountered when interacting with the registry API.
|
||||||
var Descriptors = []ErrorDescriptor{
|
var ErrorDescriptors = []ErrorDescriptor{
|
||||||
{
|
{
|
||||||
Code: ErrorCodeUnknown,
|
Code: ErrorCodeUnknown,
|
||||||
Value: "UNKNOWN",
|
Value: "UNKNOWN",
|
||||||
|
@ -131,10 +134,10 @@ var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
|
||||||
var idToDescriptors map[string]ErrorDescriptor
|
var idToDescriptors map[string]ErrorDescriptor
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(Descriptors))
|
errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors))
|
||||||
idToDescriptors = make(map[string]ErrorDescriptor, len(Descriptors))
|
idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors))
|
||||||
|
|
||||||
for _, descriptor := range Descriptors {
|
for _, descriptor := range ErrorDescriptors {
|
||||||
errorCodeToDescriptors[descriptor.Code] = descriptor
|
errorCodeToDescriptors[descriptor.Code] = descriptor
|
||||||
idToDescriptors[descriptor.Value] = descriptor
|
idToDescriptors[descriptor.Value] = descriptor
|
||||||
}
|
}
|
9
api/v2/doc.go
Normal file
9
api/v2/doc.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Package v2 describes routes, urls and the error codes used in the Docker
|
||||||
|
// Registry JSON HTTP API V2. In addition to declarations, descriptors are
|
||||||
|
// provided for routes and error codes that can be used for implementation and
|
||||||
|
// automatically generating documentation.
|
||||||
|
//
|
||||||
|
// Definitions here are considered to be locked down for the V2 registry api.
|
||||||
|
// Any changes must be considered carefully and should not proceed without a
|
||||||
|
// change proposal in docker core.
|
||||||
|
package v2
|
|
@ -1,12 +1,4 @@
|
||||||
// Package errors describes the error codes that may be returned via the
|
package v2
|
||||||
// Docker Registry JSON HTTP API V2. In addition to declaractions,
|
|
||||||
// descriptions about the error codes and the conditions causing them are
|
|
||||||
// avialable in detail.
|
|
||||||
//
|
|
||||||
// Error definitions here are considered to be locked down for the V2 registry
|
|
||||||
// api. Any changes must be considered carefully and should not proceed
|
|
||||||
// without a change proposal in docker core.
|
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package errors
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -11,7 +11,7 @@ import (
|
||||||
// TestErrorCodes ensures that error code format, mappings and
|
// TestErrorCodes ensures that error code format, mappings and
|
||||||
// marshaling/unmarshaling. round trips are stable.
|
// marshaling/unmarshaling. round trips are stable.
|
||||||
func TestErrorCodes(t *testing.T) {
|
func TestErrorCodes(t *testing.T) {
|
||||||
for _, desc := range Descriptors {
|
for _, desc := range ErrorDescriptors {
|
||||||
if desc.Code.String() != desc.Value {
|
if desc.Code.String() != desc.Value {
|
||||||
t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value)
|
t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value)
|
||||||
}
|
}
|
|
@ -1,66 +1,69 @@
|
||||||
package registry
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker-registry/common"
|
"github.com/docker/docker-registry/common"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The following are definitions of the name under which all V2 routes are
|
||||||
|
// registered. These symbols can be used to look up a route based on the name.
|
||||||
const (
|
const (
|
||||||
routeNameBase = "base"
|
RouteNameBase = "base"
|
||||||
routeNameImageManifest = "image-manifest"
|
RouteNameManifest = "manifest"
|
||||||
routeNameTags = "tags"
|
RouteNameTags = "tags"
|
||||||
routeNameBlob = "blob"
|
RouteNameBlob = "blob"
|
||||||
routeNameBlobUpload = "blob-upload"
|
RouteNameBlobUpload = "blob-upload"
|
||||||
routeNameBlobUploadResume = "blob-upload-resume"
|
RouteNameBlobUploadChunk = "blob-upload-chunk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allEndpoints = []string{
|
var allEndpoints = []string{
|
||||||
routeNameImageManifest,
|
RouteNameManifest,
|
||||||
routeNameTags,
|
RouteNameTags,
|
||||||
routeNameBlob,
|
RouteNameBlob,
|
||||||
routeNameBlobUpload,
|
RouteNameBlobUpload,
|
||||||
routeNameBlobUploadResume,
|
RouteNameBlobUploadChunk,
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2APIRouter builds a gorilla router with named routes for the various API
|
// Router builds a gorilla router with named routes for the various API
|
||||||
// methods. We may export this for use by the client.
|
// methods. This can be used directly by both server implementations and
|
||||||
func v2APIRouter() *mux.Router {
|
// clients.
|
||||||
|
func Router() *mux.Router {
|
||||||
router := mux.NewRouter().
|
router := mux.NewRouter().
|
||||||
StrictSlash(true)
|
StrictSlash(true)
|
||||||
|
|
||||||
// GET /v2/ Check Check that the registry implements API version 2(.1)
|
// GET /v2/ Check Check that the registry implements API version 2(.1)
|
||||||
router.
|
router.
|
||||||
Path("/v2/").
|
Path("/v2/").
|
||||||
Name(routeNameBase)
|
Name(RouteNameBase)
|
||||||
|
|
||||||
// GET /v2/<name>/manifest/<tag> Image Manifest Fetch the image manifest identified by name and tag.
|
// GET /v2/<name>/manifest/<tag> Image Manifest Fetch the image manifest identified by name and tag.
|
||||||
// PUT /v2/<name>/manifest/<tag> Image Manifest Upload the image manifest identified by name and tag.
|
// PUT /v2/<name>/manifest/<tag> Image Manifest Upload the image manifest identified by name and tag.
|
||||||
// DELETE /v2/<name>/manifest/<tag> Image Manifest Delete the image identified by name and tag.
|
// DELETE /v2/<name>/manifest/<tag> Image Manifest Delete the image identified by name and tag.
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/manifests/{tag:" + common.TagNameRegexp.String() + "}").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/manifests/{tag:" + common.TagNameRegexp.String() + "}").
|
||||||
Name(routeNameImageManifest)
|
Name(RouteNameManifest)
|
||||||
|
|
||||||
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
|
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list").
|
||||||
Name(routeNameTags)
|
Name(RouteNameTags)
|
||||||
|
|
||||||
// GET /v2/<name>/blob/<digest> Layer Fetch the blob identified by digest.
|
// GET /v2/<name>/blob/<digest> Layer Fetch the blob identified by digest.
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
|
||||||
Name(routeNameBlob)
|
Name(RouteNameBlob)
|
||||||
|
|
||||||
// POST /v2/<name>/blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum.
|
// POST /v2/<name>/blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum.
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/").
|
||||||
Name(routeNameBlobUpload)
|
Name(RouteNameBlobUpload)
|
||||||
|
|
||||||
// GET /v2/<name>/blob/upload/<uuid> Layer Upload Get the status of the upload identified by tarsum and uuid.
|
// GET /v2/<name>/blob/upload/<uuid> Layer Upload Get the status of the upload identified by tarsum and uuid.
|
||||||
// PUT /v2/<name>/blob/upload/<uuid> Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid.
|
// PUT /v2/<name>/blob/upload/<uuid> Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid.
|
||||||
// DELETE /v2/<name>/blob/upload/<uuid> Layer Upload Cancel the upload identified by layer and uuid
|
// DELETE /v2/<name>/blob/upload/<uuid> Layer Upload Cancel the upload identified by layer and uuid
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}").
|
||||||
Name(routeNameBlobUploadResume)
|
Name(RouteNameBlobUploadChunk)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package registry
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -25,7 +25,7 @@ type routeTestCase struct {
|
||||||
// This may go away as the application structure comes together.
|
// This may go away as the application structure comes together.
|
||||||
func TestRouter(t *testing.T) {
|
func TestRouter(t *testing.T) {
|
||||||
|
|
||||||
router := v2APIRouter()
|
router := Router()
|
||||||
|
|
||||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
testCase := routeTestCase{
|
testCase := routeTestCase{
|
||||||
|
@ -47,12 +47,12 @@ func TestRouter(t *testing.T) {
|
||||||
|
|
||||||
for _, testcase := range []routeTestCase{
|
for _, testcase := range []routeTestCase{
|
||||||
{
|
{
|
||||||
RouteName: routeNameBase,
|
RouteName: RouteNameBase,
|
||||||
RequestURI: "/v2/",
|
RequestURI: "/v2/",
|
||||||
Vars: map[string]string{},
|
Vars: map[string]string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameImageManifest,
|
RouteName: RouteNameManifest,
|
||||||
RequestURI: "/v2/foo/bar/manifests/tag",
|
RequestURI: "/v2/foo/bar/manifests/tag",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -60,14 +60,14 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameTags,
|
RouteName: RouteNameTags,
|
||||||
RequestURI: "/v2/foo/bar/tags/list",
|
RequestURI: "/v2/foo/bar/tags/list",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlob,
|
RouteName: RouteNameBlob,
|
||||||
RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234",
|
RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -75,7 +75,7 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlob,
|
RouteName: RouteNameBlob,
|
||||||
RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
|
RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -83,14 +83,14 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlobUpload,
|
RouteName: RouteNameBlobUpload,
|
||||||
RequestURI: "/v2/foo/bar/blobs/uploads/",
|
RequestURI: "/v2/foo/bar/blobs/uploads/",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlobUploadResume,
|
RouteName: RouteNameBlobUploadChunk,
|
||||||
RequestURI: "/v2/foo/bar/blobs/uploads/uuid",
|
RequestURI: "/v2/foo/bar/blobs/uploads/uuid",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -98,7 +98,7 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlobUploadResume,
|
RouteName: RouteNameBlobUploadChunk,
|
||||||
RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -106,7 +106,7 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlobUploadResume,
|
RouteName: RouteNameBlobUploadChunk,
|
||||||
RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
|
RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
|
@ -117,7 +117,7 @@ func TestRouter(t *testing.T) {
|
||||||
// Check ambiguity: ensure we can distinguish between tags for
|
// Check ambiguity: ensure we can distinguish between tags for
|
||||||
// "foo/bar/image/image" and image for "foo/bar/image" with tag
|
// "foo/bar/image/image" and image for "foo/bar/image" with tag
|
||||||
// "tags"
|
// "tags"
|
||||||
RouteName: routeNameImageManifest,
|
RouteName: RouteNameManifest,
|
||||||
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
|
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar/manifests",
|
"name": "foo/bar/manifests",
|
||||||
|
@ -127,14 +127,14 @@ func TestRouter(t *testing.T) {
|
||||||
{
|
{
|
||||||
// This case presents an ambiguity between foo/bar with tag="tags"
|
// This case presents an ambiguity between foo/bar with tag="tags"
|
||||||
// and list tags for "foo/bar/manifest"
|
// and list tags for "foo/bar/manifest"
|
||||||
RouteName: routeNameTags,
|
RouteName: RouteNameTags,
|
||||||
RequestURI: "/v2/foo/bar/manifests/tags/list",
|
RequestURI: "/v2/foo/bar/manifests/tags/list",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar/manifests",
|
"name": "foo/bar/manifests",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameBlobUploadResume,
|
RouteName: RouteNameBlobUploadChunk,
|
||||||
RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
165
api/v2/urls.go
Normal file
165
api/v2/urls.go
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/digest"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URLBuilder creates registry API urls from a single base endpoint. It can be
|
||||||
|
// used to create urls for use in a registry client or server.
|
||||||
|
//
|
||||||
|
// All urls will be created from the given base, including the api version.
|
||||||
|
// For example, if a root of "/foo/" is provided, urls generated will be fall
|
||||||
|
// under "/foo/v2/...". Most application will only provide a schema, host and
|
||||||
|
// port, such as "https://localhost:5000/".
|
||||||
|
type URLBuilder struct {
|
||||||
|
root *url.URL // url root (ie http://localhost/)
|
||||||
|
router *mux.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURLBuilder creates a URLBuilder with provided root url object.
|
||||||
|
func NewURLBuilder(root *url.URL) *URLBuilder {
|
||||||
|
return &URLBuilder{
|
||||||
|
root: root,
|
||||||
|
router: Router(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURLBuilderFromString workes identically to NewURLBuilder except it takes
|
||||||
|
// a string argument for the root, returning an error if it is not a valid
|
||||||
|
// url.
|
||||||
|
func NewURLBuilderFromString(root string) (*URLBuilder, error) {
|
||||||
|
u, err := url.Parse(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewURLBuilder(u), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURLBuilderFromRequest uses information from an *http.Request to
|
||||||
|
// construct the root url.
|
||||||
|
func NewURLBuilderFromRequest(r *http.Request) *URLBuilder {
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: r.URL.Scheme,
|
||||||
|
Host: r.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewURLBuilder(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBaseURL constructs a base url for the API, typically just "/v2/".
|
||||||
|
func (ub *URLBuilder) BuildBaseURL() (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameBase)
|
||||||
|
|
||||||
|
baseURL, err := route.URL()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTagsURL constructs a url to list the tags in the named repository.
|
||||||
|
func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameTags)
|
||||||
|
|
||||||
|
tagsURL, err := route.URL("name", name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagsURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildManifestURL constructs a url for the manifest identified by name and tag.
|
||||||
|
func (ub *URLBuilder) BuildManifestURL(name, tag string) (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameManifest)
|
||||||
|
|
||||||
|
manifestURL, err := route.URL("name", name, "tag", tag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifestURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBlobURL constructs the url for the blob identified by name and dgst.
|
||||||
|
func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameBlob)
|
||||||
|
|
||||||
|
layerURL, err := route.URL("name", name, "digest", dgst.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return layerURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBlobUploadURL constructs a url to begin a blob upload in the
|
||||||
|
// repository identified by name.
|
||||||
|
func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameBlobUpload)
|
||||||
|
|
||||||
|
uploadURL, err := route.URL("name", name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return appendValuesURL(uploadURL, values...).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid,
|
||||||
|
// including any url values. This should generally not be used by clients, as
|
||||||
|
// this url is provided by server implementations during the blob upload
|
||||||
|
// process.
|
||||||
|
func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) {
|
||||||
|
route := ub.cloneRoute(RouteNameBlobUploadChunk)
|
||||||
|
|
||||||
|
uploadURL, err := route.URL("name", name, "uuid", uuid)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return appendValuesURL(uploadURL, values...).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clondedRoute returns a clone of the named route from the router. Routes
|
||||||
|
// must be cloned to avoid modifying them during url generation.
|
||||||
|
func (ub *URLBuilder) cloneRoute(name string) *mux.Route {
|
||||||
|
route := new(mux.Route)
|
||||||
|
*route = *ub.router.GetRoute(name) // clone the route
|
||||||
|
|
||||||
|
return route.
|
||||||
|
Schemes(ub.root.Scheme).
|
||||||
|
Host(ub.root.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendValuesURL appends the parameters to the url.
|
||||||
|
func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
|
||||||
|
merged := u.Query()
|
||||||
|
|
||||||
|
for _, v := range values {
|
||||||
|
for k, vv := range v {
|
||||||
|
merged[k] = append(merged[k], vv...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = merged.Encode()
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendValues appends the parameters to the url. Panics if the string is not
|
||||||
|
// a url.
|
||||||
|
func appendValues(u string, values ...url.Values) string {
|
||||||
|
up, err := url.Parse(u)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
return appendValuesURL(up, values...).String()
|
||||||
|
}
|
100
api/v2/urls_test.go
Normal file
100
api/v2/urls_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type urlBuilderTestCase struct {
|
||||||
|
description string
|
||||||
|
expected string
|
||||||
|
build func() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestURLBuilder tests the various url building functions, ensuring they are
|
||||||
|
// returning the expected values.
|
||||||
|
func TestURLBuilder(t *testing.T) {
|
||||||
|
|
||||||
|
root := "http://localhost:5000/"
|
||||||
|
urlBuilder, err := NewURLBuilderFromString(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
description string
|
||||||
|
expected string
|
||||||
|
build func() (string, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "test base url",
|
||||||
|
expected: "http://localhost:5000/v2/",
|
||||||
|
build: urlBuilder.BuildBaseURL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test tags url",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/tags/list",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildTagsURL("foo/bar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test manifest url",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/manifests/tag",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildManifestURL("foo/bar", "tag")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "build blob url",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "build blob upload url",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/blobs/uploads/",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildBlobUploadURL("foo/bar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "build blob upload url with digest and size",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{
|
||||||
|
"size": []string{"10000"},
|
||||||
|
"digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "build blob upload chunk url",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/blobs/uploads/uuid-part",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "build blob upload chunk url with digest and size",
|
||||||
|
expected: "http://localhost:5000/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{
|
||||||
|
"size": []string{"10000"},
|
||||||
|
"digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
u, err := testcase.build()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: error building url: %v", testcase.description, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u != testcase.expected {
|
||||||
|
t.Fatalf("%s: %q != %q", testcase.description, u, testcase.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
api_test.go
49
api_test.go
|
@ -13,7 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/common/testutil"
|
"github.com/docker/docker-registry/common/testutil"
|
||||||
"github.com/docker/docker-registry/configuration"
|
"github.com/docker/docker-registry/configuration"
|
||||||
"github.com/docker/docker-registry/digest"
|
"github.com/docker/docker-registry/digest"
|
||||||
|
@ -34,13 +34,13 @@ func TestCheckAPI(t *testing.T) {
|
||||||
|
|
||||||
app := NewApp(config)
|
app := NewApp(config)
|
||||||
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
||||||
builder, err := newURLBuilderFromString(server.URL)
|
builder, err := v2.NewURLBuilderFromString(server.URL)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating url builder: %v", err)
|
t.Fatalf("error creating url builder: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL, err := builder.buildBaseURL()
|
baseURL, err := builder.BuildBaseURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error building base url: %v", err)
|
t.Fatalf("unexpected error building base url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func TestLayerAPI(t *testing.T) {
|
||||||
|
|
||||||
app := NewApp(config)
|
app := NewApp(config)
|
||||||
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
||||||
builder, err := newURLBuilderFromString(server.URL)
|
builder, err := v2.NewURLBuilderFromString(server.URL)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating url builder: %v", err)
|
t.Fatalf("error creating url builder: %v", err)
|
||||||
|
@ -98,7 +98,7 @@ func TestLayerAPI(t *testing.T) {
|
||||||
|
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
// Test fetch for non-existent content
|
// Test fetch for non-existent content
|
||||||
layerURL, err := builder.buildLayerURL(imageName, layerDigest)
|
layerURL, err := builder.BuildBlobURL(imageName, layerDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error building url: %v", err)
|
t.Fatalf("error building url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ func TestLayerAPI(t *testing.T) {
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// Upload a layer
|
// Upload a layer
|
||||||
layerUploadURL, err := builder.buildLayerUploadURL(imageName)
|
layerUploadURL, err := builder.BuildBlobUploadURL(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error building upload url: %v", err)
|
t.Fatalf("error building upload url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,7 @@ func TestManifestAPI(t *testing.T) {
|
||||||
|
|
||||||
app := NewApp(config)
|
app := NewApp(config)
|
||||||
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
||||||
builder, err := newURLBuilderFromString(server.URL)
|
builder, err := v2.NewURLBuilderFromString(server.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating url builder: %v", err)
|
t.Fatalf("unexpected error creating url builder: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ func TestManifestAPI(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
tag := "thetag"
|
tag := "thetag"
|
||||||
|
|
||||||
manifestURL, err := builder.buildManifestURL(imageName, tag)
|
manifestURL, err := builder.BuildManifestURL(imageName, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting manifest url: %v", err)
|
t.Fatalf("unexpected error getting manifest url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,7 @@ func TestManifestAPI(t *testing.T) {
|
||||||
// }
|
// }
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
var respErrs errors.Errors
|
var respErrs v2.Errors
|
||||||
if err := dec.Decode(&respErrs); err != nil {
|
if err := dec.Decode(&respErrs); err != nil {
|
||||||
t.Fatalf("unexpected error decoding error response: %v", err)
|
t.Fatalf("unexpected error decoding error response: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -236,11 +236,11 @@ func TestManifestAPI(t *testing.T) {
|
||||||
t.Fatalf("expected errors in response")
|
t.Fatalf("expected errors in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
if respErrs.Errors[0].Code != errors.ErrorCodeManifestUnknown {
|
if respErrs.Errors[0].Code != v2.ErrorCodeManifestUnknown {
|
||||||
t.Fatalf("expected manifest unknown error: got %v", respErrs)
|
t.Fatalf("expected manifest unknown error: got %v", respErrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
tagsURL, err := builder.buildTagsURL(imageName)
|
tagsURL, err := builder.BuildTagsURL(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error building tags url: %v", err)
|
t.Fatalf("unexpected error building tags url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ func TestManifestAPI(t *testing.T) {
|
||||||
t.Fatalf("expected errors in response")
|
t.Fatalf("expected errors in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
if respErrs.Errors[0].Code != errors.ErrorCodeNameUnknown {
|
if respErrs.Errors[0].Code != v2.ErrorCodeNameUnknown {
|
||||||
t.Fatalf("expected respository unknown error: got %v", respErrs)
|
t.Fatalf("expected respository unknown error: got %v", respErrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,11 +296,11 @@ func TestManifestAPI(t *testing.T) {
|
||||||
|
|
||||||
for _, err := range respErrs.Errors {
|
for _, err := range respErrs.Errors {
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case errors.ErrorCodeManifestUnverified:
|
case v2.ErrorCodeManifestUnverified:
|
||||||
unverified++
|
unverified++
|
||||||
case errors.ErrorCodeBlobUnknown:
|
case v2.ErrorCodeBlobUnknown:
|
||||||
missingLayers++
|
missingLayers++
|
||||||
case errors.ErrorCodeDigestInvalid:
|
case v2.ErrorCodeDigestInvalid:
|
||||||
// TODO(stevvooe): This error isn't quite descriptive enough --
|
// TODO(stevvooe): This error isn't quite descriptive enough --
|
||||||
// the layer with an invalid digest isn't identified.
|
// the layer with an invalid digest isn't identified.
|
||||||
invalidDigests++
|
invalidDigests++
|
||||||
|
@ -427,8 +427,8 @@ func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPushLayer(t *testing.T, ub *urlBuilder, name string) string {
|
func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) string {
|
||||||
layerUploadURL, err := ub.buildLayerUploadURL(name)
|
layerUploadURL, err := ub.BuildBlobUploadURL(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error building layer upload url: %v", err)
|
t.Fatalf("unexpected error building layer upload url: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -449,14 +449,21 @@ func startPushLayer(t *testing.T, ub *urlBuilder, name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushLayer pushes the layer content returning the url on success.
|
// pushLayer pushes the layer content returning the url on success.
|
||||||
func pushLayer(t *testing.T, ub *urlBuilder, name string, dgst digest.Digest, uploadURLBase string, rs io.ReadSeeker) string {
|
func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, rs io.ReadSeeker) string {
|
||||||
rsLength, _ := rs.Seek(0, os.SEEK_END)
|
rsLength, _ := rs.Seek(0, os.SEEK_END)
|
||||||
rs.Seek(0, os.SEEK_SET)
|
rs.Seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
uploadURL := appendValues(uploadURLBase, url.Values{
|
u, err := url.Parse(uploadURLBase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing pushLayer url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = url.Values{
|
||||||
"digest": []string{dgst.String()},
|
"digest": []string{dgst.String()},
|
||||||
"size": []string{fmt.Sprint(rsLength)},
|
"size": []string{fmt.Sprint(rsLength)},
|
||||||
})
|
}.Encode()
|
||||||
|
|
||||||
|
uploadURL := u.String()
|
||||||
|
|
||||||
// Just do a monolithic upload
|
// Just do a monolithic upload
|
||||||
req, err := http.NewRequest("PUT", uploadURL, rs)
|
req, err := http.NewRequest("PUT", uploadURL, rs)
|
||||||
|
@ -472,7 +479,7 @@ func pushLayer(t *testing.T, ub *urlBuilder, name string, dgst digest.Digest, up
|
||||||
|
|
||||||
checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
|
checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
|
||||||
|
|
||||||
expectedLayerURL, err := ub.buildLayerURL(name, dgst)
|
expectedLayerURL, err := ub.BuildBlobURL(name, dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error building expected layer url: %v", err)
|
t.Fatalf("error building expected layer url: %v", err)
|
||||||
}
|
}
|
||||||
|
|
17
app.go
17
app.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/storagedriver"
|
"github.com/docker/docker-registry/storagedriver"
|
||||||
"github.com/docker/docker-registry/storagedriver/factory"
|
"github.com/docker/docker-registry/storagedriver/factory"
|
||||||
|
|
||||||
|
@ -35,18 +36,18 @@ type App struct {
|
||||||
func NewApp(configuration configuration.Configuration) *App {
|
func NewApp(configuration configuration.Configuration) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
Config: configuration,
|
Config: configuration,
|
||||||
router: v2APIRouter(),
|
router: v2.Router(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the handler dispatchers.
|
// Register the handler dispatchers.
|
||||||
app.register(routeNameBase, func(ctx *Context, r *http.Request) http.Handler {
|
app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler {
|
||||||
return http.HandlerFunc(apiBase)
|
return http.HandlerFunc(apiBase)
|
||||||
})
|
})
|
||||||
app.register(routeNameImageManifest, imageManifestDispatcher)
|
app.register(v2.RouteNameManifest, imageManifestDispatcher)
|
||||||
app.register(routeNameTags, tagsDispatcher)
|
app.register(v2.RouteNameTags, tagsDispatcher)
|
||||||
app.register(routeNameBlob, layerDispatcher)
|
app.register(v2.RouteNameBlob, layerDispatcher)
|
||||||
app.register(routeNameBlobUpload, layerUploadDispatcher)
|
app.register(v2.RouteNameBlobUpload, layerUploadDispatcher)
|
||||||
app.register(routeNameBlobUploadResume, layerUploadDispatcher)
|
app.register(v2.RouteNameBlobUploadChunk, layerUploadDispatcher)
|
||||||
|
|
||||||
driver, err := factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters())
|
driver, err := factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters())
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
context := &Context{
|
context := &Context{
|
||||||
App: app,
|
App: app,
|
||||||
Name: vars["name"],
|
Name: vars["name"],
|
||||||
urlBuilder: newURLBuilderFromRequest(r),
|
urlBuilder: v2.NewURLBuilderFromRequest(r),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store vars for underlying handlers.
|
// Store vars for underlying handlers.
|
||||||
|
|
15
app_test.go
15
app_test.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/configuration"
|
"github.com/docker/docker-registry/configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,10 +17,10 @@ import (
|
||||||
func TestAppDispatcher(t *testing.T) {
|
func TestAppDispatcher(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
Config: configuration.Configuration{},
|
Config: configuration.Configuration{},
|
||||||
router: v2APIRouter(),
|
router: v2.Router(),
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(app)
|
server := httptest.NewServer(app)
|
||||||
router := v2APIRouter()
|
router := v2.Router()
|
||||||
|
|
||||||
serverURL, err := url.Parse(server.URL)
|
serverURL, err := url.Parse(server.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,33 +72,33 @@ func TestAppDispatcher(t *testing.T) {
|
||||||
vars []string
|
vars []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
endpoint: routeNameImageManifest,
|
endpoint: v2.RouteNameManifest,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"tag", "sometag",
|
"tag", "sometag",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameTags,
|
endpoint: v2.RouteNameTags,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameBlob,
|
endpoint: v2.RouteNameBlob,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"digest", "tarsum.v1+bogus:abcdef0123456789",
|
"digest", "tarsum.v1+bogus:abcdef0123456789",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameBlobUpload,
|
endpoint: v2.RouteNameBlobUpload,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameBlobUploadResume,
|
endpoint: v2.RouteNameBlobUploadChunk,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"uuid", "theuuid",
|
"uuid", "theuuid",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/digest"
|
"github.com/docker/docker-registry/digest"
|
||||||
"github.com/docker/docker-registry/storage"
|
"github.com/docker/docker-registry/storage"
|
||||||
)
|
)
|
||||||
|
@ -96,7 +96,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return nil, &ImageManifestNotFoundError{Name: name, Tag: tag}
|
return nil, &ImageManifestNotFoundError{Name: name, Tag: tag}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
|
@ -136,7 +136,7 @@ func (r *clientImpl) PutImageManifest(name, tag string, manifest *storage.Signed
|
||||||
case response.StatusCode == http.StatusOK:
|
case response.StatusCode == http.StatusOK:
|
||||||
return nil
|
return nil
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errors errors.Errors
|
var errors v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errors)
|
err = decoder.Decode(&errors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -169,7 +169,7 @@ func (r *clientImpl) DeleteImage(name, tag string) error {
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return &ImageManifestNotFoundError{Name: name, Tag: tag}
|
return &ImageManifestNotFoundError{Name: name, Tag: tag}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -197,7 +197,7 @@ func (r *clientImpl) ListImageTags(name string) ([]string, error) {
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return nil, &RepositoryNotFoundError{Name: name}
|
return nil, &RepositoryNotFoundError{Name: name}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -240,7 +240,7 @@ func (r *clientImpl) BlobLength(name string, dgst digest.Digest) (int, error) {
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return -1, nil
|
return -1, nil
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -279,7 +279,7 @@ func (r *clientImpl) GetBlob(name string, dgst digest.Digest, byteOffset int) (i
|
||||||
response.Body.Close()
|
response.Body.Close()
|
||||||
return nil, 0, &BlobNotFoundError{Name: name, Digest: dgst}
|
return nil, 0, &BlobNotFoundError{Name: name, Digest: dgst}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -312,7 +312,7 @@ func (r *clientImpl) InitiateBlobUpload(name string) (string, error) {
|
||||||
// case response.StatusCode == http.StatusNotFound:
|
// case response.StatusCode == http.StatusNotFound:
|
||||||
// return
|
// return
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -338,7 +338,7 @@ func (r *clientImpl) GetBlobUploadStatus(location string) (int, int, error) {
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return 0, 0, &BlobUploadNotFoundError{Location: location}
|
return 0, 0, &BlobUploadNotFoundError{Location: location}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -379,7 +379,7 @@ func (r *clientImpl) UploadBlob(location string, blob io.ReadCloser, length int,
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return &BlobUploadNotFoundError{Location: location}
|
return &BlobUploadNotFoundError{Location: location}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -430,7 +430,7 @@ func (r *clientImpl) UploadBlobChunk(location string, blobChunk io.ReadCloser, l
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return &BlobUploadNotFoundError{Location: location}
|
return &BlobUploadNotFoundError{Location: location}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -472,7 +472,7 @@ func (r *clientImpl) FinishChunkedBlobUpload(location string, length int, dgst d
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return &BlobUploadNotFoundError{Location: location}
|
return &BlobUploadNotFoundError{Location: location}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -504,7 +504,7 @@ func (r *clientImpl) CancelBlobUpload(location string) error {
|
||||||
case response.StatusCode == http.StatusNotFound:
|
case response.StatusCode == http.StatusNotFound:
|
||||||
return &BlobUploadNotFoundError{Location: location}
|
return &BlobUploadNotFoundError{Location: location}
|
||||||
case response.StatusCode >= 400 && response.StatusCode < 500:
|
case response.StatusCode >= 400 && response.StatusCode < 500:
|
||||||
var errs errors.Errors
|
var errs v2.Errors
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&errs)
|
err = decoder.Decode(&errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -14,11 +13,11 @@ import (
|
||||||
var (
|
var (
|
||||||
// ErrLayerAlreadyExists is returned when attempting to create a layer with
|
// ErrLayerAlreadyExists is returned when attempting to create a layer with
|
||||||
// a tarsum that is already in use.
|
// a tarsum that is already in use.
|
||||||
ErrLayerAlreadyExists = errors.New("Layer already exists")
|
ErrLayerAlreadyExists = fmt.Errorf("Layer already exists")
|
||||||
|
|
||||||
// ErrLayerLocked is returned when attempting to write to a layer which is
|
// ErrLayerLocked is returned when attempting to write to a layer which is
|
||||||
// currently being written to.
|
// currently being written to.
|
||||||
ErrLayerLocked = errors.New("Layer locked")
|
ErrLayerLocked = fmt.Errorf("Layer locked")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectStore is an interface which is designed to approximate the docker
|
// ObjectStore is an interface which is designed to approximate the docker
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/storage"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker-registry/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// simultaneousLayerPushWindow is the size of the parallel layer push window.
|
// simultaneousLayerPushWindow is the size of the parallel layer push window.
|
||||||
|
@ -100,7 +99,7 @@ func pushLayer(c Client, objectStore ObjectStore, name string, fsLayer storage.F
|
||||||
"currentSize": layerReader.CurrentSize(),
|
"currentSize": layerReader.CurrentSize(),
|
||||||
"size": layerReader.Size(),
|
"size": layerReader.Size(),
|
||||||
}).Warn("Local layer incomplete")
|
}).Warn("Local layer incomplete")
|
||||||
return errors.New("Local layer incomplete")
|
return fmt.Errorf("Local layer incomplete")
|
||||||
}
|
}
|
||||||
|
|
||||||
length, err := c.BlobLength(name, fsLayer.BlobSum)
|
length, err := c.BlobLength(name, fsLayer.BlobSum)
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -40,7 +40,7 @@ func dumpErrors(wr io.Writer) {
|
||||||
defer writer.Flush()
|
defer writer.Flush()
|
||||||
|
|
||||||
fmt.Fprint(writer, "|")
|
fmt.Fprint(writer, "|")
|
||||||
dtype := reflect.TypeOf(errors.ErrorDescriptor{})
|
dtype := reflect.TypeOf(v2.ErrorDescriptor{})
|
||||||
var fieldsPrinted int
|
var fieldsPrinted int
|
||||||
for i := 0; i < dtype.NumField(); i++ {
|
for i := 0; i < dtype.NumField(); i++ {
|
||||||
field := dtype.Field(i)
|
field := dtype.Field(i)
|
||||||
|
@ -61,7 +61,7 @@ func dumpErrors(wr io.Writer) {
|
||||||
|
|
||||||
fmt.Fprintln(writer, "\n"+divider)
|
fmt.Fprintln(writer, "\n"+divider)
|
||||||
|
|
||||||
for _, descriptor := range errors.Descriptors {
|
for _, descriptor := range v2.ErrorDescriptors {
|
||||||
fmt.Fprint(writer, "|")
|
fmt.Fprint(writer, "|")
|
||||||
|
|
||||||
v := reflect.ValueOf(descriptor)
|
v := reflect.ValueOf(descriptor)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context should contain the request specific context for use in across
|
// Context should contain the request specific context for use in across
|
||||||
|
@ -19,7 +19,7 @@ type Context struct {
|
||||||
// Errors is a collection of errors encountered during the request to be
|
// Errors is a collection of errors encountered during the request to be
|
||||||
// returned to the client API. If errors are added to the collection, the
|
// returned to the client API. If errors are added to the collection, the
|
||||||
// handler *must not* start the response via http.ResponseWriter.
|
// handler *must not* start the response via http.ResponseWriter.
|
||||||
Errors errors.Errors
|
Errors v2.Errors
|
||||||
|
|
||||||
// vars contains the extracted gorilla/mux variables that can be used for
|
// vars contains the extracted gorilla/mux variables that can be used for
|
||||||
// assignment.
|
// assignment.
|
||||||
|
@ -28,5 +28,5 @@ type Context struct {
|
||||||
// log provides a context specific logger.
|
// log provides a context specific logger.
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
|
|
||||||
urlBuilder *urlBuilder
|
urlBuilder *v2.URLBuilder
|
||||||
}
|
}
|
||||||
|
|
16
images.go
16
images.go
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/digest"
|
"github.com/docker/docker-registry/digest"
|
||||||
"github.com/docker/docker-registry/storage"
|
"github.com/docker/docker-registry/storage"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
|
@ -41,7 +41,7 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http
|
||||||
manifest, err := manifests.Get(imh.Name, imh.Tag)
|
manifest, err := manifests.Get(imh.Name, imh.Tag)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
imh.Errors.Push(errors.ErrorCodeManifestUnknown, err)
|
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
|
||||||
|
|
||||||
var manifest storage.SignedManifest
|
var manifest storage.SignedManifest
|
||||||
if err := dec.Decode(&manifest); err != nil {
|
if err := dec.Decode(&manifest); err != nil {
|
||||||
imh.Errors.Push(errors.ErrorCodeManifestInvalid, err)
|
imh.Errors.Push(v2.ErrorCodeManifestInvalid, err)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,14 +71,14 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
|
||||||
for _, verificationError := range err {
|
for _, verificationError := range err {
|
||||||
switch verificationError := verificationError.(type) {
|
switch verificationError := verificationError.(type) {
|
||||||
case storage.ErrUnknownLayer:
|
case storage.ErrUnknownLayer:
|
||||||
imh.Errors.Push(errors.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
||||||
case storage.ErrManifestUnverified:
|
case storage.ErrManifestUnverified:
|
||||||
imh.Errors.Push(errors.ErrorCodeManifestUnverified)
|
imh.Errors.Push(v2.ErrorCodeManifestUnverified)
|
||||||
default:
|
default:
|
||||||
if verificationError == digest.ErrDigestInvalidFormat {
|
if verificationError == digest.ErrDigestInvalidFormat {
|
||||||
// TODO(stevvooe): We need to really need to move all
|
// TODO(stevvooe): We need to really need to move all
|
||||||
// errors to types. Its much more straightforward.
|
// errors to types. Its much more straightforward.
|
||||||
imh.Errors.Push(errors.ErrorCodeDigestInvalid)
|
imh.Errors.Push(v2.ErrorCodeDigestInvalid)
|
||||||
} else {
|
} else {
|
||||||
imh.Errors.PushErr(verificationError)
|
imh.Errors.PushErr(verificationError)
|
||||||
}
|
}
|
||||||
|
@ -99,10 +99,10 @@ func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *h
|
||||||
if err := manifests.Delete(imh.Name, imh.Tag); err != nil {
|
if err := manifests.Delete(imh.Name, imh.Tag); err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownManifest:
|
case storage.ErrUnknownManifest:
|
||||||
imh.Errors.Push(errors.ErrorCodeManifestUnknown, err)
|
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
imh.Errors.Push(errors.ErrorCodeUnknown, err)
|
imh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
8
layer.go
8
layer.go
|
@ -3,7 +3,7 @@ package registry
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/digest"
|
"github.com/docker/docker-registry/digest"
|
||||||
"github.com/docker/docker-registry/storage"
|
"github.com/docker/docker-registry/storage"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
|
@ -15,7 +15,7 @@ func layerDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx.Errors.Push(errors.ErrorCodeDigestInvalid, err)
|
ctx.Errors.Push(v2.ErrorCodeDigestInvalid, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownLayer:
|
case storage.ErrUnknownLayer:
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
lh.Errors.Push(errors.ErrorCodeBlobUnknown, err.FSLayer)
|
lh.Errors.Push(v2.ErrorCodeBlobUnknown, err.FSLayer)
|
||||||
default:
|
default:
|
||||||
lh.Errors.Push(errors.ErrorCodeUnknown, err)
|
lh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/digest"
|
"github.com/docker/docker-registry/digest"
|
||||||
"github.com/docker/docker-registry/storage"
|
"github.com/docker/docker-registry/storage"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
|
@ -39,7 +39,7 @@ func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
logrus.Infof("error resolving upload: %v", err)
|
logrus.Infof("error resolving upload: %v", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.R
|
||||||
upload, err := layers.Upload(luh.Name)
|
upload, err := layers.Upload(luh.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.R
|
||||||
|
|
||||||
if err := luh.layerUploadResponse(w, r); err != nil {
|
if err := luh.layerUploadResponse(w, r); err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
@ -86,12 +86,12 @@ func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.R
|
||||||
func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Request) {
|
func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
if luh.Upload == nil {
|
if luh.Upload == nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
luh.Errors.Push(errors.ErrorCodeBlobUploadUnknown)
|
luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := luh.layerUploadResponse(w, r); err != nil {
|
if err := luh.layerUploadResponse(w, r); err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Re
|
||||||
func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Request) {
|
func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Request) {
|
||||||
if luh.Upload == nil {
|
if luh.Upload == nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
luh.Errors.Push(errors.ErrorCodeBlobUploadUnknown)
|
luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
var finished bool
|
var finished bool
|
||||||
|
@ -120,14 +120,14 @@ func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Requ
|
||||||
if err := luh.maybeCompleteUpload(w, r); err != nil {
|
if err := luh.maybeCompleteUpload(w, r); err != nil {
|
||||||
if err != errNotReadyToComplete {
|
if err != errNotReadyToComplete {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := luh.layerUploadResponse(w, r); err != nil {
|
if err := luh.layerUploadResponse(w, r); err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Requ
|
||||||
func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.Request) {
|
func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
if luh.Upload == nil {
|
if luh.Upload == nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
luh.Errors.Push(errors.ErrorCodeBlobUploadUnknown)
|
luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.
|
||||||
// chunk responses. This sets the correct headers but the response status is
|
// chunk responses. This sets the correct headers but the response status is
|
||||||
// left to the caller.
|
// left to the caller.
|
||||||
func (luh *layerUploadHandler) layerUploadResponse(w http.ResponseWriter, r *http.Request) error {
|
func (luh *layerUploadHandler) layerUploadResponse(w http.ResponseWriter, r *http.Request) error {
|
||||||
uploadURL, err := luh.urlBuilder.forLayerUpload(luh.Upload)
|
uploadURL, err := luh.urlBuilder.BuildBlobUploadChunkURL(luh.Upload.Name(), luh.Upload.UUID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Infof("error building upload url: %s", err)
|
logrus.Infof("error building upload url: %s", err)
|
||||||
return err
|
return err
|
||||||
|
@ -195,14 +195,14 @@ func (luh *layerUploadHandler) maybeCompleteUpload(w http.ResponseWriter, r *htt
|
||||||
func (luh *layerUploadHandler) completeUpload(w http.ResponseWriter, r *http.Request, size int64, dgst digest.Digest) {
|
func (luh *layerUploadHandler) completeUpload(w http.ResponseWriter, r *http.Request, size int64, dgst digest.Digest) {
|
||||||
layer, err := luh.Upload.Finish(size, dgst)
|
layer, err := luh.Upload.Finish(size, dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
layerURL, err := luh.urlBuilder.forLayer(layer)
|
layerURL, err := luh.urlBuilder.BuildBlobURL(layer.Name(), layer.Digest())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
luh.Errors.Push(errors.ErrorCodeUnknown, err)
|
luh.Errors.Push(v2.ErrorCodeUnknown, err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
4
tags.go
4
tags.go
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker-registry/api/errors"
|
"github.com/docker/docker-registry/api/v2"
|
||||||
"github.com/docker/docker-registry/storage"
|
"github.com/docker/docker-registry/storage"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownRepository:
|
case storage.ErrUnknownRepository:
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
th.Errors.Push(errors.ErrorCodeNameUnknown, map[string]string{"name": th.Name})
|
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Name})
|
||||||
default:
|
default:
|
||||||
th.Errors.PushErr(err)
|
th.Errors.PushErr(err)
|
||||||
}
|
}
|
||||||
|
|
169
urls.go
169
urls.go
|
@ -1,169 +0,0 @@
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/docker/docker-registry/digest"
|
|
||||||
"github.com/docker/docker-registry/storage"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type urlBuilder struct {
|
|
||||||
url *url.URL // url root (ie http://localhost/)
|
|
||||||
router *mux.Router
|
|
||||||
}
|
|
||||||
|
|
||||||
func newURLBuilder(root *url.URL) *urlBuilder {
|
|
||||||
return &urlBuilder{
|
|
||||||
url: root,
|
|
||||||
router: v2APIRouter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newURLBuilderFromRequest(r *http.Request) *urlBuilder {
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: r.URL.Scheme,
|
|
||||||
Host: r.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
return newURLBuilder(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newURLBuilderFromString(root string) (*urlBuilder, error) {
|
|
||||||
u, err := url.Parse(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newURLBuilder(u), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildBaseURL() (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameBase)
|
|
||||||
|
|
||||||
baseURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseURL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildTagsURL(name string) (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameTags)
|
|
||||||
|
|
||||||
tagsURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL("name", name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagsURL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) forManifest(m *storage.Manifest) (string, error) {
|
|
||||||
return ub.buildManifestURL(m.Name, m.Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildManifestURL(name, tag string) (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameImageManifest)
|
|
||||||
|
|
||||||
manifestURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL("name", name, "tag", tag)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return manifestURL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) forLayer(l storage.Layer) (string, error) {
|
|
||||||
return ub.buildLayerURL(l.Name(), l.Digest())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildLayerURL(name string, dgst digest.Digest) (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameBlob)
|
|
||||||
|
|
||||||
layerURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL("name", name, "digest", dgst.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return layerURL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildLayerUploadURL(name string) (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameBlobUpload)
|
|
||||||
|
|
||||||
uploadURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL("name", name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadURL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) forLayerUpload(layerUpload storage.LayerUpload) (string, error) {
|
|
||||||
return ub.buildLayerUploadResumeURL(layerUpload.Name(), layerUpload.UUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ub *urlBuilder) buildLayerUploadResumeURL(name, uuid string, values ...url.Values) (string, error) {
|
|
||||||
route := clonedRoute(ub.router, routeNameBlobUploadResume)
|
|
||||||
|
|
||||||
uploadURL, err := route.
|
|
||||||
Schemes(ub.url.Scheme).
|
|
||||||
Host(ub.url.Host).
|
|
||||||
URL("name", name, "uuid", uuid)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return appendValuesURL(uploadURL, values...).String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendValuesURL appends the parameters to the url.
|
|
||||||
func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
|
|
||||||
merged := u.Query()
|
|
||||||
|
|
||||||
for _, v := range values {
|
|
||||||
for k, vv := range v {
|
|
||||||
merged[k] = append(merged[k], vv...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.RawQuery = merged.Encode()
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendValues appends the parameters to the url. Panics if the string is not
|
|
||||||
// a url.
|
|
||||||
func appendValues(u string, values ...url.Values) string {
|
|
||||||
up, err := url.Parse(u)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
|
|
||||||
return appendValuesURL(up, values...).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// clondedRoute returns a clone of the named route from the router.
|
|
||||||
func clonedRoute(router *mux.Router, name string) *mux.Route {
|
|
||||||
route := new(mux.Route)
|
|
||||||
*route = *router.GetRoute(name) // clone the route
|
|
||||||
return route
|
|
||||||
}
|
|
Loading…
Reference in a new issue