diff --git a/docs/configuration.md b/docs/configuration.md index 6d6d52a9..b076191f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -118,7 +118,8 @@ information about each option that appears later in this page. age: 168h interval: 24h dryrun: false - readonly: false + readonly: + enabled: false auth: silly: realm: silly-realm @@ -667,12 +668,13 @@ Note: `age` and `interval` are strings containing a number with optional fractio ### Read-only mode -If the `readonly` parameter in the `maintenance` section is set to true, clients -will not be allowed to write to the registry. This mode is useful to temporarily -prevent writes to the backend storage so a garbage collection pass can be run. -Before running garbage collection, the registry should be restarted with -`readonly` set to true. After the garbage collection pass finishes, the registry -may be restarted again, this time with `readonly` removed from the configuration. +If the `readonly` section under `maintenance` has `enabled` set to `true`, +clients will not be allowed to write to the registry. This mode is useful to +temporarily prevent writes to the backend storage so a garbage collection pass +can be run. Before running garbage collection, the registry should be +restarted with readonly's `enabled` set to true. After the garbage collection +pass finishes, the registry may be restarted again, this time with `readonly` +removed from the configuration (or set to false). ### Openstack Swift diff --git a/registry/api/v2/errors.go b/registry/api/v2/errors.go index 97cb03e2..ece52a2c 100644 --- a/registry/api/v2/errors.go +++ b/registry/api/v2/errors.go @@ -133,14 +133,4 @@ var ( longer proceed.`, HTTPStatusCode: http.StatusNotFound, }) - - // ErrorCodeMaintenanceMode is returned when an upload can't be - // accepted because the registry is in maintenance mode. - ErrorCodeMaintenanceMode = errcode.Register(errGroup, errcode.ErrorDescriptor{ - Value: "MAINTENANCE_MODE", - Message: "registry in maintenance mode", - Description: `The upload cannot be accepted because the registry - is running read-only in maintenance mode.`, - HTTPStatusCode: http.StatusServiceUnavailable, - }) ) diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go index e85ae434..0a0b264b 100644 --- a/registry/handlers/api_test.go +++ b/registry/handlers/api_test.go @@ -658,7 +658,7 @@ func TestDeleteReadOnly(t *testing.T) { t.Fatalf("unexpected error deleting layer: %v", err) } - checkResponse(t, "deleting layer in read-only mode", resp, http.StatusServiceUnavailable) + checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed) } func TestStartPushReadOnly(t *testing.T) { @@ -678,7 +678,7 @@ func TestStartPushReadOnly(t *testing.T) { } defer resp.Body.Close() - checkResponse(t, "starting push in read-only mode", resp, http.StatusServiceUnavailable) + checkResponse(t, "starting push in read-only mode", resp, http.StatusMethodNotAllowed) } func httpDelete(url string) (*http.Response, error) { diff --git a/registry/handlers/app.go b/registry/handlers/app.go index d851714a..b11dc5b6 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -109,9 +109,15 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap } } if v, ok := mc["readonly"]; ok { - app.readOnly, ok = v.(bool) + readOnly, ok := v.(map[interface{}]interface{}) if !ok { - panic("readonly config key must have a boolean value") + panic("readonly config key must contain additional keys") + } + if readOnlyEnabled, ok := readOnly["enabled"]; ok { + app.readOnly, ok = readOnlyEnabled.(bool) + if !ok { + panic("readonly's enabled config key must have a boolean value") + } } } } diff --git a/registry/handlers/blob.go b/registry/handlers/blob.go index 69c39841..fb250acd 100644 --- a/registry/handlers/blob.go +++ b/registry/handlers/blob.go @@ -32,11 +32,16 @@ func blobDispatcher(ctx *Context, r *http.Request) http.Handler { Digest: dgst, } - return handlers.MethodHandler{ - "GET": http.HandlerFunc(blobHandler.GetBlob), - "HEAD": http.HandlerFunc(blobHandler.GetBlob), - "DELETE": mutableHandler(blobHandler.DeleteBlob, ctx), + mhandler := handlers.MethodHandler{ + "GET": http.HandlerFunc(blobHandler.GetBlob), + "HEAD": http.HandlerFunc(blobHandler.GetBlob), } + + if !ctx.readOnly { + mhandler["DELETE"] = http.HandlerFunc(blobHandler.DeleteBlob) + } + + return mhandler } // blobHandler serves http blob requests. diff --git a/registry/handlers/blobupload.go b/registry/handlers/blobupload.go index 198a8f67..1bd33d33 100644 --- a/registry/handlers/blobupload.go +++ b/registry/handlers/blobupload.go @@ -22,14 +22,17 @@ func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler { UUID: getUploadUUID(ctx), } - handler := http.Handler(handlers.MethodHandler{ - "POST": mutableHandler(buh.StartBlobUpload, ctx), - "GET": http.HandlerFunc(buh.GetUploadStatus), - "HEAD": http.HandlerFunc(buh.GetUploadStatus), - "PATCH": mutableHandler(buh.PatchBlobData, ctx), - "PUT": mutableHandler(buh.PutBlobUploadComplete, ctx), - "DELETE": mutableHandler(buh.CancelBlobUpload, ctx), - }) + handler := handlers.MethodHandler{ + "GET": http.HandlerFunc(buh.GetUploadStatus), + "HEAD": http.HandlerFunc(buh.GetUploadStatus), + } + + if !ctx.readOnly { + handler["POST"] = http.HandlerFunc(buh.StartBlobUpload) + handler["PATCH"] = http.HandlerFunc(buh.PatchBlobData) + handler["PUT"] = http.HandlerFunc(buh.PutBlobUploadComplete) + handler["DELETE"] = http.HandlerFunc(buh.CancelBlobUpload) + } if buh.UUID != "" { state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state")) @@ -93,7 +96,7 @@ func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler { } } - handler = closeResources(handler, buh.Upload) + return closeResources(handler, buh.Upload) } return handler diff --git a/registry/handlers/helpers.go b/registry/handlers/helpers.go index 9b462a19..5a3c9984 100644 --- a/registry/handlers/helpers.go +++ b/registry/handlers/helpers.go @@ -7,7 +7,6 @@ import ( ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/registry/api/errcode" - "github.com/docker/distribution/registry/api/v2" ) // closeResources closes all the provided resources after running the target @@ -61,16 +60,3 @@ func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWr return nil } - -// mutableHandler wraps a http.HandlerFunc with a check that the registry is -// not in read-only mode. If it is in read-only mode, the wrapper returns -// v2.ErrorCodeMaintenanceMode to the client. -func mutableHandler(handler http.HandlerFunc, ctx *Context) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if ctx.App.readOnly { - ctx.Errors = append(ctx.Errors, v2.ErrorCodeMaintenanceMode) - return - } - handler(w, r) - } -} diff --git a/registry/handlers/images.go b/registry/handlers/images.go index 78e36a13..0aeeb6f0 100644 --- a/registry/handlers/images.go +++ b/registry/handlers/images.go @@ -32,11 +32,16 @@ func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler { imageManifestHandler.Digest = dgst } - return handlers.MethodHandler{ - "GET": http.HandlerFunc(imageManifestHandler.GetImageManifest), - "PUT": mutableHandler(imageManifestHandler.PutImageManifest, ctx), - "DELETE": mutableHandler(imageManifestHandler.DeleteImageManifest, ctx), + mhandler := handlers.MethodHandler{ + "GET": http.HandlerFunc(imageManifestHandler.GetImageManifest), } + + if !ctx.readOnly { + mhandler["PUT"] = http.HandlerFunc(imageManifestHandler.PutImageManifest) + mhandler["DELETE"] = http.HandlerFunc(imageManifestHandler.DeleteImageManifest) + } + + return mhandler } // imageManifestHandler handles http operations on image manifests.