Merge pull request #846 from stevvooe/next-generation
Consolidate routes and error codes into v2 packagemaster
						commit
						d726630ad0
					
				|  | @ -1,7 +1,10 @@ | |||
| package errors | ||||
| package v2 | ||||
| 
 | ||||
| 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.
 | ||||
| type ErrorDescriptor struct { | ||||
| 	// Code is the error code that this descriptor describes.
 | ||||
|  | @ -26,9 +29,9 @@ type ErrorDescriptor struct { | |||
| 	HTTPStatusCodes []int | ||||
| } | ||||
| 
 | ||||
| // Descriptors provides a list of HTTP API Error codes that may be encountered
 | ||||
| // when interacting with the registry API.
 | ||||
| var Descriptors = []ErrorDescriptor{ | ||||
| // ErrorDescriptors provides a list of HTTP API Error codes that may be
 | ||||
| // encountered when interacting with the registry API.
 | ||||
| var ErrorDescriptors = []ErrorDescriptor{ | ||||
| 	{ | ||||
| 		Code:    ErrorCodeUnknown, | ||||
| 		Value:   "UNKNOWN", | ||||
|  | @ -131,10 +134,10 @@ var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor | |||
| var idToDescriptors map[string]ErrorDescriptor | ||||
| 
 | ||||
| func init() { | ||||
| 	errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(Descriptors)) | ||||
| 	idToDescriptors = make(map[string]ErrorDescriptor, len(Descriptors)) | ||||
| 	errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors)) | ||||
| 	idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors)) | ||||
| 
 | ||||
| 	for _, descriptor := range Descriptors { | ||||
| 	for _, descriptor := range ErrorDescriptors { | ||||
| 		errorCodeToDescriptors[descriptor.Code] = descriptor | ||||
| 		idToDescriptors[descriptor.Value] = descriptor | ||||
| 	} | ||||
|  | @ -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
 | ||||
| // 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 | ||||
| package v2 | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | @ -1,4 +1,4 @@ | |||
| package errors | ||||
| package v2 | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | @ -11,7 +11,7 @@ import ( | |||
| // TestErrorCodes ensures that error code format, mappings and
 | ||||
| // marshaling/unmarshaling. round trips are stable.
 | ||||
| func TestErrorCodes(t *testing.T) { | ||||
| 	for _, desc := range Descriptors { | ||||
| 	for _, desc := range ErrorDescriptors { | ||||
| 		if 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 ( | ||||
| 	"github.com/docker/docker-registry/common" | ||||
| 	"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 ( | ||||
| 	routeNameBase             = "base" | ||||
| 	routeNameImageManifest    = "image-manifest" | ||||
| 	routeNameTags             = "tags" | ||||
| 	routeNameBlob             = "blob" | ||||
| 	routeNameBlobUpload       = "blob-upload" | ||||
| 	routeNameBlobUploadResume = "blob-upload-resume" | ||||
| 	RouteNameBase            = "base" | ||||
| 	RouteNameManifest        = "manifest" | ||||
| 	RouteNameTags            = "tags" | ||||
| 	RouteNameBlob            = "blob" | ||||
| 	RouteNameBlobUpload      = "blob-upload" | ||||
| 	RouteNameBlobUploadChunk = "blob-upload-chunk" | ||||
| ) | ||||
| 
 | ||||
| var allEndpoints = []string{ | ||||
| 	routeNameImageManifest, | ||||
| 	routeNameTags, | ||||
| 	routeNameBlob, | ||||
| 	routeNameBlobUpload, | ||||
| 	routeNameBlobUploadResume, | ||||
| 	RouteNameManifest, | ||||
| 	RouteNameTags, | ||||
| 	RouteNameBlob, | ||||
| 	RouteNameBlobUpload, | ||||
| 	RouteNameBlobUploadChunk, | ||||
| } | ||||
| 
 | ||||
| // v2APIRouter builds a gorilla router with named routes for the various API
 | ||||
| // methods. We may export this for use by the client.
 | ||||
| func v2APIRouter() *mux.Router { | ||||
| // Router builds a gorilla router with named routes for the various API
 | ||||
| // methods. This can be used directly by both server implementations and
 | ||||
| // clients.
 | ||||
| func Router() *mux.Router { | ||||
| 	router := mux.NewRouter(). | ||||
| 		StrictSlash(true) | ||||
| 
 | ||||
| 	// GET /v2/	Check	Check that the registry implements API version 2(.1)
 | ||||
| 	router. | ||||
| 		Path("/v2/"). | ||||
| 		Name(routeNameBase) | ||||
| 		Name(RouteNameBase) | ||||
| 
 | ||||
| 	// 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.
 | ||||
| 	// DELETE   /v2/<name>/manifest/<tag>	Image Manifest	Delete the image identified by name and tag.
 | ||||
| 	router. | ||||
| 		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.
 | ||||
| 	router. | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list"). | ||||
| 		Name(routeNameTags) | ||||
| 		Name(RouteNameTags) | ||||
| 
 | ||||
| 	// GET	/v2/<name>/blob/<digest>	Layer	Fetch the blob identified by digest.
 | ||||
| 	router. | ||||
| 		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.
 | ||||
| 	router. | ||||
| 		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.
 | ||||
| 	// 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
 | ||||
| 	router. | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}"). | ||||
| 		Name(routeNameBlobUploadResume) | ||||
| 		Name(RouteNameBlobUploadChunk) | ||||
| 
 | ||||
| 	return router | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package registry | ||||
| package v2 | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | @ -25,7 +25,7 @@ type routeTestCase struct { | |||
| // This may go away as the application structure comes together.
 | ||||
| func TestRouter(t *testing.T) { | ||||
| 
 | ||||
| 	router := v2APIRouter() | ||||
| 	router := Router() | ||||
| 
 | ||||
| 	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		testCase := routeTestCase{ | ||||
|  | @ -47,12 +47,12 @@ func TestRouter(t *testing.T) { | |||
| 
 | ||||
| 	for _, testcase := range []routeTestCase{ | ||||
| 		{ | ||||
| 			RouteName:  routeNameBase, | ||||
| 			RouteName:  RouteNameBase, | ||||
| 			RequestURI: "/v2/", | ||||
| 			Vars:       map[string]string{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameImageManifest, | ||||
| 			RouteName:  RouteNameManifest, | ||||
| 			RequestURI: "/v2/foo/bar/manifests/tag", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
|  | @ -60,14 +60,14 @@ func TestRouter(t *testing.T) { | |||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameTags, | ||||
| 			RouteName:  RouteNameTags, | ||||
| 			RequestURI: "/v2/foo/bar/tags/list", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlob, | ||||
| 			RouteName:  RouteNameBlob, | ||||
| 			RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name":   "foo/bar", | ||||
|  | @ -75,7 +75,7 @@ func TestRouter(t *testing.T) { | |||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlob, | ||||
| 			RouteName:  RouteNameBlob, | ||||
| 			RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name":   "foo/bar", | ||||
|  | @ -83,14 +83,14 @@ func TestRouter(t *testing.T) { | |||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUpload, | ||||
| 			RouteName:  RouteNameBlobUpload, | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RouteName:  RouteNameBlobUploadChunk, | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/uuid", | ||||
| 			Vars: map[string]string{ | ||||
| 				"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", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
|  | @ -106,7 +106,7 @@ func TestRouter(t *testing.T) { | |||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RouteName:  RouteNameBlobUploadChunk, | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
|  | @ -117,7 +117,7 @@ func TestRouter(t *testing.T) { | |||
| 			// Check ambiguity: ensure we can distinguish between tags for
 | ||||
| 			// "foo/bar/image/image" and image for "foo/bar/image" with tag
 | ||||
| 			// "tags"
 | ||||
| 			RouteName:  routeNameImageManifest, | ||||
| 			RouteName:  RouteNameManifest, | ||||
| 			RequestURI: "/v2/foo/bar/manifests/manifests/tags", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar/manifests", | ||||
|  | @ -127,14 +127,14 @@ func TestRouter(t *testing.T) { | |||
| 		{ | ||||
| 			// This case presents an ambiguity between foo/bar with tag="tags"
 | ||||
| 			// and list tags for "foo/bar/manifest"
 | ||||
| 			RouteName:  routeNameTags, | ||||
| 			RouteName:  RouteNameTags, | ||||
| 			RequestURI: "/v2/foo/bar/manifests/tags/list", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar/manifests", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RouteName:  RouteNameBlobUploadChunk, | ||||
| 			RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
| 			StatusCode: http.StatusNotFound, | ||||
| 		}, | ||||
|  | @ -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() | ||||
| } | ||||
|  | @ -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" | ||||
| 	"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/configuration" | ||||
| 	"github.com/docker/docker-registry/digest" | ||||
|  | @ -34,13 +34,13 @@ func TestCheckAPI(t *testing.T) { | |||
| 
 | ||||
| 	app := NewApp(config) | ||||
| 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | ||||
| 	builder, err := newURLBuilderFromString(server.URL) | ||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error creating url builder: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	baseURL, err := builder.buildBaseURL() | ||||
| 	baseURL, err := builder.BuildBaseURL() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error building base url: %v", err) | ||||
| 	} | ||||
|  | @ -81,7 +81,7 @@ func TestLayerAPI(t *testing.T) { | |||
| 
 | ||||
| 	app := NewApp(config) | ||||
| 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | ||||
| 	builder, err := newURLBuilderFromString(server.URL) | ||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error creating url builder: %v", err) | ||||
|  | @ -98,7 +98,7 @@ func TestLayerAPI(t *testing.T) { | |||
| 
 | ||||
| 	// -----------------------------------
 | ||||
| 	// Test fetch for non-existent content
 | ||||
| 	layerURL, err := builder.buildLayerURL(imageName, layerDigest) | ||||
| 	layerURL, err := builder.BuildBlobURL(imageName, layerDigest) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error building url: %v", err) | ||||
| 	} | ||||
|  | @ -121,7 +121,7 @@ func TestLayerAPI(t *testing.T) { | |||
| 
 | ||||
| 	// ------------------------------------------
 | ||||
| 	// Upload a layer
 | ||||
| 	layerUploadURL, err := builder.buildLayerUploadURL(imageName) | ||||
| 	layerUploadURL, err := builder.BuildBlobUploadURL(imageName) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error building upload url: %v", err) | ||||
| 	} | ||||
|  | @ -196,7 +196,7 @@ func TestManifestAPI(t *testing.T) { | |||
| 
 | ||||
| 	app := NewApp(config) | ||||
| 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | ||||
| 	builder, err := newURLBuilderFromString(server.URL) | ||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error creating url builder: %v", err) | ||||
| 	} | ||||
|  | @ -204,7 +204,7 @@ func TestManifestAPI(t *testing.T) { | |||
| 	imageName := "foo/bar" | ||||
| 	tag := "thetag" | ||||
| 
 | ||||
| 	manifestURL, err := builder.buildManifestURL(imageName, tag) | ||||
| 	manifestURL, err := builder.BuildManifestURL(imageName, tag) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error getting manifest url: %v", err) | ||||
| 	} | ||||
|  | @ -227,7 +227,7 @@ func TestManifestAPI(t *testing.T) { | |||
| 	// }
 | ||||
| 	dec := json.NewDecoder(resp.Body) | ||||
| 
 | ||||
| 	var respErrs errors.Errors | ||||
| 	var respErrs v2.Errors | ||||
| 	if err := dec.Decode(&respErrs); err != nil { | ||||
| 		t.Fatalf("unexpected error decoding error response: %v", err) | ||||
| 	} | ||||
|  | @ -236,11 +236,11 @@ func TestManifestAPI(t *testing.T) { | |||
| 		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) | ||||
| 	} | ||||
| 
 | ||||
| 	tagsURL, err := builder.buildTagsURL(imageName) | ||||
| 	tagsURL, err := builder.BuildTagsURL(imageName) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error building tags url: %v", err) | ||||
| 	} | ||||
|  | @ -262,7 +262,7 @@ func TestManifestAPI(t *testing.T) { | |||
| 		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) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -296,11 +296,11 @@ func TestManifestAPI(t *testing.T) { | |||
| 
 | ||||
| 	for _, err := range respErrs.Errors { | ||||
| 		switch err.Code { | ||||
| 		case errors.ErrorCodeManifestUnverified: | ||||
| 		case v2.ErrorCodeManifestUnverified: | ||||
| 			unverified++ | ||||
| 		case errors.ErrorCodeBlobUnknown: | ||||
| 		case v2.ErrorCodeBlobUnknown: | ||||
| 			missingLayers++ | ||||
| 		case errors.ErrorCodeDigestInvalid: | ||||
| 		case v2.ErrorCodeDigestInvalid: | ||||
| 			// TODO(stevvooe): This error isn't quite descriptive enough --
 | ||||
| 			// the layer with an invalid digest isn't identified.
 | ||||
| 			invalidDigests++ | ||||
|  | @ -427,8 +427,8 @@ func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response { | |||
| 	return resp | ||||
| } | ||||
| 
 | ||||
| func startPushLayer(t *testing.T, ub *urlBuilder, name string) string { | ||||
| 	layerUploadURL, err := ub.buildLayerUploadURL(name) | ||||
| func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) string { | ||||
| 	layerUploadURL, err := ub.BuildBlobUploadURL(name) | ||||
| 	if err != nil { | ||||
| 		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.
 | ||||
| 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) | ||||
| 	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()}, | ||||
| 		"size":   []string{fmt.Sprint(rsLength)}, | ||||
| 	}) | ||||
| 	}.Encode() | ||||
| 
 | ||||
| 	uploadURL := u.String() | ||||
| 
 | ||||
| 	// Just do a monolithic upload
 | ||||
| 	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) | ||||
| 
 | ||||
| 	expectedLayerURL, err := ub.buildLayerURL(name, dgst) | ||||
| 	expectedLayerURL, err := ub.BuildBlobURL(name, dgst) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error building expected layer url: %v", err) | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										17
									
								
								app.go
								
								
								
								
							
							
						
						
									
										17
									
								
								app.go
								
								
								
								
							|  | @ -4,6 +4,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/api/v2" | ||||
| 	"github.com/docker/docker-registry/storagedriver" | ||||
| 	"github.com/docker/docker-registry/storagedriver/factory" | ||||
| 
 | ||||
|  | @ -35,18 +36,18 @@ type App struct { | |||
| func NewApp(configuration configuration.Configuration) *App { | ||||
| 	app := &App{ | ||||
| 		Config: configuration, | ||||
| 		router: v2APIRouter(), | ||||
| 		router: v2.Router(), | ||||
| 	} | ||||
| 
 | ||||
| 	// 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) | ||||
| 	}) | ||||
| 	app.register(routeNameImageManifest, imageManifestDispatcher) | ||||
| 	app.register(routeNameTags, tagsDispatcher) | ||||
| 	app.register(routeNameBlob, layerDispatcher) | ||||
| 	app.register(routeNameBlobUpload, layerUploadDispatcher) | ||||
| 	app.register(routeNameBlobUploadResume, layerUploadDispatcher) | ||||
| 	app.register(v2.RouteNameManifest, imageManifestDispatcher) | ||||
| 	app.register(v2.RouteNameTags, tagsDispatcher) | ||||
| 	app.register(v2.RouteNameBlob, layerDispatcher) | ||||
| 	app.register(v2.RouteNameBlobUpload, layerUploadDispatcher) | ||||
| 	app.register(v2.RouteNameBlobUploadChunk, layerUploadDispatcher) | ||||
| 
 | ||||
| 	driver, err := factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) | ||||
| 
 | ||||
|  | @ -114,7 +115,7 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler { | |||
| 		context := &Context{ | ||||
| 			App:        app, | ||||
| 			Name:       vars["name"], | ||||
| 			urlBuilder: newURLBuilderFromRequest(r), | ||||
| 			urlBuilder: v2.NewURLBuilderFromRequest(r), | ||||
| 		} | ||||
| 
 | ||||
| 		// Store vars for underlying handlers.
 | ||||
|  |  | |||
							
								
								
									
										15
									
								
								app_test.go
								
								
								
								
							
							
						
						
									
										15
									
								
								app_test.go
								
								
								
								
							|  | @ -6,6 +6,7 @@ import ( | |||
| 	"net/url" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/api/v2" | ||||
| 	"github.com/docker/docker-registry/configuration" | ||||
| ) | ||||
| 
 | ||||
|  | @ -16,10 +17,10 @@ import ( | |||
| func TestAppDispatcher(t *testing.T) { | ||||
| 	app := &App{ | ||||
| 		Config: configuration.Configuration{}, | ||||
| 		router: v2APIRouter(), | ||||
| 		router: v2.Router(), | ||||
| 	} | ||||
| 	server := httptest.NewServer(app) | ||||
| 	router := v2APIRouter() | ||||
| 	router := v2.Router() | ||||
| 
 | ||||
| 	serverURL, err := url.Parse(server.URL) | ||||
| 	if err != nil { | ||||
|  | @ -71,33 +72,33 @@ func TestAppDispatcher(t *testing.T) { | |||
| 		vars     []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			endpoint: routeNameImageManifest, | ||||
| 			endpoint: v2.RouteNameManifest, | ||||
| 			vars: []string{ | ||||
| 				"name", "foo/bar", | ||||
| 				"tag", "sometag", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			endpoint: routeNameTags, | ||||
| 			endpoint: v2.RouteNameTags, | ||||
| 			vars: []string{ | ||||
| 				"name", "foo/bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			endpoint: routeNameBlob, | ||||
| 			endpoint: v2.RouteNameBlob, | ||||
| 			vars: []string{ | ||||
| 				"name", "foo/bar", | ||||
| 				"digest", "tarsum.v1+bogus:abcdef0123456789", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			endpoint: routeNameBlobUpload, | ||||
| 			endpoint: v2.RouteNameBlobUpload, | ||||
| 			vars: []string{ | ||||
| 				"name", "foo/bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			endpoint: routeNameBlobUploadResume, | ||||
| 			endpoint: v2.RouteNameBlobUploadChunk, | ||||
| 			vars: []string{ | ||||
| 				"name", "foo/bar", | ||||
| 				"uuid", "theuuid", | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import ( | |||
| 	"regexp" | ||||
| 	"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/storage" | ||||
| ) | ||||
|  | @ -96,7 +96,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return nil, &ImageManifestNotFoundError{Name: name, Tag: tag} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 
 | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
|  | @ -136,7 +136,7 @@ func (r *clientImpl) PutImageManifest(name, tag string, manifest *storage.Signed | |||
| 	case response.StatusCode == http.StatusOK: | ||||
| 		return nil | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errors errors.Errors | ||||
| 		var errors v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errors) | ||||
| 		if err != nil { | ||||
|  | @ -169,7 +169,7 @@ func (r *clientImpl) DeleteImage(name, tag string) error { | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return &ImageManifestNotFoundError{Name: name, Tag: tag} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -197,7 +197,7 @@ func (r *clientImpl) ListImageTags(name string) ([]string, error) { | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return nil, &RepositoryNotFoundError{Name: name} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -240,7 +240,7 @@ func (r *clientImpl) BlobLength(name string, dgst digest.Digest) (int, error) { | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return -1, nil | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -279,7 +279,7 @@ func (r *clientImpl) GetBlob(name string, dgst digest.Digest, byteOffset int) (i | |||
| 		response.Body.Close() | ||||
| 		return nil, 0, &BlobNotFoundError{Name: name, Digest: dgst} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -312,7 +312,7 @@ func (r *clientImpl) InitiateBlobUpload(name string) (string, error) { | |||
| 	// case response.StatusCode == http.StatusNotFound:
 | ||||
| 	// return
 | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -338,7 +338,7 @@ func (r *clientImpl) GetBlobUploadStatus(location string) (int, int, error) { | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return 0, 0, &BlobUploadNotFoundError{Location: location} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -379,7 +379,7 @@ func (r *clientImpl) UploadBlob(location string, blob io.ReadCloser, length int, | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return &BlobUploadNotFoundError{Location: location} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -430,7 +430,7 @@ func (r *clientImpl) UploadBlobChunk(location string, blobChunk io.ReadCloser, l | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return &BlobUploadNotFoundError{Location: location} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -472,7 +472,7 @@ func (r *clientImpl) FinishChunkedBlobUpload(location string, length int, dgst d | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return &BlobUploadNotFoundError{Location: location} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  | @ -504,7 +504,7 @@ func (r *clientImpl) CancelBlobUpload(location string) error { | |||
| 	case response.StatusCode == http.StatusNotFound: | ||||
| 		return &BlobUploadNotFoundError{Location: location} | ||||
| 	case response.StatusCode >= 400 && response.StatusCode < 500: | ||||
| 		var errs errors.Errors | ||||
| 		var errs v2.Errors | ||||
| 		decoder := json.NewDecoder(response.Body) | ||||
| 		err = decoder.Decode(&errs) | ||||
| 		if err != nil { | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ package client | |||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"sync" | ||||
|  | @ -14,11 +13,11 @@ import ( | |||
| var ( | ||||
| 	// ErrLayerAlreadyExists is returned when attempting to create a layer with
 | ||||
| 	// 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
 | ||||
| 	// 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
 | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| package client | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/storage" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/docker/docker-registry/storage" | ||||
| ) | ||||
| 
 | ||||
| // 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(), | ||||
| 			"size":        layerReader.Size(), | ||||
| 		}).Warn("Local layer incomplete") | ||||
| 		return errors.New("Local layer incomplete") | ||||
| 		return fmt.Errorf("Local layer incomplete") | ||||
| 	} | ||||
| 
 | ||||
| 	length, err := c.BlobLength(name, fsLayer.BlobSum) | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import ( | |||
| 	"strings" | ||||
| 	"text/tabwriter" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/api/errors" | ||||
| 	"github.com/docker/docker-registry/api/v2" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
|  | @ -40,7 +40,7 @@ func dumpErrors(wr io.Writer) { | |||
| 	defer writer.Flush() | ||||
| 
 | ||||
| 	fmt.Fprint(writer, "|") | ||||
| 	dtype := reflect.TypeOf(errors.ErrorDescriptor{}) | ||||
| 	dtype := reflect.TypeOf(v2.ErrorDescriptor{}) | ||||
| 	var fieldsPrinted int | ||||
| 	for i := 0; i < dtype.NumField(); i++ { | ||||
| 		field := dtype.Field(i) | ||||
|  | @ -61,7 +61,7 @@ func dumpErrors(wr io.Writer) { | |||
| 
 | ||||
| 	fmt.Fprintln(writer, "\n"+divider) | ||||
| 
 | ||||
| 	for _, descriptor := range errors.Descriptors { | ||||
| 	for _, descriptor := range v2.ErrorDescriptors { | ||||
| 		fmt.Fprint(writer, "|") | ||||
| 
 | ||||
| 		v := reflect.ValueOf(descriptor) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ package registry | |||
| 
 | ||||
| import ( | ||||
| 	"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
 | ||||
|  | @ -19,7 +19,7 @@ type Context struct { | |||
| 	// 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
 | ||||
| 	// 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
 | ||||
| 	// assignment.
 | ||||
|  | @ -28,5 +28,5 @@ type Context struct { | |||
| 	// log provides a context specific logger.
 | ||||
| 	log *logrus.Entry | ||||
| 
 | ||||
| 	urlBuilder *urlBuilder | ||||
| 	urlBuilder *v2.URLBuilder | ||||
| } | ||||
|  |  | |||
							
								
								
									
										16
									
								
								images.go
								
								
								
								
							
							
						
						
									
										16
									
								
								images.go
								
								
								
								
							|  | @ -5,7 +5,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"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/storage" | ||||
| 	"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) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		imh.Errors.Push(errors.ErrorCodeManifestUnknown, err) | ||||
| 		imh.Errors.Push(v2.ErrorCodeManifestUnknown, err) | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
| 	} | ||||
|  | @ -58,7 +58,7 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http | |||
| 
 | ||||
| 	var manifest storage.SignedManifest | ||||
| 	if err := dec.Decode(&manifest); err != nil { | ||||
| 		imh.Errors.Push(errors.ErrorCodeManifestInvalid, err) | ||||
| 		imh.Errors.Push(v2.ErrorCodeManifestInvalid, err) | ||||
| 		w.WriteHeader(http.StatusBadRequest) | ||||
| 		return | ||||
| 	} | ||||
|  | @ -71,14 +71,14 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http | |||
| 			for _, verificationError := range err { | ||||
| 				switch verificationError := verificationError.(type) { | ||||
| 				case storage.ErrUnknownLayer: | ||||
| 					imh.Errors.Push(errors.ErrorCodeBlobUnknown, verificationError.FSLayer) | ||||
| 					imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer) | ||||
| 				case storage.ErrManifestUnverified: | ||||
| 					imh.Errors.Push(errors.ErrorCodeManifestUnverified) | ||||
| 					imh.Errors.Push(v2.ErrorCodeManifestUnverified) | ||||
| 				default: | ||||
| 					if verificationError == digest.ErrDigestInvalidFormat { | ||||
| 						// TODO(stevvooe): We need to really need to move all
 | ||||
| 						// errors to types. Its much more straightforward.
 | ||||
| 						imh.Errors.Push(errors.ErrorCodeDigestInvalid) | ||||
| 						imh.Errors.Push(v2.ErrorCodeDigestInvalid) | ||||
| 					} else { | ||||
| 						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 { | ||||
| 		switch err := err.(type) { | ||||
| 		case storage.ErrUnknownManifest: | ||||
| 			imh.Errors.Push(errors.ErrorCodeManifestUnknown, err) | ||||
| 			imh.Errors.Push(v2.ErrorCodeManifestUnknown, err) | ||||
| 			w.WriteHeader(http.StatusNotFound) | ||||
| 		default: | ||||
| 			imh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 			imh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 			w.WriteHeader(http.StatusBadRequest) | ||||
| 		} | ||||
| 		return | ||||
|  |  | |||
							
								
								
									
										8
									
								
								layer.go
								
								
								
								
							
							
						
						
									
										8
									
								
								layer.go
								
								
								
								
							|  | @ -3,7 +3,7 @@ package registry | |||
| import ( | ||||
| 	"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/storage" | ||||
| 	"github.com/gorilla/handlers" | ||||
|  | @ -15,7 +15,7 @@ func layerDispatcher(ctx *Context, r *http.Request) http.Handler { | |||
| 
 | ||||
| 	if err != nil { | ||||
| 		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) { | ||||
| 		case storage.ErrUnknownLayer: | ||||
| 			w.WriteHeader(http.StatusNotFound) | ||||
| 			lh.Errors.Push(errors.ErrorCodeBlobUnknown, err.FSLayer) | ||||
| 			lh.Errors.Push(v2.ErrorCodeBlobUnknown, err.FSLayer) | ||||
| 		default: | ||||
| 			lh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 			lh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import ( | |||
| 	"strconv" | ||||
| 
 | ||||
| 	"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/storage" | ||||
| 	"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) { | ||||
| 				logrus.Infof("error resolving upload: %v", err) | ||||
| 				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) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
 | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -76,7 +76,7 @@ func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.R | |||
| 
 | ||||
| 	if err := luh.layerUploadResponse(w, r); err != nil { | ||||
| 		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
 | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		return | ||||
| 	} | ||||
| 	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) { | ||||
| 	if luh.Upload == nil { | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		luh.Errors.Push(errors.ErrorCodeBlobUploadUnknown) | ||||
| 		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := luh.layerUploadResponse(w, r); err != nil { | ||||
| 		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
 | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		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) { | ||||
| 	if luh.Upload == nil { | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		luh.Errors.Push(errors.ErrorCodeBlobUploadUnknown) | ||||
| 		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown) | ||||
| 	} | ||||
| 
 | ||||
| 	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 != errNotReadyToComplete { | ||||
| 			w.WriteHeader(http.StatusInternalServerError) | ||||
| 			luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 			luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := luh.layerUploadResponse(w, r); err != nil { | ||||
| 		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
 | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		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) { | ||||
| 	if luh.Upload == nil { | ||||
| 		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
 | ||||
| // left to the caller.
 | ||||
| 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 { | ||||
| 		logrus.Infof("error building upload url: %s", 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) { | ||||
| 	layer, err := luh.Upload.Finish(size, dgst) | ||||
| 	if err != nil { | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		w.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	layerURL, err := luh.urlBuilder.forLayer(layer) | ||||
| 	layerURL, err := luh.urlBuilder.BuildBlobURL(layer.Name(), layer.Digest()) | ||||
| 	if err != nil { | ||||
| 		luh.Errors.Push(errors.ErrorCodeUnknown, err) | ||||
| 		luh.Errors.Push(v2.ErrorCodeUnknown, err) | ||||
| 		w.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										4
									
								
								tags.go
								
								
								
								
							
							
						
						
									
										4
									
								
								tags.go
								
								
								
								
							|  | @ -4,7 +4,7 @@ import ( | |||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/api/errors" | ||||
| 	"github.com/docker/docker-registry/api/v2" | ||||
| 	"github.com/docker/docker-registry/storage" | ||||
| 	"github.com/gorilla/handlers" | ||||
| ) | ||||
|  | @ -40,7 +40,7 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { | |||
| 		switch err := err.(type) { | ||||
| 		case storage.ErrUnknownRepository: | ||||
| 			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: | ||||
| 			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 New Issue