Merge pull request #701 from stevvooe/initial-api-routes
Initial V2 API Router Implementation
This commit is contained in:
commit
0e8647f1ce
2 changed files with 194 additions and 0 deletions
72
routes.go
Normal file
72
routes.go
Normal 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
Normal file
122
routes_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue