Move routes to urls package

To lock down V2 api routing, we are moving it to a separate package, with an
exported router creation function and route names. Updates will follow to
prepare the package for export.
This commit is contained in:
Stephen J Day 2014-12-11 21:03:45 -08:00
parent 47324d0398
commit c78d173cf7
2 changed files with 0 additions and 0 deletions

66
api/urls/routes.go Normal file
View file

@ -0,0 +1,66 @@
package registry
import (
"github.com/docker/docker-registry/common"
"github.com/gorilla/mux"
)
const (
routeNameBase = "base"
routeNameImageManifest = "image-manifest"
routeNameTags = "tags"
routeNameBlob = "blob"
routeNameBlobUpload = "blob-upload"
routeNameBlobUploadResume = "blob-upload-resume"
)
var allEndpoints = []string{
routeNameImageManifest,
routeNameTags,
routeNameBlob,
routeNameBlobUpload,
routeNameBlobUploadResume,
}
// 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 := 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() + "}/manifests/{tag:" + common.TagNameRegexp.String() + "}").
Name(routeNameImageManifest)
// 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)
// 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)
// 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)
// 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)
return router
}

184
api/urls/routes_test.go Normal file
View file

@ -0,0 +1,184 @@
package registry
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/gorilla/mux"
)
type routeTestCase struct {
RequestURI string
Vars map[string]string
RouteName string
StatusCode int
}
// TestRouter registers a test handler with all the routes and ensures that
// each route returns the expected path variables. Not method verification is
// present. This not meant to be exhaustive but as check to ensure that the
// expected variables are extracted.
//
// This may go away as the application structure comes together.
func TestRouter(t *testing.T) {
router := v2APIRouter()
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
testCase := routeTestCase{
RequestURI: r.RequestURI,
Vars: mux.Vars(r),
RouteName: mux.CurrentRoute(r).GetName(),
}
enc := json.NewEncoder(w)
if err := enc.Encode(testCase); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
// Startup test server
server := httptest.NewServer(router)
for _, testcase := range []routeTestCase{
{
RouteName: routeNameBase,
RequestURI: "/v2/",
Vars: map[string]string{},
},
{
RouteName: routeNameImageManifest,
RequestURI: "/v2/foo/bar/manifests/tag",
Vars: map[string]string{
"name": "foo/bar",
"tag": "tag",
},
},
{
RouteName: routeNameTags,
RequestURI: "/v2/foo/bar/tags/list",
Vars: map[string]string{
"name": "foo/bar",
},
},
{
RouteName: routeNameBlob,
RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234",
Vars: map[string]string{
"name": "foo/bar",
"digest": "tarsum.dev+foo:abcdef0919234",
},
},
{
RouteName: routeNameBlob,
RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
Vars: map[string]string{
"name": "foo/bar",
"digest": "sha256:abcdef0919234",
},
},
{
RouteName: routeNameBlobUpload,
RequestURI: "/v2/foo/bar/blobs/uploads/",
Vars: map[string]string{
"name": "foo/bar",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blobs/uploads/uuid",
Vars: map[string]string{
"name": "foo/bar",
"uuid": "uuid",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
Vars: map[string]string{
"name": "foo/bar",
"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
Vars: map[string]string{
"name": "foo/bar",
"uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
},
},
{
// Check ambiguity: ensure we can distinguish between tags for
// "foo/bar/image/image" and image for "foo/bar/image" with tag
// "tags"
RouteName: routeNameImageManifest,
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
Vars: map[string]string{
"name": "foo/bar/manifests",
"tag": "tags",
},
},
{
// This case presents an ambiguity between foo/bar with tag="tags"
// and list tags for "foo/bar/manifest"
RouteName: routeNameTags,
RequestURI: "/v2/foo/bar/manifests/tags/list",
Vars: map[string]string{
"name": "foo/bar/manifests",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
StatusCode: http.StatusNotFound,
},
} {
// Register the endpoint
router.GetRoute(testcase.RouteName).Handler(testHandler)
u := server.URL + testcase.RequestURI
resp, err := http.Get(u)
if err != nil {
t.Fatalf("error issuing get request: %v", err)
}
if testcase.StatusCode == 0 {
// Override default, zero-value
testcase.StatusCode = http.StatusOK
}
if resp.StatusCode != testcase.StatusCode {
t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode)
}
if testcase.StatusCode != http.StatusOK {
// We don't care about json response.
continue
}
dec := json.NewDecoder(resp.Body)
var actualRouteInfo routeTestCase
if err := dec.Decode(&actualRouteInfo); err != nil {
t.Fatalf("error reading json response: %v", err)
}
// Needs to be set out of band
actualRouteInfo.StatusCode = resp.StatusCode
if actualRouteInfo.RouteName != testcase.RouteName {
t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName)
}
if !reflect.DeepEqual(actualRouteInfo, testcase) {
t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase)
}
}
}