Recognize clients that don't support schema2, and convert manifests to schema1 on the fly

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2015-12-15 11:37:28 -08:00
parent 9c13a8295f
commit f14c6a4814
2 changed files with 58 additions and 4 deletions

View file

@ -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 {

View file

@ -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