Test storing OCI image manifests and indexes with/without a media type
OCI Image manifests and indexes are supported both with and without an embeded MediaType (the field is reserved according to the spec). Test storing and retrieving both types from the manifest store. Signed-off-by: Owen W. Taylor <otaylor@fishsoup.net>
This commit is contained in:
parent
60d9c5dfad
commit
132abc6de5
2 changed files with 176 additions and 7 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…
Reference in a new issue