Update routes to reflect specification changes

To bring the implementation inline with the specification, the names and
structure of the API routes have been updated.

The overloaded term "image" has been replaced with the term "manifest", which
may also be known as "image manifest".  The desire for the layer storage to be
more of a general blob storage is reflected in moving from "layer" api prefixes
to "blob". The "tarsum" path parameter has been replaced by a more general
"digest" parameter and is no longer required to start uploads. Another set of
changes will come along to support this change at the storage service layer.
This commit is contained in:
Stephen J Day 2014-11-18 19:38:14 -08:00
parent 9dc6fa3765
commit 5789ca7629
6 changed files with 70 additions and 63 deletions

6
app.go
View file

@ -29,10 +29,10 @@ func NewApp(configuration configuration.Configuration) *App {
// Register the handler dispatchers.
app.register(routeNameImageManifest, imageManifestDispatcher)
app.register(routeNameLayer, layerDispatcher)
app.register(routeNameBlob, layerDispatcher)
app.register(routeNameTags, tagsDispatcher)
app.register(routeNameLayerUpload, layerUploadDispatcher)
app.register(routeNameLayerUploadResume, layerUploadDispatcher)
app.register(routeNameBlobUpload, layerUploadDispatcher)
app.register(routeNameBlobUploadResume, layerUploadDispatcher)
return app
}

View file

@ -84,24 +84,22 @@ func TestAppDispatcher(t *testing.T) {
},
},
{
endpoint: routeNameLayer,
endpoint: routeNameBlob,
vars: []string{
"name", "foo/bar",
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
"digest", "tarsum.v1+bogus:abcdef0123456789",
},
},
{
endpoint: routeNameLayerUpload,
endpoint: routeNameBlobUpload,
vars: []string{
"name", "foo/bar",
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
},
},
{
endpoint: routeNameLayerUploadResume,
endpoint: routeNameBlobUploadResume,
vars: []string{
"name", "foo/bar",
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
"uuid", "theuuid",
},
},

View file

@ -17,6 +17,7 @@ func layerDispatcher(ctx *Context, r *http.Request) http.Handler {
return handlers.MethodHandler{
"GET": http.HandlerFunc(layerHandler.GetLayer),
"HEAD": http.HandlerFunc(layerHandler.GetLayer),
}
}

View file

@ -24,6 +24,7 @@ func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
return handlers.MethodHandler{
"POST": http.HandlerFunc(layerUploadHandler.StartLayerUpload),
"GET": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
"HEAD": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
"PUT": http.HandlerFunc(layerUploadHandler.PutLayerChunk),
"DELETE": http.HandlerFunc(layerUploadHandler.CancelLayerUpload),
}

View file

@ -8,17 +8,17 @@ import (
const (
routeNameImageManifest = "image-manifest"
routeNameTags = "tags"
routeNameLayer = "layer"
routeNameLayerUpload = "layer-upload"
routeNameLayerUploadResume = "layer-upload-resume"
routeNameBlob = "blob"
routeNameBlobUpload = "blob-upload"
routeNameBlobUploadResume = "blob-upload-resume"
)
var allEndpoints = []string{
routeNameImageManifest,
routeNameTags,
routeNameLayer,
routeNameLayerUpload,
routeNameLayerUploadResume,
routeNameBlob,
routeNameBlobUpload,
routeNameBlobUploadResume,
}
// v2APIRouter builds a gorilla router with named routes for the various API
@ -27,11 +27,11 @@ func v2APIRouter() *mux.Router {
router := mux.NewRouter().
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.
// 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() + "}/image/{tag:" + common.TagNameRegexp.String() + "}").
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/manifest/{tag:" + common.TagNameRegexp.String() + "}").
Name(routeNameImageManifest)
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
@ -39,22 +39,22 @@ func v2APIRouter() *mux.Router {
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list").
Name(routeNameTags)
// GET /v2/<name>/layer/<tarsum> Layer Fetch the layer identified by tarsum.
// GET /v2/<name>/blob/<digest> Layer Fetch the blob identified by digest.
router.
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}").
Name(routeNameLayer)
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
Name(routeNameBlob)
// POST /v2/<name>/layer/<tarsum>/upload/ Layer Upload Initiate an upload of the layer identified by tarsum. Requires length and a checksum parameter.
// POST /v2/<name>/blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum.
router.
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}/upload/").
Name(routeNameLayerUpload)
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/upload/").
Name(routeNameBlobUpload)
// 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
// 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() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}/upload/{uuid}").
Name(routeNameLayerUploadResume)
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/upload/{uuid}").
Name(routeNameBlobUploadResume)
return router
}

View file

@ -48,7 +48,7 @@ func TestRouter(t *testing.T) {
for _, testcase := range []routeTestCase{
{
RouteName: routeNameImageManifest,
RequestURI: "/v2/foo/bar/image/tag",
RequestURI: "/v2/foo/bar/manifest/tag",
Vars: map[string]string{
"name": "foo/bar",
"tag": "tag",
@ -62,68 +62,75 @@ func TestRouter(t *testing.T) {
},
},
{
RouteName: routeNameLayer,
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234",
RouteName: routeNameBlob,
RequestURI: "/v2/foo/bar/blob/tarsum.dev+foo:abcdef0919234",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum.dev+foo:abcdef0919234",
"digest": "tarsum.dev+foo:abcdef0919234",
},
},
{
RouteName: routeNameLayerUpload,
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/",
RouteName: routeNameBlob,
RequestURI: "/v2/foo/bar/blob/sha256:abcdef0919234",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum.dev+foo:abcdef0919234",
"digest": "sha256:abcdef0919234",
},
},
{
RouteName: routeNameLayerUploadResume,
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/uuid",
RouteName: routeNameBlobUpload,
RequestURI: "/v2/foo/bar/blob/upload/",
Vars: map[string]string{
"name": "foo/bar",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blob/upload/uuid",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum.dev+foo:abcdef0919234",
"uuid": "uuid",
},
},
{
RouteName: routeNameLayerUploadResume,
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blob/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum.dev+foo:abcdef0919234",
"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
},
},
{
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/bar/blob/upload/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/image/image/tags",
RequestURI: "/v2/foo/bar/manifest/manifest/tags",
Vars: map[string]string{
"name": "foo/bar/image",
"name": "foo/bar/manifest",
"tag": "tags",
},
},
{
// This case presents an ambiguity between foo/bar with tag="tags"
// and list tags for "foo/bar/image"
// and list tags for "foo/bar/manifest"
RouteName: routeNameTags,
RequestURI: "/v2/foo/bar/image/tags/list",
RequestURI: "/v2/foo/bar/manifest/tags/list",
Vars: map[string]string{
"name": "foo/bar/image",
"name": "foo/bar/manifest",
},
},
{
RouteName: routeNameLayerUploadResume,
RequestURI: "/v2/foo/../../layer/tarsum.dev+foo:abcdef0919234/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
Vars: map[string]string{
"name": "foo/bar",
"tarsum": "tarsum.dev+foo:abcdef0919234",
"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
},
RouteName: routeNameBlobUploadResume,
RequestURI: "/v2/foo/../../layer/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
StatusCode: http.StatusNotFound,
},
} {