From 7ad5bf791233342cdf18d2dc567a3fa00cc300a5 Mon Sep 17 00:00:00 2001 From: bin liu Date: Fri, 17 Apr 2015 12:39:52 +0000 Subject: [PATCH 01/41] fix some typos in source comments Signed-off-by: bin liu --- digest/tarsum.go | 4 ++-- doc.go | 2 +- notifications/sinks.go | 6 +++--- registry/auth/auth.go | 4 ++-- registry/auth/token/util.go | 2 +- registry/storage/driver/ipc/server.go | 6 +++--- registry/storage/driver/testsuites/testsuites.go | 2 +- registry/storage/layer_test.go | 2 +- registry/storage/layerwriter.go | 2 +- registry/storage/paths.go | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/digest/tarsum.go b/digest/tarsum.go index acf878b62..702d7dc3f 100644 --- a/digest/tarsum.go +++ b/digest/tarsum.go @@ -6,10 +6,10 @@ import ( "regexp" ) -// TarSumRegexp defines a reguler expression to match tarsum identifiers. +// TarSumRegexp defines a regular expression to match tarsum identifiers. var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+") -// TarsumRegexpCapturing defines a reguler expression to match tarsum identifiers with +// TarsumRegexpCapturing defines a regular expression to match tarsum identifiers with // capture groups corresponding to each component. var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)") diff --git a/doc.go b/doc.go index 9b981f7d8..bdd8cb708 100644 --- a/doc.go +++ b/doc.go @@ -2,6 +2,6 @@ // docker distribution. The goal is to allow users to reliably package, ship // and store content related to docker images. // -// This is currently a work in progress. More details are availalbe in the +// This is currently a work in progress. More details are available in the // README.md. package distribution diff --git a/notifications/sinks.go b/notifications/sinks.go index 2bf63e2d3..dda4a5653 100644 --- a/notifications/sinks.go +++ b/notifications/sinks.go @@ -220,7 +220,7 @@ type retryingSink struct { sink Sink closed bool - // circuit breaker hueristics + // circuit breaker heuristics failures struct { threshold int recent int @@ -317,7 +317,7 @@ func (rs *retryingSink) wait(backoff time.Duration) { time.Sleep(backoff) } -// reset marks a succesful call. +// reset marks a successful call. func (rs *retryingSink) reset() { rs.failures.recent = 0 rs.failures.last = time.Time{} @@ -330,7 +330,7 @@ func (rs *retryingSink) failure() { } // proceed returns true if the call should proceed based on circuit breaker -// hueristics. +// heuristics. func (rs *retryingSink) proceed() bool { return rs.failures.recent < rs.failures.threshold || time.Now().UTC().After(rs.failures.last.Add(rs.failures.backoff)) diff --git a/registry/auth/auth.go b/registry/auth/auth.go index a8499342d..ec82b4697 100644 --- a/registry/auth/auth.go +++ b/registry/auth/auth.go @@ -3,7 +3,7 @@ // An access controller has a simple interface with a single `Authorized` // method which checks that a given request is authorized to perform one or // more actions on one or more resources. This method should return a non-nil -// error if the requset is not authorized. +// error if the request is not authorized. // // An implementation registers its access controller by name with a constructor // which accepts an options map for configuring the access controller. @@ -50,7 +50,7 @@ type Resource struct { } // Access describes a specific action that is -// requested or allowed for a given recource. +// requested or allowed for a given resource. type Access struct { Resource Action string diff --git a/registry/auth/token/util.go b/registry/auth/token/util.go index bf3e01e83..d7f95be42 100644 --- a/registry/auth/token/util.go +++ b/registry/auth/token/util.go @@ -7,7 +7,7 @@ import ( ) // joseBase64UrlEncode encodes the given data using the standard base64 url -// encoding format but with all trailing '=' characters ommitted in accordance +// encoding format but with all trailing '=' characters omitted in accordance // with the jose specification. // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2 func joseBase64UrlEncode(b []byte) string { diff --git a/registry/storage/driver/ipc/server.go b/registry/storage/driver/ipc/server.go index 4c6f1d4de..1752f12ba 100644 --- a/registry/storage/driver/ipc/server.go +++ b/registry/storage/driver/ipc/server.go @@ -101,7 +101,7 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) { } case "ReadStream": path, _ := request.Parameters["Path"].(string) - // Depending on serialization method, Offset may be convereted to any int/uint type + // Depending on serialization method, Offset may be converted to any int/uint type offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(int64(0))).Int() reader, err := driver.ReadStream(path, offset) var response ReadStreamResponse @@ -116,9 +116,9 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) { } case "WriteStream": path, _ := request.Parameters["Path"].(string) - // Depending on serialization method, Offset may be convereted to any int/uint type + // Depending on serialization method, Offset may be converted to any int/uint type offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(int64(0))).Int() - // Depending on serialization method, Size may be convereted to any int/uint type + // Depending on serialization method, Size may be converted to any int/uint type size := reflect.ValueOf(request.Parameters["Size"]).Convert(reflect.TypeOf(int64(0))).Int() reader, _ := request.Parameters["Reader"].(io.ReadCloser) err := driver.WriteStream(path, offset, size, reader) diff --git a/registry/storage/driver/testsuites/testsuites.go b/registry/storage/driver/testsuites/testsuites.go index 74ddab6f8..9f387a627 100644 --- a/registry/storage/driver/testsuites/testsuites.go +++ b/registry/storage/driver/testsuites/testsuites.go @@ -435,7 +435,7 @@ func (suite *DriverSuite) testContinueStreamAppend(c *check.C, chunkSize int64) c.Assert(err, check.IsNil) c.Assert(received, check.DeepEquals, fullContents) - // Writing past size of file extends file (no offest error). We would like + // Writing past size of file extends file (no offset error). We would like // to write chunk 4 one chunk length past chunk 3. It should be successful // and the resulting file will be 5 chunks long, with a chunk of all // zeros. diff --git a/registry/storage/layer_test.go b/registry/storage/layer_test.go index e225d0685..f25018daa 100644 --- a/registry/storage/layer_test.go +++ b/registry/storage/layer_test.go @@ -336,7 +336,7 @@ func seekerSize(seeker io.ReadSeeker) (int64, error) { // createTestLayer creates a simple test layer in the provided driver under // tarsum dgst, returning the sha256 digest location. This is implemented -// peicemeal and should probably be replaced by the uploader when it's ready. +// piecemeal and should probably be replaced by the uploader when it's ready. func writeTestLayer(driver storagedriver.StorageDriver, pathMapper *pathMapper, name string, dgst digest.Digest, content io.Reader) (digest.Digest, error) { h := sha256.New() rd := io.TeeReader(content, h) diff --git a/registry/storage/layerwriter.go b/registry/storage/layerwriter.go index 1e5ea9187..0305d0117 100644 --- a/registry/storage/layerwriter.go +++ b/registry/storage/layerwriter.go @@ -182,7 +182,7 @@ func (lw *layerWriter) resumeHashAt(offset int64) error { } if offset == int64(lw.resumableDigester.Len()) { - // State of digester is already at the requseted offset. + // State of digester is already at the requested offset. return nil } diff --git a/registry/storage/paths.go b/registry/storage/paths.go index 7aeff6e44..fe648f519 100644 --- a/registry/storage/paths.go +++ b/registry/storage/paths.go @@ -387,7 +387,7 @@ type layerLinkPathSpec struct { func (layerLinkPathSpec) pathSpec() {} // blobAlgorithmReplacer does some very simple path sanitization for user -// input. Mostly, this is to provide some heirachry for tarsum digests. Paths +// input. Mostly, this is to provide some hierarchy for tarsum digests. Paths // should be "safe" before getting this far due to strict digest requirements // but we can add further path conversion here, if needed. var blobAlgorithmReplacer = strings.NewReplacer( From dd0effe29a62335ffca68650a3b8045a85698fdf Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 16 Apr 2015 11:37:31 -0700 Subject: [PATCH 02/41] Add path and other info to filesytem trace methods. Also fix Delete (was 'Move'). --- registry/storage/driver/base/base.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/registry/storage/driver/base/base.go b/registry/storage/driver/base/base.go index ba7a859d4..6b7bcf0f4 100644 --- a/registry/storage/driver/base/base.go +++ b/registry/storage/driver/base/base.go @@ -34,7 +34,7 @@ // } // // The type now implements StorageDriver, proxying through Base, without -// exporting an unnessecary field. +// exporting an unnecessary field. package base import ( @@ -53,7 +53,7 @@ type Base struct { // GetContent wraps GetContent of underlying storage driver. func (base *Base) GetContent(path string) ([]byte, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.GetContent") + defer done("Base.GetContent(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) { return nil, storagedriver.InvalidPathError{Path: path} @@ -65,7 +65,7 @@ func (base *Base) GetContent(path string) ([]byte, error) { // PutContent wraps PutContent of underlying storage driver. func (base *Base) PutContent(path string, content []byte) error { _, done := context.WithTrace(context.Background()) - defer done("Base.PutContent") + defer done("Base.PutContent(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) { return storagedriver.InvalidPathError{Path: path} @@ -77,7 +77,7 @@ func (base *Base) PutContent(path string, content []byte) error { // ReadStream wraps ReadStream of underlying storage driver. func (base *Base) ReadStream(path string, offset int64) (io.ReadCloser, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.ReadStream") + defer done("Base.ReadStream(\"%s\", %d)", path, offset) if offset < 0 { return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset} @@ -93,7 +93,7 @@ func (base *Base) ReadStream(path string, offset int64) (io.ReadCloser, error) { // WriteStream wraps WriteStream of underlying storage driver. func (base *Base) WriteStream(path string, offset int64, reader io.Reader) (nn int64, err error) { _, done := context.WithTrace(context.Background()) - defer done("Base.WriteStream") + defer done("Base.WriteStream(\"%s\", %d)", path, offset) if offset < 0 { return 0, storagedriver.InvalidOffsetError{Path: path, Offset: offset} @@ -109,7 +109,7 @@ func (base *Base) WriteStream(path string, offset int64, reader io.Reader) (nn i // Stat wraps Stat of underlying storage driver. func (base *Base) Stat(path string) (storagedriver.FileInfo, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.Stat") + defer done("Base.Stat(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) { return nil, storagedriver.InvalidPathError{Path: path} @@ -121,7 +121,7 @@ func (base *Base) Stat(path string) (storagedriver.FileInfo, error) { // List wraps List of underlying storage driver. func (base *Base) List(path string) ([]string, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.List") + defer done("Base.List(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) && path != "/" { return nil, storagedriver.InvalidPathError{Path: path} @@ -133,7 +133,7 @@ func (base *Base) List(path string) ([]string, error) { // Move wraps Move of underlying storage driver. func (base *Base) Move(sourcePath string, destPath string) error { _, done := context.WithTrace(context.Background()) - defer done("Base.Move") + defer done("Base.Move(\"%s\", \"%s\"", sourcePath, destPath) if !storagedriver.PathRegexp.MatchString(sourcePath) { return storagedriver.InvalidPathError{Path: sourcePath} @@ -147,7 +147,7 @@ func (base *Base) Move(sourcePath string, destPath string) error { // Delete wraps Delete of underlying storage driver. func (base *Base) Delete(path string) error { _, done := context.WithTrace(context.Background()) - defer done("Base.Move") + defer done("Base.Delete(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) { return storagedriver.InvalidPathError{Path: path} @@ -159,7 +159,7 @@ func (base *Base) Delete(path string) error { // URLFor wraps URLFor of underlying storage driver. func (base *Base) URLFor(path string, options map[string]interface{}) (string, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.URLFor") + defer done("Base.URLFor(\"%s\")", path) if !storagedriver.PathRegexp.MatchString(path) { return "", storagedriver.InvalidPathError{Path: path} From c06c6ba3bf2117c5756fb1336e17c1306027ce11 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Mon, 20 Apr 2015 11:15:01 -0700 Subject: [PATCH 03/41] Return instrumented response writer from context This is ensures that users of the ResponseWriter from the context correctly track usage. Otherwise, context reporting is incorrect. Signed-off-by: Stephen J Day --- context/http.go | 2 +- context/http_test.go | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/context/http.go b/context/http.go index 98ab436d9..2b17074b0 100644 --- a/context/http.go +++ b/context/http.go @@ -302,7 +302,7 @@ func (irw *instrumentedResponseWriter) Flush() { func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} { if keyStr, ok := key.(string); ok { if keyStr == "http.response" { - return irw.ResponseWriter + return irw } if !strings.HasPrefix(keyStr, "http.response.") { diff --git a/context/http_test.go b/context/http_test.go index 42c78b750..f133574fc 100644 --- a/context/http_test.go +++ b/context/http_test.go @@ -132,8 +132,17 @@ func TestWithResponseWriter(t *testing.T) { trw := testResponseWriter{} ctx, rw := WithResponseWriter(Background(), &trw) - if ctx.Value("http.response") != &trw { - t.Fatalf("response not available in context: %v != %v", ctx.Value("http.response"), &trw) + if ctx.Value("http.response") != rw { + t.Fatalf("response not available in context: %v != %v", ctx.Value("http.response"), rw) + } + + grw, err := GetResponseWriter(ctx) + if err != nil { + t.Fatalf("error getting response writer: %v", err) + } + + if grw != rw { + t.Fatalf("unexpected response writer returned: %#v != %#v", grw, rw) } if n, err := rw.Write(make([]byte, 1024)); err != nil { From 96f1e8539677367b0b51055ca122f099cf68b5ea Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 20 Apr 2015 16:35:09 -0700 Subject: [PATCH 04/41] Add logging for generic handler errors. Signed-off-by: Richard Scothern --- registry/handlers/app.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 28940c8e1..e35d86337 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -365,11 +365,25 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler { // future refactoring. w.WriteHeader(http.StatusBadRequest) } + app.logError(context, context.Errors) serveJSON(w, context.Errors) } }) } +func (app *App) logError(context context.Context, errors v2.Errors) { + for _, e := range errors.Errors { + c := ctxu.WithValue(context, "err.code", e.Code) + c = ctxu.WithValue(c, "err.message", e.Message) + c = ctxu.WithValue(c, "err.detail", e.Detail) + c = ctxu.WithLogger(c, ctxu.GetLogger(c, + "err.code", + "err.message", + "err.detail")) + ctxu.GetLogger(c).Errorf("An error occured") + } +} + // context constructs the context object for the application. This only be // called once per request. func (app *App) context(w http.ResponseWriter, r *http.Request) *Context { From 4686b3c0f4a9fc14158406a3a7a4601cce7af548 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Mon, 20 Apr 2015 18:43:19 -0700 Subject: [PATCH 05/41] Attempt to deal with eventual consistency by retrying Rather than accept the resulting of a layer validation, we retry up to three times, backing off 100ms after each try. The thought is that we allow s3 files to make their way into the correct location increasing the liklihood the verification can proceed, if possible. Signed-off-by: Stephen J Day --- registry/storage/layerwriter.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/registry/storage/layerwriter.go b/registry/storage/layerwriter.go index 0305d0117..fe1185978 100644 --- a/registry/storage/layerwriter.go +++ b/registry/storage/layerwriter.go @@ -46,16 +46,37 @@ func (lw *layerWriter) StartedAt() time.Time { // uploaded layer. The final size and checksum are validated against the // contents of the uploaded layer. The checksum should be provided in the // format :. -func (lw *layerWriter) Finish(digest digest.Digest) (distribution.Layer, error) { +func (lw *layerWriter) Finish(dgst digest.Digest) (distribution.Layer, error) { ctxu.GetLogger(lw.layerStore.repository.ctx).Debug("(*layerWriter).Finish") if err := lw.bufferedFileWriter.Close(); err != nil { return nil, err } - canonical, err := lw.validateLayer(digest) - if err != nil { + var ( + canonical digest.Digest + err error + ) + + // HACK(stevvooe): To deal with s3's lack of consistency, attempt to retry + // validation on failure. Three attempts are made, backing off 100ms each + // time. + for retries := 0; ; retries++ { + canonical, err = lw.validateLayer(dgst) + if err == nil { + break + } + + ctxu.GetLoggerWithField(lw.layerStore.repository.ctx, "retries", retries). + Errorf("error validating layer: %v", err) + + if retries < 3 { + time.Sleep(100 * time.Millisecond) + continue + } + return nil, err + } if err := lw.moveLayer(canonical); err != nil { @@ -64,7 +85,7 @@ func (lw *layerWriter) Finish(digest digest.Digest) (distribution.Layer, error) } // Link the layer blob into the repository. - if err := lw.linkLayer(canonical, digest); err != nil { + if err := lw.linkLayer(canonical, dgst); err != nil { return nil, err } From 56b18134fae5aa777389d90102a086e2dcb14725 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Tue, 21 Apr 2015 11:34:18 -0700 Subject: [PATCH 06/41] log canonical digest on verification error Signed-off-by: Stephen J Day --- registry/storage/layerwriter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/registry/storage/layerwriter.go b/registry/storage/layerwriter.go index fe1185978..0a42aa40b 100644 --- a/registry/storage/layerwriter.go +++ b/registry/storage/layerwriter.go @@ -345,6 +345,8 @@ func (lw *layerWriter) validateLayer(dgst digest.Digest) (digest.Digest, error) } if !verified { + ctxu.GetLoggerWithField(lw.layerStore.repository.ctx, "canonical", dgst). + Errorf("canonical digest does match provided digest") return "", distribution.ErrLayerInvalidDigest{ Digest: dgst, Reason: fmt.Errorf("content does not match digest"), From 6cb2104945db05298ecacdc76ca2abb44bfdc791 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Tue, 21 Apr 2015 12:10:48 -0700 Subject: [PATCH 07/41] Backoff retry on verification to give s3 time to propagate Signed-off-by: Stephen J Day --- registry/storage/layerwriter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/storage/layerwriter.go b/registry/storage/layerwriter.go index 0a42aa40b..3efd60a45 100644 --- a/registry/storage/layerwriter.go +++ b/registry/storage/layerwriter.go @@ -59,8 +59,8 @@ func (lw *layerWriter) Finish(dgst digest.Digest) (distribution.Layer, error) { ) // HACK(stevvooe): To deal with s3's lack of consistency, attempt to retry - // validation on failure. Three attempts are made, backing off 100ms each - // time. + // validation on failure. Three attempts are made, backing off + // retries*100ms each time. for retries := 0; ; retries++ { canonical, err = lw.validateLayer(dgst) if err == nil { @@ -71,7 +71,7 @@ func (lw *layerWriter) Finish(dgst digest.Digest) (distribution.Layer, error) { Errorf("error validating layer: %v", err) if retries < 3 { - time.Sleep(100 * time.Millisecond) + time.Sleep(100 * time.Millisecond * time.Duration(retries+1)) continue } From 3020aa0fe84b279b8c33032e674215469f377775 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 12:32:59 -0700 Subject: [PATCH 08/41] Update goamz package dependency Signed-off-by: Stephen J Day --- Godeps/Godeps.json | 6 +- .../src/github.com/AdRoll/goamz/aws/aws.go | 7 +- .../github.com/AdRoll/goamz/aws/regions.go | 34 +++ .../src/github.com/AdRoll/goamz/s3/s3.go | 25 +- .../src/github.com/AdRoll/goamz/s3/s3_test.go | 16 ++ .../AdRoll/goamz/s3/s3test/server.go | 230 ++++++++++++++++-- 6 files changed, 281 insertions(+), 37 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index dc643683d..ab255849d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,15 +12,15 @@ }, { "ImportPath": "github.com/AdRoll/goamz/aws", - "Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103" + "Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99" }, { "ImportPath": "github.com/AdRoll/goamz/cloudfront", - "Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103" + "Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99" }, { "ImportPath": "github.com/AdRoll/goamz/s3", - "Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103" + "Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99" }, { "ImportPath": "github.com/MSOpenTech/azure-sdk-for-go/storage", diff --git a/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go b/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go index 38c9e6566..87c2d6da7 100644 --- a/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go +++ b/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go @@ -62,6 +62,7 @@ type Region struct { SESEndpoint string IAMEndpoint string ELBEndpoint string + KMSEndpoint string DynamoDBEndpoint string CloudWatchServicepoint ServiceInfo AutoScalingEndpoint string @@ -83,6 +84,7 @@ var Regions = map[string]Region{ USWest2.Name: USWest2, USGovWest.Name: USGovWest, SAEast.Name: SAEast, + CNNorth1.Name: CNNorth1, } // Designates a signer interface suitable for signing AWS requests, params @@ -208,7 +210,10 @@ func (a *Auth) Token() string { return "" } if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock - *a, _ = GetAuth("", "", "", time.Time{}) + auth, err := GetAuth("", "", "", time.Time{}) + if err == nil { + *a = auth + } } return a.token } diff --git a/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go b/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go index 4e39069ef..fdc2626b8 100644 --- a/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go +++ b/Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go @@ -13,6 +13,7 @@ var USGovWest = Region{ "", "https://iam.us-gov.amazonaws.com", "https://elasticloadbalancing.us-gov-west-1.amazonaws.com", + "", "https://dynamodb.us-gov-west-1.amazonaws.com", ServiceInfo{"https://monitoring.us-gov-west-1.amazonaws.com", V2Signature}, "https://autoscaling.us-gov-west-1.amazonaws.com", @@ -36,6 +37,7 @@ var USEast = Region{ "https://email.us-east-1.amazonaws.com", "https://iam.amazonaws.com", "https://elasticloadbalancing.us-east-1.amazonaws.com", + "https://kms.us-east-1.amazonaws.com", "https://dynamodb.us-east-1.amazonaws.com", ServiceInfo{"https://monitoring.us-east-1.amazonaws.com", V2Signature}, "https://autoscaling.us-east-1.amazonaws.com", @@ -59,6 +61,7 @@ var USWest = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.us-west-1.amazonaws.com", + "https://kms.us-west-1.amazonaws.com", "https://dynamodb.us-west-1.amazonaws.com", ServiceInfo{"https://monitoring.us-west-1.amazonaws.com", V2Signature}, "https://autoscaling.us-west-1.amazonaws.com", @@ -82,6 +85,7 @@ var USWest2 = Region{ "https://email.us-west-2.amazonaws.com", "https://iam.amazonaws.com", "https://elasticloadbalancing.us-west-2.amazonaws.com", + "https://kms.us-west-2.amazonaws.com", "https://dynamodb.us-west-2.amazonaws.com", ServiceInfo{"https://monitoring.us-west-2.amazonaws.com", V2Signature}, "https://autoscaling.us-west-2.amazonaws.com", @@ -105,6 +109,7 @@ var EUWest = Region{ "https://email.eu-west-1.amazonaws.com", "https://iam.amazonaws.com", "https://elasticloadbalancing.eu-west-1.amazonaws.com", + "https://kms.eu-west-1.amazonaws.com", "https://dynamodb.eu-west-1.amazonaws.com", ServiceInfo{"https://monitoring.eu-west-1.amazonaws.com", V2Signature}, "https://autoscaling.eu-west-1.amazonaws.com", @@ -128,6 +133,7 @@ var EUCentral = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.eu-central-1.amazonaws.com", + "https://kms.eu-central-1.amazonaws.com", "https://dynamodb.eu-central-1.amazonaws.com", ServiceInfo{"https://monitoring.eu-central-1.amazonaws.com", V2Signature}, "https://autoscaling.eu-central-1.amazonaws.com", @@ -151,6 +157,7 @@ var APSoutheast = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.ap-southeast-1.amazonaws.com", + "https://kms.ap-southeast-1.amazonaws.com", "https://dynamodb.ap-southeast-1.amazonaws.com", ServiceInfo{"https://monitoring.ap-southeast-1.amazonaws.com", V2Signature}, "https://autoscaling.ap-southeast-1.amazonaws.com", @@ -174,6 +181,7 @@ var APSoutheast2 = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.ap-southeast-2.amazonaws.com", + "https://kms.ap-southeast-2.amazonaws.com", "https://dynamodb.ap-southeast-2.amazonaws.com", ServiceInfo{"https://monitoring.ap-southeast-2.amazonaws.com", V2Signature}, "https://autoscaling.ap-southeast-2.amazonaws.com", @@ -197,6 +205,7 @@ var APNortheast = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.ap-northeast-1.amazonaws.com", + "https://kms.ap-northeast-1.amazonaws.com", "https://dynamodb.ap-northeast-1.amazonaws.com", ServiceInfo{"https://monitoring.ap-northeast-1.amazonaws.com", V2Signature}, "https://autoscaling.ap-northeast-1.amazonaws.com", @@ -220,6 +229,7 @@ var SAEast = Region{ "", "https://iam.amazonaws.com", "https://elasticloadbalancing.sa-east-1.amazonaws.com", + "https://kms.sa-east-1.amazonaws.com", "https://dynamodb.sa-east-1.amazonaws.com", ServiceInfo{"https://monitoring.sa-east-1.amazonaws.com", V2Signature}, "https://autoscaling.sa-east-1.amazonaws.com", @@ -229,3 +239,27 @@ var SAEast = Region{ "https://cloudformation.sa-east-1.amazonaws.com", "https://elasticache.sa-east-1.amazonaws.com", } + +var CNNorth1 = Region{ + "cn-north-1", + "https://ec2.cn-north-1.amazonaws.com.cn", + "https://s3.cn-north-1.amazonaws.com.cn", + "", + true, + true, + "", + "https://sns.cn-north-1.amazonaws.com.cn", + "https://sqs.cn-north-1.amazonaws.com.cn", + "", + "https://iam.cn-north-1.amazonaws.com.cn", + "https://elasticloadbalancing.cn-north-1.amazonaws.com.cn", + "", + "https://dynamodb.cn-north-1.amazonaws.com.cn", + ServiceInfo{"https://monitoring.cn-north-1.amazonaws.com.cn", V4Signature}, + "https://autoscaling.cn-north-1.amazonaws.com.cn", + ServiceInfo{"https://rds.cn-north-1.amazonaws.com.cn", V4Signature}, + "", + "https://sts.cn-north-1.amazonaws.com.cn", + "", + "", +} diff --git a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go index 18313c282..69b2e071d 100644 --- a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go +++ b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go @@ -25,6 +25,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "path" "strconv" "strings" "time" @@ -70,9 +71,8 @@ type Options struct { ContentMD5 string ContentDisposition string Range string + StorageClass StorageClass // What else? - //// The following become headers so they are []strings rather than strings... I think - // x-amz-storage-class []string } type CopyOptions struct { @@ -96,7 +96,7 @@ var attempts = aws.AttemptStrategy{ // New creates a new S3. func New(auth aws.Auth, region aws.Region) *S3 { - return &S3{auth, region, 0, 0, 0, aws.V2Signature} + return &S3{auth, region, 0, 0, aws.V2Signature, 0} } // Bucket returns a Bucket with the given name. @@ -164,6 +164,13 @@ const ( BucketOwnerFull = ACL("bucket-owner-full-control") ) +type StorageClass string + +const ( + ReducedRedundancy = StorageClass("REDUCED_REDUNDANCY") + StandardStorage = StorageClass("STANDARD") +) + // PutBucket creates a new bucket. // // See http://goo.gl/ndjnR for details. @@ -401,6 +408,10 @@ func (o Options) addHeaders(headers map[string][]string) { if len(o.ContentDisposition) != 0 { headers["Content-Disposition"] = []string{o.ContentDisposition} } + if len(o.StorageClass) != 0 { + headers["x-amz-storage-class"] = []string{string(o.StorageClass)} + + } for k, v := range o.Meta { headers["x-amz-meta-"+k] = v } @@ -816,8 +827,8 @@ func (b *Bucket) SignedURLWithMethod(method, path string, expires time.Time, par // UploadSignedURL returns a signed URL that allows anyone holding the URL // to upload the object at path. The signature is valid until expires. // contenttype is a string like image/png -// path is the resource name in s3 terminalogy like images/ali.png [obviously exclusing the bucket name itself] -func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time.Time) string { +// name is the resource name in s3 terminology like images/ali.png [obviously excluding the bucket name itself] +func (b *Bucket) UploadSignedURL(name, method, content_type string, expires time.Time) string { expire_date := expires.Unix() if method != "POST" { method = "PUT" @@ -830,7 +841,7 @@ func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time tokenData = "x-amz-security-token:" + a.Token() + "\n" } - stringToSign := method + "\n\n" + content_type + "\n" + strconv.FormatInt(expire_date, 10) + "\n" + tokenData + "/" + b.Name + "/" + path + stringToSign := method + "\n\n" + content_type + "\n" + strconv.FormatInt(expire_date, 10) + "\n" + tokenData + "/" + path.Join(b.Name, name) secretKey := a.SecretKey accessId := a.AccessKey mac := hmac.New(sha1.New, []byte(secretKey)) @@ -844,7 +855,7 @@ func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time log.Println("ERROR sining url for S3 upload", err) return "" } - signedurl.Path += path + signedurl.Path = name params := url.Values{} params.Add("AWSAccessKeyId", accessId) params.Add("Expires", strconv.FormatInt(expire_date, 10)) diff --git a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go index 87b23ad0c..161bb3af9 100644 --- a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go +++ b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go @@ -230,6 +230,22 @@ func (s *S) TestPutObject(c *check.C) { c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"}) } +func (s *S) TestPutObjectReducedRedundancy(c *check.C) { + testServer.Response(200, nil, "") + + b := s.s3.Bucket("bucket") + err := b.Put("name", []byte("content"), "content-type", s3.Private, s3.Options{StorageClass: s3.ReducedRedundancy}) + c.Assert(err, check.IsNil) + + req := testServer.WaitRequest() + c.Assert(req.Method, check.Equals, "PUT") + c.Assert(req.URL.Path, check.Equals, "/bucket/name") + c.Assert(req.Header["Date"], check.Not(check.DeepEquals), []string{""}) + c.Assert(req.Header["Content-Type"], check.DeepEquals, []string{"content-type"}) + c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"7"}) + c.Assert(req.Header["X-Amz-Storage-Class"], check.DeepEquals, []string{"REDUCED_REDUNDANCY"}) +} + // PutCopy docs: http://goo.gl/mhEHtA func (s *S) TestPutCopy(c *check.C) { testServer.Response(200, nil, PutCopyResultDump) diff --git a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go index 4dc95eae0..d54a638c5 100644 --- a/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go +++ b/Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go @@ -11,6 +11,7 @@ import ( "io" "io/ioutil" "log" + "math/rand" "net" "net/http" "net/url" @@ -51,6 +52,10 @@ type Config struct { // all other regions. // http://docs.amazonwebservices.com/AmazonS3/latest/API/ErrorResponses.html Send409Conflict bool + + // Address on which to listen. By default, a random port is assigned by the + // operating system and the server listens on localhost. + ListenAddress string } func (c *Config) send409Conflict() bool { @@ -72,10 +77,11 @@ type Server struct { } type bucket struct { - name string - acl s3.ACL - ctime time.Time - objects map[string]*object + name string + acl s3.ACL + ctime time.Time + objects map[string]*object + multipartUploads map[string][]*multipartUploadPart } type object struct { @@ -86,6 +92,12 @@ type object struct { data []byte } +type multipartUploadPart struct { + data []byte + etag string + lastModified time.Time +} + // A resource encapsulates the subject of an HTTP request. // The resource referred to may or may not exist // when the request is made. @@ -97,7 +109,13 @@ type resource interface { } func NewServer(config *Config) (*Server, error) { - l, err := net.Listen("tcp", "localhost:0") + listenAddress := "localhost:0" + + if config != nil && config.ListenAddress != "" { + listenAddress = config.ListenAddress + } + + l, err := net.Listen("tcp", listenAddress) if err != nil { return nil, fmt.Errorf("cannot listen on localhost: %v", err) } @@ -217,10 +235,8 @@ var unimplementedBucketResourceNames = map[string]bool{ } var unimplementedObjectResourceNames = map[string]bool{ - "uploadId": true, - "acl": true, - "torrent": true, - "uploads": true, + "acl": true, + "torrent": true, } var pathRegexp = regexp.MustCompile("/(([^/]+)(/(.*))?)?") @@ -420,7 +436,8 @@ func (r bucketResource) put(a *action) interface{} { r.bucket = &bucket{ name: r.name, // TODO default acl - objects: make(map[string]*object), + objects: make(map[string]*object), + multipartUploads: make(map[string][]*multipartUploadPart), } a.srv.buckets[r.name] = r.bucket created = true @@ -615,12 +632,29 @@ func (objr objectResource) put(a *action) interface{} { // TODO x-amz-server-side-encryption // TODO x-amz-storage-class - // TODO is this correct, or should we erase all previous metadata? - obj := objr.object - if obj == nil { - obj = &object{ - name: objr.name, - meta: make(http.Header), + uploadId := a.req.URL.Query().Get("uploadId") + + // Check that the upload ID is valid if this is a multipart upload + if uploadId != "" { + if _, ok := objr.bucket.multipartUploads[uploadId]; !ok { + fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.") + } + + partNumberStr := a.req.URL.Query().Get("partNumber") + + if partNumberStr == "" { + fatalf(400, "InvalidRequest", "Missing partNumber parameter") + } + + partNumber, err := strconv.ParseUint(partNumberStr, 10, 32) + + if err != nil { + fatalf(400, "InvalidRequest", "partNumber is not a number") + } + + // Parts are 1-indexed for multipart uploads + if uint(partNumber)-1 != uint(len(objr.bucket.multipartUploads[uploadId])) { + fatalf(400, "InvalidRequest", "Invalid part number") } } @@ -646,26 +680,170 @@ func (objr objectResource) put(a *action) interface{} { fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header") } - // PUT request has been successful - save data and metadata - for key, values := range a.req.Header { - key = http.CanonicalHeaderKey(key) - if metaHeaders[key] || strings.HasPrefix(key, "X-Amz-Meta-") { - obj.meta[key] = values + etag := fmt.Sprintf("\"%x\"", gotHash) + + a.w.Header().Add("ETag", etag) + + if uploadId == "" { + // For traditional uploads + + // TODO is this correct, or should we erase all previous metadata? + obj := objr.object + if obj == nil { + obj = &object{ + name: objr.name, + meta: make(http.Header), + } } + + // PUT request has been successful - save data and metadata + for key, values := range a.req.Header { + key = http.CanonicalHeaderKey(key) + if metaHeaders[key] || strings.HasPrefix(key, "X-Amz-Meta-") { + obj.meta[key] = values + } + } + obj.data = data + obj.checksum = gotHash + obj.mtime = time.Now() + objr.bucket.objects[objr.name] = obj + } else { + // For multipart commit + + parts := objr.bucket.multipartUploads[uploadId] + part := &multipartUploadPart{ + data, + etag, + time.Now(), + } + + objr.bucket.multipartUploads[uploadId] = append(parts, part) } - obj.data = data - obj.checksum = gotHash - obj.mtime = time.Now() - objr.bucket.objects[objr.name] = obj + return nil } func (objr objectResource) delete(a *action) interface{} { - delete(objr.bucket.objects, objr.name) + uploadId := a.req.URL.Query().Get("uploadId") + + if uploadId == "" { + // Traditional object delete + delete(objr.bucket.objects, objr.name) + } else { + // Multipart commit abort + _, ok := objr.bucket.multipartUploads[uploadId] + + if !ok { + fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.") + } + + delete(objr.bucket.multipartUploads, uploadId) + } return nil } func (objr objectResource) post(a *action) interface{} { + // Check if we're initializing a multipart upload + if _, ok := a.req.URL.Query()["uploads"]; ok { + type multipartInitResponse struct { + XMLName struct{} `xml:"InitiateMultipartUploadResult"` + Bucket string + Key string + UploadId string + } + + uploadId := strconv.FormatInt(rand.Int63(), 16) + + objr.bucket.multipartUploads[uploadId] = []*multipartUploadPart{} + + return &multipartInitResponse{ + Bucket: objr.bucket.name, + Key: objr.name, + UploadId: uploadId, + } + } + + // Check if we're completing a multipart upload + if uploadId := a.req.URL.Query().Get("uploadId"); uploadId != "" { + type multipartCompleteRequestPart struct { + XMLName struct{} `xml:"Part"` + PartNumber uint + ETag string + } + + type multipartCompleteRequest struct { + XMLName struct{} `xml:"CompleteMultipartUpload"` + Part []multipartCompleteRequestPart + } + + type multipartCompleteResponse struct { + XMLName struct{} `xml:"CompleteMultipartUploadResult"` + Location string + Bucket string + Key string + ETag string + } + + parts, ok := objr.bucket.multipartUploads[uploadId] + + if !ok { + fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.") + } + + req := &multipartCompleteRequest{} + + if err := xml.NewDecoder(a.req.Body).Decode(req); err != nil { + fatalf(400, "InvalidRequest", err.Error()) + } + + if len(req.Part) != len(parts) { + fatalf(400, "InvalidRequest", fmt.Sprintf("Number of parts does not match: expected %d, received %d", len(parts), len(req.Part))) + } + + sum := md5.New() + data := &bytes.Buffer{} + w := io.MultiWriter(sum, data) + + for i, p := range parts { + reqPart := req.Part[i] + + if reqPart.PartNumber != uint(1+i) { + fatalf(400, "InvalidRequest", "Bad part number") + } + + if reqPart.ETag != p.etag { + fatalf(400, "InvalidRequest", fmt.Sprintf("Invalid etag for part %d", reqPart.PartNumber)) + } + + w.Write(p.data) + } + + delete(objr.bucket.multipartUploads, uploadId) + + obj := objr.object + + if obj == nil { + obj = &object{ + name: objr.name, + meta: make(http.Header), + } + } + + obj.data = data.Bytes() + obj.checksum = sum.Sum(nil) + obj.mtime = time.Now() + objr.bucket.objects[objr.name] = obj + + objectLocation := fmt.Sprintf("http://%s/%s/%s", a.srv.listener.Addr().String(), objr.bucket.name, objr.name) + + return &multipartCompleteResponse{ + Location: objectLocation, + Bucket: objr.bucket.name, + Key: objr.name, + ETag: uploadId, + } + } + fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource") return nil } From bccca791ad6c8f47b54d9cf334fc52a7fa910a04 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 12:12:59 -0700 Subject: [PATCH 09/41] Check error returned from io.Copy Signed-off-by: Stephen J Day --- registry/handlers/layerupload.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/registry/handlers/layerupload.go b/registry/handlers/layerupload.go index b728d0e1a..8c96b7a6e 100644 --- a/registry/handlers/layerupload.go +++ b/registry/handlers/layerupload.go @@ -198,7 +198,11 @@ func (luh *layerUploadHandler) PutLayerUploadComplete(w http.ResponseWriter, r * // may miss a root cause. // Read in the final chunk, if any. - io.Copy(luh.Upload, r.Body) + if _, err := io.Copy(luh.Upload, r.Body); err != nil { + ctxu.GetLogger(luh).Errorf("unknown error copying into upload: %v", err) + w.WriteHeader(http.StatusInternalServerError) + luh.Errors.Push(v2.ErrorCodeUnknown, err) + } layer, err := luh.Upload.Finish(dgst) if err != nil { From af0c2625e0a58d4ec4fbf39e530887cd7fadca49 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 14:31:34 -0700 Subject: [PATCH 10/41] Allow configuration of chunksize parameter The code using values from the yaml package wasn't careful enought with the possible incoming types. Turns out, it is just an int but we've made this section somewhat bulletproof in case that package changes the behavior. This code likely never worked. The configuration system should be decoupled from the object instantiation. Signed-off-by: Stephen J Day --- registry/storage/driver/s3/s3.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index 402f2eaac..cf58df04c 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -20,6 +20,7 @@ import ( "io" "io/ioutil" "net/http" + "reflect" "strconv" "strings" "time" @@ -148,9 +149,23 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) { chunkSize := int64(defaultChunkSize) chunkSizeParam, ok := parameters["chunksize"] if ok { - chunkSize, ok = chunkSizeParam.(int64) - if !ok || chunkSize < minChunkSize { - return nil, fmt.Errorf("The chunksize parameter should be a number that is larger than 5*1024*1024") + switch v := chunkSizeParam.(type) { + case string: + vv, err := strconv.ParseInt(v, 0, 64) + if err != nil { + return nil, fmt.Errorf("chunksize parameter must be an integer, %v invalid", chunkSizeParam) + } + chunkSize = vv + case int64: + chunkSize = v + case int, uint, int32, uint32, uint64: + chunkSize = reflect.ValueOf(v).Convert(reflect.TypeOf(chunkSize)).Int() + default: + return nil, fmt.Errorf("invalid valud for chunksize: %#v", chunkSizeParam) + } + + if chunkSize <= minChunkSize { + return nil, fmt.Errorf("The chunksize %#v parameter should be a number that is larger than or equal to %d", chunkSize, minChunkSize) } } From f3443f8f649f5dc3852114b5fa422df8cc32922f Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 15:07:18 -0700 Subject: [PATCH 11/41] Pool buffers used in S3.WriteStream Signed-off-by: Stephen J Day --- registry/storage/driver/s3/s3.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index cf58df04c..92267fc46 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -23,6 +23,7 @@ import ( "reflect" "strconv" "strings" + "sync" "time" "github.com/AdRoll/goamz/aws" @@ -73,6 +74,9 @@ type driver struct { ChunkSize int64 Encrypt bool RootDirectory string + + pool sync.Pool // pool []byte buffers used for WriteStream + zeros []byte // shared, zero-valued buffer used for WriteStream } type baseEmbed struct { @@ -239,6 +243,11 @@ func New(params DriverParameters) (*Driver, error) { ChunkSize: params.ChunkSize, Encrypt: params.Encrypt, RootDirectory: params.RootDirectory, + zeros: make([]byte, params.ChunkSize), + } + + d.pool.New = func() interface{} { + return make([]byte, d.ChunkSize) } return &Driver{ @@ -302,8 +311,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total return 0, err } - buf := make([]byte, d.ChunkSize) - zeroBuf := make([]byte, d.ChunkSize) + buf := d.getbuf() // We never want to leave a dangling multipart upload, our only consistent state is // when there is a whole object at path. This is in order to remain consistent with @@ -329,6 +337,8 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total } } } + + d.putbuf(buf) // needs to be here to pick up new buf value }() // Fills from 0 to total from current @@ -382,6 +392,8 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total } go func(bytesRead int, from int64, buf []byte) { + defer d.putbuf(buf) // this buffer gets dropped after this call + // parts and partNumber are safe, because this function is the only one modifying them and we // force it to be executed serially. if bytesRead > 0 { @@ -396,7 +408,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total putErrChan <- nil }(bytesRead, from, buf) - buf = make([]byte, d.ChunkSize) + buf = d.getbuf() // use a new buffer for the next call return nil } @@ -444,7 +456,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total fromZeroFillSmall := func(from, to int64) error { bytesRead = 0 for from+int64(bytesRead) < to { - nn, err := bytes.NewReader(zeroBuf).Read(buf[from+int64(bytesRead) : to]) + nn, err := bytes.NewReader(d.zeros).Read(buf[from+int64(bytesRead) : to]) bytesRead += nn if err != nil { return err @@ -458,7 +470,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total fromZeroFillLarge := func(from, to int64) error { bytesRead64 := int64(0) for to-(from+bytesRead64) >= d.ChunkSize { - part, err := multi.PutPart(int(partNumber), bytes.NewReader(zeroBuf)) + part, err := multi.PutPart(int(partNumber), bytes.NewReader(d.zeros)) if err != nil { return err } @@ -739,3 +751,13 @@ func getPermissions() s3.ACL { func (d *driver) getContentType() string { return "application/octet-stream" } + +// getbuf returns a buffer from the driver's pool with length d.ChunkSize. +func (d *driver) getbuf() []byte { + return d.pool.Get().([]byte) +} + +func (d *driver) putbuf(p []byte) { + copy(p, d.zeros) + d.pool.Put(p) +} From b645555422fea2ac5255048d32a3653aa764576f Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 17:30:01 -0700 Subject: [PATCH 12/41] Require storage drivers to report their name Signed-off-by: Stephen J Day --- registry/storage/driver/azure/azure.go | 3 +++ registry/storage/driver/filesystem/driver.go | 4 ++++ registry/storage/driver/inmemory/driver.go | 4 ++++ registry/storage/driver/s3/s3.go | 4 ++++ registry/storage/driver/storagedriver.go | 5 +++++ 5 files changed, 20 insertions(+) diff --git a/registry/storage/driver/azure/azure.go b/registry/storage/driver/azure/azure.go index 1473f5230..b985b7a95 100644 --- a/registry/storage/driver/azure/azure.go +++ b/registry/storage/driver/azure/azure.go @@ -94,6 +94,9 @@ func New(accountName, accountKey, container, realm string) (*Driver, error) { } // Implement the storagedriver.StorageDriver interface. +func (d *driver) Name() string { + return driverName +} // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(path string) ([]byte, error) { diff --git a/registry/storage/driver/filesystem/driver.go b/registry/storage/driver/filesystem/driver.go index 0e5aea755..9ffe08887 100644 --- a/registry/storage/driver/filesystem/driver.go +++ b/registry/storage/driver/filesystem/driver.go @@ -71,6 +71,10 @@ func New(rootDirectory string) *Driver { // Implement the storagedriver.StorageDriver interface +func (d *driver) Name() string { + return driverName +} + // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(path string) ([]byte, error) { rc, err := d.ReadStream(path, 0) diff --git a/registry/storage/driver/inmemory/driver.go b/registry/storage/driver/inmemory/driver.go index f2c9c3ffb..e0694de2e 100644 --- a/registry/storage/driver/inmemory/driver.go +++ b/registry/storage/driver/inmemory/driver.go @@ -64,6 +64,10 @@ func New() *Driver { // Implement the storagedriver.StorageDriver interface. +func (d *driver) Name() string { + return driverName +} + // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(path string) ([]byte, error) { d.mutex.RLock() diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index 92267fc46..4fd14b44b 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -261,6 +261,10 @@ func New(params DriverParameters) (*Driver, error) { // Implement the storagedriver.StorageDriver interface +func (d *driver) Name() string { + return driverName +} + // GetContent retrieves the content stored at "path" as a []byte. func (d *driver) GetContent(path string) ([]byte, error) { content, err := d.Bucket.Get(d.s3Path(path)) diff --git a/registry/storage/driver/storagedriver.go b/registry/storage/driver/storagedriver.go index 442dc2575..cda1c37d8 100644 --- a/registry/storage/driver/storagedriver.go +++ b/registry/storage/driver/storagedriver.go @@ -35,6 +35,11 @@ const CurrentVersion Version = "0.1" // StorageDriver defines methods that a Storage Driver must implement for a // filesystem-like key/value object storage. type StorageDriver interface { + // Name returns the human-readable "name" of the driver, useful in error + // messages and logging. By convention, this will just be the registration + // name, but drivers may provide other information here. + Name() string + // GetContent retrieves the content stored at "path" as a []byte. // This should primarily be used for small objects. GetContent(path string) ([]byte, error) From 81c465cef0b56958a4c90ce8bb86ef03f93c5bb3 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 17:30:31 -0700 Subject: [PATCH 13/41] Include driver name in trace messsages Signed-off-by: Stephen J Day --- registry/storage/driver/base/base.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/registry/storage/driver/base/base.go b/registry/storage/driver/base/base.go index 6b7bcf0f4..8fa747dd6 100644 --- a/registry/storage/driver/base/base.go +++ b/registry/storage/driver/base/base.go @@ -53,7 +53,7 @@ type Base struct { // GetContent wraps GetContent of underlying storage driver. func (base *Base) GetContent(path string) ([]byte, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.GetContent(\"%s\")", path) + defer done("%s.GetContent(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) { return nil, storagedriver.InvalidPathError{Path: path} @@ -65,7 +65,7 @@ func (base *Base) GetContent(path string) ([]byte, error) { // PutContent wraps PutContent of underlying storage driver. func (base *Base) PutContent(path string, content []byte) error { _, done := context.WithTrace(context.Background()) - defer done("Base.PutContent(\"%s\")", path) + defer done("%s.PutContent(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) { return storagedriver.InvalidPathError{Path: path} @@ -77,7 +77,7 @@ func (base *Base) PutContent(path string, content []byte) error { // ReadStream wraps ReadStream of underlying storage driver. func (base *Base) ReadStream(path string, offset int64) (io.ReadCloser, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.ReadStream(\"%s\", %d)", path, offset) + defer done("%s.ReadStream(%q, %d)", base.Name(), path, offset) if offset < 0 { return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset} @@ -93,7 +93,7 @@ func (base *Base) ReadStream(path string, offset int64) (io.ReadCloser, error) { // WriteStream wraps WriteStream of underlying storage driver. func (base *Base) WriteStream(path string, offset int64, reader io.Reader) (nn int64, err error) { _, done := context.WithTrace(context.Background()) - defer done("Base.WriteStream(\"%s\", %d)", path, offset) + defer done("%s.WriteStream(%q, %d)", base.Name(), path, offset) if offset < 0 { return 0, storagedriver.InvalidOffsetError{Path: path, Offset: offset} @@ -109,7 +109,7 @@ func (base *Base) WriteStream(path string, offset int64, reader io.Reader) (nn i // Stat wraps Stat of underlying storage driver. func (base *Base) Stat(path string) (storagedriver.FileInfo, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.Stat(\"%s\")", path) + defer done("%s.Stat(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) { return nil, storagedriver.InvalidPathError{Path: path} @@ -121,7 +121,7 @@ func (base *Base) Stat(path string) (storagedriver.FileInfo, error) { // List wraps List of underlying storage driver. func (base *Base) List(path string) ([]string, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.List(\"%s\")", path) + defer done("%s.List(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) && path != "/" { return nil, storagedriver.InvalidPathError{Path: path} @@ -133,7 +133,7 @@ func (base *Base) List(path string) ([]string, error) { // Move wraps Move of underlying storage driver. func (base *Base) Move(sourcePath string, destPath string) error { _, done := context.WithTrace(context.Background()) - defer done("Base.Move(\"%s\", \"%s\"", sourcePath, destPath) + defer done("%s.Move(%q, %q", base.Name(), sourcePath, destPath) if !storagedriver.PathRegexp.MatchString(sourcePath) { return storagedriver.InvalidPathError{Path: sourcePath} @@ -147,7 +147,7 @@ func (base *Base) Move(sourcePath string, destPath string) error { // Delete wraps Delete of underlying storage driver. func (base *Base) Delete(path string) error { _, done := context.WithTrace(context.Background()) - defer done("Base.Delete(\"%s\")", path) + defer done("%s.Delete(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) { return storagedriver.InvalidPathError{Path: path} @@ -159,7 +159,7 @@ func (base *Base) Delete(path string) error { // URLFor wraps URLFor of underlying storage driver. func (base *Base) URLFor(path string, options map[string]interface{}) (string, error) { _, done := context.WithTrace(context.Background()) - defer done("Base.URLFor(\"%s\")", path) + defer done("%s.URLFor(%q)", base.Name(), path) if !storagedriver.PathRegexp.MatchString(path) { return "", storagedriver.InvalidPathError{Path: path} From b6def3be1a4c25428fe9e74f8de9a6029d425c19 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 23 Apr 2015 13:13:13 -0700 Subject: [PATCH 14/41] Return after error in handler This adds a missing return statement. It is not strictly needed since if the io.Copy fails, the Finish operation will fail. Currently, the client reports both errors where this new code will correctly only report the io.Copy error. Signed-off-by: Stephen J Day --- registry/handlers/layerupload.go | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/handlers/layerupload.go b/registry/handlers/layerupload.go index 8c96b7a6e..5cfa4554c 100644 --- a/registry/handlers/layerupload.go +++ b/registry/handlers/layerupload.go @@ -202,6 +202,7 @@ func (luh *layerUploadHandler) PutLayerUploadComplete(w http.ResponseWriter, r * ctxu.GetLogger(luh).Errorf("unknown error copying into upload: %v", err) w.WriteHeader(http.StatusInternalServerError) luh.Errors.Push(v2.ErrorCodeUnknown, err) + return } layer, err := luh.Upload.Finish(dgst) From 92ee0fa837b4e7e092f467d3db87268e82a41e27 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 23 Apr 2015 16:31:41 -0700 Subject: [PATCH 15/41] Correctly check s3 chunksize parameter Signed-off-by: Stephen J Day --- registry/storage/driver/s3/s3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index 4fd14b44b..e8566fa45 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -168,7 +168,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) { return nil, fmt.Errorf("invalid valud for chunksize: %#v", chunkSizeParam) } - if chunkSize <= minChunkSize { + if chunkSize < minChunkSize { return nil, fmt.Errorf("The chunksize %#v parameter should be a number that is larger than or equal to %d", chunkSize, minChunkSize) } } From 1c51db293dff9307ddd5f9e048d3c7af336e6503 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 23 Apr 2015 20:07:32 -0700 Subject: [PATCH 16/41] Attempt to address intermittent s3 RequestTimeout error Signed-off-by: Stephen J Day --- registry/storage/driver/s3/s3.go | 65 +++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index e8566fa45..fe23262ec 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -28,6 +28,7 @@ import ( "github.com/AdRoll/goamz/aws" "github.com/AdRoll/goamz/s3" + "github.com/Sirupsen/logrus" storagedriver "github.com/docker/distribution/registry/storage/driver" "github.com/docker/distribution/registry/storage/driver/base" "github.com/docker/distribution/registry/storage/driver/factory" @@ -398,18 +399,64 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total go func(bytesRead int, from int64, buf []byte) { defer d.putbuf(buf) // this buffer gets dropped after this call - // parts and partNumber are safe, because this function is the only one modifying them and we - // force it to be executed serially. - if bytesRead > 0 { - part, putErr := multi.PutPart(int(partNumber), bytes.NewReader(buf[0:int64(bytesRead)+from])) - if putErr != nil { - putErrChan <- putErr + // DRAGONS(stevvooe): There are few things one might want to know + // about this section. First, the putErrChan is expecting an error + // and a nil or just a nil to come through the channel. This is + // covered by the silly defer below. The other aspect is the s3 + // retry backoff to deal with RequestTimeout errors. Even though + // the underlying s3 library should handle it, it doesn't seem to + // be part of the shouldRetry function (see AdRoll/goamz/s3). + defer func() { + putErrChan <- nil // for some reason, we do this no matter what. + }() + + if bytesRead <= 0 { + return + } + + var err error + var part s3.Part + + loop: + for retries := 0; retries < 5; retries++ { + part, err = multi.PutPart(int(partNumber), bytes.NewReader(buf[0:int64(bytesRead)+from])) + if err == nil { + break // success! } - parts = append(parts, part) - partNumber++ + // NOTE(stevvooe): This retry code tries to only retry under + // conditions where the s3 package does not. We may add s3 + // error codes to the below if we see others bubble up in the + // application. Right now, the most troubling is + // RequestTimeout, which seems to only triggered when a tcp + // connection to s3 slows to a crawl. If the RequestTimeout + // ends up getting added to the s3 library and we don't see + // other errors, this retry loop can be removed. + switch err := err.(type) { + case *s3.Error: + switch err.Code { + case "RequestTimeout": + // allow retries on only this error. + default: + break loop + } + } + + backoff := 100 * time.Millisecond * time.Duration(retries+1) + logrus.Errorf("error putting part, retrying after %v: %v", err, backoff.String()) + time.Sleep(backoff) } - putErrChan <- nil + + if err != nil { + logrus.Errorf("error putting part, aborting: %v", err) + putErrChan <- err + } + + // parts and partNumber are safe, because this function is the + // only one modifying them and we force it to be executed + // serially. + parts = append(parts, part) + partNumber++ }(bytesRead, from, buf) buf = d.getbuf() // use a new buffer for the next call From 46e1d280707788a4043e935c48c5b3d0ea2a5f6f Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Fri, 24 Apr 2015 14:04:48 -0700 Subject: [PATCH 17/41] Updated urlbuilder X-Forwarded-Host logic According to the Apache mod_proxy docs, X-Forwarded-Host can be a comma-separated list of hosts, to which each proxy appends the requested host. We want to grab only the first from this comma-separated list to get the original requested Host when building URLs. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- registry/api/v2/urls.go | 7 ++++++- registry/api/v2/urls_test.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/registry/api/v2/urls.go b/registry/api/v2/urls.go index 4b42dd162..60aad5659 100644 --- a/registry/api/v2/urls.go +++ b/registry/api/v2/urls.go @@ -62,7 +62,12 @@ func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { host := r.Host forwardedHost := r.Header.Get("X-Forwarded-Host") if len(forwardedHost) > 0 { - host = forwardedHost + // According to the Apache mod_proxy docs, X-Forwarded-Host can be a + // comma-separated list of hosts, to which each proxy appends the + // requested host. We want to grab the first from this comma-separated + // list. + hosts := strings.SplitN(forwardedHost, ",", 2) + host = strings.TrimSpace(hosts[0]) } basePath := routeDescriptorsMap[RouteNameBase].Path diff --git a/registry/api/v2/urls_test.go b/registry/api/v2/urls_test.go index 237d0f615..1113a7dde 100644 --- a/registry/api/v2/urls_test.go +++ b/registry/api/v2/urls_test.go @@ -151,6 +151,12 @@ func TestBuilderFromRequest(t *testing.T) { forwardedProtoHeader := make(http.Header, 1) forwardedProtoHeader.Set("X-Forwarded-Proto", "https") + forwardedHostHeader1 := make(http.Header, 1) + forwardedHostHeader1.Set("X-Forwarded-Host", "first.example.com") + + forwardedHostHeader2 := make(http.Header, 1) + forwardedHostHeader2.Set("X-Forwarded-Host", "first.example.com, proxy1.example.com") + testRequests := []struct { request *http.Request base string @@ -163,6 +169,14 @@ func TestBuilderFromRequest(t *testing.T) { request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader}, base: "https://example.com", }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader1}, + base: "http://first.example.com", + }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2}, + base: "http://first.example.com", + }, } for _, tr := range testRequests { From 84559affdcd32d317d995a49197d1b591ae42eae Mon Sep 17 00:00:00 2001 From: xiekeyang Date: Mon, 27 Apr 2015 15:18:55 +0800 Subject: [PATCH 18/41] simplify the embedded method expression of repository Signed-off-by: xiekeyang --- registry/storage/layerstore.go | 8 ++++---- registry/storage/layerwriter.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/registry/storage/layerstore.go b/registry/storage/layerstore.go index 1c7428a9f..a86b668f7 100644 --- a/registry/storage/layerstore.go +++ b/registry/storage/layerstore.go @@ -65,7 +65,7 @@ func (ls *layerStore) Upload() (distribution.LayerUpload, error) { uuid := uuid.New() startedAt := time.Now().UTC() - path, err := ls.repository.registry.pm.path(uploadDataPathSpec{ + path, err := ls.repository.pm.path(uploadDataPathSpec{ name: ls.repository.Name(), uuid: uuid, }) @@ -74,7 +74,7 @@ func (ls *layerStore) Upload() (distribution.LayerUpload, error) { return nil, err } - startedAtPath, err := ls.repository.registry.pm.path(uploadStartedAtPathSpec{ + startedAtPath, err := ls.repository.pm.path(uploadStartedAtPathSpec{ name: ls.repository.Name(), uuid: uuid, }) @@ -95,7 +95,7 @@ func (ls *layerStore) Upload() (distribution.LayerUpload, error) { // state of the upload. func (ls *layerStore) Resume(uuid string) (distribution.LayerUpload, error) { ctxu.GetLogger(ls.repository.ctx).Debug("(*layerStore).Resume") - startedAtPath, err := ls.repository.registry.pm.path(uploadStartedAtPathSpec{ + startedAtPath, err := ls.repository.pm.path(uploadStartedAtPathSpec{ name: ls.repository.Name(), uuid: uuid, }) @@ -152,7 +152,7 @@ func (ls *layerStore) newLayerUpload(uuid, path string, startedAt time.Time) (di func (ls *layerStore) path(dgst digest.Digest) (string, error) { // We must traverse this path through the link to enforce ownership. - layerLinkPath, err := ls.repository.registry.pm.path(layerLinkPathSpec{name: ls.repository.Name(), digest: dgst}) + layerLinkPath, err := ls.repository.pm.path(layerLinkPathSpec{name: ls.repository.Name(), digest: dgst}) if err != nil { return "", err } diff --git a/registry/storage/layerwriter.go b/registry/storage/layerwriter.go index 3efd60a45..adf68ca93 100644 --- a/registry/storage/layerwriter.go +++ b/registry/storage/layerwriter.go @@ -158,7 +158,7 @@ type hashStateEntry struct { // getStoredHashStates returns a slice of hashStateEntries for this upload. func (lw *layerWriter) getStoredHashStates() ([]hashStateEntry, error) { - uploadHashStatePathPrefix, err := lw.layerStore.repository.registry.pm.path(uploadHashStatePathSpec{ + uploadHashStatePathPrefix, err := lw.layerStore.repository.pm.path(uploadHashStatePathSpec{ name: lw.layerStore.repository.Name(), uuid: lw.uuid, alg: lw.resumableDigester.Digest().Algorithm(), @@ -271,7 +271,7 @@ func (lw *layerWriter) resumeHashAt(offset int64) error { } func (lw *layerWriter) storeHashState() error { - uploadHashStatePath, err := lw.layerStore.repository.registry.pm.path(uploadHashStatePathSpec{ + uploadHashStatePath, err := lw.layerStore.repository.pm.path(uploadHashStatePathSpec{ name: lw.layerStore.repository.Name(), uuid: lw.uuid, alg: lw.resumableDigester.Digest().Algorithm(), @@ -360,7 +360,7 @@ func (lw *layerWriter) validateLayer(dgst digest.Digest) (digest.Digest, error) // identified by dgst. The layer should be validated before commencing the // move. func (lw *layerWriter) moveLayer(dgst digest.Digest) error { - blobPath, err := lw.layerStore.repository.registry.pm.path(blobDataPathSpec{ + blobPath, err := lw.layerStore.repository.pm.path(blobDataPathSpec{ digest: dgst, }) @@ -426,7 +426,7 @@ func (lw *layerWriter) linkLayer(canonical digest.Digest, aliases ...digest.Dige } seenDigests[dgst] = struct{}{} - layerLinkPath, err := lw.layerStore.repository.registry.pm.path(layerLinkPathSpec{ + layerLinkPath, err := lw.layerStore.repository.pm.path(layerLinkPathSpec{ name: lw.layerStore.repository.Name(), digest: dgst, }) @@ -435,7 +435,7 @@ func (lw *layerWriter) linkLayer(canonical digest.Digest, aliases ...digest.Dige return err } - if err := lw.layerStore.repository.registry.driver.PutContent(layerLinkPath, []byte(canonical)); err != nil { + if err := lw.layerStore.repository.driver.PutContent(layerLinkPath, []byte(canonical)); err != nil { return err } } @@ -447,7 +447,7 @@ func (lw *layerWriter) linkLayer(canonical digest.Digest, aliases ...digest.Dige // instance. An error will be returned if the clean up cannot proceed. If the // resources are already not present, no error will be returned. func (lw *layerWriter) removeResources() error { - dataPath, err := lw.layerStore.repository.registry.pm.path(uploadDataPathSpec{ + dataPath, err := lw.layerStore.repository.pm.path(uploadDataPathSpec{ name: lw.layerStore.repository.Name(), uuid: lw.uuid, }) From a72fb20b85fa2807605deb9c6de2c3d6d0575935 Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 16 Apr 2015 18:34:29 -0700 Subject: [PATCH 19/41] Add configuration for upload purging Signed-off-by: Richard Scothern --- cmd/registry/config.yml | 4 ++ configuration/configuration.go | 4 ++ docs/configuration.md | 36 +++++++++++++- docs/deploying.md | 3 ++ registry/handlers/app.go | 91 +++++++++++++++++++++++++++++----- 5 files changed, 125 insertions(+), 13 deletions(-) diff --git a/cmd/registry/config.yml b/cmd/registry/config.yml index 5dd39cb3b..b1a8f48dc 100644 --- a/cmd/registry/config.yml +++ b/cmd/registry/config.yml @@ -9,6 +9,9 @@ storage: layerinfo: inmemory filesystem: rootdirectory: /tmp/registry-dev + maintenance: + uploadpurging: + enabled: false http: addr: :5000 secret: asecretforlocaldevelopment @@ -39,3 +42,4 @@ notifications: threshold: 10 backoff: 1s disabled: true + \ No newline at end of file diff --git a/configuration/configuration.go b/configuration/configuration.go index 3d302e1cc..074471b4f 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -188,6 +188,8 @@ func (storage Storage) Type() string { // Return only key in this map for k := range storage { switch k { + case "maintenance": + // allow configuration of maintenance case "cache": // allow configuration of caching default: @@ -217,6 +219,8 @@ func (storage *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error { types := make([]string, 0, len(storageMap)) for k := range storageMap { switch k { + case "maintenance": + // allow for configuration of maintenance case "cache": // allow configuration of caching default: diff --git a/docs/configuration.md b/docs/configuration.md index bf40238c1..771d4e7df 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -43,6 +43,12 @@ storage: rootdirectory: /s3/object/name/prefix cache: layerinfo: inmemory + maintenance: + uploadpurging: + enabled: true + age: 168h + interval: 24h + dryrun: false auth: silly: realm: silly-realm @@ -221,6 +227,12 @@ storage: rootdirectory: /s3/object/name/prefix cache: layerinfo: inmemory + maintenance: + uploadpurging: + enabled: true + age: 168h + interval: 24h + dryrun: false ``` The storage option is **required** and defines which storage backend is in use. @@ -410,6 +422,27 @@ 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. + +### 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 +must be set. + + +| Parameter | Required | Description + --------- | -------- | ----------- +`enabled` | yes | Set to true to enable upload purging. Default=true. | +`age` | yes | Upload directories which are older than this age will be deleted. Default=168h (1 week) +`interval` | yes | The interval between upload directory purging. Default=24h. +`dryrun` | yes | dryrun can be set to true to obtain a summary of what directories will be deleted. Default=false. + +Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week). ## auth @@ -1139,7 +1172,8 @@ Configure the behavior of the Redis connection pool. - + + ## Example: Development configuration The following is a simple example you can use for local development: diff --git a/docs/deploying.md b/docs/deploying.md index c36660127..c7e440205 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -205,6 +205,9 @@ storage: layerinfo: inmemory filesystem: rootdirectory: /tmp/registry-dev + maintenance: + uploadpurging: + enabled: false http: addr: :5000 secret: asecretforlocaldevelopment diff --git a/registry/handlers/app.go b/registry/handlers/app.go index e35d86337..3cc360c66 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -81,7 +81,18 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App panic(err) } - startUploadPurger(app.driver, ctxu.GetLogger(app)) + purgeConfig := uploadPurgeDefaultConfig() + if mc, ok := configuration.Storage["maintenance"]; ok { + for k, v := range mc { + switch k { + case "uploadpurging": + purgeConfig = v.(map[interface{}]interface{}) + } + } + + } + + startUploadPurger(app.driver, ctxu.GetLogger(app), purgeConfig) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { @@ -568,26 +579,82 @@ func applyStorageMiddleware(driver storagedriver.StorageDriver, middlewares []co return driver, nil } +// uploadPurgeDefaultConfig provides a default configuration for upload +// purging to be used in the absence of configuration in the +// confifuration file +func uploadPurgeDefaultConfig() map[interface{}]interface{} { + config := map[interface{}]interface{}{} + config["enabled"] = true + config["age"] = "168h" + config["interval"] = "24h" + config["dryrun"] = false + return config +} + +func badPurgeUploadConfig(reason string) { + panic(fmt.Sprintf("Unable to parse upload purge configuration: %s", reason)) +} + // startUploadPurger schedules a goroutine which will periodically // check upload directories for old files and delete them -func startUploadPurger(storageDriver storagedriver.StorageDriver, log ctxu.Logger) { - rand.Seed(time.Now().Unix()) - jitter := time.Duration(rand.Int()%60) * time.Minute +func startUploadPurger(storageDriver storagedriver.StorageDriver, log ctxu.Logger, config map[interface{}]interface{}) { + if config["enabled"] == false { + return + } - // Start with reasonable defaults - // TODO:(richardscothern) make configurable - purgeAge := time.Duration(7 * 24 * time.Hour) - timeBetweenPurges := time.Duration(1 * 24 * time.Hour) + var purgeAgeDuration time.Duration + var err error + purgeAge, ok := config["age"] + if ok { + ageStr, ok := purgeAge.(string) + if !ok { + badPurgeUploadConfig("age is not a string") + } + purgeAgeDuration, err = time.ParseDuration(ageStr) + if err != nil { + badPurgeUploadConfig(fmt.Sprintf("Cannot parse duration: %s", err.Error())) + } + } else { + badPurgeUploadConfig("age missing") + } + + var intervalDuration time.Duration + interval, ok := config["interval"] + if ok { + intervalStr, ok := interval.(string) + if !ok { + badPurgeUploadConfig("interval is not a string") + } + + intervalDuration, err = time.ParseDuration(intervalStr) + if err != nil { + badPurgeUploadConfig(fmt.Sprintf("Cannot parse interval: %s", err.Error())) + } + } else { + badPurgeUploadConfig("interval missing") + } + + var dryRunBool bool + dryRun, ok := config["dryrun"] + if ok { + dryRunBool, ok = dryRun.(bool) + if !ok { + badPurgeUploadConfig("cannot parse dryrun") + } + } else { + badPurgeUploadConfig("dryrun missing") + } go func() { + rand.Seed(time.Now().Unix()) + jitter := time.Duration(rand.Int()%60) * time.Minute log.Infof("Starting upload purge in %s", jitter) time.Sleep(jitter) for { - storage.PurgeUploads(storageDriver, time.Now().Add(-purgeAge), true) - log.Infof("Starting upload purge in %s", timeBetweenPurges) - time.Sleep(timeBetweenPurges) + storage.PurgeUploads(storageDriver, time.Now().Add(-purgeAgeDuration), !dryRunBool) + log.Infof("Starting upload purge in %s", intervalDuration) + time.Sleep(intervalDuration) } }() - } From f132ff7702a6e4a02f78f86540856d96c7a8ffb3 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Tue, 28 Apr 2015 14:06:24 -0700 Subject: [PATCH 20/41] Use done channel to avoid goroutine leak This deals with a memory leak, caused by goroutines, experienced when using the s3 driver. Unfortunately, this section of the code leaks goroutines like a sieve. There is probably some refactoring that could be done to avoid this but instead, we have a done channel that will cause waiting goroutines to exit. Signed-off-by: Stephen J Day --- registry/storage/driver/s3/s3.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/registry/storage/driver/s3/s3.go b/registry/storage/driver/s3/s3.go index fe23262ec..57871b5d6 100644 --- a/registry/storage/driver/s3/s3.go +++ b/registry/storage/driver/s3/s3.go @@ -310,6 +310,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total var putErrChan chan error parts := []s3.Part{} var part s3.Part + done := make(chan struct{}) // stopgap to free up waiting goroutines multi, err := d.Bucket.InitMulti(d.s3Path(path), d.getContentType(), getPermissions(), d.getOptions()) if err != nil { @@ -344,6 +345,7 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total } d.putbuf(buf) // needs to be here to pick up new buf value + close(done) // free up any waiting goroutines }() // Fills from 0 to total from current @@ -407,7 +409,11 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total // the underlying s3 library should handle it, it doesn't seem to // be part of the shouldRetry function (see AdRoll/goamz/s3). defer func() { - putErrChan <- nil // for some reason, we do this no matter what. + select { + case putErrChan <- nil: // for some reason, we do this no matter what. + case <-done: + return // ensure we don't leak the goroutine + } }() if bytesRead <= 0 { @@ -449,7 +455,11 @@ func (d *driver) WriteStream(path string, offset int64, reader io.Reader) (total if err != nil { logrus.Errorf("error putting part, aborting: %v", err) - putErrChan <- err + select { + case putErrChan <- err: + case <-done: + return // don't leak the goroutine + } } // parts and partNumber are safe, because this function is the From 269286192d4aa947f2a6c779c2f59f4873ec5616 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 29 Apr 2015 18:31:01 -0700 Subject: [PATCH 21/41] Address possible goroutine leak in notification library Signed-off-by: Stephen J Day --- notifications/http.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/notifications/http.go b/notifications/http.go index 15b3574cf..465434f1c 100644 --- a/notifications/http.go +++ b/notifications/http.go @@ -53,6 +53,7 @@ type httpStatusListener interface { func (hs *httpSink) Write(events ...Event) error { hs.mu.Lock() defer hs.mu.Unlock() + defer hs.client.Transport.(*headerRoundTripper).CloseIdleConnections() if hs.closed { return ErrSinkClosed @@ -83,6 +84,7 @@ func (hs *httpSink) Write(events ...Event) error { return fmt.Errorf("%v: error posting: %v", hs, err) } + defer resp.Body.Close() // The notifier will treat any 2xx or 3xx response as accepted by the // endpoint. From e04c70235aaa1c5492675f737260095445e5bb73 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Fri, 1 May 2015 17:13:11 -0700 Subject: [PATCH 22/41] Update API spec to reference digest instead of tarsum Signed-off-by: Derek McGowan (github: dmcgowan) --- docs/spec/api.md | 12 ++++++------ registry/api/v2/descriptors.go | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/spec/api.md b/docs/spec/api.md index 032ddcf3c..c5768305b 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -989,7 +989,7 @@ Content-Type: application/json; charset=utf-8 "tag": , "fsLayers": [ { - "blobSum": + "blobSum": "" }, ... ] @@ -1120,7 +1120,7 @@ Content-Type: application/json; charset=utf-8 "tag": , "fsLayers": [ { - "blobSum": + "blobSum": "" }, ... ] @@ -1242,7 +1242,7 @@ Content-Type: application/json; charset=utf-8 "code": "BLOB_UNKNOWN", "message": "blob unknown to registry", "detail": { - "digest": + "digest": "" } }, ... @@ -1446,7 +1446,7 @@ The error codes that may be included in the response body are enumerated below: ### Blob -Fetch the blob identified by `name` and `digest`. Used to fetch layers by tarsum digest. +Fetch the blob identified by `name` and `digest`. Used to fetch layers by digest. @@ -1794,7 +1794,7 @@ Initiate a resumable blob upload. If successful, an upload location will be prov ##### Initiate Monolithic Blob Upload ``` -POST /v2//blobs/uploads/?digest= +POST /v2//blobs/uploads/?digest= Host: Authorization: Content-Length: @@ -2341,7 +2341,7 @@ Complete the upload specified by `uuid`, optionally appending the body as the fi ``` -PUT /v2//blobs/uploads/?digest= +PUT /v2//blobs/uploads/?digest= Host: Authorization: Content-Range: - diff --git a/registry/api/v2/descriptors.go b/registry/api/v2/descriptors.go index 833bff8b2..0baa5ee7f 100644 --- a/registry/api/v2/descriptors.go +++ b/registry/api/v2/descriptors.go @@ -135,7 +135,7 @@ const ( "tag": , "fsLayers": [ { - "blobSum": + "blobSum": "" }, ... ] @@ -606,7 +606,7 @@ var routeDescriptors = []RouteDescriptor{ "code": "BLOB_UNKNOWN", "message": "blob unknown to registry", "detail": { - "digest": + "digest": "" } }, ... @@ -712,7 +712,7 @@ var routeDescriptors = []RouteDescriptor{ Name: RouteNameBlob, Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}", Entity: "Blob", - Description: "Fetch the blob identified by `name` and `digest`. Used to fetch layers by tarsum digest.", + Description: "Fetch the blob identified by `name` and `digest`. Used to fetch layers by digest.", Methods: []MethodDescriptor{ { @@ -898,7 +898,7 @@ var routeDescriptors = []RouteDescriptor{ { Name: "digest", Type: "query", - Format: "", + Format: "", Regexp: digest.DigestRegexp, Description: `Digest of uploaded blob. If present, the upload will be completed, in a single request, with contents of the request body as the resulting blob.`, }, @@ -1173,7 +1173,7 @@ var routeDescriptors = []RouteDescriptor{ { Name: "digest", Type: "string", - Format: "", + Format: "", Regexp: digest.DigestRegexp, Required: true, Description: `Digest of uploaded blob.`, From e0b3f4099454a4776dd2cb8f9ecf8dce3a2d6a06 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 5 May 2015 14:21:33 -0700 Subject: [PATCH 23/41] Ensure the instrumentedResponseWriter correctly sets the http status in the context. Signed-off-by: Richard Scothern --- context/http.go | 4 +--- context/http_test.go | 4 ++++ registry/handlers/api_test.go | 12 +++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/context/http.go b/context/http.go index 2b17074b0..91bcda95a 100644 --- a/context/http.go +++ b/context/http.go @@ -322,9 +322,7 @@ func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} { case "written": return irw.written case "status": - if irw.status != 0 { - return irw.status - } + return irw.status case "contenttype": contentType := irw.Header().Get("Content-Type") if contentType != "" { diff --git a/context/http_test.go b/context/http_test.go index f133574fc..3d4b3c8eb 100644 --- a/context/http_test.go +++ b/context/http_test.go @@ -145,6 +145,10 @@ func TestWithResponseWriter(t *testing.T) { t.Fatalf("unexpected response writer returned: %#v != %#v", grw, rw) } + if ctx.Value("http.response.status") != 0 { + t.Fatalf("response status should always be a number and should be zero here: %v != 0", ctx.Value("http.response.status")) + } + if n, err := rw.Write(make([]byte, 1024)); err != nil { t.Fatalf("unexpected error writing: %v", err) } else if n != 1024 { diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go index ab8187c16..3dd7e6ec0 100644 --- a/registry/handlers/api_test.go +++ b/registry/handlers/api_test.go @@ -93,7 +93,7 @@ func TestURLPrefix(t *testing.T) { } -// TestLayerAPI conducts a full of the of the layer api. +// TestLayerAPI conducts a full test of the of the layer api. func TestLayerAPI(t *testing.T) { // TODO(stevvooe): This test code is complete junk but it should cover the // complete flow. This must be broken down and checked against the @@ -246,6 +246,16 @@ func TestLayerAPI(t *testing.T) { t.Fatalf("response body did not pass verification") } + // ---------------- + // Fetch the layer with an invalid digest + badURL := strings.Replace(layerURL, "tarsum", "trsum", 1) + resp, err = http.Get(badURL) + if err != nil { + t.Fatalf("unexpected error fetching layer: %v", err) + } + + checkResponse(t, "fetching layer bad digest", resp, http.StatusBadRequest) + // Missing tests: // - Upload the same tarsum file under and different repository and // ensure the content remains uncorrupted. From c0db47e76e5e188f82f7f4dce3f5d8086ccdd149 Mon Sep 17 00:00:00 2001 From: Spencer Rinehart Date: Thu, 16 Apr 2015 16:10:20 -0500 Subject: [PATCH 24/41] Fix registry link to point to localhost in deploy doc. Signed-off-by: Spencer Rinehart --- docs/deploying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying.md b/docs/deploying.md index c7e440205..6cef48187 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -104,7 +104,7 @@ a local registry. * Connection #0 to host localhost left intact You can also get this information by entering the - `http://52.10.125.146:5000/v2/hello-mine/tags/list` address in your browser. + `http://localhost:5000/v2/hello-mine/tags/list` address in your browser. 8. Remove all the unused images from your local environment: From 61ce5f91ba771a15d4b2c2df0541fea20c3fb4f2 Mon Sep 17 00:00:00 2001 From: Michael Prokop Date: Fri, 17 Apr 2015 23:05:07 +0200 Subject: [PATCH 25/41] docs: drop newlines in URLs to avoid 404 The docs as available at https://github.com/docker/distribution/blob/master/docs/configuration.md result in 404 errors: https://github.com/docker/distribution/blob/master/cmd/registry/%0Aconfig.yml http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/%0AAboutAWSCredentials.html#KeyPairs instead of pointing to the correct ones, being: https://github.com/docker/distribution/blob/master/cmd/registry/config.yml http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs So avoid the newlines in the corresponding source files. Signed-off-by: Michael Prokop --- docs/configuration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 771d4e7df..6d3e0adf0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1198,8 +1198,8 @@ The above configures the registry instance to run on port `5000`, binding to verbose. A similar simple configuration is available at -[config.yml](https://github.com/docker/distribution/blob/master/cmd/registry/ -config.yml). Both are generally useful for local development. +[config.yml](https://github.com/docker/distribution/blob/master/cmd/registry/config.yml). +Both are generally useful for local development. ## Example: Middleware configuration @@ -1255,6 +1255,6 @@ middleware: >**Note**: Cloudfront keys exist separately to other AWS keys. See ->[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/ ->AboutAWSCredentials.html#KeyPairs) for more information. +>[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs) +>for more information. From e38fa8bff8d8c1186c7495ccbc103068f2975bd0 Mon Sep 17 00:00:00 2001 From: Shawn Falkner-Horine Date: Fri, 17 Apr 2015 21:13:24 +0000 Subject: [PATCH 26/41] Fix deployment guide typos re: image name consistency. Signed-off-by: Shawn Falkner-Horine --- docs/deploying.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/deploying.md b/docs/deploying.md index 6cef48187..6d511bbd1 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -331,7 +331,7 @@ support. 2. Run your new image. - $ docker run -p 5000:5000 registry_local:latest + $ docker run -p 5000:5000 secure_registry:latest time="2015-04-12T03:06:18.616502588Z" level=info msg="endpoint local-8082 disabled, skipping" environment=development instance.id=bf33c9dc-2564-406b-97c3-6ee69dc20ec6 service=registry time="2015-04-12T03:06:18.617012948Z" level=info msg="endpoint local-8083 disabled, skipping" environment=development instance.id=bf33c9dc-2564-406b-97c3-6ee69dc20ec6 service=registry time="2015-04-12T03:06:18.617190113Z" level=info msg="using inmemory layerinfo cache" environment=development instance.id=bf33c9dc-2564-406b-97c3-6ee69dc20ec6 service=registry @@ -545,11 +545,11 @@ procedure. The directory includes an example `compose` configuration. 4. Use `curl` to list the image in the registry. - $ curl -v -X GET http://localhost:32777/v2/registry1/tags/list + $ curl -v -X GET http://localhost:32777/v2/registry_one/tags/list * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 32777 (#0) - > GET /v2/registry1/tags/list HTTP/1.1 + > GET /v2/registry_one/tags/list HTTP/1.1 > User-Agent: curl/7.36.0 > Host: localhost:32777 > Accept: */* From e8675e69adec681af02700ceb5d2b18a9fc3a436 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sat, 18 Apr 2015 17:06:51 -0700 Subject: [PATCH 27/41] Fixes #391 for registry top page Fixes #390 to add building link to README Docker Registry Service to Docker Registry Signed-off-by: Mary Anthony --- README.md | 8 +++++--- docs/Dockerfile | 10 +++++----- docs/configuration.md | 2 +- docs/deploying.md | 12 ++++++------ docs/distribution.md | 4 ++-- docs/{overview.md => index.md} | 2 +- docs/migration.md | 2 +- docs/mkdocs.yml | 16 ++++++++-------- docs/notifications.md | 2 +- 9 files changed, 30 insertions(+), 28 deletions(-) rename docs/{overview.md => index.md} (99%) diff --git a/README.md b/README.md index fcb470c85..ac7672580 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The Docker toolset to pack, ship, store, and deliver content. -This repository's main product is the Docker Registry Service 2.0 implementation +This repository's main product is the Docker Registry 2.0 implementation for storing and distributing Docker images. It supersedes the [docker/docker- registry](https://github.com/docker/docker-registry) project with a new API design, focused around security and performance. @@ -15,7 +15,7 @@ This repository contains the following components: | **libraries** | A rich set of libraries for interacting with,distribution components. Please see [godoc](http://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. | | **dist** | An _experimental_ tool to provide distribution, oriented functionality without the `docker` daemon. | | **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) | -| **documentation** | Docker's full documentation set is available at [docs.docker.com](http://docs.docker.com). This repository [contains the subset](docs/overview.md) related just to the registry. | +| **documentation** | Docker's full documentation set is available at [docs.docker.com](http://docs.docker.com). This repository [contains the subset](docs/index.md) related just to the registry. | ### How does this integrate with Docker engine? @@ -69,7 +69,9 @@ may be the better choice. ## Contribute -Please see [CONTRIBUTING.md](CONTRIBUTING.md). +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute +issues, fixes, and patches to this project. If you are contributing code, see +the instructions for [building a development environment](docs/building.md). ## Support diff --git a/docs/Dockerfile b/docs/Dockerfile index 17a740258..1613e13aa 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -12,11 +12,11 @@ RUN git reset --hard # The above line causes a floating point error in our tools # RUN grep "VERSION =" /src/version/version.go | sed 's/.*"\(.*\)".*/\1/' > /docs/VERSION -COPY docs/* /docs/sources/distribution/ -COPY docs/images/* /docs/sources/distribution/images/ -COPY docs/spec/* /docs/sources/distribution/spec/ -COPY docs/spec/auth/* /docs/sources/distribution/spec/auth/ -COPY docs/storage-drivers/* /docs/sources/distribution/storage-drivers/ +COPY docs/* /docs/sources/registry/ +COPY docs/images/* /docs/sources/registry/images/ +COPY docs/spec/* /docs/sources/registry/spec/ +COPY docs/spec/auth/* /docs/sources/registry/spec/auth/ +COPY docs/storage-drivers/* /docs/sources/registry/storage-drivers/ COPY docs/mkdocs.yml /docs/mkdocs-distribution.yml diff --git a/docs/configuration.md b/docs/configuration.md index 6d3e0adf0..ab783af39 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,5 +1,5 @@ page_title: Configure a Registry -page_description: Explains how to deploy a registry service +page_description: Explains how to deploy a registry page_keywords: registry, service, images, repository diff --git a/docs/deploying.md b/docs/deploying.md index 6d511bbd1..b26499da0 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -1,10 +1,10 @@ -page_title: Deploying a registry service -page_description: Explains how to deploy a registry service +page_title: Deploying a registry server +page_description: Explains how to deploy a registry server page_keywords: registry, service, images, repository -# Deploying a registry service +# Deploying a registry server -This section explains how to deploy a Docker Registry Service either privately +This section explains how to deploy a Docker Registry either privately for your own company or publicly for other users. For example, your company may require a private registry to support your continuous integration (CI) system as it builds new releases or test servers. Alternatively, your company may have a @@ -37,7 +37,7 @@ a local registry. The `run` command automatically pulls a `hello-world` image from Docker's official images. -3. Start a registry service on your localhost. +3. Start a registry on your localhost. $ docker run -p 5000:5000 registry:2.0 @@ -82,7 +82,7 @@ a local registry. 511136ea3c5a: Image already exists Digest: sha256:a1b13bc01783882434593119198938b9b9ef2bd32a0a246f16ac99b01383ef7a -7. Use the `curl` command and the Docker Registry Service API v2 to list your +7. Use the `curl` command and the Docker Registry API v2 to list your image in the registry: $ curl -v -X GET http://localhost:5000/v2/hello-mine/tags/list diff --git a/docs/distribution.md b/docs/distribution.md index 3907b91ab..bad7362f5 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -1,11 +1,11 @@ # Project ## Contents -- [Docker Registry Service 2.0](overview.md) +- [Docker Registry 2.0](index.md) - [Architecture](architecture.md) - [Build the development environment](building.md) - [Configure a registry](configuration.md) -- [Deploying a registry service](deploying.md) +- [Deploying a registry server](deploying.md) - [Microsoft Azure storage driver](storage-drivers/azure.md) - [Filesystem storage driver](storage-drivers/filesystem.md) - [In-memory storage driver](storage-drivers/inmemory.md) diff --git a/docs/overview.md b/docs/index.md similarity index 99% rename from docs/overview.md rename to docs/index.md index 0d97549af..7c21d17ff 100644 --- a/docs/overview.md +++ b/docs/index.md @@ -22,7 +22,7 @@ is collection of images. Users interact with the registry by pushing images to or pulling images from the registry. The Docker Registry includes several optional features that you can configure according to your needs. -![](../images/registry.png) +![](images/registry.png) The architecture supports a configurable storage backend. You can store images on a file system or on a service such as Amazon S3 or Microsoft Azure. The diff --git a/docs/migration.md b/docs/migration.md index c1eae91cb..be3f02bbe 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -10,7 +10,7 @@ One can migrate images from one version to the other by pulling images from the ----- -The Docker Registry Service 2.0 is backward compatible with images created by the earlier specification. If you are migrating a private registry to version 2.0, you should use the following process: +The Docker Registry 2.0 is backward compatible with images created by the earlier specification. If you are migrating a private registry to version 2.0, you should use the following process: 1. Configure and test a 2.0 registry image in a sandbox environment. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 025896c9d..5ff559096 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,9 +1,9 @@ -- ['distribution/overview.md', 'Reference', 'Docker Registry Service 2.0'] -- ['distribution/deploying.md', 'Reference', '-- Deploy a registry' ] -- ['distribution/configuration.md', 'Reference', '-- Configure a registry' ] -- ['distribution/storagedrivers.md', 'Reference', '-- Storage driver model' ] -- ['distribution/notifications.md', 'Reference', '-- Work with notifications' ] -- ['distribution/spec/api.md', 'Reference', '-- Registry Service API v2' ] -- ['distribution/spec/json.md', 'Reference', '-- JSON format' ] -- ['distribution/spec/auth/token.md', 'Reference', '-- Authenticate via central service' ] +- ['registry/index.md', 'Reference', 'Docker Registry 2.0'] +- ['registry/deploying.md', 'Reference', '    ▪  Deploy a registry' ] +- ['registry/configuration.md', 'Reference', '    ▪  Configure a registry' ] +- ['registry/storagedrivers.md', 'Reference', '    ▪  Storage driver model' ] +- ['registry/notifications.md', 'Reference', '    ▪  Work with notifications' ] +- ['registry/spec/api.md', 'Reference', '    ▪  Registry Service API v2' ] +- ['registry/spec/json.md', 'Reference', '    ▪  JSON format' ] +- ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] diff --git a/docs/notifications.md b/docs/notifications.md index 264a7dc8d..b8fdf3716 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -1,5 +1,5 @@ page_title: Work with Notifications -page_description: Explains how to deploy a registry service +page_description: Explains how to deploy a registry server page_keywords: registry, service, images, repository # Notifications From 4d232aaa4f316388394c6d25c27f9592d1068994 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 22 Apr 2015 12:54:01 -0700 Subject: [PATCH 28/41] Disable go1.3 tests for circle and upgrade to go1.4.2 Signed-off-by: Stephen J Day --- circle.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/circle.yml b/circle.yml index 963b60275..656efa693 100644 --- a/circle.yml +++ b/circle.yml @@ -6,8 +6,8 @@ machine: post: # Install many go versions - - gvm install go1.3.3 -B --name=old - - gvm install go1.4 -B --name=stable + # - gvm install go1.3.3 -B --name=old + - gvm install go1.4.2 -B --name=stable # - gvm install tip --name=bleed environment: @@ -28,10 +28,10 @@ machine: dependencies: pre: # Copy the code to the gopath of all go versions - - > - gvm use old && - mkdir -p "$(dirname $BASE_OLD)" && - cp -R "$CHECKOUT" "$BASE_OLD" + # - > + # gvm use old && + # mkdir -p "$(dirname $BASE_OLD)" && + # cp -R "$CHECKOUT" "$BASE_OLD" - > gvm use stable && @@ -45,8 +45,8 @@ dependencies: override: # Install dependencies for every copied clone/go version - - gvm use old && go get github.com/tools/godep: - pwd: $BASE_OLD + # - gvm use old && go get github.com/tools/godep: + # pwd: $BASE_OLD - gvm use stable && go get github.com/tools/godep: pwd: $BASE_STABLE @@ -63,7 +63,7 @@ dependencies: test: pre: # Output the go versions we are going to test - - gvm use old && go version + # - gvm use old && go version - gvm use stable && go version # - gvm use bleed && go version @@ -81,9 +81,9 @@ test: override: # Test every version we have (but stable) - - gvm use old; godep go test -test.v -test.short ./...: - timeout: 600 - pwd: $BASE_OLD + # - gvm use old; godep go test -test.v -test.short ./...: + # timeout: 600 + # pwd: $BASE_OLD # - gvm use bleed; go test -test.v -test.short ./...: # timeout: 600 From 7cae65efd097e7bdc50d6ae57beb9489549c7389 Mon Sep 17 00:00:00 2001 From: Ian Babrou Date: Mon, 20 Apr 2015 10:41:32 +0300 Subject: [PATCH 29/41] docs: fixed links for storage drivers Signed-off-by: Ian Babrou --- docs/mkdocs.yml | 4 ++++ docs/storagedrivers.md | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 5ff559096..814b89097 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -7,3 +7,7 @@ - ['registry/spec/json.md', 'Reference', '    ▪  JSON format' ] - ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] +- ['registry/storage-drivers/azure.md', 'HIDDEN' ] +- ['registry/storage-drivers/filesystem.md', 'HIDDEN' ] +- ['registry/storage-drivers/inmemory.md', 'HIDDEN' ] +- ['registry/storage-drivers/s3.md', 'Reference', 'HIDDEN' ] diff --git a/docs/storagedrivers.md b/docs/storagedrivers.md index 788bdd0d7..2db67e282 100644 --- a/docs/storagedrivers.md +++ b/docs/storagedrivers.md @@ -8,10 +8,10 @@ Provided Drivers This storage driver package comes bundled with several drivers: -- [inmemory](storage-drivers/inmemory): A temporary storage driver using a local inmemory map. This exists solely for reference and testing. -- [filesystem](storage-drivers/filesystem): A local storage driver configured to use a directory tree in the local filesystem. -- [s3](storage-drivers/s3): A driver storing objects in an Amazon Simple Storage Solution (S3) bucket. -- [azure](storage-drivers/azure): A driver storing objects in [Microsoft Azure Blob Storage](http://azure.microsoft.com/en-us/services/storage/). +- [inmemory](storage-drivers/inmemory.md): A temporary storage driver using a local inmemory map. This exists solely for reference and testing. +- [filesystem](storage-drivers/filesystem.md): A local storage driver configured to use a directory tree in the local filesystem. +- [s3](storage-drivers/s3.md): A driver storing objects in an Amazon Simple Storage Solution (S3) bucket. +- [azure](storage-drivers/azure.md): A driver storing objects in [Microsoft Azure Blob Storage](http://azure.microsoft.com/en-us/services/storage/). Storage Driver API ================== From a588f49425ac1d71c1a6b29acf7174150de4bf3c Mon Sep 17 00:00:00 2001 From: Kelsey Hightower Date: Sun, 26 Apr 2015 18:38:34 -0700 Subject: [PATCH 30/41] docs: document running Registry natively on OS X Signed-off-by: Kelsey Hightower --- docs/osx-setup-guide.md | 56 ++++++++++++++++++++++++++++++ docs/osx/com.docker.registry.plist | 42 ++++++++++++++++++++++ docs/osx/config.yml | 16 +++++++++ 3 files changed, 114 insertions(+) create mode 100644 docs/osx-setup-guide.md create mode 100644 docs/osx/com.docker.registry.plist create mode 100644 docs/osx/config.yml diff --git a/docs/osx-setup-guide.md b/docs/osx-setup-guide.md new file mode 100644 index 000000000..cd9a24298 --- /dev/null +++ b/docs/osx-setup-guide.md @@ -0,0 +1,56 @@ +# OS X Setup Guide + +This guide will walk you through running the new Go based [Docker registry](https://github.com/docker/distribution) on your local OS X machine. + +## Checkout the Docker Distribution source tree + +``` +mkdir -p $GOPATH/src/github.com/docker +git clone https://github.com/docker/distribution.git $GOPATH/src/github.com/docker/distribution +cd $GOPATH/src/github.com/docker/distribution +``` + +## Build the registry binary + +``` +GOPATH=$(PWD)/Godeps/_workspace:$GOPATH make binaries +sudo cp bin/registry /usr/local/libexec/registry +``` + +## Setup + +Copy the registry configuration file in place: + +``` +mkdir /Users/Shared/Registry +cp docs/osx/config.yml /Users/Shared/Registry/config.yml +``` + +## Running the Docker Registry under launchd + +Copy the Docker registry plist into place: + +``` +plutil -lint docs/osx/com.docker.registry.plist +cp docs/osx/com.docker.registry.plist ~/Library/LaunchAgents/ +chmod 644 ~/Library/LaunchAgents/com.docker.registry.plist +``` + +Start the Docker registry: + +``` +launchctl load ~/Library/LaunchAgents/com.docker.registry.plist +``` + +### Restarting the docker registry service + +``` +launchctl stop com.docker.registry +launchctl start com.docker.registry +``` + +### Unloading the docker registry service + +``` +launchctl unload ~/Library/LaunchAgents/com.docker.registry.plist +``` diff --git a/docs/osx/com.docker.registry.plist b/docs/osx/com.docker.registry.plist new file mode 100644 index 000000000..0982349f4 --- /dev/null +++ b/docs/osx/com.docker.registry.plist @@ -0,0 +1,42 @@ + + + + + Label + com.docker.registry + KeepAlive + + StandardErrorPath + /Users/Shared/Registry/registry.log + StandardOutPath + /Users/Shared/Registry/registry.log + Program + /usr/local/libexec/registry + ProgramArguments + + /usr/local/libexec/registry + /Users/Shared/Registry/config.yml + + Sockets + + http-listen-address + + SockServiceName + 5000 + SockType + dgram + SockFamily + IPv4 + + http-debug-address + + SockServiceName + 5001 + SockType + dgram + SockFamily + IPv4 + + + + diff --git a/docs/osx/config.yml b/docs/osx/config.yml new file mode 100644 index 000000000..7c19e5f0f --- /dev/null +++ b/docs/osx/config.yml @@ -0,0 +1,16 @@ +version: 0.1 +log: + level: info + fields: + service: registry + environment: macbook-air +storage: + cache: + layerinfo: inmemory + filesystem: + rootdirectory: /Users/Shared/Registry +http: + addr: 0.0.0.0:5000 + secret: mytokensecret + debug: + addr: localhost:5001 From ccef5cc0a6966ccc6e1be4529ddb730ec989345a Mon Sep 17 00:00:00 2001 From: Henri Gomez Date: Fri, 24 Apr 2015 09:50:05 +0200 Subject: [PATCH 31/41] move apache.conf to apache subdir Signed-off-by: Henri Gomez --- contrib/apache/apache.conf | 150 +++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 contrib/apache/apache.conf diff --git a/contrib/apache/apache.conf b/contrib/apache/apache.conf new file mode 100644 index 000000000..e4c6176a3 --- /dev/null +++ b/contrib/apache/apache.conf @@ -0,0 +1,150 @@ +# +# Sample Apache 2.x configuration where : +# +# http://registry.example.com proxify Docker Registry 1.0 in Mirror mode +# https://registry.example.com proxify Docker Registry 1.0 or 2.0 in Hosting mode +# +# 3 Docker containers should be started +# +# Docker Registry 1.0 in Mirror mode : port 5001 +# Docker Registry 1.0 in Hosting mode : port 5000 +# Docker Registry 2.0 in Hosting mode : port 5002 +# +# Registry v1 : +# docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/docker-registry/storage/hosting-v1:/tmp -p 5000:5000 registry:0.9.1" +# +# Mirror : +# docker run -d -e SETTINGS_FLAVOR=dev -e STANDALONE=false -e MIRROR_SOURCE=https://registry-1.docker.io -e MIRROR_SOURCE_INDEX=https://index.docker.io \ +# -e MIRROR_TAGS_CACHE_TTL=172800 -v /var/lib/docker-registry/storage/mirror:/tmp -p 5001:5000 registry:0.9.1" +# +# Registry v2 : +# docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/axway/docker-registry/storage/hosting2-v2:/tmp -p 5002:5000 registry:2.0" +# +# For Hosting mode : +#  +# users should have account (valid-user) to be able to fetch images +# only users using account docker-deployer will be allowed to push images + + + + ServerName registry.example.com + ServerAlias www.registry.example.com + + ProxyRequests off + ProxyPreserveHost on + + # no proxy for /error/ (Apache HTTPd errors messages) + ProxyPass /error/ ! + + ProxyPass /_ping http://localhost:5001/_ping + ProxyPassReverse /_ping http://localhost:5001/_ping + + ProxyPass /v1 http://localhost:5001/v1 + ProxyPassReverse /v1 http://localhost:5001/v1 + + # Logs + ErrorLog ${APACHE_LOG_DIR}/mirror_error_log + CustomLog ${APACHE_LOG_DIR}/mirror_access_log combined env=!dontlog + + + + + + + ServerName registry.example.com + ServerAlias www.registry.example.com + + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/registry.example.com.crt + SSLCertificateKeyFile /etc/apache2/ssl/registry.example.com.key + + # Higher Strength SSL Ciphers + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 + SSLCipherSuite RC4-SHA:HIGH + SSLHonorCipherOrder on + + # Logs + ErrorLog ${APACHE_LOG_DIR}/registry_error_ssl_log + CustomLog ${APACHE_LOG_DIR}/registry_access_ssl_log combined env=!dontlog + + Header set Host "registry.example.com" + Header set "Docker-Distribution-Api-Version" "registry/2.0" + RequestHeader set X-Forwarded-Proto "https" + + ProxyRequests off + ProxyPreserveHost on + + # no proxy for /error/ (Apache HTTPd errors messages) + ProxyPass /error/ ! + + # + # Registry v1 + # + + ProxyPass /v1 http://localhost:5000/v1 + ProxyPassReverse /v1 http://localhost:5000/v1 + + ProxyPass /_ping http://localhost:5000/_ping + ProxyPassReverse /_ping http://localhost:5000/_ping + + # Authentication require for push + + Order deny,allow + Allow from all + AuthName "Registry Authentication" + AuthType basic + AuthUserFile "/etc/apache2/htpasswd/registry-htpasswd" + + # Read access to authentified users + + Require valid-user + + + # Write access to docker-deployer account only + + Require user docker-deployer + + + + + # Allow ping to run unauthenticated. + + Satisfy any + Allow from all + + + # Allow ping to run unauthenticated. + + Satisfy any + Allow from all + + + # + # Registry v2 + # + + ProxyPass /v2 http://localhost:5002/v2 + ProxyPassReverse /v2 http://localhost:5002/v2 + + + Order deny,allow + Allow from all + AuthName "Registry Authentication" + AuthType basic + AuthUserFile "/etc/apache2/htpasswd/registry-htpasswd" + + # Read access to authentified users + + Require valid-user + + + # Write access to docker-deployer only + + Require user docker-deployer + + + + + + + From 4a3e107c92888b953a68255e18253d59e91ede0d Mon Sep 17 00:00:00 2001 From: Henri Gomez Date: Mon, 27 Apr 2015 12:54:41 +0200 Subject: [PATCH 32/41] Update apache.conf Signed-off-by: Henri Gomez --- contrib/apache/apache.conf | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/contrib/apache/apache.conf b/contrib/apache/apache.conf index e4c6176a3..b7581fd16 100644 --- a/contrib/apache/apache.conf +++ b/contrib/apache/apache.conf @@ -1,29 +1,6 @@ # # Sample Apache 2.x configuration where : # -# http://registry.example.com proxify Docker Registry 1.0 in Mirror mode -# https://registry.example.com proxify Docker Registry 1.0 or 2.0 in Hosting mode -# -# 3 Docker containers should be started -# -# Docker Registry 1.0 in Mirror mode : port 5001 -# Docker Registry 1.0 in Hosting mode : port 5000 -# Docker Registry 2.0 in Hosting mode : port 5002 -# -# Registry v1 : -# docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/docker-registry/storage/hosting-v1:/tmp -p 5000:5000 registry:0.9.1" -# -# Mirror : -# docker run -d -e SETTINGS_FLAVOR=dev -e STANDALONE=false -e MIRROR_SOURCE=https://registry-1.docker.io -e MIRROR_SOURCE_INDEX=https://index.docker.io \ -# -e MIRROR_TAGS_CACHE_TTL=172800 -v /var/lib/docker-registry/storage/mirror:/tmp -p 5001:5000 registry:0.9.1" -# -# Registry v2 : -# docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/axway/docker-registry/storage/hosting2-v2:/tmp -p 5002:5000 registry:2.0" -# -# For Hosting mode : -#  -# users should have account (valid-user) to be able to fetch images -# only users using account docker-deployer will be allowed to push images From 03a529171b55be6cb9897351bbfbea0e63ac66b3 Mon Sep 17 00:00:00 2001 From: Henri Gomez Date: Mon, 27 Apr 2015 12:59:17 +0200 Subject: [PATCH 33/41] Create README.MD Signed-off-by: Henri Gomez --- contrib/apache/README.MD | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 contrib/apache/README.MD diff --git a/contrib/apache/README.MD b/contrib/apache/README.MD new file mode 100644 index 000000000..f7e14b5b4 --- /dev/null +++ b/contrib/apache/README.MD @@ -0,0 +1,36 @@ +# Apache HTTPd sample for Registry v1, v2 and mirror + +3 containers involved + +* Docker Registry v1 (registry 0.9.1) +* Docker Registry v2 (registry 2.0.0) +* Docker Registry v1 in mirror mode + +HTTP for mirror and HTTPS for v1 & v2 + +* http://registry.example.com proxify Docker Registry 1.0 in Mirror mode +* https://registry.example.com proxify Docker Registry 1.0 or 2.0 in Hosting mode + +## 3 Docker containers should be started + +* Docker Registry 1.0 in Mirror mode : port 5001 +* Docker Registry 1.0 in Hosting mode : port 5000 +* Docker Registry 2.0 in Hosting mode : port 5002 + +### Registry v1 + + docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/docker-registry/storage/hosting-v1:/tmp -p 5000:5000 registry:0.9.1" + +### Mirror + + docker run -d -e SETTINGS_FLAVOR=dev -e STANDALONE=false -e MIRROR_SOURCE=https://registry-1.docker.io -e MIRROR_SOURCE_INDEX=https://index.docker.io \ + -e MIRROR_TAGS_CACHE_TTL=172800 -v /var/lib/docker-registry/storage/mirror:/tmp -p 5001:5000 registry:0.9.1" + +### Registry v2 + + docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/axway/docker-registry/storage/hosting2-v2:/tmp -p 5002:5000 registry:2.0" + +# For Hosting mode access + +* users should have account (valid-user) to be able to fetch images +* only users using account docker-deployer will be allowed to push images From a1fc1108911f494f8fa0b7bebce16412851e95cb Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 27 Apr 2015 11:19:36 -0700 Subject: [PATCH 34/41] Add environment variable override instructions Signed-off-by: Richard Scothern --- docs/configuration.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index ab783af39..ae1715742 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,6 +9,18 @@ You configure a registry server using a YAML file. This page explains the configuration options and the values they can take. You'll also find examples of middleware and development environment configurations. +## Overriding configuration options +Environment variables may be used to override configuration parameters other than +version. To override a configuration option, create an environment variable named +REGISTRY\_variable_ where *variable* is the name of the configuration option. + +e.g +``` +REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/tmp/registry/test +``` + +will set the storage root directory to `/tmp/registry/test` + ## List of configuration options This section lists all the registry configuration options. Some options in From 71b07878ef04f0bf9e226b8dd27f4a9fb9804223 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 27 Apr 2015 11:27:49 -0700 Subject: [PATCH 35/41] Pushing fix for mkdocs.yml introduced Signed-off-by: Mary Anthony --- docs/mkdocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 814b89097..844658206 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -7,7 +7,7 @@ - ['registry/spec/json.md', 'Reference', '    ▪  JSON format' ] - ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] -- ['registry/storage-drivers/azure.md', 'HIDDEN' ] -- ['registry/storage-drivers/filesystem.md', 'HIDDEN' ] -- ['registry/storage-drivers/inmemory.md', 'HIDDEN' ] -- ['registry/storage-drivers/s3.md', 'Reference', 'HIDDEN' ] +- ['registry/storage-drivers/azure.md', '**HIDDEN**' ] +- ['registry/storage-drivers/filesystem.md', '**HIDDEN**' ] +- ['registry/storage-drivers/inmemory.md', '**HIDDEN**' ] +- ['registry/storage-drivers/s3.md','**HIDDEN**' ] From fb499fd607d2bfef48bc527c4c8ca59b39c00b49 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 27 Apr 2015 14:46:33 -0700 Subject: [PATCH 36/41] Updating configuration with required header Signed-off-by: Mary Anthony --- contrib/compose/nginx/registry.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/compose/nginx/registry.conf b/contrib/compose/nginx/registry.conf index 28542a47c..3c5549213 100644 --- a/contrib/compose/nginx/registry.conf +++ b/contrib/compose/nginx/registry.conf @@ -25,6 +25,9 @@ server { if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) { return 404; } + + # The docker client expects this header from the /v2/ endpoint. + more_set_headers 'Docker-Distribution-Api-Version: registry/2.0'; include docker-registry-v2.conf; } From 1a181e88875e148d3625eb6bd7511ef418f8ceea Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 27 Apr 2015 15:49:19 -0700 Subject: [PATCH 37/41] Updating env var documentation The position was a bit too early. Fleshed out the example. Also, using the _ underscore for emphasis was confusing because it is also used to indicate a level change. Signed-off-by: Mary Anthony --- docs/configuration.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ae1715742..385943699 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,18 +9,6 @@ You configure a registry server using a YAML file. This page explains the configuration options and the values they can take. You'll also find examples of middleware and development environment configurations. -## Overriding configuration options -Environment variables may be used to override configuration parameters other than -version. To override a configuration option, create an environment variable named -REGISTRY\_variable_ where *variable* is the name of the configuration option. - -e.g -``` -REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/tmp/registry/test -``` - -will set the storage root directory to `/tmp/registry/test` - ## List of configuration options This section lists all the registry configuration options. Some options in @@ -134,6 +122,34 @@ options marked as **required**. This indicates that you can omit the parent with all its children. However, if the parent is included, you must also include all the children marked **required**. +## Override configuration options + +You can use environment variables to override most configuration parameters. The +exception is the `version` variable which cannot be overridden. You can set +environment variables on the command line using the `-e` flag on `docker run` or +from within a Dockerfile using the `ENV` instruction. + +To override a configuration option, create an environment variable named +`REGISTRY\variable_` where *`variable`* is the name of the configuration option +and the `_` (underscore) represents indention levels. For example, you can +configure the `rootdirectory` of the `filesystem` storage backend: + +``` +storage: + filesystem: + rootdirectory: /tmp/registry +``` + +To override this value, set an environment variable like this: + +``` +REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/tmp/registry/test +``` + +This variable overrides the `/tmp/registry` value to the `/tmp/registry/test` +directory. + + ## version ```yaml From edda3edb269884e7fc7dc58149959538b441236d Mon Sep 17 00:00:00 2001 From: Yann ROBERT Date: Tue, 28 Apr 2015 18:07:22 +0200 Subject: [PATCH 38/41] fixed missing word in CONTRIBUTING.md Signed-off-by: Yann ROBERT --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6674574dc..7f36c94b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ Then immediately submit this new file as a pull-request, in order to get early f Eventually, you will have to update your proposal to accommodate the feedback you received. -Usually, it's not advisable to start working too much on the implementation itself before the proposal receives sufficient feedback, since it can significantly altered (or rejected). +Usually, it's not advisable to start working too much on the implementation itself before the proposal receives sufficient feedback, since it can be significantly altered (or rejected). Your implementation should then be submitted as a separate PR, that will be reviewed as well. From 7bfb2c1e19baa410f12009160479f6ed550ae584 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 28 Apr 2015 15:10:52 -0700 Subject: [PATCH 39/41] Fixing headings Signed-off-by: Mary Anthony --- docs/storagedrivers.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/storagedrivers.md b/docs/storagedrivers.md index 2db67e282..e532d3a00 100644 --- a/docs/storagedrivers.md +++ b/docs/storagedrivers.md @@ -1,10 +1,8 @@ -Docker-Registry Storage Driver -============================== +# Docker-Registry Storage Driver This document describes the registry storage driver model, implementation, and explains how to contribute new storage drivers. -Provided Drivers -================ +## Provided Drivers This storage driver package comes bundled with several drivers: @@ -13,8 +11,7 @@ This storage driver package comes bundled with several drivers: - [s3](storage-drivers/s3.md): A driver storing objects in an Amazon Simple Storage Solution (S3) bucket. - [azure](storage-drivers/azure.md): A driver storing objects in [Microsoft Azure Blob Storage](http://azure.microsoft.com/en-us/services/storage/). -Storage Driver API -================== +## Storage Driver API The storage driver API is designed to model a filesystem-like key/value storage in a manner abstract enough to support a range of drivers from the local filesystem to Amazon S3 or other distributed object storage systems. @@ -22,23 +19,21 @@ Storage drivers are required to implement the `storagedriver.StorageDriver` inte Storage drivers are intended (but not required) to be written in go, providing compile-time validation of the `storagedriver.StorageDriver` interface, although an IPC driver wrapper means that it is not required for drivers to be included in the compiled registry. The `storagedriver/ipc` package provides a client/server protocol for running storage drivers provided in external executables as a managed child server process. -Driver Selection and Configuration -================================== +## Driver Selection and Configuration The preferred method of selecting a storage driver is using the `StorageDriverFactory` interface in the `storagedriver/factory` package. These factories provide a common interface for constructing storage drivers with a parameters map. The factory model is based off of the [Register](http://golang.org/pkg/database/sql/#Register) and [Open](http://golang.org/pkg/database/sql/#Open) methods in the builtin [database/sql](http://golang.org/pkg/database/sql) package. Storage driver factories may be registered by name using the `factory.Register` method, and then later invoked by calling `factory.Create` with a driver name and parameters map. If no driver is registered with the given name, this factory will attempt to find an executable storage driver with the executable name "registry-storage-\" and return an IPC storage driver wrapper managing the driver subprocess. If no such storage driver can be found, `factory.Create` will return an `InvalidStorageDriverError`. -Driver Contribution -=================== +## Driver Contribution -## Writing new storage drivers +### Writing new storage drivers To create a valid storage driver, one must implement the `storagedriver.StorageDriver` interface and make sure to expose this driver via the factory system and as a distributable IPC server executable. -### In-process drivers +#### In-process drivers Storage drivers should call `factory.Register` with their driver name in an `init` method, allowing callers of `factory.New` to construct instances of this driver without requiring modification of imports throughout the codebase. -### Out-of-process drivers +#### Out-of-process drivers As many users will run the registry as a pre-constructed docker container, storage drivers should also be distributable as IPC server executables. Drivers written in go should model the main method provided in `storagedriver/filesystem/registry-storage-filesystem/filesystem.go`. Parameters to IPC drivers will be provided as a JSON-serialized map in the first argument to the process. These parameters should be validated and then a blocking call to `ipc.StorageDriverServer` should be made with a new storage driver. Out-of-process drivers must also implement the `ipc.IPCStorageDriver` interface, which exposes a `Version` check for the storage driver. This is used to validate storage driver api compatibility at driver load-time. From 05a9847f53a3a66d88d28ee7789cef3467ec3de4 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 29 Apr 2015 12:20:09 -0700 Subject: [PATCH 40/41] Fixes Issue #471 with Publish - Add sed to Dockerfile; this sed exists on publish script; breaks headings/nav in files without metadata - Ensure sed runs over storage-driver/ subdir - Add metadata to all the files (including specs) that don't have it; this ensures they display correctly on publish - Implement the fix for the showing up in Github - Update template with GITHUB IGNORES Signed-off-by: Mary Anthony --- docs/Dockerfile | 15 ++++++++++++++- docs/configuration.md | 2 ++ docs/deploying.md | 3 +++ docs/index.md | 2 ++ docs/notifications.md | 3 +++ docs/spec/api.md | 6 ++++++ docs/spec/api.md.tmpl | 6 ++++++ docs/spec/auth/token.md | 7 +++++++ docs/spec/json.md | 7 +++++++ docs/storage-drivers/azure.md | 7 ++++++- docs/storage-drivers/filesystem.md | 6 ++++++ docs/storage-drivers/inmemory.md | 6 ++++++ docs/storage-drivers/s3.md | 6 ++++++ docs/storagedrivers.md | 8 +++++++- 14 files changed, 81 insertions(+), 3 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 1613e13aa..d4e60012c 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -7,11 +7,13 @@ COPY . /src # Reset the /docs dir so we can replace the theme meta with the new repo's git info RUN git reset --hard +RUN grep "VERSION =" /src/version/version.go | sed 's/.*"\(.*\)".*/\1/' > /docs/VERSION + + # # RUN git describe --match 'v[0-9]*' --dirty='.m' --always > /docs/VERSION # The above line causes a floating point error in our tools # -RUN grep "VERSION =" /src/version/version.go | sed 's/.*"\(.*\)".*/\1/' > /docs/VERSION COPY docs/* /docs/sources/registry/ COPY docs/images/* /docs/sources/registry/images/ COPY docs/spec/* /docs/sources/registry/spec/ @@ -19,6 +21,17 @@ COPY docs/spec/auth/* /docs/sources/registry/spec/auth/ COPY docs/storage-drivers/* /docs/sources/registry/storage-drivers/ COPY docs/mkdocs.yml /docs/mkdocs-distribution.yml +RUN sed -i.old '1s;^;no_version_dropdown: true;' \ + /docs/sources/registry/*.md \ + /docs/sources/registry/spec/*.md \ + /docs/sources/registry/spec/auth/*.md \ + /docs/sources/registry/storage-drivers/*.md + +RUN sed -i.old -e '/^/g'\ + /docs/sources/registry/*.md \ + /docs/sources/registry/spec/*.md \ + /docs/sources/registry/spec/auth/*.md \ + /docs/sources/registry/storage-drivers/*.md # Then build everything together, ready for mkdocs RUN /docs/build.sh diff --git a/docs/configuration.md b/docs/configuration.md index 385943699..84025a62c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,8 @@ + # Registry Configuration Reference diff --git a/docs/deploying.md b/docs/deploying.md index b26499da0..10bf6b811 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -1,6 +1,9 @@ + + # Deploying a registry server diff --git a/docs/index.md b/docs/index.md index 7c21d17ff..f8f9c8b06 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,8 @@ + # Docker Registry 2.0 diff --git a/docs/notifications.md b/docs/notifications.md index b8fdf3716..fb5897fc9 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -1,6 +1,9 @@ + + # Notifications diff --git a/docs/spec/api.md b/docs/spec/api.md index c5768305b..1d7540ed1 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -1,3 +1,9 @@ + + # Docker Registry HTTP API V2 ## Introduction diff --git a/docs/spec/api.md.tmpl b/docs/spec/api.md.tmpl index 1a68a4744..68a7dff9c 100644 --- a/docs/spec/api.md.tmpl +++ b/docs/spec/api.md.tmpl @@ -1,3 +1,9 @@ + + # Docker Registry HTTP API V2 ## Introduction diff --git a/docs/spec/auth/token.md b/docs/spec/auth/token.md index 921622578..5a394cbe4 100644 --- a/docs/spec/auth/token.md +++ b/docs/spec/auth/token.md @@ -1,3 +1,10 @@ + + + # Docker Registry v2 authentication via central service Today a Docker Registry can run in standalone mode in which there are no diff --git a/docs/spec/json.md b/docs/spec/json.md index 5504a40bf..34440c717 100644 --- a/docs/spec/json.md +++ b/docs/spec/json.md @@ -1,3 +1,10 @@ + + + # Docker Distribution JSON Canonicalization To provide consistent content hashing of JSON objects throughout Docker diff --git a/docs/storage-drivers/azure.md b/docs/storage-drivers/azure.md index 630034dbf..fd46ece39 100644 --- a/docs/storage-drivers/azure.md +++ b/docs/storage-drivers/azure.md @@ -1,5 +1,10 @@ -# Microsoft Azure storage driver + +# Microsoft Azure storage driver An implementation of the `storagedriver.StorageDriver` interface which uses [Microsoft Azure Blob Storage][azure-blob-storage] for object storage. diff --git a/docs/storage-drivers/filesystem.md b/docs/storage-drivers/filesystem.md index e5620913c..fa9f8259e 100644 --- a/docs/storage-drivers/filesystem.md +++ b/docs/storage-drivers/filesystem.md @@ -1,3 +1,9 @@ + + # Filesystem storage driver An implementation of the `storagedriver.StorageDriver` interface which uses the local filesystem. diff --git a/docs/storage-drivers/inmemory.md b/docs/storage-drivers/inmemory.md index a2ebc9e88..948cd5bcb 100644 --- a/docs/storage-drivers/inmemory.md +++ b/docs/storage-drivers/inmemory.md @@ -1,3 +1,9 @@ + + # In-memory storage driver An implementation of the `storagedriver.StorageDriver` interface which uses local memory for object storage. diff --git a/docs/storage-drivers/s3.md b/docs/storage-drivers/s3.md index e2714a4b3..e6c113485 100644 --- a/docs/storage-drivers/s3.md +++ b/docs/storage-drivers/s3.md @@ -1,3 +1,9 @@ + + # S3 storage driver An implementation of the `storagedriver.StorageDriver` interface which uses Amazon S3 for object storage. diff --git a/docs/storagedrivers.md b/docs/storagedrivers.md index e532d3a00..e476457d3 100644 --- a/docs/storagedrivers.md +++ b/docs/storagedrivers.md @@ -1,4 +1,10 @@ -# Docker-Registry Storage Driver + + +# Docker Registry Storage Driver This document describes the registry storage driver model, implementation, and explains how to contribute new storage drivers. From ab492bb962ec3e7774c3bc1a139aa10ad9f7cedc Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 5 May 2015 15:15:42 -0700 Subject: [PATCH 41/41] Disabled coveralls reporting: build breaking sending coverage data to coveralls Signed-off-by: Richard Scothern --- circle.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 656efa693..b841cdecc 100644 --- a/circle.yml +++ b/circle.yml @@ -103,10 +103,11 @@ test: # Aggregate and report to coveralls - gvm use stable; go list ./... | xargs -L 1 -I{} cat "$GOPATH/src/{}/coverage.out" | grep -v "$CIRCLE_PAIN" >> ~/goverage.report: pwd: $BASE_STABLE - - gvm use stable; goveralls -service circleci -coverprofile=/home/ubuntu/goverage.report -repotoken $COVERALLS_TOKEN: - pwd: $BASE_STABLE +# - gvm use stable; goveralls -service circleci -coverprofile=/home/ubuntu/goverage.report -repotoken $COVERALLS_TOKEN: +# pwd: $BASE_STABLE ## Notes + # Disabled coveralls reporting: build breaking sending coverage data to coveralls # Disabled the -race detector due to massive memory usage. # Do we want these as well? # - go get code.google.com/p/go.tools/cmd/goimports