Merge pull request #1 from owtaylor/oci-media-types
Handle OCI manifests and image indexes without a media type Signed-off-by: Mike Brown <brownwm@us.ibm.com>
This commit is contained in:
		
						commit
						321d636e76
					
				
					 9 changed files with 411 additions and 26 deletions
				
			
		|  | @ -38,6 +38,13 @@ func init() { | ||||||
| 			return nil, distribution.Descriptor{}, err | 			return nil, distribution.Descriptor{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if m.MediaType != MediaTypeManifestList { | ||||||
|  | 			err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'", | ||||||
|  | 				MediaTypeManifestList, m.MediaType) | ||||||
|  | 
 | ||||||
|  | 			return nil, distribution.Descriptor{}, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		dgst := digest.FromBytes(b) | 		dgst := digest.FromBytes(b) | ||||||
| 		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err | 		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err | ||||||
| 	} | 	} | ||||||
|  | @ -53,6 +60,13 @@ func init() { | ||||||
| 			return nil, distribution.Descriptor{}, err | 			return nil, distribution.Descriptor{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex { | ||||||
|  | 			err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'", | ||||||
|  | 				v1.MediaTypeImageIndex, m.MediaType) | ||||||
|  | 
 | ||||||
|  | 			return nil, distribution.Descriptor{}, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		dgst := digest.FromBytes(b) | 		dgst := digest.FromBytes(b) | ||||||
| 		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err | 		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err | ||||||
| 	} | 	} | ||||||
|  | @ -130,15 +144,23 @@ type DeserializedManifestList struct { | ||||||
| // DeserializedManifestList which contains the resulting manifest list | // DeserializedManifestList which contains the resulting manifest list | ||||||
| // and its JSON representation. | // and its JSON representation. | ||||||
| func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) { | func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) { | ||||||
| 	var m ManifestList | 	var mediaType string | ||||||
| 	if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest { | 	if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest { | ||||||
| 		m = ManifestList{ | 		mediaType = v1.MediaTypeImageIndex | ||||||
| 			Versioned: OCISchemaVersion, |  | ||||||
| 		} |  | ||||||
| 	} else { | 	} else { | ||||||
| 		m = ManifestList{ | 		mediaType = MediaTypeManifestList | ||||||
| 			Versioned: SchemaVersion, | 	} | ||||||
| 		} | 
 | ||||||
|  | 	return FromDescriptorsWithMediaType(descriptors, mediaType) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // For testing purposes, it's useful to be able to specify the media type explicitly | ||||||
|  | func FromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) { | ||||||
|  | 	m := ManifestList{ | ||||||
|  | 		Versioned: manifest.Versioned{ | ||||||
|  | 			SchemaVersion: 2, | ||||||
|  | 			MediaType:     mediaType, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors)) | 	m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors)) | ||||||
|  | @ -183,5 +205,12 @@ func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) { | ||||||
| // Payload returns the raw content of the manifest list. The contents can be | // Payload returns the raw content of the manifest list. The contents can be | ||||||
| // used to calculate the content identifier. | // used to calculate the content identifier. | ||||||
| func (m DeserializedManifestList) Payload() (string, []byte, error) { | func (m DeserializedManifestList) Payload() (string, []byte, error) { | ||||||
| 	return m.MediaType, m.canonical, nil | 	var mediaType string | ||||||
|  | 	if m.MediaType == "" { | ||||||
|  | 		mediaType = v1.MediaTypeImageIndex | ||||||
|  | 	} else { | ||||||
|  | 		mediaType = m.MediaType | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return mediaType, m.canonical, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ var expectedManifestListSerialization = []byte(`{ | ||||||
|    ] |    ] | ||||||
| }`) | }`) | ||||||
| 
 | 
 | ||||||
| func TestManifestList(t *testing.T) { | func makeTestManifestList(t *testing.T, mediaType string) ([]ManifestDescriptor, *DeserializedManifestList) { | ||||||
| 	manifestDescriptors := []ManifestDescriptor{ | 	manifestDescriptors := []ManifestDescriptor{ | ||||||
| 		{ | 		{ | ||||||
| 			Descriptor: distribution.Descriptor{ | 			Descriptor: distribution.Descriptor{ | ||||||
|  | @ -65,11 +65,16 @@ func TestManifestList(t *testing.T) { | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	deserialized, err := FromDescriptors(manifestDescriptors) | 	deserialized, err := FromDescriptorsWithMediaType(manifestDescriptors, mediaType) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("error creating DeserializedManifestList: %v", err) | 		t.Fatalf("error creating DeserializedManifestList: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return manifestDescriptors, deserialized | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestManifestList(t *testing.T) { | ||||||
|  | 	manifestDescriptors, deserialized := makeTestManifestList(t, MediaTypeManifestList) | ||||||
| 	mediaType, canonical, _ := deserialized.Payload() | 	mediaType, canonical, _ := deserialized.Payload() | ||||||
| 
 | 
 | ||||||
| 	if mediaType != MediaTypeManifestList { | 	if mediaType != MediaTypeManifestList { | ||||||
|  | @ -160,7 +165,7 @@ var expectedOCIImageIndexSerialization = []byte(`{ | ||||||
|    ] |    ] | ||||||
| }`) | }`) | ||||||
| 
 | 
 | ||||||
| func TestOCIImageIndex(t *testing.T) { | func makeTestOCIImageIndex(t *testing.T, mediaType string) ([]ManifestDescriptor, *DeserializedManifestList) { | ||||||
| 	manifestDescriptors := []ManifestDescriptor{ | 	manifestDescriptors := []ManifestDescriptor{ | ||||||
| 		{ | 		{ | ||||||
| 			Descriptor: distribution.Descriptor{ | 			Descriptor: distribution.Descriptor{ | ||||||
|  | @ -196,11 +201,17 @@ func TestOCIImageIndex(t *testing.T) { | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	deserialized, err := FromDescriptors(manifestDescriptors) | 	deserialized, err := FromDescriptorsWithMediaType(manifestDescriptors, mediaType) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("error creating DeserializedManifestList: %v", err) | 		t.Fatalf("error creating DeserializedManifestList: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return manifestDescriptors, deserialized | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestOCIImageIndex(t *testing.T) { | ||||||
|  | 	manifestDescriptors, deserialized := makeTestOCIImageIndex(t, v1.MediaTypeImageIndex) | ||||||
|  | 
 | ||||||
| 	mediaType, canonical, _ := deserialized.Payload() | 	mediaType, canonical, _ := deserialized.Payload() | ||||||
| 
 | 
 | ||||||
| 	if mediaType != v1.MediaTypeImageIndex { | 	if mediaType != v1.MediaTypeImageIndex { | ||||||
|  | @ -241,3 +252,54 @@ func TestOCIImageIndex(t *testing.T) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func mediaTypeTest(t *testing.T, contentType string, mediaType string, shouldError bool) { | ||||||
|  | 	var m *DeserializedManifestList | ||||||
|  | 	if contentType == MediaTypeManifestList { | ||||||
|  | 		_, m = makeTestManifestList(t, mediaType) | ||||||
|  | 	} else { | ||||||
|  | 		_, m = makeTestOCIImageIndex(t, mediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, canonical, err := m.Payload() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("error getting payload, %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unmarshalled, descriptor, err := distribution.UnmarshalManifest( | ||||||
|  | 		contentType, | ||||||
|  | 		canonical) | ||||||
|  | 
 | ||||||
|  | 	if shouldError { | ||||||
|  | 		if err == nil { | ||||||
|  | 			t.Fatalf("bad content type should have produced error") | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("error unmarshaling manifest, %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		asManifest := unmarshalled.(*DeserializedManifestList) | ||||||
|  | 		if asManifest.MediaType != mediaType { | ||||||
|  | 			t.Fatalf("Bad media type '%v' as unmarshalled", asManifest.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if descriptor.MediaType != contentType { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for descriptor", descriptor.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		unmarshalledMediaType, _, _ := unmarshalled.Payload() | ||||||
|  | 		if unmarshalledMediaType != contentType { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for payload", unmarshalledMediaType) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMediaTypes(t *testing.T) { | ||||||
|  | 	mediaTypeTest(t, MediaTypeManifestList, "", true) | ||||||
|  | 	mediaTypeTest(t, MediaTypeManifestList, MediaTypeManifestList, false) | ||||||
|  | 	mediaTypeTest(t, MediaTypeManifestList, MediaTypeManifestList+"XXX", true) | ||||||
|  | 	mediaTypeTest(t, v1.MediaTypeImageIndex, "", false) | ||||||
|  | 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex, false) | ||||||
|  | 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex+"XXX", true) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -4,12 +4,13 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution" | 	"github.com/docker/distribution" | ||||||
|  | 	"github.com/docker/distribution/manifest" | ||||||
| 	"github.com/opencontainers/go-digest" | 	"github.com/opencontainers/go-digest" | ||||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // builder is a type for constructing manifests. | // builder is a type for constructing manifests. | ||||||
| type builder struct { | type Builder struct { | ||||||
| 	// bs is a BlobService used to publish the configuration blob. | 	// bs is a BlobService used to publish the configuration blob. | ||||||
| 	bs distribution.BlobService | 	bs distribution.BlobService | ||||||
| 
 | 
 | ||||||
|  | @ -22,26 +23,43 @@ type builder struct { | ||||||
| 
 | 
 | ||||||
| 	// Annotations contains arbitrary metadata relating to the targeted content. | 	// Annotations contains arbitrary metadata relating to the targeted content. | ||||||
| 	annotations map[string]string | 	annotations map[string]string | ||||||
|  | 
 | ||||||
|  | 	// For testing purposes | ||||||
|  | 	mediaType string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewManifestBuilder is used to build new manifests for the current schema | // NewManifestBuilder is used to build new manifests for the current schema | ||||||
| // version. It takes a BlobService so it can publish the configuration blob | // version. It takes a BlobService so it can publish the configuration blob | ||||||
| // as part of the Build process, and annotations. | // as part of the Build process, and annotations. | ||||||
| func NewManifestBuilder(bs distribution.BlobService, configJSON []byte, annotations map[string]string) distribution.ManifestBuilder { | func NewManifestBuilder(bs distribution.BlobService, configJSON []byte, annotations map[string]string) distribution.ManifestBuilder { | ||||||
| 	mb := &builder{ | 	mb := &Builder{ | ||||||
| 		bs:          bs, | 		bs:          bs, | ||||||
| 		configJSON:  make([]byte, len(configJSON)), | 		configJSON:  make([]byte, len(configJSON)), | ||||||
| 		annotations: annotations, | 		annotations: annotations, | ||||||
|  | 		mediaType:   v1.MediaTypeImageManifest, | ||||||
| 	} | 	} | ||||||
| 	copy(mb.configJSON, configJSON) | 	copy(mb.configJSON, configJSON) | ||||||
| 
 | 
 | ||||||
| 	return mb | 	return mb | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // For testing purposes, we want to be able to create an OCI image with | ||||||
|  | // either an MediaType either empty, or with the OCI image value | ||||||
|  | func (mb *Builder) SetMediaType(mediaType string) { | ||||||
|  | 	if mediaType != "" && mediaType != v1.MediaTypeImageManifest { | ||||||
|  | 		panic("Invalid media type for OCI image manifest") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mb.mediaType = mediaType | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Build produces a final manifest from the given references. | // Build produces a final manifest from the given references. | ||||||
| func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) { | func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) { | ||||||
| 	m := Manifest{ | 	m := Manifest{ | ||||||
| 		Versioned:   SchemaVersion, | 		Versioned: manifest.Versioned{ | ||||||
|  | 			SchemaVersion: 2, | ||||||
|  | 			MediaType:     mb.mediaType, | ||||||
|  | 		}, | ||||||
| 		Layers:      make([]distribution.Descriptor, len(mb.layers)), | 		Layers:      make([]distribution.Descriptor, len(mb.layers)), | ||||||
| 		Annotations: mb.annotations, | 		Annotations: mb.annotations, | ||||||
| 	} | 	} | ||||||
|  | @ -76,12 +94,12 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AppendReference adds a reference to the current ManifestBuilder. | // AppendReference adds a reference to the current ManifestBuilder. | ||||||
| func (mb *builder) AppendReference(d distribution.Describable) error { | func (mb *Builder) AppendReference(d distribution.Describable) error { | ||||||
| 	mb.layers = append(mb.layers, d.Descriptor()) | 	mb.layers = append(mb.layers, d.Descriptor()) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // References returns the current references added to this builder. | // References returns the current references added to this builder. | ||||||
| func (mb *builder) References() []distribution.Descriptor { | func (mb *Builder) References() []distribution.Descriptor { | ||||||
| 	return mb.layers | 	return mb.layers | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -97,6 +97,11 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if manifest.MediaType != "" && manifest.MediaType != v1.MediaTypeImageManifest { | ||||||
|  | 		return fmt.Errorf("if present, mediaType in manifest should be '%s' not '%s'", | ||||||
|  | 			v1.MediaTypeImageManifest, manifest.MediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	m.Manifest = manifest | 	m.Manifest = manifest | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -115,5 +120,5 @@ func (m *DeserializedManifest) MarshalJSON() ([]byte, error) { | ||||||
| // Payload returns the raw content of the manifest. The contents can be used to | // Payload returns the raw content of the manifest. The contents can be used to | ||||||
| // calculate the content identifier. | // calculate the content identifier. | ||||||
| func (m DeserializedManifest) Payload() (string, []byte, error) { | func (m DeserializedManifest) Payload() (string, []byte, error) { | ||||||
| 	return m.MediaType, m.canonical, nil | 	return v1.MediaTypeImageManifest, m.canonical, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution" | 	"github.com/docker/distribution" | ||||||
|  | 	"github.com/docker/distribution/manifest" | ||||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -36,9 +37,12 @@ var expectedManifestSerialization = []byte(`{ | ||||||
|    } |    } | ||||||
| }`) | }`) | ||||||
| 
 | 
 | ||||||
| func TestManifest(t *testing.T) { | func makeTestManifest(mediaType string) Manifest { | ||||||
| 	manifest := Manifest{ | 	return Manifest{ | ||||||
| 		Versioned: SchemaVersion, | 		Versioned: manifest.Versioned{ | ||||||
|  | 			SchemaVersion: 2, | ||||||
|  | 			MediaType:     mediaType, | ||||||
|  | 		}, | ||||||
| 		Config: distribution.Descriptor{ | 		Config: distribution.Descriptor{ | ||||||
| 			Digest:      "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | 			Digest:      "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | ||||||
| 			Size:        985, | 			Size:        985, | ||||||
|  | @ -55,6 +59,10 @@ func TestManifest(t *testing.T) { | ||||||
| 		}, | 		}, | ||||||
| 		Annotations: map[string]string{"hot": "potato"}, | 		Annotations: map[string]string{"hot": "potato"}, | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestManifest(t *testing.T) { | ||||||
|  | 	manifest := makeTestManifest(v1.MediaTypeImageManifest) | ||||||
| 
 | 
 | ||||||
| 	deserialized, err := FromStruct(manifest) | 	deserialized, err := FromStruct(manifest) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -131,3 +139,46 @@ func TestManifest(t *testing.T) { | ||||||
| 		t.Fatalf("unexpected annotation in reference: %s", references[1].Annotations["lettuce"]) | 		t.Fatalf("unexpected annotation in reference: %s", references[1].Annotations["lettuce"]) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func mediaTypeTest(t *testing.T, mediaType string, shouldError bool) { | ||||||
|  | 	manifest := makeTestManifest(mediaType) | ||||||
|  | 
 | ||||||
|  | 	deserialized, err := FromStruct(manifest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("error creating DeserializedManifest: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unmarshalled, descriptor, err := distribution.UnmarshalManifest( | ||||||
|  | 		v1.MediaTypeImageManifest, | ||||||
|  | 		deserialized.canonical) | ||||||
|  | 
 | ||||||
|  | 	if shouldError { | ||||||
|  | 		if err == nil { | ||||||
|  | 			t.Fatalf("bad content type should have produced error") | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("error unmarshaling manifest, %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		asManifest := unmarshalled.(*DeserializedManifest) | ||||||
|  | 		if asManifest.MediaType != mediaType { | ||||||
|  | 			t.Fatalf("Bad media type '%v' as unmarshalled", asManifest.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if descriptor.MediaType != v1.MediaTypeImageManifest { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for descriptor", descriptor.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		unmarshalledMediaType, _, _ := unmarshalled.Payload() | ||||||
|  | 		if unmarshalledMediaType != v1.MediaTypeImageManifest { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for payload", unmarshalledMediaType) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMediaTypes(t *testing.T) { | ||||||
|  | 	mediaTypeTest(t, "", false) | ||||||
|  | 	mediaTypeTest(t, v1.MediaTypeImageManifest, false) | ||||||
|  | 	mediaTypeTest(t, v1.MediaTypeImageManifest+"XXX", true) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -116,6 +116,12 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if manifest.MediaType != MediaTypeManifest { | ||||||
|  | 		return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", | ||||||
|  | 			MediaTypeManifest, manifest.MediaType) | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	m.Manifest = manifest | 	m.Manifest = manifest | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution" | 	"github.com/docker/distribution" | ||||||
|  | 	"github.com/docker/distribution/manifest" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var expectedManifestSerialization = []byte(`{ | var expectedManifestSerialization = []byte(`{ | ||||||
|  | @ -26,9 +27,12 @@ var expectedManifestSerialization = []byte(`{ | ||||||
|    ] |    ] | ||||||
| }`) | }`) | ||||||
| 
 | 
 | ||||||
| func TestManifest(t *testing.T) { | func makeTestManifest(mediaType string) Manifest { | ||||||
| 	manifest := Manifest{ | 	return Manifest{ | ||||||
| 		Versioned: SchemaVersion, | 		Versioned: manifest.Versioned{ | ||||||
|  | 			SchemaVersion: 2, | ||||||
|  | 			MediaType:     mediaType, | ||||||
|  | 		}, | ||||||
| 		Config: distribution.Descriptor{ | 		Config: distribution.Descriptor{ | ||||||
| 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | ||||||
| 			Size:      985, | 			Size:      985, | ||||||
|  | @ -42,6 +46,10 @@ func TestManifest(t *testing.T) { | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestManifest(t *testing.T) { | ||||||
|  | 	manifest := makeTestManifest(MediaTypeManifest) | ||||||
| 
 | 
 | ||||||
| 	deserialized, err := FromStruct(manifest) | 	deserialized, err := FromStruct(manifest) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -109,3 +117,46 @@ func TestManifest(t *testing.T) { | ||||||
| 		t.Fatalf("unexpected size in reference: %d", references[0].Size) | 		t.Fatalf("unexpected size in reference: %d", references[0].Size) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func mediaTypeTest(t *testing.T, mediaType string, shouldError bool) { | ||||||
|  | 	manifest := makeTestManifest(mediaType) | ||||||
|  | 
 | ||||||
|  | 	deserialized, err := FromStruct(manifest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("error creating DeserializedManifest: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unmarshalled, descriptor, err := distribution.UnmarshalManifest( | ||||||
|  | 		MediaTypeManifest, | ||||||
|  | 		deserialized.canonical) | ||||||
|  | 
 | ||||||
|  | 	if shouldError { | ||||||
|  | 		if err == nil { | ||||||
|  | 			t.Fatalf("bad content type should have produced error") | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("error unmarshaling manifest, %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		asManifest := unmarshalled.(*DeserializedManifest) | ||||||
|  | 		if asManifest.MediaType != mediaType { | ||||||
|  | 			t.Fatalf("Bad media type '%v' as unmarshalled", asManifest.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if descriptor.MediaType != MediaTypeManifest { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for descriptor", descriptor.MediaType) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		unmarshalledMediaType, _, _ := unmarshalled.Payload() | ||||||
|  | 		if unmarshalledMediaType != MediaTypeManifest { | ||||||
|  | 			t.Fatalf("Bad media type '%v' for payload", unmarshalledMediaType) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMediaTypes(t *testing.T) { | ||||||
|  | 	mediaTypeTest(t, "", true) | ||||||
|  | 	mediaTypeTest(t, MediaTypeManifest, false) | ||||||
|  | 	mediaTypeTest(t, MediaTypeManifest+"XXX", true) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -106,6 +106,18 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options .. | ||||||
| 			return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) | 			return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) | ||||||
| 		case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex: | 		case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex: | ||||||
| 			return ms.manifestListHandler.Unmarshal(ctx, dgst, content) | 			return ms.manifestListHandler.Unmarshal(ctx, dgst, content) | ||||||
|  | 		case "": | ||||||
|  | 			// OCI image or image index - no media type in the content | ||||||
|  | 
 | ||||||
|  | 			// First see if it looks like an image index | ||||||
|  | 			res, err := ms.manifestListHandler.Unmarshal(ctx, dgst, content) | ||||||
|  | 			resIndex := res.(*manifestlist.DeserializedManifestList) | ||||||
|  | 			if err == nil && resIndex.Manifests != nil { | ||||||
|  | 				return resIndex, nil | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Otherwise, assume it must be an image manifest | ||||||
|  | 			return ms.ocischemaHandler.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)} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution" | 	"github.com/docker/distribution" | ||||||
| 	"github.com/docker/distribution/manifest" | 	"github.com/docker/distribution/manifest" | ||||||
|  | 	"github.com/docker/distribution/manifest/manifestlist" | ||||||
|  | 	"github.com/docker/distribution/manifest/ocischema" | ||||||
| 	"github.com/docker/distribution/manifest/schema1" | 	"github.com/docker/distribution/manifest/schema1" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| 	"github.com/docker/distribution/registry/storage/cache/memory" | 	"github.com/docker/distribution/registry/storage/cache/memory" | ||||||
|  | @ -17,6 +19,7 @@ import ( | ||||||
| 	"github.com/docker/distribution/testutil" | 	"github.com/docker/distribution/testutil" | ||||||
| 	"github.com/docker/libtrust" | 	"github.com/docker/libtrust" | ||||||
| 	"github.com/opencontainers/go-digest" | 	"github.com/opencontainers/go-digest" | ||||||
|  | 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type manifestStoreTestEnv struct { | type manifestStoreTestEnv struct { | ||||||
|  | @ -356,6 +359,155 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestOCIManifestStorage(t *testing.T) { | ||||||
|  | 	testOCIManifestStorage(t, "includeMediaTypes=true", true) | ||||||
|  | 	testOCIManifestStorage(t, "includeMediaTypes=false", false) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes bool) { | ||||||
|  | 	var imageMediaType string | ||||||
|  | 	var indexMediaType string | ||||||
|  | 	if includeMediaTypes { | ||||||
|  | 		imageMediaType = v1.MediaTypeImageManifest | ||||||
|  | 		indexMediaType = v1.MediaTypeImageIndex | ||||||
|  | 	} else { | ||||||
|  | 		imageMediaType = "" | ||||||
|  | 		indexMediaType = "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	repoName, _ := reference.WithName("foo/bar") | ||||||
|  | 	env := newManifestStoreTestEnv(t, repoName, "thetag", | ||||||
|  | 		BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), | ||||||
|  | 		EnableDelete, EnableRedirect) | ||||||
|  | 
 | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	ms, err := env.repository.Manifests(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Build a manifest and store it and its layers in the registry | ||||||
|  | 
 | ||||||
|  | 	blobStore := env.repository.Blobs(ctx) | ||||||
|  | 	builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{}) | ||||||
|  | 	builder.(*ocischema.Builder).SetMediaType(imageMediaType) | ||||||
|  | 
 | ||||||
|  | 	// Add some layers | ||||||
|  | 	for i := 0; i < 2; i++ { | ||||||
|  | 		rs, ds, err := testutil.CreateRandomTarFile() | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("%s: unexpected error generating test layer file", testname) | ||||||
|  | 		} | ||||||
|  | 		dgst := digest.Digest(ds) | ||||||
|  | 
 | ||||||
|  | 		wr, err := env.repository.Blobs(env.ctx).Create(env.ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("%s: unexpected error creating test upload: %v", testname, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, err := io.Copy(wr, rs); err != nil { | ||||||
|  | 			t.Fatalf("%s: unexpected error copying to upload: %v", testname, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil { | ||||||
|  | 			t.Fatalf("%s: unexpected error finishing upload: %v", testname, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		builder.AppendReference(distribution.Descriptor{Digest: dgst}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	manifest, err := builder.Build(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error generating manifest: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var manifestDigest digest.Digest | ||||||
|  | 	if manifestDigest, err = ms.Put(ctx, manifest); err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error putting manifest: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Also create an image index that contains the manifest | ||||||
|  | 
 | ||||||
|  | 	descriptor, err := env.registry.BlobStatter().Stat(ctx, manifestDigest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error getting manifest descriptor", testname) | ||||||
|  | 	} | ||||||
|  | 	descriptor.MediaType = v1.MediaTypeImageManifest | ||||||
|  | 
 | ||||||
|  | 	platformSpec := manifestlist.PlatformSpec{ | ||||||
|  | 		Architecture: "atari2600", | ||||||
|  | 		OS:           "CP/M", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	manifestDescriptors := []manifestlist.ManifestDescriptor{ | ||||||
|  | 		manifestlist.ManifestDescriptor{ | ||||||
|  | 			Descriptor: descriptor, | ||||||
|  | 			Platform:   platformSpec, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	imageIndex, err := manifestlist.FromDescriptorsWithMediaType(manifestDescriptors, indexMediaType) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error creating image index: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var indexDigest digest.Digest | ||||||
|  | 	if indexDigest, err = ms.Put(ctx, imageIndex); err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error putting image index: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Now check that we can retrieve the manifest | ||||||
|  | 
 | ||||||
|  | 	fromStore, err := ms.Get(ctx, manifestDigest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error fetching manifest: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fetchedManifest, ok := fromStore.(*ocischema.DeserializedManifest) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatalf("%s: unexpected type for fetched manifest", testname) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if fetchedManifest.MediaType != imageMediaType { | ||||||
|  | 		t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	payloadMediaType, _, err := fromStore.Payload() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: error getting payload %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if payloadMediaType != v1.MediaTypeImageManifest { | ||||||
|  | 		t.Fatalf("%s: unexpected MediaType for manifest payload, %s", testname, payloadMediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// and the image index | ||||||
|  | 
 | ||||||
|  | 	fromStore, err = ms.Get(ctx, indexDigest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: unexpected error fetching image index: %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fetchedIndex, ok := fromStore.(*manifestlist.DeserializedManifestList) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatalf("%s: unexpected type for fetched manifest", testname) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if fetchedIndex.MediaType != indexMediaType { | ||||||
|  | 		t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	payloadMediaType, _, err = fromStore.Payload() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("%s: error getting payload %v", testname, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if payloadMediaType != v1.MediaTypeImageIndex { | ||||||
|  | 		t.Fatalf("%s: unexpected MediaType for index payload, %s", testname, payloadMediaType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // TestLinkPathFuncs ensures that the link path functions behavior are locked | // TestLinkPathFuncs ensures that the link path functions behavior are locked | ||||||
| // down and implemented as expected. | // down and implemented as expected. | ||||||
| func TestLinkPathFuncs(t *testing.T) { | func TestLinkPathFuncs(t *testing.T) { | ||||||
|  | @ -387,5 +539,4 @@ func TestLinkPathFuncs(t *testing.T) { | ||||||
| 			t.Fatalf("incorrect path returned: %q != %q", p, testcase.expected) | 			t.Fatalf("incorrect path returned: %q != %q", p, testcase.expected) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue