forked from TrueCloudLab/distribution
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.
This commit is contained in:
parent
da205085f3
commit
fec2afc93f
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