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:
parent
594263a3f5
commit
825da388a4
7 changed files with 56 additions and 36 deletions
29
docs/app.go
29
docs/app.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue