Initial V2 API Router Implementation

This commit includes the initial API router, based on gorilla mux and a test
suite ensuring the expected variables are extracted. Currently unexported, the
structure here will likely change as this definition will be shared with the
API client.
pull/4/head
Stephen J Day 2014-11-07 16:08:14 -08:00
parent da205085f3
commit fec2afc93f
2 changed files with 194 additions and 0 deletions

72
routes.go 100644
View File

@ -0,0 +1,72 @@
package registry
import (
"github.com/gorilla/mux"
)
const (
routeNameRoot = "root"
routeNameName = "name"
routeNameImageManifest = "image-manifest"
routeNameTags = "tags"
routeNameLayer = "layer"
routeNameStartLayerUpload = "start-layer-upload"
routeNameLayerUpload = "layer-upload"
)
var allEndpoints = []string{
routeNameImageManifest,
routeNameTags,
routeNameLayer,
routeNameStartLayerUpload,
routeNameLayerUpload,
}
// 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()
rootRouter := router.
PathPrefix("/v2").
Name(routeNameRoot).
Subrouter()
// All routes are subordinate to named routes
namedRouter := rootRouter.
PathPrefix("/{name:[A-Za-z0-9-_]+/[A-Za-z0-9-_]+}"). // TODO(stevvooe): Verify this format with core
Name(routeNameName).
Subrouter().
StrictSlash(true)
// GET /v2/<name>/image/<tag> Image Manifest Fetch the image manifest identified by name and tag.
// PUT /v2/<name>/image/<tag> Image Manifest Upload the image manifest identified by name and tag.
// DELETE /v2/<name>/image/<tag> Image Manifest Delete the image identified by name and tag.
namedRouter.
Path("/image/{tag:[A-Za-z0-9-_]+}").
Name(routeNameImageManifest)
// GET /v2/<name>/tags Tags Fetch the tags under the repository identified by name.
namedRouter.
Path("/tags").
Name(routeNameTags)
// GET /v2/<name>/layer/<tarsum> Layer Fetch the layer identified by tarsum.
namedRouter.
Path("/layer/{tarsum}").
Name(routeNameLayer)
// POST /v2/<name>/layer/<tarsum>/upload/ Layer Upload Initiate an upload of the layer identified by tarsum. Requires length and a checksum parameter.
namedRouter.
Path("/layer/{tarsum}/upload/").
Name(routeNameStartLayerUpload)
// GET /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Get the status of the upload identified by tarsum and uuid.
// PUT /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid.
// DELETE /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Cancel the upload identified by layer and uuid
namedRouter.
Path("/layer/{tarsum}/upload/{uuid}").
Name(routeNameLayerUpload)
return router
}

122
routes_test.go 100644
View File

@ -0,0 +1,122 @@
package registry
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/gorilla/mux"
)
type routeInfo struct {
RequestURI string
Vars map[string]string
}
// 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) {
routeInfo := routeInfo{
RequestURI: r.RequestURI,
Vars: mux.Vars(r),
}
enc := json.NewEncoder(w)
if err := enc.Encode(routeInfo); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
// Startup test server
server := httptest.NewServer(router)
for _, testcase := range []struct {
routeName string
expectedRouteInfo routeInfo
}{
{
routeName: routeNameImageManifest,
expectedRouteInfo: routeInfo{
RequestURI: "/v2/foo/bar/image/tag",
Vars: map[string]string{
"name": "foo/bar",
"tag": "tag",
},
},
},
{
routeName: routeNameTags,
expectedRouteInfo: routeInfo{
RequestURI: "/v2/foo/bar/tags",
Vars: map[string]string{
"name": "foo/bar",
},
},
},
{
routeName: routeNameLayer,
expectedRouteInfo: routeInfo{
RequestURI: "/v2/foo/bar/layer/tarsum",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum",
},
},
},
{
routeName: routeNameStartLayerUpload,
expectedRouteInfo: routeInfo{
RequestURI: "/v2/foo/bar/layer/tarsum/upload/",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum",
},
},
},
{
routeName: routeNameLayerUpload,
expectedRouteInfo: routeInfo{
RequestURI: "/v2/foo/bar/layer/tarsum/upload/uuid",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum",
"uuid": "uuid",
},
},
},
} {
// Register the endpoint
router.GetRoute(testcase.routeName).Handler(testHandler)
u := server.URL + testcase.expectedRouteInfo.RequestURI
resp, err := http.Get(u)
if err != nil {
t.Fatalf("error issuing get request: %v", err)
}
dec := json.NewDecoder(resp.Body)
var actualRouteInfo routeInfo
if err := dec.Decode(&actualRouteInfo); err != nil {
t.Fatalf("error reading json response: %v", err)
}
if !reflect.DeepEqual(actualRouteInfo, testcase.expectedRouteInfo) {
t.Fatalf("actual does not equal expected: %v != %v", actualRouteInfo, testcase.expectedRouteInfo)
}
}
}