Merge pull request #1999 from stevvooe/fold-target-references
manifest: references should cover all children
This commit is contained in:
commit
8234784a1a
9 changed files with 81 additions and 63 deletions
|
@ -9,11 +9,10 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/libtrust"
|
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
type diffID digest.Digest
|
type diffID digest.Digest
|
||||||
|
@ -95,7 +94,7 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
|
if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
|
||||||
return nil, errors.New("number of descriptors and number of layers in rootfs must match")
|
return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate IDs for each layer
|
// Generate IDs for each layer
|
||||||
|
|
|
@ -203,8 +203,8 @@ func TestBuilder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
references := manifest.References()
|
references := manifest.References()
|
||||||
|
expected := append([]distribution.Descriptor{manifest.Target()}, descriptors...)
|
||||||
if !reflect.DeepEqual(references, descriptors) {
|
if !reflect.DeepEqual(references, expected) {
|
||||||
t.Fatal("References() does not match the descriptors added")
|
t.Fatal("References() does not match the descriptors added")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,10 @@ type Manifest struct {
|
||||||
|
|
||||||
// References returnes the descriptors of this manifests references.
|
// References returnes the descriptors of this manifests references.
|
||||||
func (m Manifest) References() []distribution.Descriptor {
|
func (m Manifest) References() []distribution.Descriptor {
|
||||||
return m.Layers
|
references := make([]distribution.Descriptor, 0, 1+len(m.Layers))
|
||||||
|
references = append(references, m.Config)
|
||||||
|
references = append(references, m.Layers...)
|
||||||
|
return references
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target returns the target of this signed manifest.
|
// Target returns the target of this signed manifest.
|
||||||
|
|
|
@ -90,16 +90,22 @@ func TestManifest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
references := deserialized.References()
|
references := deserialized.References()
|
||||||
if len(references) != 1 {
|
if len(references) != 2 {
|
||||||
t.Fatalf("unexpected number of references: %d", len(references))
|
t.Fatalf("unexpected number of references: %d", len(references))
|
||||||
}
|
}
|
||||||
if references[0].Digest != "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b" {
|
|
||||||
|
if !reflect.DeepEqual(references[0], target) {
|
||||||
|
t.Fatalf("first reference should be target: %v != %v", references[0], target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the second reference
|
||||||
|
if references[1].Digest != "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b" {
|
||||||
t.Fatalf("unexpected digest in reference: %s", references[0].Digest.String())
|
t.Fatalf("unexpected digest in reference: %s", references[0].Digest.String())
|
||||||
}
|
}
|
||||||
if references[0].MediaType != MediaTypeLayer {
|
if references[1].MediaType != MediaTypeLayer {
|
||||||
t.Fatalf("unexpected media type in reference: %s", references[0].MediaType)
|
t.Fatalf("unexpected media type in reference: %s", references[0].MediaType)
|
||||||
}
|
}
|
||||||
if references[0].Size != 153263 {
|
if references[1].Size != 153263 {
|
||||||
t.Fatalf("unexpected size in reference: %d", references[0].Size)
|
t.Fatalf("unexpected size in reference: %d", references[0].Size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
manifests.go
12
manifests.go
|
@ -12,8 +12,13 @@ import (
|
||||||
// references and an optional target
|
// references and an optional target
|
||||||
type Manifest interface {
|
type Manifest interface {
|
||||||
// References returns a list of objects which make up this manifest.
|
// References returns a list of objects which make up this manifest.
|
||||||
// The references are strictly ordered from base to head. A reference
|
// A reference is anything which can be represented by a
|
||||||
// is anything which can be represented by a distribution.Descriptor
|
// distribution.Descriptor. These can consist of layers, resources or other
|
||||||
|
// manifests.
|
||||||
|
//
|
||||||
|
// While no particular order is required, implementations should return
|
||||||
|
// them from highest to lowest priority. For example, one might want to
|
||||||
|
// return the base layer before the top layer.
|
||||||
References() []Descriptor
|
References() []Descriptor
|
||||||
|
|
||||||
// Payload provides the serialized format of the manifest, in addition to
|
// Payload provides the serialized format of the manifest, in addition to
|
||||||
|
@ -36,6 +41,9 @@ type ManifestBuilder interface {
|
||||||
// AppendReference includes the given object in the manifest after any
|
// AppendReference includes the given object in the manifest after any
|
||||||
// existing dependencies. If the add fails, such as when adding an
|
// existing dependencies. If the add fails, such as when adding an
|
||||||
// unsupported dependency, an error may be returned.
|
// unsupported dependency, an error may be returned.
|
||||||
|
//
|
||||||
|
// The destination of the reference is dependent on the manifest type and
|
||||||
|
// the dependency type.
|
||||||
AppendReference(dependency Describable) error
|
AppendReference(dependency Describable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,7 @@ func (imh *imageManifestHandler) convertSchema2Manifest(schema2Manifest *schema2
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON)
|
builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON)
|
||||||
for _, d := range schema2Manifest.References() {
|
for _, d := range schema2Manifest.Layers {
|
||||||
if err := builder.AppendReference(d); err != nil {
|
if err := builder.AppendReference(d); err != nil {
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
@ -63,14 +62,6 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
emit("%s: marking blob %s", repoName, descriptor.Digest)
|
emit("%s: marking blob %s", repoName, descriptor.Digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch manifest.(type) {
|
|
||||||
case *schema2.DeserializedManifest:
|
|
||||||
config := manifest.(*schema2.DeserializedManifest).Config
|
|
||||||
emit("%s: marking configuration %s", repoName, config.Digest)
|
|
||||||
markSet[config.Digest] = struct{}{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,53 +72,62 @@ func (ms *schema2ManifestHandler) Put(ctx context.Context, manifest distribution
|
||||||
func (ms *schema2ManifestHandler) verifyManifest(ctx context.Context, mnfst schema2.DeserializedManifest, skipDependencyVerification bool) error {
|
func (ms *schema2ManifestHandler) verifyManifest(ctx context.Context, mnfst schema2.DeserializedManifest, skipDependencyVerification bool) error {
|
||||||
var errs distribution.ErrManifestVerification
|
var errs distribution.ErrManifestVerification
|
||||||
|
|
||||||
if !skipDependencyVerification {
|
if skipDependencyVerification {
|
||||||
target := mnfst.Target()
|
return nil
|
||||||
_, err := ms.repository.Blobs(ctx).Stat(ctx, target.Digest)
|
}
|
||||||
|
|
||||||
|
manifestService, err := ms.repository.Manifests(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
blobsService := ms.repository.Blobs(ctx)
|
||||||
|
|
||||||
|
for _, descriptor := range mnfst.References() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch descriptor.MediaType {
|
||||||
|
case schema2.MediaTypeForeignLayer:
|
||||||
|
// Clients download this layer from an external URL, so do not check for
|
||||||
|
// its presense.
|
||||||
|
if len(descriptor.URLs) == 0 {
|
||||||
|
err = errMissingURL
|
||||||
|
}
|
||||||
|
allow := ms.manifestURLs.allow
|
||||||
|
deny := ms.manifestURLs.deny
|
||||||
|
for _, u := range descriptor.URLs {
|
||||||
|
var pu *url.URL
|
||||||
|
pu, err = url.Parse(u)
|
||||||
|
if err != nil || (pu.Scheme != "http" && pu.Scheme != "https") || pu.Fragment != "" || (allow != nil && !allow.MatchString(u)) || (deny != nil && deny.MatchString(u)) {
|
||||||
|
err = errInvalidURL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case schema2.MediaTypeManifest, schema1.MediaTypeManifest:
|
||||||
|
var exists bool
|
||||||
|
exists, err = manifestService.Exists(ctx, descriptor.Digest)
|
||||||
|
if err != nil || !exists {
|
||||||
|
err = distribution.ErrBlobUnknown // just coerce to unknown.
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough // double check the blob store.
|
||||||
|
default:
|
||||||
|
// forward all else to blob storage
|
||||||
|
if len(descriptor.URLs) == 0 {
|
||||||
|
_, err = blobsService.Stat(ctx, descriptor.Digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != distribution.ErrBlobUnknown {
|
if err != distribution.ErrBlobUnknown {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On error here, we always append unknown blob errors.
|
// On error here, we always append unknown blob errors.
|
||||||
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: target.Digest})
|
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: descriptor.Digest})
|
||||||
}
|
|
||||||
|
|
||||||
for _, fsLayer := range mnfst.References() {
|
|
||||||
var err error
|
|
||||||
if fsLayer.MediaType != schema2.MediaTypeForeignLayer {
|
|
||||||
if len(fsLayer.URLs) == 0 {
|
|
||||||
_, err = ms.repository.Blobs(ctx).Stat(ctx, fsLayer.Digest)
|
|
||||||
} else {
|
|
||||||
err = errUnexpectedURL
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clients download this layer from an external URL, so do not check for
|
|
||||||
// its presense.
|
|
||||||
if len(fsLayer.URLs) == 0 {
|
|
||||||
err = errMissingURL
|
|
||||||
}
|
|
||||||
allow := ms.manifestURLs.allow
|
|
||||||
deny := ms.manifestURLs.deny
|
|
||||||
for _, u := range fsLayer.URLs {
|
|
||||||
var pu *url.URL
|
|
||||||
pu, err = url.Parse(u)
|
|
||||||
if err != nil || (pu.Scheme != "http" && pu.Scheme != "https") || pu.Fragment != "" || (allow != nil && !allow.MatchString(u)) || (deny != nil && deny.MatchString(u)) {
|
|
||||||
err = errInvalidURL
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err != distribution.ErrBlobUnknown {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// On error here, we always append unknown blob errors.
|
|
||||||
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.Digest})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,10 @@ func TestVerifyManifestForeignLayer(t *testing.T) {
|
||||||
errMissingURL,
|
errMissingURL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// regular layers may have foreign urls
|
||||||
layer,
|
layer,
|
||||||
[]string{"http://foo/bar"},
|
[]string{"http://foo/bar"},
|
||||||
errUnexpectedURL,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
foreignLayer,
|
foreignLayer,
|
||||||
|
|
Loading…
Reference in a new issue