forked from TrueCloudLab/distribution
74563efe98
This change adds a basic catalog endpoint to the API, which returns a list, or partial list, of all of the repositories contained in the registry. Calls to this endpoint are somewhat expensive, as every call requires walking a large part of the registry. Instead, to maintain a list of repositories, you would first call the catalog endpoint to get an initial list, and then use the events API to maintain any future repositories. Signed-off-by: Patrick Devine <patrick.devine@docker.com>
154 lines
4.4 KiB
Go
154 lines
4.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
|
|
"github.com/docker/distribution"
|
|
ctxu "github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/digest"
|
|
"github.com/docker/distribution/registry/api/errcode"
|
|
"github.com/docker/distribution/registry/api/v2"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// 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
|
|
context.Context
|
|
|
|
// Repository is the repository for the current request. All requests
|
|
// should be scoped to a single repository. This field may be nil.
|
|
Repository distribution.Repository
|
|
|
|
// 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 errcode.Errors
|
|
|
|
urlBuilder *v2.URLBuilder
|
|
|
|
// Catalog allows getting a complete list of the contents of the registry.
|
|
Catalog distribution.CatalogService
|
|
|
|
// TODO(stevvooe): The goal is too completely factor this context and
|
|
// dispatching out of the web application. Ideally, we should lean on
|
|
// context.Context for injection of these resources.
|
|
}
|
|
|
|
// Value overrides context.Context.Value to ensure that calls are routed to
|
|
// correct context.
|
|
func (ctx *Context) Value(key interface{}) interface{} {
|
|
return ctx.Context.Value(key)
|
|
}
|
|
|
|
func getName(ctx context.Context) (name string) {
|
|
return ctxu.GetStringValue(ctx, "vars.name")
|
|
}
|
|
|
|
func getReference(ctx context.Context) (reference string) {
|
|
return ctxu.GetStringValue(ctx, "vars.reference")
|
|
}
|
|
|
|
var errDigestNotAvailable = fmt.Errorf("digest not available in context")
|
|
|
|
func getDigest(ctx context.Context) (dgst digest.Digest, err error) {
|
|
dgstStr := ctxu.GetStringValue(ctx, "vars.digest")
|
|
|
|
if dgstStr == "" {
|
|
ctxu.GetLogger(ctx).Errorf("digest not available")
|
|
return "", errDigestNotAvailable
|
|
}
|
|
|
|
d, err := digest.ParseDigest(dgstStr)
|
|
if err != nil {
|
|
ctxu.GetLogger(ctx).Errorf("error parsing digest=%q: %v", dgstStr, err)
|
|
return "", err
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
func getUploadUUID(ctx context.Context) (uuid string) {
|
|
return ctxu.GetStringValue(ctx, "vars.uuid")
|
|
}
|
|
|
|
// getUserName attempts to resolve a username from the context and request. If
|
|
// a username cannot be resolved, the empty string is returned.
|
|
func getUserName(ctx context.Context, r *http.Request) string {
|
|
username := ctxu.GetStringValue(ctx, "auth.user.name")
|
|
|
|
// Fallback to request user with basic auth
|
|
if username == "" {
|
|
var ok bool
|
|
uname, _, ok := basicAuth(r)
|
|
if ok {
|
|
username = uname
|
|
}
|
|
}
|
|
|
|
return username
|
|
}
|
|
|
|
// contextManager allows us to associate net/context.Context instances with a
|
|
// request, based on the memory identity of http.Request. This prepares http-
|
|
// level context, which is not application specific. If this is called,
|
|
// (*contextManager).release must be called on the context when the request is
|
|
// completed.
|
|
//
|
|
// Providing this circumvents a lot of necessity for dispatchers with the
|
|
// benefit of instantiating the request context much earlier.
|
|
//
|
|
// TODO(stevvooe): Consider making this facility a part of the context package.
|
|
type contextManager struct {
|
|
contexts map[*http.Request]context.Context
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// defaultContextManager is just a global instance to register request contexts.
|
|
var defaultContextManager = newContextManager()
|
|
|
|
func newContextManager() *contextManager {
|
|
return &contextManager{
|
|
contexts: make(map[*http.Request]context.Context),
|
|
}
|
|
}
|
|
|
|
// context either returns a new context or looks it up in the manager.
|
|
func (cm *contextManager) context(parent context.Context, w http.ResponseWriter, r *http.Request) context.Context {
|
|
cm.mu.Lock()
|
|
defer cm.mu.Unlock()
|
|
|
|
ctx, ok := cm.contexts[r]
|
|
if ok {
|
|
return ctx
|
|
}
|
|
|
|
if parent == nil {
|
|
parent = ctxu.Background()
|
|
}
|
|
|
|
ctx = ctxu.WithRequest(parent, r)
|
|
ctx, w = ctxu.WithResponseWriter(ctx, w)
|
|
ctx = ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx))
|
|
cm.contexts[r] = ctx
|
|
|
|
return ctx
|
|
}
|
|
|
|
// releases frees any associated with resources from request.
|
|
func (cm *contextManager) release(ctx context.Context) {
|
|
cm.mu.Lock()
|
|
defer cm.mu.Unlock()
|
|
|
|
r, err := ctxu.GetRequest(ctx)
|
|
if err != nil {
|
|
ctxu.GetLogger(ctx).Errorf("no request found in context during release")
|
|
return
|
|
}
|
|
delete(cm.contexts, r)
|
|
}
|