Skip to content

Commit

Permalink
Added prune route to docker registry
Browse files Browse the repository at this point in the history
`POST admin/prune` route triggers a garbage collection in docker registry
where orphaned repositories, manifest revisions, layers, signatures and
blobs are deleted.

Signed-off-by: Michal Minar <[email protected]>
  • Loading branch information
Michal Minar committed Oct 19, 2015
1 parent 8c32373 commit 53a34bf
Show file tree
Hide file tree
Showing 6 changed files with 884 additions and 172 deletions.
30 changes: 3 additions & 27 deletions pkg/cmd/dockerregistry/dockerregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/auth"
"github.com/docker/distribution/registry/handlers"
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
Expand Down Expand Up @@ -61,38 +59,16 @@ func Execute(configFile io.Reader) {
}

app.RegisterRoute(
// DELETE /admin/blobs/<digest>
adminRouter.Path("/blobs/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"),
// POST /admin/prune
adminRouter.Path("/prune").Methods("POST"),
// handler
server.BlobDispatcher,
server.PruneHandler,
// repo name not required in url
handlers.NameNotRequired,
// custom access records
pruneAccessRecords,
)

app.RegisterRoute(
// DELETE /admin/<repo>/manifests/<digest>
adminRouter.Path("/{name:"+v2.RepositoryNameRegexp.String()+"}/manifests/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"),
// handler
server.ManifestDispatcher,
// repo name required in url
handlers.NameRequired,
// custom access records
pruneAccessRecords,
)

app.RegisterRoute(
// DELETE /admin/<repo>/layers/<digest>
adminRouter.Path("/{name:"+v2.RepositoryNameRegexp.String()+"}/layers/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"),
// handler
server.LayerDispatcher,
// repo name required in url
handlers.NameRequired,
// custom access records
pruneAccessRecords,
)

handler := gorillahandlers.CombinedLoggingHandler(os.Stdout, app)

if config.HTTP.TLS.Certificate == "" {
Expand Down
149 changes: 31 additions & 118 deletions pkg/dockerregistry/server/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,153 +4,66 @@ import (
"fmt"
"net/http"

ctxu "github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/handlers"
"github.com/docker/distribution/registry/storage"
storagedriver "github.com/docker/distribution/registry/storage/driver"
"github.com/docker/distribution/registry/storage/driver/factory"
gorillahandlers "github.com/gorilla/handlers"
)

// BlobDispatcher takes the request context and builds the appropriate handler
// for handling blob requests.
func BlobDispatcher(ctx *handlers.Context, r *http.Request) http.Handler {
reference := ctxu.GetStringValue(ctx, "vars.digest")
dgst, _ := digest.ParseDigest(reference)

blobHandler := &blobHandler{
// PruneHandler takes the request context and builds an appropriate handler for
// handling prune requests.
func PruneHandler(ctx *handlers.Context, r *http.Request) http.Handler {
pruneHandler := &pruneHandler{
Context: ctx,
Digest: dgst,
}

return gorillahandlers.MethodHandler{
"DELETE": http.HandlerFunc(blobHandler.Delete),
"POST": http.HandlerFunc(pruneHandler.Prune),
}
}

// blobHandler handles http operations on blobs.
type blobHandler struct {
// pruneHandler handles http operations on registry
type pruneHandler struct {
*handlers.Context

Digest digest.Digest
}

// Delete deletes the blob from the storage backend.
func (bh *blobHandler) Delete(w http.ResponseWriter, req *http.Request) {
// Prune collects orphaned objects and deletes them from registry's storage
// and OpenShift's etcd store.
func (ph *pruneHandler) Prune(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
log.Infof("pruneHandler.Prune: starting")
defer log.Infof("pruneHandler.Prune: terminating")

if len(bh.Digest) == 0 {
bh.Errors = append(bh.Errors, v2.ErrorCodeBlobUnknown)
return
}

bd, err := storage.RegistryBlobDeleter(bh.Registry())
sd, err := factory.Create(ph.Config.Storage.Type(), ph.Config.Storage.Parameters())
if err != nil {
bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
ph.Errors = append(ph.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("failed to create storage driver: %v", err)))
log.Errorf("failed to create storage driver: %v", err)
return
}
err = bd.Delete(bh, bh.Digest)
if err != nil {
// Ignore PathNotFoundError
if _, ok := err.(storagedriver.PathNotFoundError); !ok {
bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("error deleting blob %q: %v", bh.Digest, err)))
return
}
}

w.WriteHeader(http.StatusNoContent)
}

// LayerDispatcher takes the request context and builds the appropriate handler
// for handling layer requests.
func LayerDispatcher(ctx *handlers.Context, r *http.Request) http.Handler {
reference := ctxu.GetStringValue(ctx, "vars.digest")
dgst, _ := digest.ParseDigest(reference)

layerHandler := &layerHandler{
Context: ctx,
Digest: dgst,
}

return gorillahandlers.MethodHandler{
"DELETE": http.HandlerFunc(layerHandler.Delete),
}
}

// layerHandler handles http operations on layers.
type layerHandler struct {
*handlers.Context

Digest digest.Digest
}

// Delete deletes the layer link from the repository from the storage backend.
func (lh *layerHandler) Delete(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()

if len(lh.Digest) == 0 {
lh.Errors = append(lh.Errors, v2.ErrorCodeBlobUnknown)
reg, err := newRegistry(ph, ph.Namespace(), nil)
if err != nil {
ph.Errors = append(ph.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("failed to create registry: %v", err)))
log.Errorf("failed to create registry: %v", err)
return
}

err := lh.Repository.Blobs(lh).Delete(lh, lh.Digest)
rg, err := LoadRegistryGraph(ph, reg.(*registry), sd)
if err != nil {
// Ignore PathNotFoundError
if _, ok := err.(storagedriver.PathNotFoundError); !ok {
lh.Errors = append(lh.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("error unlinking layer %q from repo %q: %v", lh.Digest, lh.Repository.Name(), err)))
return
}
}

w.WriteHeader(http.StatusNoContent)
}

// ManifestDispatcher takes the request context and builds the appropriate
// handler for handling manifest requests.
func ManifestDispatcher(ctx *handlers.Context, r *http.Request) http.Handler {
reference := ctxu.GetStringValue(ctx, "vars.digest")
dgst, _ := digest.ParseDigest(reference)

manifestHandler := &manifestHandler{
Context: ctx,
Digest: dgst,
}

return gorillahandlers.MethodHandler{
"DELETE": http.HandlerFunc(manifestHandler.Delete),
}
}

// manifestHandler handles http operations on mainfests.
type manifestHandler struct {
*handlers.Context

Digest digest.Digest
}

// Delete deletes the manifest information from the repository from the storage
// backend.
func (mh *manifestHandler) Delete(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()

if len(mh.Digest) == 0 {
mh.Errors = append(mh.Errors, v2.ErrorCodeManifestUnknown)
ph.Errors = append(ph.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("failed to read registry storage: %v", err)))
log.Errorf("Failed to load registry storage: %v", err)
return
}

manService, err := mh.Repository.Manifests(mh)
if err != nil {
mh.Errors = append(mh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
err = manService.Delete(mh.Digest)
if err != nil {
// Ignore PathNotFoundError
if _, ok := err.(storagedriver.PathNotFoundError); !ok {
mh.Errors = append(mh.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("error deleting repo %q, manifest %q: %v", mh.Repository.Name(), mh.Digest, err)))
return
rg.IgnoreErrors = true
errors := rg.PruneOrphanedObjects()
if len(errors) > 0 {
for _, err := range errors {
ph.Errors = append(ph.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("prune error: %v", err)))
log.Errorf("prune error: %v", err)
}
return
}

w.WriteHeader(http.StatusNoContent)
Expand Down
Loading

0 comments on commit 53a34bf

Please sign in to comment.