Merge pull request #842 from stevvooe/next-generation
Pluralize API methods and add the base endpoint to support API checksmaster
						commit
						18ace1f454
					
				
							
								
								
									
										45
									
								
								api_test.go
								
								
								
								
							
							
						
						
									
										45
									
								
								api_test.go
								
								
								
								
							|  | @ -5,6 +5,7 @@ import ( | |||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"net/http/httputil" | ||||
|  | @ -22,6 +23,50 @@ import ( | |||
| 	"github.com/gorilla/handlers" | ||||
| ) | ||||
| 
 | ||||
| // TestCheckAPI hits the base endpoint (/v2/) ensures we return the specified
 | ||||
| // 200 OK response.
 | ||||
| func TestCheckAPI(t *testing.T) { | ||||
| 	config := configuration.Configuration{ | ||||
| 		Storage: configuration.Storage{ | ||||
| 			"inmemory": configuration.Parameters{}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	app := NewApp(config) | ||||
| 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | ||||
| 	builder, err := newURLBuilderFromString(server.URL) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error creating url builder: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	baseURL, err := builder.buildBaseURL() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error building base url: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := http.Get(baseURL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error issuing request: %v", err) | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	checkResponse(t, "issuing api base check", resp, http.StatusOK) | ||||
| 	checkHeaders(t, resp, http.Header{ | ||||
| 		"Content-Type":   []string{"application/json"}, | ||||
| 		"Content-Length": []string{"2"}, | ||||
| 	}) | ||||
| 
 | ||||
| 	p, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error reading response body: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if string(p) != "{}" { | ||||
| 		t.Fatalf("unexpected response body: %v", string(p)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TestLayerAPI conducts a full of the of the layer api.
 | ||||
| func TestLayerAPI(t *testing.T) { | ||||
| 	// TODO(stevvooe): This test code is complete junk but it should cover the
 | ||||
|  |  | |||
							
								
								
									
										15
									
								
								app.go
								
								
								
								
							
							
						
						
									
										15
									
								
								app.go
								
								
								
								
							|  | @ -1,6 +1,7 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/docker/docker-registry/storagedriver" | ||||
|  | @ -38,6 +39,9 @@ func NewApp(configuration configuration.Configuration) *App { | |||
| 	} | ||||
| 
 | ||||
| 	// Register the handler dispatchers.
 | ||||
| 	app.register(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) | ||||
|  | @ -134,3 +138,14 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler { | |||
| 		} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // apiBase implements a simple yes-man for doing overall checks against the
 | ||||
| // api. This can support auth roundtrips to support docker login.
 | ||||
| func apiBase(w http.ResponseWriter, r *http.Request) { | ||||
| 	const emptyJSON = "{}" | ||||
| 	// Provide a simple /v2/ 200 OK response with empty json response.
 | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
| 	w.Header().Set("Content-Length", fmt.Sprint(len(emptyJSON))) | ||||
| 
 | ||||
| 	fmt.Fprint(w, emptyJSON) | ||||
| } | ||||
|  |  | |||
|  | @ -222,7 +222,7 @@ func (r *clientImpl) ListImageTags(name string) ([]string, error) { | |||
| } | ||||
| 
 | ||||
| func (r *clientImpl) BlobLength(name string, dgst digest.Digest) (int, error) { | ||||
| 	response, err := http.Head(fmt.Sprintf("%s/v2/%s/blob/%s", r.Endpoint, name, dgst)) | ||||
| 	response, err := http.Head(fmt.Sprintf("%s/v2/%s/blobs/%s", r.Endpoint, name, dgst)) | ||||
| 	if err != nil { | ||||
| 		return -1, err | ||||
| 	} | ||||
|  | @ -255,7 +255,7 @@ func (r *clientImpl) BlobLength(name string, dgst digest.Digest) (int, error) { | |||
| 
 | ||||
| func (r *clientImpl) GetBlob(name string, dgst digest.Digest, byteOffset int) (io.ReadCloser, int, error) { | ||||
| 	getRequest, err := http.NewRequest("GET", | ||||
| 		fmt.Sprintf("%s/v2/%s/blob/%s", r.Endpoint, name, dgst), nil) | ||||
| 		fmt.Sprintf("%s/v2/%s/blobs/%s", r.Endpoint, name, dgst), nil) | ||||
| 	if err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | @ -294,7 +294,7 @@ func (r *clientImpl) GetBlob(name string, dgst digest.Digest, byteOffset int) (i | |||
| 
 | ||||
| func (r *clientImpl) InitiateBlobUpload(name string) (string, error) { | ||||
| 	postRequest, err := http.NewRequest("POST", | ||||
| 		fmt.Sprintf("%s/v2/%s/blob/upload/", r.Endpoint, name), nil) | ||||
| 		fmt.Sprintf("%s/v2/%s/blobs/uploads/", r.Endpoint, name), nil) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | @ -519,7 +519,7 @@ func (r *clientImpl) CancelBlobUpload(location string) error { | |||
| // imageManifestURL is a helper method for returning the full url to an image
 | ||||
| // manifest
 | ||||
| func (r *clientImpl) imageManifestURL(name, tag string) string { | ||||
| 	return fmt.Sprintf("%s/v2/%s/manifest/%s", r.Endpoint, name, tag) | ||||
| 	return fmt.Sprintf("%s/v2/%s/manifests/%s", r.Endpoint, name, tag) | ||||
| } | ||||
| 
 | ||||
| // parseRangeHeader parses out the offset and length from a returned Range
 | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ func TestPush(t *testing.T) { | |||
| 		// because we can't know which blob will get which location.
 | ||||
| 		// It's sort of okay because we're using unique digests, but this needs
 | ||||
| 		// to change at some point.
 | ||||
| 		uploadLocations[i] = fmt.Sprintf("/v2/%s/blob/test-uuid", name) | ||||
| 		uploadLocations[i] = fmt.Sprintf("/v2/%s/blobs/test-uuid", name) | ||||
| 		blobs[i] = storage.FSLayer{BlobSum: blob.digest} | ||||
| 		history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()} | ||||
| 	} | ||||
|  | @ -66,7 +66,7 @@ func TestPush(t *testing.T) { | |||
| 		blobRequestResponseMappings[2*i] = testutil.RequestResponseMapping{ | ||||
| 			Request: testutil.Request{ | ||||
| 				Method: "POST", | ||||
| 				Route:  "/v2/" + name + "/blob/upload/", | ||||
| 				Route:  "/v2/" + name + "/blobs/uploads/", | ||||
| 			}, | ||||
| 			Response: testutil.Response{ | ||||
| 				StatusCode: http.StatusAccepted, | ||||
|  | @ -94,7 +94,7 @@ func TestPush(t *testing.T) { | |||
| 	handler := testutil.NewHandler(append(blobRequestResponseMappings, testutil.RequestResponseMapping{ | ||||
| 		Request: testutil.Request{ | ||||
| 			Method: "PUT", | ||||
| 			Route:  "/v2/" + name + "/manifest/" + tag, | ||||
| 			Route:  "/v2/" + name + "/manifests/" + tag, | ||||
| 			Body:   manifest.Raw, | ||||
| 		}, | ||||
| 		Response: testutil.Response{ | ||||
|  | @ -185,7 +185,7 @@ func TestPull(t *testing.T) { | |||
| 		blobRequestResponseMappings[i] = testutil.RequestResponseMapping{ | ||||
| 			Request: testutil.Request{ | ||||
| 				Method: "GET", | ||||
| 				Route:  "/v2/" + name + "/blob/" + blob.digest.String(), | ||||
| 				Route:  "/v2/" + name + "/blobs/" + blob.digest.String(), | ||||
| 			}, | ||||
| 			Response: testutil.Response{ | ||||
| 				StatusCode: http.StatusOK, | ||||
|  | @ -197,7 +197,7 @@ func TestPull(t *testing.T) { | |||
| 	handler := testutil.NewHandler(append(blobRequestResponseMappings, testutil.RequestResponseMapping{ | ||||
| 		Request: testutil.Request{ | ||||
| 			Method: "GET", | ||||
| 			Route:  "/v2/" + name + "/manifest/" + tag, | ||||
| 			Route:  "/v2/" + name + "/manifests/" + tag, | ||||
| 		}, | ||||
| 		Response: testutil.Response{ | ||||
| 			StatusCode: http.StatusOK, | ||||
|  | @ -292,7 +292,7 @@ func TestPullResume(t *testing.T) { | |||
| 		layerRequestResponseMappings[2*i] = testutil.RequestResponseMapping{ | ||||
| 			Request: testutil.Request{ | ||||
| 				Method: "GET", | ||||
| 				Route:  "/v2/" + name + "/blob/" + blob.digest.String(), | ||||
| 				Route:  "/v2/" + name + "/blobs/" + blob.digest.String(), | ||||
| 			}, | ||||
| 			Response: testutil.Response{ | ||||
| 				StatusCode: http.StatusOK, | ||||
|  | @ -305,7 +305,7 @@ func TestPullResume(t *testing.T) { | |||
| 		layerRequestResponseMappings[2*i+1] = testutil.RequestResponseMapping{ | ||||
| 			Request: testutil.Request{ | ||||
| 				Method: "GET", | ||||
| 				Route:  "/v2/" + name + "/blob/" + blob.digest.String(), | ||||
| 				Route:  "/v2/" + name + "/blobs/" + blob.digest.String(), | ||||
| 			}, | ||||
| 			Response: testutil.Response{ | ||||
| 				StatusCode: http.StatusOK, | ||||
|  | @ -318,7 +318,7 @@ func TestPullResume(t *testing.T) { | |||
| 		layerRequestResponseMappings = append(layerRequestResponseMappings, testutil.RequestResponseMapping{ | ||||
| 			Request: testutil.Request{ | ||||
| 				Method: "GET", | ||||
| 				Route:  "/v2/" + name + "/manifest/" + tag, | ||||
| 				Route:  "/v2/" + name + "/manifests/" + tag, | ||||
| 			}, | ||||
| 			Response: testutil.Response{ | ||||
| 				StatusCode: http.StatusOK, | ||||
|  |  | |||
							
								
								
									
										14
									
								
								routes.go
								
								
								
								
							
							
						
						
									
										14
									
								
								routes.go
								
								
								
								
							|  | @ -6,6 +6,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	routeNameBase             = "base" | ||||
| 	routeNameImageManifest    = "image-manifest" | ||||
| 	routeNameTags             = "tags" | ||||
| 	routeNameBlob             = "blob" | ||||
|  | @ -27,11 +28,16 @@ func v2APIRouter() *mux.Router { | |||
| 	router := mux.NewRouter(). | ||||
| 		StrictSlash(true) | ||||
| 
 | ||||
| 	// GET /v2/	Check	Check that the registry implements API version 2(.1)
 | ||||
| 	router. | ||||
| 		Path("/v2/"). | ||||
| 		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() + "}/manifest/{tag:" + common.TagNameRegexp.String() + "}"). | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/manifests/{tag:" + common.TagNameRegexp.String() + "}"). | ||||
| 		Name(routeNameImageManifest) | ||||
| 
 | ||||
| 	// GET	/v2/<name>/tags/list	Tags	Fetch the tags under the repository identified by name.
 | ||||
|  | @ -41,19 +47,19 @@ func v2APIRouter() *mux.Router { | |||
| 
 | ||||
| 	// GET	/v2/<name>/blob/<digest>	Layer	Fetch the blob identified by digest.
 | ||||
| 	router. | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/{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) | ||||
| 
 | ||||
| 	// POST	/v2/<name>/blob/upload/	Layer Upload	Initiate an upload of the layer identified by tarsum.
 | ||||
| 	router. | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/upload/"). | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/"). | ||||
| 		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() + "}/blob/upload/{uuid}"). | ||||
| 		Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}"). | ||||
| 		Name(routeNameBlobUploadResume) | ||||
| 
 | ||||
| 	return router | ||||
|  |  | |||
|  | @ -46,9 +46,14 @@ func TestRouter(t *testing.T) { | |||
| 	server := httptest.NewServer(router) | ||||
| 
 | ||||
| 	for _, testcase := range []routeTestCase{ | ||||
| 		{ | ||||
| 			RouteName:  routeNameBase, | ||||
| 			RequestURI: "/v2/", | ||||
| 			Vars:       map[string]string{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameImageManifest, | ||||
| 			RequestURI: "/v2/foo/bar/manifest/tag", | ||||
| 			RequestURI: "/v2/foo/bar/manifests/tag", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 				"tag":  "tag", | ||||
|  | @ -63,7 +68,7 @@ func TestRouter(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlob, | ||||
| 			RequestURI: "/v2/foo/bar/blob/tarsum.dev+foo:abcdef0919234", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name":   "foo/bar", | ||||
| 				"digest": "tarsum.dev+foo:abcdef0919234", | ||||
|  | @ -71,7 +76,7 @@ func TestRouter(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlob, | ||||
| 			RequestURI: "/v2/foo/bar/blob/sha256:abcdef0919234", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name":   "foo/bar", | ||||
| 				"digest": "sha256:abcdef0919234", | ||||
|  | @ -79,14 +84,14 @@ func TestRouter(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUpload, | ||||
| 			RequestURI: "/v2/foo/bar/blob/upload/", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RequestURI: "/v2/foo/bar/blob/upload/uuid", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/uuid", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 				"uuid": "uuid", | ||||
|  | @ -94,7 +99,7 @@ func TestRouter(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RequestURI: "/v2/foo/bar/blob/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 				"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
|  | @ -102,7 +107,7 @@ func TestRouter(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RequestURI: "/v2/foo/bar/blob/upload/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", | ||||
| 			RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar", | ||||
| 				"uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", | ||||
|  | @ -113,9 +118,9 @@ func TestRouter(t *testing.T) { | |||
| 			// "foo/bar/image/image" and image for "foo/bar/image" with tag
 | ||||
| 			// "tags"
 | ||||
| 			RouteName:  routeNameImageManifest, | ||||
| 			RequestURI: "/v2/foo/bar/manifest/manifest/tags", | ||||
| 			RequestURI: "/v2/foo/bar/manifests/manifests/tags", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar/manifest", | ||||
| 				"name": "foo/bar/manifests", | ||||
| 				"tag":  "tags", | ||||
| 			}, | ||||
| 		}, | ||||
|  | @ -123,14 +128,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, | ||||
| 			RequestURI: "/v2/foo/bar/manifest/tags/list", | ||||
| 			RequestURI: "/v2/foo/bar/manifests/tags/list", | ||||
| 			Vars: map[string]string{ | ||||
| 				"name": "foo/bar/manifest", | ||||
| 				"name": "foo/bar/manifests", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			RouteName:  routeNameBlobUploadResume, | ||||
| 			RequestURI: "/v2/foo/../../layer/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
| 			RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", | ||||
| 			StatusCode: http.StatusNotFound, | ||||
| 		}, | ||||
| 	} { | ||||
|  |  | |||
							
								
								
									
										14
									
								
								urls.go
								
								
								
								
							
							
						
						
									
										14
									
								
								urls.go
								
								
								
								
							|  | @ -39,6 +39,20 @@ func newURLBuilderFromString(root string) (*urlBuilder, error) { | |||
| 	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) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue