Merge pull request #827 from aaronlehmann/read-only-mode-2

Add a read-only mode as a configuration option
This commit is contained in:
Stephen Day 2015-10-15 11:50:31 -07:00
commit dfe60f4cb1
6 changed files with 113 additions and 25 deletions

View file

@ -118,6 +118,8 @@ information about each option that appears later in this page.
age: 168h
interval: 24h
dryrun: false
readonly:
enabled: false
auth:
silly:
realm: silly-realm
@ -644,8 +646,9 @@ This storage backend uses Amazon's Simple Storage Service (S3).
### Maintenance
Currently the registry can perform one maintenance function: upload purging. This and future
maintenance functions which are related to storage can be configured under the maintenance section.
Currently upload purging and read-only mode are the only maintenance functions available.
These and future maintenance functions which are related to storage can be configured under
the maintenance section.
### Upload Purging
@ -664,6 +667,16 @@ must be set.
Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week).
### Read-only mode
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
This storage backend uses Openstack Swift object storage.

View file

@ -633,6 +633,54 @@ func TestDeleteDisabled(t *testing.T) {
checkResponse(t, "deleting layer with delete disabled", resp, http.StatusMethodNotAllowed)
}
func TestDeleteReadOnly(t *testing.T) {
env := newTestEnv(t, true)
imageName := "foo/bar"
// "build" our layer file
layerFile, tarSumStr, err := testutil.CreateRandomTarFile()
if err != nil {
t.Fatalf("error creating random layer file: %v", err)
}
layerDigest := digest.Digest(tarSumStr)
layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
if err != nil {
t.Fatalf("Error building blob URL")
}
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
env.app.readOnly = true
resp, err := httpDelete(layerURL)
if err != nil {
t.Fatalf("unexpected error deleting layer: %v", err)
}
checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed)
}
func TestStartPushReadOnly(t *testing.T) {
env := newTestEnv(t, true)
env.app.readOnly = true
imageName := "foo/bar"
layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
if err != nil {
t.Fatalf("unexpected error building layer upload url: %v", err)
}
resp, err := http.Post(layerUploadURL, "", nil)
if err != nil {
t.Fatalf("unexpected error starting layer push: %v", err)
}
defer resp.Body.Close()
checkResponse(t, "starting push in read-only mode", resp, http.StatusMethodNotAllowed)
}
func httpDelete(url string) (*http.Response, error) {
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {

View file

@ -69,6 +69,9 @@ type App struct {
// true if this registry is configured as a pull through cache
isCache bool
// true if the registry is in a read-only maintenance mode
readOnly bool
}
// NewApp takes a configuration and returns a configured app, ready to serve
@ -104,13 +107,24 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
purgeConfig := uploadPurgeDefaultConfig()
if mc, ok := configuration.Storage["maintenance"]; ok {
for k, v := range mc {
switch k {
case "uploadpurging":
purgeConfig = v.(map[interface{}]interface{})
if v, ok := mc["uploadpurging"]; ok {
purgeConfig, ok = v.(map[interface{}]interface{})
if !ok {
panic("uploadpurging config key must contain additional keys")
}
}
if v, ok := mc["readonly"]; ok {
readOnly, ok := v.(map[interface{}]interface{})
if !ok {
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")
}
}
}
}
startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)

View file

@ -32,11 +32,16 @@ func blobDispatcher(ctx *Context, r *http.Request) http.Handler {
Digest: dgst,
}
return handlers.MethodHandler{
mhandler := handlers.MethodHandler{
"GET": http.HandlerFunc(blobHandler.GetBlob),
"HEAD": http.HandlerFunc(blobHandler.GetBlob),
"DELETE": http.HandlerFunc(blobHandler.DeleteBlob),
}
if !ctx.readOnly {
mhandler["DELETE"] = http.HandlerFunc(blobHandler.DeleteBlob)
}
return mhandler
}
// blobHandler serves http blob requests.

View file

@ -22,14 +22,17 @@ func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
UUID: getUploadUUID(ctx),
}
handler := http.Handler(handlers.MethodHandler{
"POST": http.HandlerFunc(buh.StartBlobUpload),
handler := handlers.MethodHandler{
"GET": http.HandlerFunc(buh.GetUploadStatus),
"HEAD": http.HandlerFunc(buh.GetUploadStatus),
"PATCH": http.HandlerFunc(buh.PatchBlobData),
"PUT": http.HandlerFunc(buh.PutBlobUploadComplete),
"DELETE": http.HandlerFunc(buh.CancelBlobUpload),
})
}
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

View file

@ -32,11 +32,16 @@ func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
imageManifestHandler.Digest = dgst
}
return handlers.MethodHandler{
mhandler := handlers.MethodHandler{
"GET": http.HandlerFunc(imageManifestHandler.GetImageManifest),
"PUT": http.HandlerFunc(imageManifestHandler.PutImageManifest),
"DELETE": http.HandlerFunc(imageManifestHandler.DeleteImageManifest),
}
if !ctx.readOnly {
mhandler["PUT"] = http.HandlerFunc(imageManifestHandler.PutImageManifest)
mhandler["DELETE"] = http.HandlerFunc(imageManifestHandler.DeleteImageManifest)
}
return mhandler
}
// imageManifestHandler handles http operations on image manifests.