Merge pull request #827 from aaronlehmann/read-only-mode-2
Add a read-only mode as a configuration optionmaster
						commit
						dfe60f4cb1
					
				|  | @ -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,14 +646,15 @@ 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 | ||||
| 
 | ||||
| Upload purging is a background process that periodically removes orphaned files from the upload | ||||
| directories of the registry.  Upload purging is enabled by default.  To | ||||
|  configure upload directory purging, the following parameters | ||||
| configure upload directory purging, the following parameters | ||||
| must be set. | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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": http.HandlerFunc(blobHandler.DeleteBlob), | ||||
| 	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.
 | ||||
|  |  | |||
|  | @ -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), | ||||
| 		"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), | ||||
| 	}) | ||||
| 	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 | ||||
|  |  | |||
|  | @ -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":    http.HandlerFunc(imageManifestHandler.PutImageManifest), | ||||
| 		"DELETE": http.HandlerFunc(imageManifestHandler.DeleteImageManifest), | ||||
| 	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.
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue