Update the registry app to use the new storage interfaces

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-01-16 18:32:27 -08:00
parent ea5b999fc0
commit ff4a1700cc
7 changed files with 56 additions and 36 deletions

View file

@ -26,8 +26,8 @@ type App struct {
// driver maintains the app global storage driver instance. // driver maintains the app global storage driver instance.
driver storagedriver.StorageDriver driver storagedriver.StorageDriver
// services contains the main services instance for the application. // registry is the primary registry backend for the app instance.
services *storage.Services registry storage.Registry
layerHandler storage.LayerHandler layerHandler storage.LayerHandler
@ -63,7 +63,7 @@ func NewApp(configuration configuration.Configuration) *App {
} }
app.driver = driver app.driver = driver
app.services = storage.NewServices(app.driver) app.registry = storage.NewRegistryWithDriver(app.driver)
authType := configuration.Auth.Type() authType := configuration.Auth.Type()
if authType != "" { if authType != "" {
@ -136,11 +136,11 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
context := app.context(r) context := app.context(r)
if err := app.authorized(w, r, context); err != nil { if err := app.authorized(w, r, context, context.vars["name"]); err != nil {
return return
} }
context.log = log.WithField("name", context.Name) context.log = log.WithField("name", context.Repository.Name())
handler := dispatch(context, r) handler := dispatch(context, r)
ssrw := &singleStatusResponseWriter{ResponseWriter: w} ssrw := &singleStatusResponseWriter{ResponseWriter: w}
@ -165,7 +165,6 @@ func (app *App) context(r *http.Request) *Context {
vars := mux.Vars(r) vars := mux.Vars(r)
context := &Context{ context := &Context{
App: app, App: app,
Name: vars["name"],
urlBuilder: v2.NewURLBuilderFromRequest(r), urlBuilder: v2.NewURLBuilderFromRequest(r),
} }
@ -175,19 +174,23 @@ func (app *App) context(r *http.Request) *Context {
return context return context
} }
// authorized checks if the request can proceed with with request access- // authorized checks if the request can proceed with access to the requested
// level. If it cannot, the method will return an error. // repository. If it succeeds, the repository will be available on the
func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Context) error { // context. An error will be if access is not available.
func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Context, repo string) error {
if app.accessController == nil { if app.accessController == nil {
// No access controller, so we simply provide access.
context.Repository = app.registry.Repository(repo)
return nil // access controller is not enabled. return nil // access controller is not enabled.
} }
var accessRecords []auth.Access var accessRecords []auth.Access
if context.Name != "" { if repo != "" {
resource := auth.Resource{ resource := auth.Resource{
Type: "repository", Type: "repository",
Name: context.Name, Name: repo,
} }
switch r.Method { switch r.Method {
@ -256,6 +259,10 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
return err return err
} }
// At this point, the request should have access to the repository under
// the requested operation. Make is available on the context.
context.Repository = app.registry.Repository(repo)
return nil return nil
} }

View file

@ -10,6 +10,8 @@ import (
"github.com/docker/distribution/api/v2" "github.com/docker/distribution/api/v2"
_ "github.com/docker/distribution/auth/silly" _ "github.com/docker/distribution/auth/silly"
"github.com/docker/distribution/configuration" "github.com/docker/distribution/configuration"
"github.com/docker/distribution/storage"
"github.com/docker/distribution/storagedriver/inmemory"
) )
// TestAppDispatcher builds an application with a test dispatcher and ensures // TestAppDispatcher builds an application with a test dispatcher and ensures
@ -17,9 +19,12 @@ import (
// This only tests the dispatch mechanism. The underlying dispatchers must be // This only tests the dispatch mechanism. The underlying dispatchers must be
// tested individually. // tested individually.
func TestAppDispatcher(t *testing.T) { func TestAppDispatcher(t *testing.T) {
driver := inmemory.New()
app := &App{ app := &App{
Config: configuration.Configuration{}, Config: configuration.Configuration{},
router: v2.Router(), router: v2.Router(),
driver: driver,
registry: storage.NewRegistryWithDriver(driver),
} }
server := httptest.NewServer(app) server := httptest.NewServer(app)
router := v2.Router() router := v2.Router()
@ -32,8 +37,8 @@ func TestAppDispatcher(t *testing.T) {
varCheckingDispatcher := func(expectedVars map[string]string) dispatchFunc { varCheckingDispatcher := func(expectedVars map[string]string) dispatchFunc {
return func(ctx *Context, r *http.Request) http.Handler { return func(ctx *Context, r *http.Request) http.Handler {
// Always checks the same name context // Always checks the same name context
if ctx.Name != ctx.vars["name"] { if ctx.Repository.Name() != ctx.vars["name"] {
t.Fatalf("unexpected name: %q != %q", ctx.Name, "foo/bar") t.Fatalf("unexpected name: %q != %q", ctx.Repository.Name(), "foo/bar")
} }
// Check that we have all that is expected // Check that we have all that is expected

View file

@ -3,6 +3,7 @@ package registry
import ( import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/api/v2" "github.com/docker/distribution/api/v2"
"github.com/docker/distribution/storage"
) )
// Context should contain the request specific context for use in across // Context should contain the request specific context for use in across
@ -12,9 +13,9 @@ type Context struct {
// App points to the application structure that created this context. // App points to the application structure that created this context.
*App *App
// Name is the prefix for the current request. Corresponds to the // Repository is the repository for the current request. All requests
// namespace/repository associated with the image. // should be scoped to a single repository. This field may be nil.
Name string Repository storage.Repository
// Errors is a collection of errors encountered during the request to be // 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 // returned to the client API. If errors are added to the collection, the

View file

@ -38,8 +38,8 @@ type imageManifestHandler struct {
// GetImageManifest fetches the image manifest from the storage backend, if it exists. // GetImageManifest fetches the image manifest from the storage backend, if it exists.
func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) { func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) {
manifests := imh.services.Manifests() manifests := imh.Repository.Manifests()
manifest, err := manifests.Get(imh.Name, imh.Tag) manifest, err := manifests.Get(imh.Tag)
if err != nil { if err != nil {
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err) imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
@ -54,7 +54,7 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http
// PutImageManifest validates and stores and image in the registry. // PutImageManifest validates and stores and image in the registry.
func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) { func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
manifests := imh.services.Manifests() manifests := imh.Repository.Manifests()
dec := json.NewDecoder(r.Body) dec := json.NewDecoder(r.Body)
var manifest manifest.SignedManifest var manifest manifest.SignedManifest
@ -64,7 +64,7 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
return return
} }
if err := manifests.Put(imh.Name, imh.Tag, &manifest); err != nil { if err := manifests.Put(imh.Tag, &manifest); err != nil {
// TODO(stevvooe): These error handling switches really need to be // TODO(stevvooe): These error handling switches really need to be
// handled by an app global mapper. // handled by an app global mapper.
switch err := err.(type) { switch err := err.(type) {
@ -96,8 +96,8 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
// DeleteImageManifest removes the image with the given tag from the registry. // DeleteImageManifest removes the image with the given tag from the registry.
func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) { func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) {
manifests := imh.services.Manifests() manifests := imh.Repository.Manifests()
if err := manifests.Delete(imh.Name, imh.Tag); err != nil { if err := manifests.Delete(imh.Tag); err != nil {
switch err := err.(type) { switch err := err.(type) {
case storage.ErrUnknownManifest: case storage.ErrUnknownManifest:
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err) imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)

View file

@ -42,9 +42,8 @@ type layerHandler struct {
// GetLayer fetches the binary data from backend storage returns it in the // GetLayer fetches the binary data from backend storage returns it in the
// response. // response.
func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) { func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) {
layers := lh.services.Layers() layers := lh.Repository.Layers()
layer, err := layers.Fetch(lh.Digest)
layer, err := layers.Fetch(lh.Name, lh.Digest)
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {

View file

@ -43,6 +43,14 @@ func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
} }
luh.State = state luh.State = state
if state.Name != ctx.Repository.Name() {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx.log.Infof("mismatched repository name in upload state: %q != %q", state.Name, luh.Repository.Name())
w.WriteHeader(http.StatusBadRequest)
luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
})
}
if state.UUID != luh.UUID { if state.UUID != luh.UUID {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx.log.Infof("mismatched uuid in upload state: %q != %q", state.UUID, luh.UUID) ctx.log.Infof("mismatched uuid in upload state: %q != %q", state.UUID, luh.UUID)
@ -51,8 +59,8 @@ func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
}) })
} }
layers := ctx.services.Layers() layers := ctx.Repository.Layers()
upload, err := layers.Resume(luh.Name, luh.UUID) upload, err := layers.Resume(luh.UUID)
if err != nil { if err != nil {
ctx.log.Errorf("error resolving upload: %v", err) ctx.log.Errorf("error resolving upload: %v", err)
if err == storage.ErrLayerUploadUnknown { if err == storage.ErrLayerUploadUnknown {
@ -114,8 +122,8 @@ type layerUploadHandler struct {
// StartLayerUpload begins the layer upload process and allocates a server- // StartLayerUpload begins the layer upload process and allocates a server-
// side upload session. // side upload session.
func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.Request) { func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.Request) {
layers := luh.services.Layers() layers := luh.Repository.Layers()
upload, err := layers.Upload(luh.Name) upload, err := layers.Upload()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) // Error conditions here? w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
luh.Errors.Push(v2.ErrorCodeUnknown, err) luh.Errors.Push(v2.ErrorCodeUnknown, err)
@ -222,7 +230,7 @@ func (luh *layerUploadHandler) layerUploadResponse(w http.ResponseWriter, r *htt
} }
// TODO(stevvooe): Need a better way to manage the upload state automatically. // TODO(stevvooe): Need a better way to manage the upload state automatically.
luh.State.Name = luh.Name luh.State.Name = luh.Repository.Name()
luh.State.UUID = luh.Upload.UUID() luh.State.UUID = luh.Upload.UUID()
luh.State.Offset = offset luh.State.Offset = offset
luh.State.StartedAt = luh.Upload.StartedAt() luh.State.StartedAt = luh.Upload.StartedAt()

View file

@ -33,14 +33,14 @@ type tagsAPIResponse struct {
// GetTags returns a json list of tags for a specific image name. // GetTags returns a json list of tags for a specific image name.
func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
manifests := th.services.Manifests() manifests := th.Repository.Manifests()
tags, err := manifests.Tags(th.Name) tags, err := manifests.Tags()
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case storage.ErrUnknownRepository: case storage.ErrUnknownRepository:
w.WriteHeader(404) w.WriteHeader(404)
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Name}) th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Repository.Name()})
default: default:
th.Errors.PushErr(err) th.Errors.PushErr(err)
} }
@ -51,7 +51,7 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
if err := enc.Encode(tagsAPIResponse{ if err := enc.Encode(tagsAPIResponse{
Name: th.Name, Name: th.Repository.Name(),
Tags: tags, Tags: tags,
}); err != nil { }); err != nil {
th.Errors.PushErr(err) th.Errors.PushErr(err)