OCI media types; annotation support; oci index
Signed-off-by: Mike Brown <brownwm@us.ibm.com>master
							parent
							
								
									6fcea22b0a
								
							
						
					
					
						commit
						c94f28805e
					
				
							
								
								
									
										8
									
								
								blobs.go
								
								
								
								
							
							
						
						
									
										8
									
								
								blobs.go
								
								
								
								
							|  | @ -10,6 +10,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
|  | @ -72,6 +73,13 @@ type Descriptor struct { | |||
| 	// URLs contains the source URLs of this content.
 | ||||
| 	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
 | ||||
| 	// other options have been exhausted. Much of the type relationships
 | ||||
| 	// depend on the simplicity of this type.
 | ||||
|  |  | |||
|  | @ -7,16 +7,13 @@ import ( | |||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/manifest" | ||||
| 	"github.com/docker/distribution/manifest/ocischema" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// MediaTypeManifestList specifies the mediaType for manifest lists.
 | ||||
| 	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
 | ||||
|  | @ -30,7 +27,7 @@ var SchemaVersion = manifest.Versioned{ | |||
| // packages OCIschema version of the manifest.
 | ||||
| var OCISchemaVersion = manifest.Versioned{ | ||||
| 	SchemaVersion: 2, | ||||
| 	MediaType:     MediaTypeOCIManifestList, | ||||
| 	MediaType:     v1.MediaTypeImageIndex, | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
|  | @ -92,6 +89,9 @@ type ManifestList struct { | |||
| 
 | ||||
| 	// Config references the image configuration as a blob.
 | ||||
| 	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
 | ||||
|  | @ -119,7 +119,7 @@ type DeserializedManifestList struct { | |||
| // and its JSON representation.
 | ||||
| func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) { | ||||
| 	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{ | ||||
| 			Versioned: OCISchemaVersion, | ||||
| 		} | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| var expectedManifestListSerialization = []byte(`{ | ||||
|  | @ -110,9 +111,9 @@ func TestManifestList(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| var expectedOCIManifestListSerialization = []byte(`{ | ||||
| var expectedOCIImageIndexSerialization = []byte(`{ | ||||
|    "schemaVersion": 2, | ||||
|    "mediaType": "application/vnd.oci.image.manifest.list.v1+json", | ||||
|    "mediaType": "application/vnd.oci.image.index.v1+json", | ||||
|    "manifests": [ | ||||
|       { | ||||
|          "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{ | ||||
| 		{ | ||||
| 			Descriptor: distribution.Descriptor{ | ||||
|  | @ -172,7 +173,7 @@ func TestOCIManifestList(t *testing.T) { | |||
| 
 | ||||
| 	mediaType, canonical, _ := deserialized.Payload() | ||||
| 
 | ||||
| 	if mediaType != MediaTypeOCIManifestList { | ||||
| 	if mediaType != v1.MediaTypeImageIndex { | ||||
| 		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.
 | ||||
| 	if !bytes.Equal(expectedOCIManifestListSerialization, canonical) { | ||||
| 		t.Fatalf("manifest bytes not equal: %q != %q", string(canonical), string(expectedOCIManifestListSerialization)) | ||||
| 	if !bytes.Equal(expectedOCIImageIndexSerialization, canonical) { | ||||
| 		t.Fatalf("manifest bytes not equal: %q != %q", string(canonical), string(expectedOCIImageIndexSerialization)) | ||||
| 	} | ||||
| 
 | ||||
| 	var unmarshalled DeserializedManifestList | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import ( | |||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| // builder is a type for constructing manifests.
 | ||||
|  | @ -48,7 +49,7 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) { | |||
| 	case nil: | ||||
| 		// Override MediaType, since Put always replaces the specified media
 | ||||
| 		// type with application/octet-stream in the descriptor it returns.
 | ||||
| 		m.Config.MediaType = MediaTypeConfig | ||||
| 		m.Config.MediaType = v1.MediaTypeImageConfig | ||||
| 		return FromStruct(m) | ||||
| 	case distribution.ErrBlobUnknown: | ||||
| 		// nop
 | ||||
|  | @ -57,10 +58,10 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) { | |||
| 	} | ||||
| 
 | ||||
| 	// 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
 | ||||
| 	// type with application/octet-stream in the descriptor it returns.
 | ||||
| 	m.Config.MediaType = MediaTypeConfig | ||||
| 	m.Config.MediaType = v1.MediaTypeImageConfig | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| type mockBlobService struct { | ||||
|  | @ -151,17 +152,17 @@ func TestBuilder(t *testing.T) { | |||
| 		{ | ||||
| 			Digest:    digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), | ||||
| 			Size:      5312, | ||||
| 			MediaType: MediaTypeLayer, | ||||
| 			MediaType: v1.MediaTypeImageLayerGzip, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Digest:    digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"), | ||||
| 			Size:      235231, | ||||
| 			MediaType: MediaTypeLayer, | ||||
| 			MediaType: v1.MediaTypeImageLayerGzip, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Digest:    digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), | ||||
| 			Size:      639152, | ||||
| 			MediaType: MediaTypeLayer, | ||||
| 			MediaType: v1.MediaTypeImageLayerGzip, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
|  | @ -195,7 +196,7 @@ func TestBuilder(t *testing.T) { | |||
| 	if target.Digest != configDigest { | ||||
| 		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) | ||||
| 	} | ||||
| 	if target.Size != 3153 { | ||||
|  |  | |||
|  | @ -8,32 +8,15 @@ import ( | |||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/manifest" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| ) | ||||
| 
 | ||||
| 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" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// SchemaVersion provides a pre-initialized version structure for this
 | ||||
| 	// packages version of the manifest.
 | ||||
| 	SchemaVersion = manifest.Versioned{ | ||||
| 		SchemaVersion: 2, // Mike: todo this could confusing cause oci version 1 is closer to docker 2 than 1
 | ||||
| 		MediaType:     MediaTypeManifest, | ||||
| 		SchemaVersion: 2, // TODO: (mikebrow/stevvooe) this could be confusing cause oci version 1 is closer to docker 2 than 1
 | ||||
| 		MediaType:     v1.MediaTypeImageManifest, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
|  | @ -46,15 +29,15 @@ func init() { | |||
| 		} | ||||
| 
 | ||||
| 		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 { | ||||
| 		panic(fmt.Sprintf("Unable to register manifest: %s", err)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Manifest defines a schema2 manifest.
 | ||||
| // Manifest defines a ocischema manifest.
 | ||||
| type Manifest struct { | ||||
| 	manifest.Versioned | ||||
| 
 | ||||
|  | @ -64,6 +47,9 @@ type Manifest struct { | |||
| 	// Layers lists descriptors for the layers referenced by the
 | ||||
| 	// configuration.
 | ||||
| 	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.
 | ||||
|  | @ -71,6 +57,7 @@ func (m Manifest) References() []distribution.Descriptor { | |||
| 	references := make([]distribution.Descriptor, 0, 1+len(m.Layers)) | ||||
| 	references = append(references, m.Config) | ||||
| 	references = append(references, m.Layers...) | ||||
| 	// TODO: (mikebrow/stevvooe) should we return annotations as references
 | ||||
| 	return references | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| var expectedManifestSerialization = []byte(`{ | ||||
|  | @ -32,13 +33,13 @@ func TestManifest(t *testing.T) { | |||
| 		Config: distribution.Descriptor{ | ||||
| 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | ||||
| 			Size:      985, | ||||
| 			MediaType: MediaTypeConfig, | ||||
| 			MediaType: v1.MediaTypeImageConfig, | ||||
| 		}, | ||||
| 		Layers: []distribution.Descriptor{ | ||||
| 			{ | ||||
| 				Digest:    "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b", | ||||
| 				Size:      153263, | ||||
| 				MediaType: MediaTypeLayer, | ||||
| 				MediaType: v1.MediaTypeImageLayerGzip, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | @ -48,9 +49,9 @@ func TestManifest(t *testing.T) { | |||
| 		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) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -82,7 +83,7 @@ func TestManifest(t *testing.T) { | |||
| 	if target.Digest != "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b" { | ||||
| 		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) | ||||
| 	} | ||||
| 	if target.Size != 985 { | ||||
|  | @ -102,7 +103,7 @@ func TestManifest(t *testing.T) { | |||
| 	if references[1].Digest != "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b" { | ||||
| 		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) | ||||
| 	} | ||||
| 	if references[1].Size != 153263 { | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import ( | |||
| 	"github.com/docker/distribution/registry/auth" | ||||
| 	"github.com/gorilla/handlers" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| // 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 | ||||
| 	supportsManifestList := 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
 | ||||
| 	// https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202
 | ||||
| 	for _, acceptHeader := range r.Header["Accept"] { | ||||
|  | @ -100,15 +101,15 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | |||
| 			if mediaType == manifestlist.MediaTypeManifestList { | ||||
| 				supportsManifestList = true | ||||
| 			} | ||||
| 			if mediaType == ocischema.MediaTypeManifest { | ||||
| 			if mediaType == v1.MediaTypeImageManifest { | ||||
| 				supportsOCISchema = true | ||||
| 			} | ||||
| 			if mediaType == manifestlist.MediaTypeOCIManifestList { | ||||
| 				supportsOCIManifestList = true | ||||
| 			if mediaType == v1.MediaTypeImageIndex { | ||||
| 				supportsOCIImageIndex = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	supportsOCI := supportsOCISchema || supportsOCIManifestList | ||||
| 	supportsOCI := supportsOCISchema || supportsOCIImageIndex | ||||
| 
 | ||||
| 	var manifest distribution.Manifest | ||||
| 	if imh.Tag != "" { | ||||
|  | @ -151,15 +152,15 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | |||
| 
 | ||||
| 	schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest) | ||||
| 	manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList) | ||||
| 	isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == ocischema.MediaTypeManifest) | ||||
| 	isAnOCIManifestList := isManifestList && (manifestList.MediaType == manifestlist.MediaTypeOCIManifestList) | ||||
| 	isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == v1.MediaTypeImageManifest) | ||||
| 	isAnOCIImageIndex := isManifestList && (manifestList.MediaType == v1.MediaTypeImageIndex) | ||||
| 
 | ||||
| 	if (isSchema2 && !isAnOCIManifest) && (supportsOCISchema && !supportsSchema2) { | ||||
| 		fmt.Printf("\n\nmanifest is schema2, but accept header only supports OCISchema \n\n") | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		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") | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
|  | @ -169,7 +170,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | |||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		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") | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
|  | @ -185,7 +186,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | |||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIManifestList) { | ||||
| 	} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIImageIndex) { | ||||
| 		// Rewrite manifest in schema1 format
 | ||||
| 		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 | ||||
| 	supportsManifestList := 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
 | ||||
| 	// 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 { | ||||
| 				supportsManifestList = true | ||||
| 			} | ||||
| 			if mediaType == ocischema.MediaTypeManifest { | ||||
| 			if mediaType == v1.MediaTypeImageManifest { | ||||
| 				supportsOCISchema = true | ||||
| 			} | ||||
| 			if mediaType == manifestlist.MediaTypeOCIManifestList { | ||||
| 				supportsOCIManifestList = true | ||||
| 			if mediaType == v1.MediaTypeImageIndex { | ||||
| 				supportsOCIImageIndex = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	supportsOCI := supportsOCISchema || supportsOCIManifestList | ||||
| 	supportsOCI := supportsOCISchema || supportsOCIImageIndex | ||||
| 
 | ||||
| 	ctxu.GetLogger(imh).Debug("GetImageManifest") | ||||
| 	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) | ||||
| 	manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList) | ||||
| 	isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == ocischema.MediaTypeManifest) | ||||
| 	isAnOCIManifestList := isManifestList && (manifestList.MediaType == manifestlist.MediaTypeOCIManifestList) | ||||
| 	isAnOCIManifest := isSchema2 && (schema2Manifest.MediaType == v1.MediaTypeImageManifest) | ||||
| 	isAnOCIImageIndex := isManifestList && (manifestList.MediaType == v1.MediaTypeImageIndex) | ||||
| 
 | ||||
| 	badCombinations := [][]bool{ | ||||
| 		{isSchema2 && !isAnOCIManifest, supportsOCISchema && !supportsSchema2}, | ||||
| 		{isManifestList && !isAnOCIManifestList, supportsOCIManifestList && !supportsManifestList}, | ||||
| 		{isManifestList && !isAnOCIImageIndex, supportsOCIImageIndex && !supportsManifestList}, | ||||
| 		{isAnOCIManifest, !supportsOCISchema && supportsSchema2}, | ||||
| 		{isAnOCIManifestList, !supportsOCIManifestList && supportsManifestList}, | ||||
| 		{isAnOCIImageIndex, !supportsOCIImageIndex && supportsManifestList}, | ||||
| 	} | ||||
| 	for i, combo := range badCombinations { | ||||
| 		if combo[0] && combo[1] { | ||||
|  | @ -360,7 +361,7 @@ func (imh *manifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Requ | |||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIManifestList) { | ||||
| 	} else if imh.Tag != "" && isManifestList && !(supportsManifestList || supportsOCIImageIndex) { | ||||
| 		// Rewrite manifest in schema1 format
 | ||||
| 		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 | ||||
| 	} | ||||
| 
 | ||||
| 	isAnOCIManifest := mediaType == ocischema.MediaTypeManifest || mediaType == manifestlist.MediaTypeOCIManifestList | ||||
| 	isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex | ||||
| 
 | ||||
| 	if isAnOCIManifest { | ||||
| 		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) | ||||
| 			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 == "" { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import ( | |||
| 	"github.com/docker/distribution/manifest/schema1" | ||||
| 	"github.com/docker/distribution/manifest/schema2" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| // 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 { | ||||
| 		case schema2.MediaTypeManifest: | ||||
| 			return ms.schema2Handler.Unmarshal(ctx, dgst, content) | ||||
| 		case ocischema.MediaTypeManifest: | ||||
| 		case v1.MediaTypeImageManifest: | ||||
| 			return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) | ||||
| 		case manifestlist.MediaTypeManifestList, manifestlist.MediaTypeOCIManifestList: | ||||
| 		case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex: | ||||
| 			return ms.manifestListHandler.Unmarshal(ctx, dgst, content) | ||||
| 		default: | ||||
| 			return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)} | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/manifest/ocischema" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| ) | ||||
| 
 | ||||
| //ocischemaManifestHandler is a ManifestHandler that covers ocischema manifests.
 | ||||
|  | @ -79,7 +80,8 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc | |||
| 		var err error | ||||
| 
 | ||||
| 		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
 | ||||
| 			// its presense.
 | ||||
| 			if len(descriptor.URLs) == 0 { | ||||
|  | @ -95,7 +97,7 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc | |||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		case ocischema.MediaTypeManifest: | ||||
| 		case v1.MediaTypeImageManifest: | ||||
| 			var exists bool | ||||
| 			exists, err = manifestService.Exists(ctx, descriptor.Digest) | ||||
| 			if err != nil || !exists { | ||||
|  |  | |||
|  | @ -9,9 +9,10 @@ import ( | |||
| 	"github.com/docker/distribution/manifest" | ||||
| 	"github.com/docker/distribution/manifest/ocischema" | ||||
| 	"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() | ||||
| 	inmemoryDriver := inmemory.New() | ||||
| 	registry := createRegistry(t, inmemoryDriver, | ||||
|  | @ -20,26 +21,26 @@ func TestVerifyOCIManifestForeignLayer(t *testing.T) { | |||
| 	repo := makeRepository(t, registry, "test") | ||||
| 	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 { | ||||
| 		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 { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	foreignLayer := distribution.Descriptor{ | ||||
| 	nonDistributableLayer := distribution.Descriptor{ | ||||
| 		Digest:    "sha256:463435349086340864309863409683460843608348608934092322395278926a", | ||||
| 		Size:      6323, | ||||
| 		MediaType: ocischema.MediaTypeForeignLayer, | ||||
| 		MediaType: v1.MediaTypeImageLayerNonDistributableGzip, | ||||
| 	} | ||||
| 
 | ||||
| 	template := ocischema.Manifest{ | ||||
| 		Versioned: manifest.Versioned{ | ||||
| 			SchemaVersion: 2, | ||||
| 			MediaType:     ocischema.MediaTypeManifest, | ||||
| 			MediaType:     v1.MediaTypeImageManifest, | ||||
| 		}, | ||||
| 		Config: config, | ||||
| 	} | ||||
|  | @ -52,58 +53,58 @@ func TestVerifyOCIManifestForeignLayer(t *testing.T) { | |||
| 
 | ||||
| 	cases := []testcase{ | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			nil, | ||||
| 			errMissingURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// regular layers may have foreign urls
 | ||||
| 			// regular layers may have foreign urls (non-Distributable Layers)
 | ||||
| 			layer, | ||||
| 			[]string{"http://foo/bar"}, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"file:///local/file"}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"http://foo/bar#baz"}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{""}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"https://foo/bar", ""}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"", "https://foo/bar"}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"http://nope/bar"}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"http://foo/nope"}, | ||||
| 			errInvalidURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"http://foo/bar"}, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			foreignLayer, | ||||
| 			nonDistributableLayer, | ||||
| 			[]string{"https://foo/bar"}, | ||||
| 			nil, | ||||
| 		}, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue