forked from TrueCloudLab/distribution
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:
parent
9dc6fa3765
commit
5789ca7629
6 changed files with 70 additions and 63 deletions
6
app.go
6
app.go
|
@ -29,10 +29,10 @@ func NewApp(configuration configuration.Configuration) *App {
|
||||||
|
|
||||||
// Register the handler dispatchers.
|
// Register the handler dispatchers.
|
||||||
app.register(routeNameImageManifest, imageManifestDispatcher)
|
app.register(routeNameImageManifest, imageManifestDispatcher)
|
||||||
app.register(routeNameLayer, layerDispatcher)
|
app.register(routeNameBlob, layerDispatcher)
|
||||||
app.register(routeNameTags, tagsDispatcher)
|
app.register(routeNameTags, tagsDispatcher)
|
||||||
app.register(routeNameLayerUpload, layerUploadDispatcher)
|
app.register(routeNameBlobUpload, layerUploadDispatcher)
|
||||||
app.register(routeNameLayerUploadResume, layerUploadDispatcher)
|
app.register(routeNameBlobUploadResume, layerUploadDispatcher)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
10
app_test.go
10
app_test.go
|
@ -84,24 +84,22 @@ func TestAppDispatcher(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameLayer,
|
endpoint: routeNameBlob,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
|
"digest", "tarsum.v1+bogus:abcdef0123456789",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameLayerUpload,
|
endpoint: routeNameBlobUpload,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: routeNameLayerUploadResume,
|
endpoint: routeNameBlobUploadResume,
|
||||||
vars: []string{
|
vars: []string{
|
||||||
"name", "foo/bar",
|
"name", "foo/bar",
|
||||||
"tarsum", "tarsum.v1+bogus:abcdef0123456789",
|
|
||||||
"uuid", "theuuid",
|
"uuid", "theuuid",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
3
layer.go
3
layer.go
|
@ -16,7 +16,8 @@ func layerDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
layerHandler.log = layerHandler.log.WithField("tarsum", layerHandler.TarSum)
|
layerHandler.log = layerHandler.log.WithField("tarsum", layerHandler.TarSum)
|
||||||
|
|
||||||
return handlers.MethodHandler{
|
return handlers.MethodHandler{
|
||||||
"GET": http.HandlerFunc(layerHandler.GetLayer),
|
"GET": http.HandlerFunc(layerHandler.GetLayer),
|
||||||
|
"HEAD": http.HandlerFunc(layerHandler.GetLayer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
return handlers.MethodHandler{
|
return handlers.MethodHandler{
|
||||||
"POST": http.HandlerFunc(layerUploadHandler.StartLayerUpload),
|
"POST": http.HandlerFunc(layerUploadHandler.StartLayerUpload),
|
||||||
"GET": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
|
"GET": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
|
||||||
|
"HEAD": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
|
||||||
"PUT": http.HandlerFunc(layerUploadHandler.PutLayerChunk),
|
"PUT": http.HandlerFunc(layerUploadHandler.PutLayerChunk),
|
||||||
"DELETE": http.HandlerFunc(layerUploadHandler.CancelLayerUpload),
|
"DELETE": http.HandlerFunc(layerUploadHandler.CancelLayerUpload),
|
||||||
}
|
}
|
||||||
|
|
46
routes.go
46
routes.go
|
@ -6,19 +6,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
routeNameImageManifest = "image-manifest"
|
routeNameImageManifest = "image-manifest"
|
||||||
routeNameTags = "tags"
|
routeNameTags = "tags"
|
||||||
routeNameLayer = "layer"
|
routeNameBlob = "blob"
|
||||||
routeNameLayerUpload = "layer-upload"
|
routeNameBlobUpload = "blob-upload"
|
||||||
routeNameLayerUploadResume = "layer-upload-resume"
|
routeNameBlobUploadResume = "blob-upload-resume"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allEndpoints = []string{
|
var allEndpoints = []string{
|
||||||
routeNameImageManifest,
|
routeNameImageManifest,
|
||||||
routeNameTags,
|
routeNameTags,
|
||||||
routeNameLayer,
|
routeNameBlob,
|
||||||
routeNameLayerUpload,
|
routeNameBlobUpload,
|
||||||
routeNameLayerUploadResume,
|
routeNameBlobUploadResume,
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2APIRouter builds a gorilla router with named routes for the various API
|
// v2APIRouter builds a gorilla router with named routes for the various API
|
||||||
|
@ -27,11 +27,11 @@ func v2APIRouter() *mux.Router {
|
||||||
router := mux.NewRouter().
|
router := mux.NewRouter().
|
||||||
StrictSlash(true)
|
StrictSlash(true)
|
||||||
|
|
||||||
// GET /v2/<name>/image/<tag> Image Manifest Fetch the image manifest identified by name and tag.
|
// GET /v2/<name>/manifest/<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.
|
// PUT /v2/<name>/manifest/<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.
|
// DELETE /v2/<name>/manifest/<tag> Image Manifest Delete the image identified by name and tag.
|
||||||
router.
|
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)
|
Name(routeNameImageManifest)
|
||||||
|
|
||||||
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
|
// 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").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/tags/list").
|
||||||
Name(routeNameTags)
|
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.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
|
||||||
Name(routeNameLayer)
|
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.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}/upload/").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/upload/").
|
||||||
Name(routeNameLayerUpload)
|
Name(routeNameBlobUpload)
|
||||||
|
|
||||||
// GET /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Get the status of the upload identified by tarsum and uuid.
|
// GET /v2/<name>/blob/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.
|
// PUT /v2/<name>/blob/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
|
// DELETE /v2/<name>/blob/upload/<uuid> Layer Upload Cancel the upload identified by layer and uuid
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/layer/{tarsum:" + common.TarsumRegexp.String() + "}/upload/{uuid}").
|
Path("/v2/{name:" + common.RepositoryNameRegexp.String() + "}/blob/upload/{uuid}").
|
||||||
Name(routeNameLayerUploadResume)
|
Name(routeNameBlobUploadResume)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ func TestRouter(t *testing.T) {
|
||||||
for _, testcase := range []routeTestCase{
|
for _, testcase := range []routeTestCase{
|
||||||
{
|
{
|
||||||
RouteName: routeNameImageManifest,
|
RouteName: routeNameImageManifest,
|
||||||
RequestURI: "/v2/foo/bar/image/tag",
|
RequestURI: "/v2/foo/bar/manifest/tag",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tag": "tag",
|
"tag": "tag",
|
||||||
|
@ -62,68 +62,75 @@ func TestRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameLayer,
|
RouteName: routeNameBlob,
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234",
|
RequestURI: "/v2/foo/bar/blob/tarsum.dev+foo:abcdef0919234",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tarsum": "tarsum.dev+foo:abcdef0919234",
|
"digest": "tarsum.dev+foo:abcdef0919234",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameLayerUpload,
|
RouteName: routeNameBlob,
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/",
|
RequestURI: "/v2/foo/bar/blob/sha256:abcdef0919234",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tarsum": "tarsum.dev+foo:abcdef0919234",
|
"digest": "sha256:abcdef0919234",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameLayerUploadResume,
|
RouteName: routeNameBlobUpload,
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/uuid",
|
RequestURI: "/v2/foo/bar/blob/upload/",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tarsum": "tarsum.dev+foo:abcdef0919234",
|
|
||||||
"uuid": "uuid",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameLayerUploadResume,
|
RouteName: routeNameBlobUploadResume,
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum.dev+foo:abcdef0919234/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
RequestURI: "/v2/foo/bar/blob/upload/uuid",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tarsum": "tarsum.dev+foo:abcdef0919234",
|
"uuid": "uuid",
|
||||||
"uuid": "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",
|
||||||
|
"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
|
// Check ambiguity: ensure we can distinguish between tags for
|
||||||
// "foo/bar/image/image" and image for "foo/bar/image" with tag
|
// "foo/bar/image/image" and image for "foo/bar/image" with tag
|
||||||
// "tags"
|
// "tags"
|
||||||
RouteName: routeNameImageManifest,
|
RouteName: routeNameImageManifest,
|
||||||
RequestURI: "/v2/foo/bar/image/image/tags",
|
RequestURI: "/v2/foo/bar/manifest/manifest/tags",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar/image",
|
"name": "foo/bar/manifest",
|
||||||
"tag": "tags",
|
"tag": "tags",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// This case presents an ambiguity between foo/bar with 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,
|
RouteName: routeNameTags,
|
||||||
RequestURI: "/v2/foo/bar/image/tags/list",
|
RequestURI: "/v2/foo/bar/manifest/tags/list",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar/image",
|
"name": "foo/bar/manifest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RouteName: routeNameLayerUploadResume,
|
RouteName: routeNameBlobUploadResume,
|
||||||
RequestURI: "/v2/foo/../../layer/tarsum.dev+foo:abcdef0919234/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
RequestURI: "/v2/foo/../../layer/upload/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||||
Vars: map[string]string{
|
|
||||||
"name": "foo/bar",
|
|
||||||
"tarsum": "tarsum.dev+foo:abcdef0919234",
|
|
||||||
"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
|
||||||
},
|
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|
Loading…
Reference in a new issue