Merge pull request #712 from stevvooe/application-structure
Carve out initial application structure
This commit is contained in:
commit
d245a502b2
94
app.go
Normal file
94
app.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/configuration"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is a global registry application object. Shared resources can be placed
|
||||||
|
// on this object that will be accessible from all requests. Any writable
|
||||||
|
// fields should be protected.
|
||||||
|
type App struct {
|
||||||
|
Config configuration.Configuration
|
||||||
|
|
||||||
|
router *mux.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp takes a configuration and returns a configured app, ready to serve
|
||||||
|
// requests. The app only implements ServeHTTP and can be wrapped in other
|
||||||
|
// handlers accordingly.
|
||||||
|
func NewApp(configuration configuration.Configuration) *App {
|
||||||
|
app := &App{
|
||||||
|
Config: configuration,
|
||||||
|
router: v2APIRouter(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the handler dispatchers.
|
||||||
|
app.register(routeNameImageManifest, imageManifestDispatcher)
|
||||||
|
app.register(routeNameLayer, layerDispatcher)
|
||||||
|
app.register(routeNameTags, tagsDispatcher)
|
||||||
|
app.register(routeNameLayerUpload, layerUploadDispatcher)
|
||||||
|
app.register(routeNameLayerUploadResume, layerUploadDispatcher)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
app.router.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register a handler with the application, by route name. The handler will be
|
||||||
|
// passed through the application filters and context will be constructed at
|
||||||
|
// request time.
|
||||||
|
func (app *App) register(routeName string, dispatch dispatchFunc) {
|
||||||
|
|
||||||
|
// TODO(stevvooe): This odd dispatcher/route registration is by-product of
|
||||||
|
// some limitations in the gorilla/mux router. We are using it to keep
|
||||||
|
// routing consistent between the client and server, but we may want to
|
||||||
|
// replace it with manual routing and structure-based dispatch for better
|
||||||
|
// control over the request execution.
|
||||||
|
|
||||||
|
app.router.GetRoute(routeName).Handler(app.dispatcher(dispatch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatchFunc takes a context and request and returns a constructed handler
|
||||||
|
// for the route. The dispatcher will use this to dynamically create request
|
||||||
|
// specific handlers for each endpoint without creating a new router for each
|
||||||
|
// request.
|
||||||
|
type dispatchFunc func(ctx *Context, r *http.Request) http.Handler
|
||||||
|
|
||||||
|
// TODO(stevvooe): dispatchers should probably have some validation error
|
||||||
|
// chain with proper error reporting.
|
||||||
|
|
||||||
|
// dispatcher returns a handler that constructs a request specific context and
|
||||||
|
// handler, using the dispatch factory function.
|
||||||
|
func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
context := &Context{
|
||||||
|
App: app,
|
||||||
|
Name: vars["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store vars for underlying handlers.
|
||||||
|
context.vars = vars
|
||||||
|
|
||||||
|
context.log = log.WithField("name", context.Name)
|
||||||
|
handler := dispatch(context, r)
|
||||||
|
|
||||||
|
context.log.Infoln("handler", resolveHandlerName(r.Method, handler))
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
// Automated error response handling here. Handlers may return their
|
||||||
|
// own errors if they need different behavior (such as range errors
|
||||||
|
// for layer upload).
|
||||||
|
if len(context.Errors.Errors) > 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
serveJSON(w, context.Errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
127
app_test.go
Normal file
127
app_test.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAppDispatcher builds an application with a test dispatcher and ensures
|
||||||
|
// that requests are properly dispatched and the handlers are constructed.
|
||||||
|
// This only tests the dispatch mechanism. The underlying dispatchers must be
|
||||||
|
// tested individually.
|
||||||
|
func TestAppDispatcher(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
Config: configuration.Configuration{},
|
||||||
|
router: v2APIRouter(),
|
||||||
|
}
|
||||||
|
server := httptest.NewServer(app)
|
||||||
|
router := v2APIRouter()
|
||||||
|
|
||||||
|
serverURL, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing server url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
varCheckingDispatcher := func(expectedVars map[string]string) dispatchFunc {
|
||||||
|
return func(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
// Always checks the same name context
|
||||||
|
if ctx.Name != ctx.vars["name"] {
|
||||||
|
t.Fatalf("unexpected name: %q != %q", ctx.Name, "foo/bar")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we have all that is expected
|
||||||
|
for expectedK, expectedV := range expectedVars {
|
||||||
|
if ctx.vars[expectedK] != expectedV {
|
||||||
|
t.Fatalf("unexpected %s in context vars: %q != %q", expectedK, ctx.vars[expectedK], expectedV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we only have variables that are expected
|
||||||
|
for k, v := range ctx.vars {
|
||||||
|
_, ok := expectedVars[k]
|
||||||
|
|
||||||
|
if !ok { // name is checked on context
|
||||||
|
// We have an unexpected key, fail
|
||||||
|
t.Fatalf("unexpected key %q in vars with value %q", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unflatten a list of variables, suitable for gorilla/mux, to a map[string]string
|
||||||
|
unflatten := func(vars []string) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for i := 0; i < len(vars)-1; i = i + 2 {
|
||||||
|
m[vars[i]] = vars[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
endpoint string
|
||||||
|
vars []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
endpoint: routeNameImageManifest,
|
||||||
|
vars: []string{
|
||||||
|
"name", "foo/bar",
|
||||||
|
"tag", "sometag",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: routeNameTags,
|
||||||
|
vars: []string{
|
||||||
|
"name", "foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: routeNameLayer,
|
||||||
|
vars: []string{
|
||||||
|
"name", "foo/bar",
|
||||||
|
"tarsum", "thetarsum",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: routeNameLayerUpload,
|
||||||
|
vars: []string{
|
||||||
|
"name", "foo/bar",
|
||||||
|
"tarsum", "thetarsum",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
endpoint: routeNameLayerUploadResume,
|
||||||
|
vars: []string{
|
||||||
|
"name", "foo/bar",
|
||||||
|
"tarsum", "thetarsum",
|
||||||
|
"uuid", "theuuid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
app.register(testcase.endpoint, varCheckingDispatcher(unflatten(testcase.vars)))
|
||||||
|
route := router.GetRoute(testcase.endpoint).Host(serverURL.Host)
|
||||||
|
u, err := route.URL(testcase.vars...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(u.String())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected status code: %v != %v", resp.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
context.go
Normal file
34
context.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context should contain the request specific context for use in across
|
||||||
|
// handlers. Resources that don't need to be shared across handlers should not
|
||||||
|
// be on this object.
|
||||||
|
type Context struct {
|
||||||
|
// App points to the application structure that created this context.
|
||||||
|
*App
|
||||||
|
|
||||||
|
// Name is the prefix for the current request. Corresponds to the
|
||||||
|
// namespace/repository associated with the image.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Errors is a collection of errors encountered during the request to be
|
||||||
|
// returned to the client API. If errors are added to the collection, the
|
||||||
|
// handler *must not* start the response via http.ResponseWriter.
|
||||||
|
Errors Errors
|
||||||
|
|
||||||
|
// TODO(stevvooe): Context would be a good place to create a
|
||||||
|
// representation of the "authorized resource". Perhaps, rather than
|
||||||
|
// having fields like "name", the context should be a set of parameters
|
||||||
|
// then we do routing from there.
|
||||||
|
|
||||||
|
// vars contains the extracted gorilla/mux variables that can be used for
|
||||||
|
// assignment.
|
||||||
|
vars map[string]string
|
||||||
|
|
||||||
|
// log provides a context specific logger.
|
||||||
|
log *logrus.Entry
|
||||||
|
}
|
20
helpers.go
Normal file
20
helpers.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serveJSON marshals v and sets the content-type header to
|
||||||
|
// 'application/json'. If a different status code is required, call
|
||||||
|
// ResponseWriter.WriteHeader before this function.
|
||||||
|
func serveJSON(w http.ResponseWriter, v interface{}) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
|
||||||
|
if err := enc.Encode(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
46
images.go
Normal file
46
images.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// imageManifestDispatcher takes the request context and builds the
|
||||||
|
// appropriate handler for handling image manifest requests.
|
||||||
|
func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
imageManifestHandler := &imageManifestHandler{
|
||||||
|
Context: ctx,
|
||||||
|
Tag: ctx.vars["tag"],
|
||||||
|
}
|
||||||
|
|
||||||
|
imageManifestHandler.log = imageManifestHandler.log.WithField("tag", imageManifestHandler.Tag)
|
||||||
|
|
||||||
|
return handlers.MethodHandler{
|
||||||
|
"GET": http.HandlerFunc(imageManifestHandler.GetImageManifest),
|
||||||
|
"PUT": http.HandlerFunc(imageManifestHandler.PutImageManifest),
|
||||||
|
"DELETE": http.HandlerFunc(imageManifestHandler.DeleteImageManifest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageManifestHandler handles http operations on image manifests.
|
||||||
|
type imageManifestHandler struct {
|
||||||
|
*Context
|
||||||
|
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImageManifest fetches the image manifest from the storage backend, if it exists.
|
||||||
|
func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutImageManifest validates and stores and image in the registry.
|
||||||
|
func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteImageManifest removes the image with the given tag from the registry.
|
||||||
|
func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
34
layer.go
Normal file
34
layer.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// layerDispatcher uses the request context to build a layerHandler.
|
||||||
|
func layerDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
layerHandler := &layerHandler{
|
||||||
|
Context: ctx,
|
||||||
|
TarSum: ctx.vars["tarsum"],
|
||||||
|
}
|
||||||
|
|
||||||
|
layerHandler.log = layerHandler.log.WithField("tarsum", layerHandler.TarSum)
|
||||||
|
|
||||||
|
return handlers.MethodHandler{
|
||||||
|
"GET": http.HandlerFunc(layerHandler.GetLayer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// layerHandler serves http layer requests.
|
||||||
|
type layerHandler struct {
|
||||||
|
*Context
|
||||||
|
|
||||||
|
TarSum string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLayer fetches the binary data from backend storage returns it in the
|
||||||
|
// response.
|
||||||
|
func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
63
layerupload.go
Normal file
63
layerupload.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// layerUploadDispatcher constructs and returns the layer upload handler for
|
||||||
|
// the given request context.
|
||||||
|
func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
layerUploadHandler := &layerUploadHandler{
|
||||||
|
Context: ctx,
|
||||||
|
TarSum: ctx.vars["tarsum"],
|
||||||
|
UUID: ctx.vars["uuid"],
|
||||||
|
}
|
||||||
|
|
||||||
|
layerUploadHandler.log = layerUploadHandler.log.WithField("tarsum", layerUploadHandler.TarSum)
|
||||||
|
|
||||||
|
if layerUploadHandler.UUID != "" {
|
||||||
|
layerUploadHandler.log = layerUploadHandler.log.WithField("uuid", layerUploadHandler.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers.MethodHandler{
|
||||||
|
"POST": http.HandlerFunc(layerUploadHandler.StartLayerUpload),
|
||||||
|
"GET": http.HandlerFunc(layerUploadHandler.GetUploadStatus),
|
||||||
|
"PUT": http.HandlerFunc(layerUploadHandler.PutLayerChunk),
|
||||||
|
"DELETE": http.HandlerFunc(layerUploadHandler.CancelLayerUpload),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// layerUploadHandler handles the http layer upload process.
|
||||||
|
type layerUploadHandler struct {
|
||||||
|
*Context
|
||||||
|
|
||||||
|
// TarSum is the unique identifier of the layer being uploaded.
|
||||||
|
TarSum string
|
||||||
|
|
||||||
|
// UUID identifies the upload instance for the current request.
|
||||||
|
UUID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartLayerUpload begins the layer upload process and allocates a server-
|
||||||
|
// side upload session.
|
||||||
|
func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUploadStatus returns the status of a given upload, identified by uuid.
|
||||||
|
func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutLayerChunk receives a layer chunk during the layer upload process,
|
||||||
|
// possible completing the upload with a checksum and length.
|
||||||
|
func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelLayerUpload cancels an in-progress upload of a layer.
|
||||||
|
func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
20
routes.go
20
routes.go
@ -5,21 +5,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
routeNameRoot = "root"
|
routeNameRoot = "root"
|
||||||
routeNameName = "name"
|
routeNameName = "name"
|
||||||
routeNameImageManifest = "image-manifest"
|
routeNameImageManifest = "image-manifest"
|
||||||
routeNameTags = "tags"
|
routeNameTags = "tags"
|
||||||
routeNameLayer = "layer"
|
routeNameLayer = "layer"
|
||||||
routeNameStartLayerUpload = "start-layer-upload"
|
routeNameLayerUpload = "layer-upload"
|
||||||
routeNameLayerUpload = "layer-upload"
|
routeNameLayerUploadResume = "layer-upload-resume"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allEndpoints = []string{
|
var allEndpoints = []string{
|
||||||
routeNameImageManifest,
|
routeNameImageManifest,
|
||||||
routeNameTags,
|
routeNameTags,
|
||||||
routeNameLayer,
|
routeNameLayer,
|
||||||
routeNameStartLayerUpload,
|
|
||||||
routeNameLayerUpload,
|
routeNameLayerUpload,
|
||||||
|
routeNameLayerUploadResume,
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2APIRouter builds a gorilla router with named routes for the various API
|
// v2APIRouter builds a gorilla router with named routes for the various API
|
||||||
@ -59,14 +59,14 @@ func v2APIRouter() *mux.Router {
|
|||||||
// 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>/layer/<tarsum>/upload/ Layer Upload Initiate an upload of the layer identified by tarsum. Requires length and a checksum parameter.
|
||||||
namedRouter.
|
namedRouter.
|
||||||
Path("/layer/{tarsum}/upload/").
|
Path("/layer/{tarsum}/upload/").
|
||||||
Name(routeNameStartLayerUpload)
|
Name(routeNameLayerUpload)
|
||||||
|
|
||||||
// GET /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Get the status of the upload identified by tarsum and uuid.
|
// 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.
|
// 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
|
// DELETE /v2/<name>/layer/<tarsum>/upload/<uuid> Layer Upload Cancel the upload identified by layer and uuid
|
||||||
namedRouter.
|
namedRouter.
|
||||||
Path("/layer/{tarsum}/upload/{uuid}").
|
Path("/layer/{tarsum}/upload/{uuid}").
|
||||||
Name(routeNameLayerUpload)
|
Name(routeNameLayerUploadResume)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func TestRouter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
routeName: routeNameStartLayerUpload,
|
routeName: routeNameLayerUpload,
|
||||||
expectedRouteInfo: routeInfo{
|
expectedRouteInfo: routeInfo{
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum/upload/",
|
RequestURI: "/v2/foo/bar/layer/tarsum/upload/",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
@ -86,7 +86,7 @@ func TestRouter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
routeName: routeNameLayerUpload,
|
routeName: routeNameLayerUploadResume,
|
||||||
expectedRouteInfo: routeInfo{
|
expectedRouteInfo: routeInfo{
|
||||||
RequestURI: "/v2/foo/bar/layer/tarsum/upload/uuid",
|
RequestURI: "/v2/foo/bar/layer/tarsum/upload/uuid",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
|
28
tags.go
Normal file
28
tags.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagsDispatcher constructs the tags handler api endpoint.
|
||||||
|
func tagsDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||||
|
tagsHandler := &tagsHandler{
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers.MethodHandler{
|
||||||
|
"GET": http.HandlerFunc(tagsHandler.GetTags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagsHandler handles requests for lists of tags under a repository name.
|
||||||
|
type tagsHandler struct {
|
||||||
|
*Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTags returns a json list of tags for a specific image name.
|
||||||
|
func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO(stevvooe): Implement this method.
|
||||||
|
}
|
27
util.go
Normal file
27
util.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// functionName returns the name of the function fn.
|
||||||
|
func functionName(fn interface{}) string {
|
||||||
|
return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveHandlerName attempts to resolve a nice, pretty name for the passed
|
||||||
|
// in handler.
|
||||||
|
func resolveHandlerName(method string, handler http.Handler) string {
|
||||||
|
switch v := handler.(type) {
|
||||||
|
case handlers.MethodHandler:
|
||||||
|
return functionName(v[method])
|
||||||
|
case http.HandlerFunc:
|
||||||
|
return functionName(v)
|
||||||
|
default:
|
||||||
|
return functionName(handler.ServeHTTP)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user