OCI media types; annotation support; oci index

Signed-off-by: Mike Brown <brownwm@us.ibm.com>
This commit is contained in:
Mike Brown 2017-07-11 14:19:47 -05:00
parent 6fcea22b0a
commit c94f28805e
11 changed files with 102 additions and 90 deletions

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
var ( var (
@ -72,6 +73,13 @@ type Descriptor struct {
// URLs contains the source URLs of this content. // URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"` URLs []string `json:"urls,omitempty"`
// Annotations contains arbitrary metadata relating to the targeted content.
Annotations map[string]string `json:"annotations,omitempty"`
// Platform describes the platform which the image in the manifest runs on.
// This should only be used when referring to a manifest.
Platform *v1.Platform `json:"platform,omitempty"`
// NOTE: Before adding a field here, please ensure that all // NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships // other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type. // depend on the simplicity of this type.

View file

@ -7,16 +7,13 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/manifest" "github.com/docker/distribution/manifest"
"github.com/docker/distribution/manifest/ocischema"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
const ( const (
// MediaTypeManifestList specifies the mediaType for manifest lists. // MediaTypeManifestList specifies the mediaType for manifest lists.
MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json" MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
// MediaTypeOCIManifestList specifies the mediaType for OCI compliant manifest
// lists.
MediaTypeOCIManifestList = "application/vnd.oci.image.manifest.list.v1+json"
) )
// SchemaVersion provides a pre-initialized version structure for this // SchemaVersion provides a pre-initialized version structure for this
@ -30,7 +27,7 @@ var SchemaVersion = manifest.Versioned{
// packages OCIschema version of the manifest. // packages OCIschema version of the manifest.
var OCISchemaVersion = manifest.Versioned{ var OCISchemaVersion = manifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: MediaTypeOCIManifestList, MediaType: v1.MediaTypeImageIndex,
} }
func init() { func init() {
@ -92,6 +89,9 @@ type ManifestList struct {
// Config references the image configuration as a blob. // Config references the image configuration as a blob.
Manifests []ManifestDescriptor `json:"manifests"` Manifests []ManifestDescriptor `json:"manifests"`
// Annotations contains arbitrary metadata for the image index.
Annotations map[string]string `json:"annotations,omitempty"`
} }
// References returns the distribution descriptors for the referenced image // References returns the distribution descriptors for the referenced image
@ -119,7 +119,7 @@ type DeserializedManifestList struct {
// and its JSON representation. // and its JSON representation.
func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) { func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
var m ManifestList var m ManifestList
if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == ocischema.MediaTypeManifest { if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest {
m = ManifestList{ m = ManifestList{
Versioned: OCISchemaVersion, Versioned: OCISchemaVersion,
} }

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
var expectedManifestListSerialization = []byte(`{ var expectedManifestListSerialization = []byte(`{
@ -110,9 +111,9 @@ func TestManifestList(t *testing.T) {
} }
} }
var expectedOCIManifestListSerialization = []byte(`{ var expectedOCIImageIndexSerialization = []byte(`{
"schemaVersion": 2, "schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.list.v1+json", "mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [ "manifests": [
{ {
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
@ -138,7 +139,7 @@ var expectedOCIManifestListSerialization = []byte(`{
] ]
}`) }`)
func TestOCIManifestList(t *testing.T) { func TestOCIImageIndex(t *testing.T) {
manifestDescriptors := []ManifestDescriptor{ manifestDescriptors := []ManifestDescriptor{
{ {
Descriptor: distribution.Descriptor{ Descriptor: distribution.Descriptor{
@ -172,7 +173,7 @@ func TestOCIManifestList(t *testing.T) {
mediaType, canonical, _ := deserialized.Payload() mediaType, canonical, _ := deserialized.Payload()
if mediaType != MediaTypeOCIManifestList { if mediaType != v1.MediaTypeImageIndex {
t.Fatalf("unexpected media type: %s", mediaType) t.Fatalf("unexpected media type: %s", mediaType)
} }
@ -187,8 +188,8 @@ func TestOCIManifestList(t *testing.T) {
} }
// Check that the canonical field has the expected value. // Check that the canonical field has the expected value.
if !bytes.Equal(expectedOCIManifestListSerialization, canonical) { if !bytes.Equal(expectedOCIImageIndexSerialization, canonical) {
t.Fatalf("manifest bytes not equal: %q != %q", string(canonical), string(expectedOCIManifestListSerialization)) t.Fatalf("manifest bytes not equal: %q != %q", string(canonical), string(expectedOCIImageIndexSerialization))
} }
var unmarshalled DeserializedManifestList var unmarshalled DeserializedManifestList

View file

@ -4,6 +4,7 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
// builder is a type for constructing manifests. // builder is a type for constructing manifests.
@ -48,7 +49,7 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) {
case nil: case nil:
// Override MediaType, since Put always replaces the specified media // Override MediaType, since Put always replaces the specified media
// type with application/octet-stream in the descriptor it returns. // type with application/octet-stream in the descriptor it returns.
m.Config.MediaType = MediaTypeConfig m.Config.MediaType = v1.MediaTypeImageConfig
return FromStruct(m) return FromStruct(m)
case distribution.ErrBlobUnknown: case distribution.ErrBlobUnknown:
// nop // nop
@ -57,10 +58,10 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) {
} }
// Add config to the blob store // Add config to the blob store
m.Config, err = mb.bs.Put(ctx, MediaTypeConfig, mb.configJSON) m.Config, err = mb.bs.Put(ctx, v1.MediaTypeImageConfig, mb.configJSON)
// Override MediaType, since Put always replaces the specified media // Override MediaType, since Put always replaces the specified media
// type with application/octet-stream in the descriptor it returns. // type with application/octet-stream in the descriptor it returns.
m.Config.MediaType = MediaTypeConfig m.Config.MediaType = v1.MediaTypeImageConfig
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -7,6 +7,7 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
type mockBlobService struct { type mockBlobService struct {
@ -151,17 +152,17 @@ func TestBuilder(t *testing.T) {
{ {
Digest: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), Digest: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
Size: 5312, Size: 5312,
MediaType: MediaTypeLayer, MediaType: v1.MediaTypeImageLayerGzip,
}, },
{ {
Digest: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"), Digest: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"),
Size: 235231, Size: 235231,
MediaType: MediaTypeLayer, MediaType: v1.MediaTypeImageLayerGzip,
}, },
{ {
Digest: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), Digest: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
Size: 639152, Size: 639152,
MediaType: MediaTypeLayer, MediaType: v1.MediaTypeImageLayerGzip,
}, },
} }
@ -195,7 +196,7 @@ func TestBuilder(t *testing.T) {
if target.Digest != configDigest { if target.Digest != configDigest {
t.Fatalf("unexpected digest in target: %s", target.Digest.String()) t.Fatalf("unexpected digest in target: %s", target.Digest.String())
} }
if target.MediaType != MediaTypeConfig { if target.MediaType != v1.MediaTypeImageConfig {
t.Fatalf("unexpected media type in target: %s", target.MediaType) t.Fatalf("unexpected media type in target: %s", target.MediaType)
} }
if target.Size != 3153 { if target.Size != 3153 {

View file

@ -8,32 +8,15 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/manifest" "github.com/docker/distribution/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) "github.com/opencontainers/image-spec/specs-go/v1"
const (
// MediaTypeManifest specifies the mediaType for the current version.
MediaTypeManifest = "application/vnd.oci.image.manifest.v1+json"
// MediaTypeConfig specifies the mediaType for the image configuration.
MediaTypeConfig = "application/vnd.oci.image.config.v1+json"
// MediaTypePluginConfig specifies the mediaType for plugin configuration.
MediaTypePluginConfig = "application/vnd.docker.plugin.v1+json"
// MediaTypeLayer is the mediaType used for layers referenced by the manifest.
MediaTypeLayer = "application/vnd.oci.image.layer.v1.tar+gzip"
// MediaTypeForeignLayer is the mediaType used for layers that must be
// downloaded from foreign URLs.
MediaTypeForeignLayer = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
) )
var ( var (
// SchemaVersion provides a pre-initialized version structure for this // SchemaVersion provides a pre-initialized version structure for this
// packages version of the manifest. // packages version of the manifest.
SchemaVersion = manifest.Versioned{ SchemaVersion = manifest.Versioned{
SchemaVersion: 2, // Mike: todo this could confusing cause oci version 1 is closer to docker 2 than 1 SchemaVersion: 2, // TODO: (mikebrow/stevvooe) this could be confusing cause oci version 1 is closer to docker 2 than 1
MediaType: MediaTypeManifest, MediaType: v1.MediaTypeImageManifest,
} }
) )
@ -46,15 +29,15 @@ func init() {
} }
dgst := digest.FromBytes(b) dgst := digest.FromBytes(b)
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifest}, err return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageManifest}, err
} }
err := distribution.RegisterManifestSchema(MediaTypeManifest, ocischemaFunc) err := distribution.RegisterManifestSchema(v1.MediaTypeImageManifest, ocischemaFunc)
if err != nil { if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err)) panic(fmt.Sprintf("Unable to register manifest: %s", err))
} }
} }
// Manifest defines a schema2 manifest. // Manifest defines a ocischema manifest.
type Manifest struct { type Manifest struct {
manifest.Versioned manifest.Versioned
@ -64,6 +47,9 @@ type Manifest struct {
// Layers lists descriptors for the layers referenced by the // Layers lists descriptors for the layers referenced by the
// configuration. // configuration.
Layers []distribution.Descriptor `json:"layers"` Layers []distribution.Descriptor `json:"layers"`
// Annotations contains arbitrary metadata for the image manifest.
Annotations map[string]string `json:"annotations,omitempty"`
} }
// References returnes the descriptors of this manifests references. // References returnes the descriptors of this manifests references.
@ -71,6 +57,7 @@ func (m Manifest) References() []distribution.Descriptor {
references := make([]distribution.Descriptor, 0, 1+len(m.Layers)) references := make([]distribution.Descriptor, 0, 1+len(m.Layers))
references = append(references, m.Config) references = append(references, m.Config)
references = append(references, m.Layers...) references = append(references, m.Layers...)
// TODO: (mikebrow/stevvooe) should we return annotations as references
return references return references
} }

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
var expectedManifestSerialization = []byte(`{ var expectedManifestSerialization = []byte(`{
@ -32,13 +33,13 @@ func TestManifest(t *testing.T) {
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
Size: 985, Size: 985,
MediaType: MediaTypeConfig, MediaType: v1.MediaTypeImageConfig,
}, },
Layers: []distribution.Descriptor{ Layers: []distribution.Descriptor{
{ {
Digest: "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b", Digest: "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b",
Size: 153263, Size: 153263,
MediaType: MediaTypeLayer, MediaType: v1.MediaTypeImageLayerGzip,
}, },
}, },
} }
@ -48,9 +49,9 @@ func TestManifest(t *testing.T) {
t.Fatalf("error creating DeserializedManifest: %v", err) t.Fatalf("error creating DeserializedManifest: %v", err)
} }
mediaType, canonical, err := deserialized.Payload() mediaType, canonical, _ := deserialized.Payload()
if mediaType != MediaTypeManifest { if mediaType != v1.MediaTypeImageManifest {
t.Fatalf("unexpected media type: %s", mediaType) t.Fatalf("unexpected media type: %s", mediaType)
} }
@ -82,7 +83,7 @@ func TestManifest(t *testing.T) {
if target.Digest != "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b" { if target.Digest != "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b" {
t.Fatalf("unexpected digest in target: %s", target.Digest.String()) t.Fatalf("unexpected digest in target: %s", target.Digest.String())
} }
if target.MediaType != MediaTypeConfig { if target.MediaType != v1.MediaTypeImageConfig {
t.Fatalf("unexpected media type in target: %s", target.MediaType) t.Fatalf("unexpected media type in target: %s", target.MediaType)
} }
if target.Size != 985 { if target.Size != 985 {
@ -102,7 +103,7 @@ func TestManifest(t *testing.T) {
if references[1].Digest != "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b" { 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[1].MediaType != MediaTypeLayer { if references[1].MediaType != v1.MediaTypeImageLayerGzip {
t.Fatalf("unexpected media type in reference: %s", references[0].MediaType) t.Fatalf("unexpected media type in reference: %s", references[0].MediaType)
} }
if references[1].Size != 153263 { if references[1].Size != 153263 {

View file

@ -18,6 +18,7 @@ import (
"github.com/docker/distribution/registry/auth" "github.com/docker/distribution/registry/auth"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
// These constants determine which architecture and OS to choose from a // These constants determine which architecture and OS to choose from a
@ -76,7 +77,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
supportsSchema2 := false supportsSchema2 := false
supportsManifestList := false supportsManifestList := false
supportsOCISchema := false supportsOCISchema := false
supportsOCIManifestList := false supportsOCIImageIndex := false
// this parsing of Accept headers is not quite as full-featured as godoc.org's parser, but we don't care about "q=" values // this parsing of Accept headers is not quite as full-featured as godoc.org's parser, but we don't care about "q=" values
// https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202 // https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202
for _, acceptHeader := range r.Header["Accept"] { for _, acceptHeader := range r.Header["Accept"] {
@ -100,15 +101,15 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
if mediaType == manifestlist.MediaTypeManifestList { if mediaType == manifestlist.MediaTypeManifestList {
supportsManifestList = true supportsManifestList = true
} }
if mediaType == ocischema.MediaTypeManifest { if mediaType == v1.MediaTypeImageManifest {
supportsOCISchema = true supportsOCISchema = true
} }
if mediaType == manifestlist.MediaTypeOCIManifestList { if mediaType == v1.MediaTypeImageIndex {
supportsOCIManifestList = true supportsOCIImageIndex = true
} }
} }
} }
supportsOCI := supportsOCISchema || supportsOCIManifestList supportsOCI := supportsOCISchema || supportsOCIImageIndex
var manifest distribution.Manifest var manifest distribution.Manifest
if imh.Tag != "" { if imh.Tag != "" {
@ -151,15 +152,15 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest) schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList) manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == ocischema.MediaTypeManifest) isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == v1.MediaTypeImageManifest)
isAnOCIManifestList := isManifestList && (manifestList.MediaType == manifestlist.MediaTypeOCIManifestList) isAnOCIImageIndex := isManifestList && (manifestList.MediaType == v1.MediaTypeImageIndex)
if (isSchema2 && !isAnOCIManifest) && (supportsOCISchema && !supportsSchema2) { if (isSchema2 && !isAnOCIManifest) && (supportsOCISchema && !supportsSchema2) {
fmt.Printf("\n\nmanifest is schema2, but accept header only supports OCISchema \n\n") fmt.Printf("\n\nmanifest is schema2, but accept header only supports OCISchema \n\n")
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
if (isManifestList && !isAnOCIManifestList) && (supportsOCIManifestList && !supportsManifestList) { if (isManifestList && !isAnOCIImageIndex) && (supportsOCIImageIndex && !supportsManifestList) {
fmt.Printf("\n\nmanifestlist is not OCI, but accept header only supports an OCI manifestlist\n\n") fmt.Printf("\n\nmanifestlist is not OCI, but accept header only supports an OCI manifestlist\n\n")
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@ -169,7 +170,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
if isAnOCIManifestList && (!supportsOCIManifestList && supportsManifestList) { if isAnOCIImageIndex && (!supportsOCIImageIndex && supportsManifestList) {
fmt.Printf("\n\nmanifestlist is OCI, but accept header only supports non-OCI manifestlists\n\n") fmt.Printf("\n\nmanifestlist is OCI, but accept header only supports non-OCI manifestlists\n\n")
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@ -185,7 +186,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
if err != nil { if err != nil {
return return
} }
} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIManifestList) { } else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIImageIndex) {
// Rewrite manifest in schema1 format // Rewrite manifest in schema1 format
ctxu.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String()) ctxu.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
@ -243,7 +244,7 @@ func (imh *manifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Requ
supportsSchema2 := false supportsSchema2 := false
supportsManifestList := false supportsManifestList := false
supportsOCISchema := false supportsOCISchema := false
supportsOCIManifestList := false supportsOCIImageIndex := false
// this parsing of Accept headers is not quite as full-featured as godoc.org's parser, but we don't care about "q=" values // this parsing of Accept headers is not quite as full-featured as godoc.org's parser, but we don't care about "q=" values
// https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202 // https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202
@ -268,15 +269,15 @@ func (imh *manifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Requ
if mediaType == manifestlist.MediaTypeManifestList { if mediaType == manifestlist.MediaTypeManifestList {
supportsManifestList = true supportsManifestList = true
} }
if mediaType == ocischema.MediaTypeManifest { if mediaType == v1.MediaTypeImageManifest {
supportsOCISchema = true supportsOCISchema = true
} }
if mediaType == manifestlist.MediaTypeOCIManifestList { if mediaType == v1.MediaTypeImageIndex {
supportsOCIManifestList = true supportsOCIImageIndex = true
} }
} }
} }
supportsOCI := supportsOCISchema || supportsOCIManifestList supportsOCI := supportsOCISchema || supportsOCIImageIndex
ctxu.GetLogger(imh).Debug("GetImageManifest") ctxu.GetLogger(imh).Debug("GetImageManifest")
manifests, err := imh.Repository.Manifests(imh) manifests, err := imh.Repository.Manifests(imh)
@ -325,14 +326,14 @@ func (imh *manifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Requ
schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest) schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList) manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == ocischema.MediaTypeManifest) isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == v1.MediaTypeImageManifest)
isAnOCIManifestList := isManifestList && (manifestList.MediaType == manifestlist.MediaTypeOCIManifestList) isAnOCIImageIndex := isManifestList && (manifestList.MediaType == v1.MediaTypeImageIndex)
badCombinations := [][]bool{ badCombinations := [][]bool{
{isSchema2 && !isAnOCIManifest, supportsOCISchema && !supportsSchema2}, {isSchema2 && !isAnOCIManifest, supportsOCISchema && !supportsSchema2},
{isManifestList && !isAnOCIManifestList, supportsOCIManifestList && !supportsManifestList}, {isManifestList && !isAnOCIImageIndex, supportsOCIImageIndex && !supportsManifestList},
{isAnOCIManifest, !supportsOCISchema && supportsSchema2}, {isAnOCIManifest, !supportsOCISchema && supportsSchema2},
{isAnOCIManifestList, !supportsOCIManifestList && supportsManifestList}, {isAnOCIImageIndex, !supportsOCIImageIndex && supportsManifestList},
} }
for i, combo := range badCombinations { for i, combo := range badCombinations {
if combo[0] && combo[1] { if combo[0] && combo[1] {
@ -360,7 +361,7 @@ func (imh *manifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Requ
if err != nil { if err != nil {
return return
} }
} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIManifestList) { } else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIImageIndex) {
// Rewrite manifest in schema1 format // Rewrite manifest in schema1 format
dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String()) dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
@ -499,7 +500,7 @@ func (imh *manifestHandler) PutManifest(w http.ResponseWriter, r *http.Request)
return return
} }
isAnOCIManifest := mediaType == ocischema.MediaTypeManifest || mediaType == manifestlist.MediaTypeOCIManifestList isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex
if isAnOCIManifest { if isAnOCIManifest {
fmt.Printf("\n\nPutting an OCI Manifest!\n\n\n") fmt.Printf("\n\nPutting an OCI Manifest!\n\n\n")
@ -615,6 +616,14 @@ func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest)
message := fmt.Sprintf("unknown manifest class for %s", m.Config.MediaType) message := fmt.Sprintf("unknown manifest class for %s", m.Config.MediaType)
return errcode.ErrorCodeDenied.WithMessage(message) return errcode.ErrorCodeDenied.WithMessage(message)
} }
case *ocischema.DeserializedManifest:
switch m.Config.MediaType {
case v1.MediaTypeImageConfig:
class = "image"
default:
message := fmt.Sprintf("unknown manifest class for %s", m.Config.MediaType)
return errcode.ErrorCodeDenied.WithMessage(message)
}
} }
if class == "" { if class == "" {

View file

@ -13,6 +13,7 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
// A ManifestHandler gets and puts manifests of a particular type. // A ManifestHandler gets and puts manifests of a particular type.
@ -101,9 +102,9 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ..
switch versioned.MediaType { switch versioned.MediaType {
case schema2.MediaTypeManifest: case schema2.MediaTypeManifest:
return ms.schema2Handler.Unmarshal(ctx, dgst, content) return ms.schema2Handler.Unmarshal(ctx, dgst, content)
case ocischema.MediaTypeManifest: case v1.MediaTypeImageManifest:
return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) return ms.ocischemaHandler.Unmarshal(ctx, dgst, content)
case manifestlist.MediaTypeManifestList, manifestlist.MediaTypeOCIManifestList: case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex:
return ms.manifestListHandler.Unmarshal(ctx, dgst, content) return ms.manifestListHandler.Unmarshal(ctx, dgst, content)
default: default:
return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)} return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)}

View file

@ -9,6 +9,7 @@ import (
"github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/docker/distribution/manifest/ocischema" "github.com/docker/distribution/manifest/ocischema"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
//ocischemaManifestHandler is a ManifestHandler that covers ocischema manifests. //ocischemaManifestHandler is a ManifestHandler that covers ocischema manifests.
@ -79,7 +80,8 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc
var err error var err error
switch descriptor.MediaType { switch descriptor.MediaType {
case ocischema.MediaTypeForeignLayer: // TODO: mikebrow/steveoe verify we should treat oci nondistributable like foreign layers?
case v1.MediaTypeImageLayerNonDistributable, v1.MediaTypeImageLayerNonDistributableGzip:
// Clients download this layer from an external URL, so do not check for // Clients download this layer from an external URL, so do not check for
// its presense. // its presense.
if len(descriptor.URLs) == 0 { if len(descriptor.URLs) == 0 {
@ -95,7 +97,7 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc
break break
} }
} }
case ocischema.MediaTypeManifest: case v1.MediaTypeImageManifest:
var exists bool var exists bool
exists, err = manifestService.Exists(ctx, descriptor.Digest) exists, err = manifestService.Exists(ctx, descriptor.Digest)
if err != nil || !exists { if err != nil || !exists {

View file

@ -9,9 +9,10 @@ import (
"github.com/docker/distribution/manifest" "github.com/docker/distribution/manifest"
"github.com/docker/distribution/manifest/ocischema" "github.com/docker/distribution/manifest/ocischema"
"github.com/docker/distribution/registry/storage/driver/inmemory" "github.com/docker/distribution/registry/storage/driver/inmemory"
"github.com/opencontainers/image-spec/specs-go/v1"
) )
func TestVerifyOCIManifestForeignLayer(t *testing.T) { func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
ctx := context.Background() ctx := context.Background()
inmemoryDriver := inmemory.New() inmemoryDriver := inmemory.New()
registry := createRegistry(t, inmemoryDriver, registry := createRegistry(t, inmemoryDriver,
@ -20,26 +21,26 @@ func TestVerifyOCIManifestForeignLayer(t *testing.T) {
repo := makeRepository(t, registry, "test") repo := makeRepository(t, registry, "test")
manifestService := makeManifestService(t, repo) manifestService := makeManifestService(t, repo)
config, err := repo.Blobs(ctx).Put(ctx, ocischema.MediaTypeConfig, nil) config, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageConfig, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
layer, err := repo.Blobs(ctx).Put(ctx, ocischema.MediaTypeLayer, nil) layer, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageLayerGzip, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
foreignLayer := distribution.Descriptor{ nonDistributableLayer := distribution.Descriptor{
Digest: "sha256:463435349086340864309863409683460843608348608934092322395278926a", Digest: "sha256:463435349086340864309863409683460843608348608934092322395278926a",
Size: 6323, Size: 6323,
MediaType: ocischema.MediaTypeForeignLayer, MediaType: v1.MediaTypeImageLayerNonDistributableGzip,
} }
template := ocischema.Manifest{ template := ocischema.Manifest{
Versioned: manifest.Versioned{ Versioned: manifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: ocischema.MediaTypeManifest, MediaType: v1.MediaTypeImageManifest,
}, },
Config: config, Config: config,
} }
@ -52,58 +53,58 @@ func TestVerifyOCIManifestForeignLayer(t *testing.T) {
cases := []testcase{ cases := []testcase{
{ {
foreignLayer, nonDistributableLayer,
nil, nil,
errMissingURL, errMissingURL,
}, },
{ {
// regular layers may have foreign urls // regular layers may have foreign urls (non-Distributable Layers)
layer, layer,
[]string{"http://foo/bar"}, []string{"http://foo/bar"},
nil, nil,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"file:///local/file"}, []string{"file:///local/file"},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"http://foo/bar#baz"}, []string{"http://foo/bar#baz"},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{""}, []string{""},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"https://foo/bar", ""}, []string{"https://foo/bar", ""},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"", "https://foo/bar"}, []string{"", "https://foo/bar"},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"http://nope/bar"}, []string{"http://nope/bar"},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"http://foo/nope"}, []string{"http://foo/nope"},
errInvalidURL, errInvalidURL,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"http://foo/bar"}, []string{"http://foo/bar"},
nil, nil,
}, },
{ {
foreignLayer, nonDistributableLayer,
[]string{"https://foo/bar"}, []string{"https://foo/bar"},
nil, nil,
}, },