From f14c6a4814bef8e3510e68fb909caf0f293294c2 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Tue, 15 Dec 2015 11:37:28 -0800 Subject: [PATCH] Recognize clients that don't support schema2, and convert manifests to schema1 on the fly Signed-off-by: Aaron Lehmann --- docs/handlers/app.go | 17 ++++++++++++++-- docs/handlers/images.go | 45 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/docs/handlers/app.go b/docs/handlers/app.go index 63a3ac29..8ea89da8 100644 --- a/docs/handlers/app.go +++ b/docs/handlers/app.go @@ -30,6 +30,7 @@ import ( storagedriver "github.com/docker/distribution/registry/storage/driver" "github.com/docker/distribution/registry/storage/driver/factory" storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware" + "github.com/docker/libtrust" "github.com/garyburd/redigo/redis" "github.com/gorilla/mux" "golang.org/x/net/context" @@ -67,10 +68,15 @@ type App struct { redis *redis.Pool - // true if this registry is configured as a pull through cache + // trustKey is a deprecated key used to sign manifests converted to + // schema1 for backward compatibility. It should not be used for any + // other purposes. + trustKey libtrust.PrivateKey + + // isCache is true if this registry is configured as a pull through cache isCache bool - // true if the registry is in a read-only maintenance mode + // readOnly is true if the registry is in a read-only maintenance mode readOnly bool } @@ -139,6 +145,13 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap app.configureRedis(configuration) app.configureLogHook(configuration) + // Generate an ephemeral key to be used for signing converted manifests + // for clients that don't support schema2. + app.trustKey, err = libtrust.GenerateECP256PrivateKey() + if err != nil { + panic(err) + } + if configuration.HTTP.Host != "" { u, err := url.Parse(configuration.HTTP.Host) if err != nil { diff --git a/docs/handlers/images.go b/docs/handlers/images.go index be14b00a..be586602 100644 --- a/docs/handlers/images.go +++ b/docs/handlers/images.go @@ -8,6 +8,8 @@ import ( "github.com/docker/distribution" ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/digest" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/v2" "github.com/gorilla/handlers" @@ -51,8 +53,6 @@ type imageManifestHandler struct { } // GetImageManifest fetches the image manifest from the storage backend, if it exists. -// todo(richardscothern): this assumes v2 schema 1 manifests for now but in the future -// get the version from the Accept HTTP header func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) { ctxu.GetLogger(imh).Debug("GetImageManifest") manifests, err := imh.Repository.Manifests(imh) @@ -83,6 +83,47 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http return } + // Only rewrite schema2 manifests when they are being fetched by tag. + // If they are being fetched by digest, we can't return something not + // matching the digest. + if _, isSchema2 := manifest.(*schema2.DeserializedManifest); imh.Tag != "" && isSchema2 { + supportsSchema2 := false + if acceptHeaders, ok := r.Header["Accept"]; ok { + for _, mediaType := range acceptHeaders { + if mediaType == schema2.MediaTypeManifest { + supportsSchema2 = true + break + } + } + } + + if !supportsSchema2 { + // Rewrite manifest in schema1 format + ctxu.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String()) + + targetDescriptor := manifest.Target() + blobs := imh.Repository.Blobs(imh) + configJSON, err := blobs.Get(imh, targetDescriptor.Digest) + if err != nil { + imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) + return + } + + builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, imh.Repository.Name(), imh.Tag, configJSON) + for _, d := range manifest.References() { + if err := builder.AppendReference(d); err != nil { + imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) + return + } + } + manifest, err = builder.Build(imh) + if err != nil { + imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) + return + } + } + } + ct, p, err := manifest.Payload() if err != nil { return