From e6d40b6ff29f6f36b4faedbc242ce9475751ab7f Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Tue, 22 Aug 2023 14:20:13 +0800 Subject: [PATCH] refactor: add `PackManifest` and deprecate `Pack` (#570) This PR refactors `oras.Pack` that was updated by #532. 1. Move the support of Image Manifest `v1.1.0-rc4` to `PackManifest` 2. Deprecate `Pack` Resolves: #568 Signed-off-by: Lixia (Sylvia) Lei --- example_pack_test.go | 98 +++++++++++ example_test.go | 5 +- pack.go | 190 ++++++++++++-------- pack_test.go | 406 ++++++++++++++++++++++++++----------------- 4 files changed, 459 insertions(+), 240 deletions(-) create mode 100644 example_pack_test.go diff --git a/example_pack_test.go b/example_pack_test.go new file mode 100644 index 00000000..a1d301c5 --- /dev/null +++ b/example_pack_test.go @@ -0,0 +1,98 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package oras_test + +import ( + "context" + "fmt" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/content/memory" +) + +// ExampleImageV11RC4 demonstrates packing an OCI Image Manifest as defined in +// image-spec v1.1.0-rc4. +func ExamplePackManifest_imageV11RC4() { + // 0. Create a storage + store := memory.New() + + // 1. Set optional parameters + opts := oras.PackManifestOptions{ + ManifestAnnotations: map[string]string{ + // this timestamp will be automatically generated if not specified + // use a fixed value here in order to test the output + ocispec.AnnotationCreated: "2000-01-01T00:00:00Z", + }, + } + ctx := context.Background() + + // 2. Pack a manifest + artifactType := "application/vnd.example+type" + manifestDesc, err := oras.PackManifest(ctx, store, oras.PackManifestVersion1_1_RC4, artifactType, opts) + if err != nil { + panic(err) + } + fmt.Println("Manifest descriptor:", manifestDesc) + + // 3. Verify the packed manifest + manifestData, err := content.FetchAll(ctx, store, manifestDesc) + if err != nil { + panic(err) + } + fmt.Println("Manifest content:", string(manifestData)) + + // Output: + // Manifest descriptor: {application/vnd.oci.image.manifest.v1+json sha256:c259a195a48d8029d75449579c81269ca6225cd5b57d36073a7de6458afdfdbd 528 [] map[org.opencontainers.image.created:2000-01-01T00:00:00Z] [] application/vnd.example+type} + // Manifest content: {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/vnd.example+type","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="},"layers":[{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="}],"annotations":{"org.opencontainers.image.created":"2000-01-01T00:00:00Z"}} +} + +// ExampleImageV10 demonstrates packing an OCI Image Manifest as defined in +// image-spec v1.0.2. +func ExamplePackManifest_imageV10() { + // 0. Create a storage + store := memory.New() + + // 1. Set optional parameters + opts := oras.PackManifestOptions{ + ManifestAnnotations: map[string]string{ + // this timestamp will be automatically generated if not specified + // use a fixed value here in order to test the output + ocispec.AnnotationCreated: "2000-01-01T00:00:00Z", + }, + } + ctx := context.Background() + + // 2. Pack a manifest + artifactType := "application/vnd.example+type" + manifestDesc, err := oras.PackManifest(ctx, store, oras.PackManifestVersion1_0, artifactType, opts) + if err != nil { + panic(err) + } + fmt.Println("Manifest descriptor:", manifestDesc) + + // 3. Verify the packed manifest + manifestData, err := content.FetchAll(ctx, store, manifestDesc) + if err != nil { + panic(err) + } + fmt.Println("Manifest content:", string(manifestData)) + + // Output: + // Manifest descriptor: {application/vnd.oci.image.manifest.v1+json sha256:da221a11559704e4971c3dcf6564303707a333c8de8cb5475fc48b0072b36c19 308 [] map[org.opencontainers.image.created:2000-01-01T00:00:00Z] [] application/vnd.example+type} + // Manifest content: {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.example+type","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[],"annotations":{"org.opencontainers.image.created":"2000-01-01T00:00:00Z"}} +} diff --git a/example_test.go b/example_test.go index 7b36f05b..aa7ef53b 100644 --- a/example_test.go +++ b/example_test.go @@ -125,7 +125,10 @@ func Example_pushFilesToRemoteRepository() { // 2. Pack the files and tag the packed manifest artifactType := "example/files" - manifestDescriptor, err := oras.Pack(ctx, fs, artifactType, fileDescriptors, oras.DefaultPackOptions) + opts := oras.PackManifestOptions{ + Layers: fileDescriptors, + } + manifestDescriptor, err := oras.PackManifest(ctx, fs, oras.PackManifestVersion1_1_RC4, artifactType, opts) if err != nil { panic(err) } diff --git a/pack.go b/pack.go index a6c72e35..bd703d16 100644 --- a/pack.go +++ b/pack.go @@ -31,10 +31,11 @@ import ( ) const ( - // MediaTypeUnknownConfig is the default mediaType used for [Pack] when - // PackOptions.PackImageManifest is true and PackOptions.PackManifestType - // is PackManifestTypeImageV1_1_0_RC2 and PackOptions.ConfigDescriptor - // is not specified. + // MediaTypeUnknownConfig is the default config mediaType used + // - for [Pack] when PackOptions.PackImageManifest is true and + // PackOptions.ConfigDescriptor is not specified. + // - for [PackManifest] when packManifestVersion is PackManifestVersion1_0 + // and PackManifestOptions.ConfigDescriptor is not specified. MediaTypeUnknownConfig = "application/vnd.unknown.config.v1+json" // MediaTypeUnknownArtifact is the default artifactType used for [Pack] @@ -43,38 +44,85 @@ const ( MediaTypeUnknownArtifact = "application/vnd.unknown.artifact.v1" ) -// PackManifestType represents the manifest type used for [Pack]. -type PackManifestType int - -const ( - // PackManifestTypeImageV1_1_0_RC2 represents the OCI Image Manifest type - // defined in image-spec v1.1.0-rc2. - // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/manifest.md - // - // Deprecated: This type is deprecated and not recommended for future use. - // Use PackManifestTypeImageV1_1_0_RC4 instead. - PackManifestTypeImageV1_1_0_RC2 PackManifestType = 0 - - // PackManifestTypeImageV1_1_0_RC4 represents the OCI Image Manifest type - // defined since image-spec v1.1.0-rc4. - // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/manifest.md - PackManifestTypeImageV1_1_0_RC4 PackManifestType = 1 -) - var ( - // ErrInvalidDateTimeFormat is returned by [Pack] when + // ErrInvalidDateTimeFormat is returned by [Pack] and [PackManifest] when // AnnotationArtifactCreated or AnnotationCreated is provided, but its value // is not in RFC 3339 format. // Reference: https://www.rfc-editor.org/rfc/rfc3339#section-5.6 ErrInvalidDateTimeFormat = errors.New("invalid date and time format") - // ErrMissingArtifactType is returned by [Pack] when artifactType is not - // specified and the config media type is set to + // ErrMissingArtifactType is returned by [PackManifest] when + // packManifestVersion is PackManifestVersion1_1_RC4 and artifactType is + // empty and the config media type is set to // "application/vnd.oci.empty.v1+json". ErrMissingArtifactType = errors.New("missing artifact type") ) -// PackOptions contains parameters for [Pack]. +// PackManifestVersion represents the manifest version used for [PackManifest]. +type PackManifestVersion int + +const ( + // PackManifestVersion1_0 represents the OCI Image Manifest defined in + // image-spec v1.0.2. + // Reference: https://github.com/opencontainers/image-spec/blob/v1.0.2/manifest.md + PackManifestVersion1_0 PackManifestVersion = 1 + + // PackManifestVersion1_1_RC4 represents the OCI Image Manifest defined + // in image-spec v1.1.0-rc4. + // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/manifest.md + PackManifestVersion1_1_RC4 PackManifestVersion = 2 +) + +// PackManifestOptions contains optional parameters for [PackManifest]. +type PackManifestOptions struct { + // Subject is the subject of the manifest. + // This option is only valid when PackManifestVersion is + // NOT PackManifestVersion1_0. + Subject *ocispec.Descriptor + + // Layers is the layers of the manifest. + Layers []ocispec.Descriptor + + // ManifestAnnotations is the annotation map of the manifest. + ManifestAnnotations map[string]string + + // ConfigDescriptor is a pointer to the descriptor of the config blob. + // If not nil, ConfigAnnotations will be ignored. + ConfigDescriptor *ocispec.Descriptor + + // ConfigAnnotations is the annotation map of the config descriptor. + // This option is valid only when ConfigDescriptor is nil. + ConfigAnnotations map[string]string +} + +// PackManifest generates an OCI Image Manifest based on the given parameters +// and pushes the packed manifest to a content storage using pusher. The version +// of the manifest to be packed is determined by packManifestVersion +// (Recommended value: PackManifestVersion1_1_RC4). +// +// - If packManifestVersion is [PackManifestVersion1_1_RC4], +// artifactType MUST NOT be empty unless opts.ConfigDescriptor is specified. +// - If packManifestVersion is [PackManifestVersion1_0], +// artifactType will be used as the the config media type unless +// opts.ConfigDescriptor is specified. If artifactType is empty, +// "application/vnd.unknown.config.v1+json" will be used. +// +// If succeeded, returns a descriptor of the packed manifest. +func PackManifest(ctx context.Context, pusher content.Pusher, packManifestVersion PackManifestVersion, artifactType string, opts PackManifestOptions) (ocispec.Descriptor, error) { + switch packManifestVersion { + case PackManifestVersion1_0: + return packManifestV1_0(ctx, pusher, artifactType, opts) + case PackManifestVersion1_1_RC4: + return packManifestV1_1_RC4(ctx, pusher, artifactType, opts) + default: + return ocispec.Descriptor{}, fmt.Errorf("PackManifestVersion(%v): %w", packManifestVersion, errdef.ErrUnsupported) + } +} + +// PackOptions contains optional parameters for [Pack]. +// +// Deprecated: This type is deprecated and not recommended for future use. +// Use [PackManifestOptions] instead. type PackOptions struct { // Subject is the subject of the manifest. Subject *ocispec.Descriptor @@ -87,16 +135,8 @@ type PackOptions struct { // - If false, pack an OCI Artifact Manifest (deprecated). // // Default value: false. - // Recommended value: true (See DefaultPackOptions). PackImageManifest bool - // PackManifestType controls which type of manifest to pack. - // This option is valid only when PackImageManifest is true. - // - // Default value: PackManifestTypeImageV1_1_0_RC2 (deprecated). - // Recommended value: PackManifestTypeImageV1_1_0_RC4 (See DefaultPackOptions). - PackManifestType PackManifestType - // ConfigDescriptor is a pointer to the descriptor of the config blob. // If not nil, artifactType will be implied by the mediaType of the // specified ConfigDescriptor, and ConfigAnnotations will be ignored. @@ -109,44 +149,32 @@ type PackOptions struct { ConfigAnnotations map[string]string } -// DefaultPackOptions provides the default PackOptions. -// Note that the default options are subject to change in the future. -var DefaultPackOptions PackOptions = PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, -} - // Pack packs the given blobs, generates a manifest for the pack, // and pushes it to a content storage. // -// - If opts.PackImageManifest is true and opts.PackManifestType is -// [PackManifestTypeImageV1_1_0_RC2], -// artifactType will be used as the the config media type of the image -// manifest when opts.ConfigDescriptor is not specified. -// - If opts.PackImageManifest is true and opts.PackManifestType is -// [PackManifestTypeImageV1_1_0_RC4], -// [ErrMissingArtifactType] will be returned when none of artifactType and -// opts.ConfigDescriptor is specified. +// When opts.PackImageManifest is true, artifactType will be used as the +// the config descriptor mediaType of the image manifest. // // If succeeded, returns a descriptor of the manifest. +// +// Deprecated: This method is deprecated and not recommended for future use. +// Use [PackManifest] instead. func Pack(ctx context.Context, pusher content.Pusher, artifactType string, blobs []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { - if !opts.PackImageManifest { - return packArtifact(ctx, pusher, artifactType, blobs, opts) - } - - switch opts.PackManifestType { - case PackManifestTypeImageV1_1_0_RC2: - return packImageRC2(ctx, pusher, artifactType, blobs, opts) - case PackManifestTypeImageV1_1_0_RC4: - return packImageRC4(ctx, pusher, artifactType, blobs, opts) - default: - return ocispec.Descriptor{}, fmt.Errorf("PackManifestType(%v): %w", opts.PackManifestType, errdef.ErrUnsupported) + if opts.PackImageManifest { + packOpts := PackManifestOptions{ + Layers: blobs, + Subject: opts.Subject, + ManifestAnnotations: opts.ManifestAnnotations, + ConfigDescriptor: opts.ConfigDescriptor, + ConfigAnnotations: opts.ConfigAnnotations, + } + return packManifestV1_1_RC2(ctx, pusher, artifactType, packOpts) } + return packArtifact(ctx, pusher, artifactType, blobs, opts) } -// packArtifact packs the given blobs, generates an artifact manifest for the -// pack, and pushes it to a content storage. -// If succeeded, returns a descriptor of the manifest. +// packArtifact packs an Artifact manifest as defined in image-spec v1.1.0-rc2. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/artifact.md func packArtifact(ctx context.Context, pusher content.Pusher, artifactType string, blobs []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { if artifactType == "" { artifactType = MediaTypeUnknownArtifact @@ -166,11 +194,10 @@ func packArtifact(ctx context.Context, pusher content.Pusher, artifactType strin return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.ArtifactType, manifest.Annotations) } -// packImageRC2 packs the given blobs, generates an image manifest for the -// pack, and pushes it to a content storage. -// If succeeded, returns a descriptor of the manifest. +// packManifestV1_1_RC2 packs an image manifest as defined in image-spec +// v1.1.0-rc2. // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/manifest.md -func packImageRC2(ctx context.Context, pusher content.Pusher, configMediaType string, layers []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { +func packManifestV1_1_RC2(ctx context.Context, pusher content.Pusher, configMediaType string, opts PackManifestOptions) (ocispec.Descriptor, error) { if configMediaType == "" { configMediaType = MediaTypeUnknownConfig } @@ -196,8 +223,8 @@ func packImageRC2(ctx context.Context, pusher content.Pusher, configMediaType st if err != nil { return ocispec.Descriptor{}, err } - if layers == nil { - layers = []ocispec.Descriptor{} // make it an empty array to prevent potential server-side bugs + if opts.Layers == nil { + opts.Layers = []ocispec.Descriptor{} // make it an empty array to prevent potential server-side bugs } manifest := ocispec.Manifest{ Versioned: specs.Versioned{ @@ -205,18 +232,16 @@ func packImageRC2(ctx context.Context, pusher content.Pusher, configMediaType st }, Config: configDesc, MediaType: ocispec.MediaTypeImageManifest, - Layers: layers, + Layers: opts.Layers, Subject: opts.Subject, Annotations: annotations, } return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.Config.MediaType, manifest.Annotations) } -// packImageRC4 packs the given blobs, generates an image manifest for the pack, -// and pushes it to a content storage. -// If succeeded, returns a descriptor of the manifest. +// packManifestV1_1_RC4 packs an image manifest defined in image-spec v1.1.0-rc4. // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/manifest.md#guidelines-for-artifact-usage -func packImageRC4(ctx context.Context, pusher content.Pusher, artifactType string, layers []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { +func packManifestV1_1_RC4(ctx context.Context, pusher content.Pusher, artifactType string, opts PackManifestOptions) (ocispec.Descriptor, error) { if artifactType == "" && (opts.ConfigDescriptor == nil || opts.ConfigDescriptor.MediaType == ocispec.MediaTypeEmptyJSON) { // artifactType MUST be set when config.mediaType is set to the empty value return ocispec.Descriptor{}, ErrMissingArtifactType @@ -242,7 +267,7 @@ func packImageRC4(ctx context.Context, pusher content.Pusher, artifactType strin if err != nil { return ocispec.Descriptor{}, err } - if len(layers) == 0 { + if len(opts.Layers) == 0 { // use the empty descriptor as the single layer layerDesc := ocispec.DescriptorEmptyJSON layerData := ocispec.DescriptorEmptyJSON.Data @@ -251,7 +276,7 @@ func packImageRC4(ctx context.Context, pusher content.Pusher, artifactType strin return ocispec.Descriptor{}, fmt.Errorf("failed to push layer: %w", err) } } - layers = []ocispec.Descriptor{layerDesc} + opts.Layers = []ocispec.Descriptor{layerDesc} } manifest := ocispec.Manifest{ @@ -260,7 +285,7 @@ func packImageRC4(ctx context.Context, pusher content.Pusher, artifactType strin }, Config: configDesc, MediaType: ocispec.MediaTypeImageManifest, - Layers: layers, + Layers: opts.Layers, Subject: opts.Subject, ArtifactType: artifactType, Annotations: annotations, @@ -268,6 +293,17 @@ func packImageRC4(ctx context.Context, pusher content.Pusher, artifactType strin return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.ArtifactType, manifest.Annotations) } +// packManifestV1_0 packs an image manifest defined in image-spec v1.0.2. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.0.2/manifest.md +func packManifestV1_0(ctx context.Context, pusher content.Pusher, configMediaType string, opts PackManifestOptions) (ocispec.Descriptor, error) { + if opts.Subject != nil { + return ocispec.Descriptor{}, fmt.Errorf("subject is not supported for manifest version %v: %w", PackManifestVersion1_0, errdef.ErrUnsupported) + } + + // manifest v1.0 is equivalent to manifest v1.1.0-rc2 without subject + return packManifestV1_1_RC2(ctx, pusher, configMediaType, opts) +} + // pushIfNotExist pushes data described by desc if it does not exist in the // target. func pushIfNotExist(ctx context.Context, pusher content.Pusher, desc ocispec.Descriptor, data []byte) error { diff --git a/pack_test.go b/pack_test.go index b67580d6..7faf80fa 100644 --- a/pack_test.go +++ b/pack_test.go @@ -110,6 +110,7 @@ func Test_Pack_Artifact_WithOptions(t *testing.T) { artifactType := "application/vnd.test" annotations := map[string]string{ spec.AnnotationArtifactCreated: "2000-01-01T00:00:00Z", + "foo": "bar", } subjectManifest := []byte(`{"layers":[]}`) subjectDesc := ocispec.Descriptor{ @@ -128,9 +129,8 @@ func Test_Pack_Artifact_WithOptions(t *testing.T) { opts := PackOptions{ Subject: &subjectDesc, ManifestAnnotations: annotations, - ConfigDescriptor: &configDesc, // should not work - ConfigAnnotations: configAnnotations, // should not work - PackManifestType: PackManifestTypeImageV1_1_0_RC4, // should not work + ConfigDescriptor: &configDesc, // should not work + ConfigAnnotations: configAnnotations, // should not work } manifestDesc, err := Pack(ctx, s, artifactType, blobs, opts) if err != nil { @@ -251,7 +251,7 @@ func Test_Pack_Artifact_InvalidDateTimeFormat(t *testing.T) { } } -func Test_Pack_ImageRC2(t *testing.T) { +func Test_Pack_ImageV1_1_RC2(t *testing.T) { s := memory.New() // prepare test content @@ -318,7 +318,7 @@ func Test_Pack_ImageRC2(t *testing.T) { } } -func Test_Pack_ImageRC2_WithOptions(t *testing.T) { +func Test_Pack_ImageV1_1_RC2_WithOptions(t *testing.T) { s := memory.New() // prepare test content @@ -331,6 +331,7 @@ func Test_Pack_ImageRC2_WithOptions(t *testing.T) { configAnnotations := map[string]string{"foo": "bar"} annotations := map[string]string{ ocispec.AnnotationCreated: "2000-01-01T00:00:00Z", + "foo": "bar", } artifactType := "application/vnd.test" subjectManifest := []byte(`{"layers":[]}`) @@ -396,7 +397,6 @@ func Test_Pack_ImageRC2_WithOptions(t *testing.T) { // test Pack without ConfigDescriptor opts = PackOptions{ PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC2, Subject: &subjectDesc, ConfigAnnotations: configAnnotations, ManifestAnnotations: annotations, @@ -448,7 +448,7 @@ func Test_Pack_ImageRC2_WithOptions(t *testing.T) { } } -func Test_Pack_ImageRC2_NoArtifactType(t *testing.T) { +func Test_Pack_ImageV1_1_RC2_NoArtifactType(t *testing.T) { s := memory.New() ctx := context.Background() @@ -478,7 +478,7 @@ func Test_Pack_ImageRC2_NoArtifactType(t *testing.T) { } } -func Test_Pack_ImageRC2_NoLayer(t *testing.T) { +func Test_Pack_ImageV1_1_RC2_NoLayer(t *testing.T) { s := memory.New() // test Pack @@ -507,7 +507,7 @@ func Test_Pack_ImageRC2_NoLayer(t *testing.T) { } } -func Test_Pack_ImageRC2_InvalidDateTimeFormat(t *testing.T) { +func Test_Pack_ImageV1_1_RC2_InvalidDateTimeFormat(t *testing.T) { s := memory.New() ctx := context.Background() @@ -523,28 +523,16 @@ func Test_Pack_ImageRC2_InvalidDateTimeFormat(t *testing.T) { } } -func Test_Pack_ImageRC4(t *testing.T) { +func Test_PackManifest_ImageV1_1_RC4(t *testing.T) { s := memory.New() - // prepare test content - layers := []ocispec.Descriptor{ - content.NewDescriptorFromBytes("test", []byte("hello world")), - content.NewDescriptorFromBytes("test", []byte("goodbye world")), - } - - // verify Pack + // test PackManifest ctx := context.Background() - artifactType := "application/vnd.test" - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, - } - manifestDesc, err := Pack(ctx, s, artifactType, layers, opts) + manifestDesc, err := PackManifest(ctx, s, PackManifestVersion1_1_RC4, "test", PackManifestOptions{}) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) } - // verify manifest var manifest ocispec.Manifest rc, err := s.Fetch(ctx, manifestDesc) if err != nil { @@ -557,50 +545,14 @@ func Test_Pack_ImageRC4(t *testing.T) { t.Fatal("Store.Fetch().Close() error =", err) } - // verify media type - got := manifest.MediaType - if got != ocispec.MediaTypeImageManifest { - t.Fatalf("got media type = %s, want %s", got, ocispec.MediaTypeImageManifest) - } - - // verify config - expectedConfig := ocispec.DescriptorEmptyJSON - if !reflect.DeepEqual(manifest.Config, expectedConfig) { - t.Errorf("got config = %v, want %v", manifest.Config, expectedConfig) - } - // verify layers - if !reflect.DeepEqual(manifest.Layers, layers) { - t.Errorf("got layers = %v, want %v", manifest.Layers, layers) - } - - // verify created time annotation - createdTime, ok := manifest.Annotations[ocispec.AnnotationCreated] - if !ok { - t.Errorf("Annotation %s = %v, want %v", ocispec.AnnotationCreated, ok, true) - } - _, err = time.Parse(time.RFC3339, createdTime) - if err != nil { - t.Errorf("error parsing created time: %s, error = %v", createdTime, err) - } - - // verify artifact type - if !reflect.DeepEqual(manifest.ArtifactType, artifactType) { - t.Errorf("got artifactType = %v, want %v", manifest.ArtifactType, artifactType) - } - - // verify descriptor artifact type - if want := manifest.ArtifactType; !reflect.DeepEqual(manifestDesc.ArtifactType, want) { - t.Errorf("got descriptor artifactType = %v, want %v", manifestDesc.ArtifactType, want) - } - - // verify descriptor annotations - if want := manifest.Annotations; !reflect.DeepEqual(manifestDesc.Annotations, want) { - t.Errorf("got descriptor annotations = %v, want %v", manifestDesc.Annotations, want) + expectedLayers := []ocispec.Descriptor{ocispec.DescriptorEmptyJSON} + if !reflect.DeepEqual(manifest.Layers, expectedLayers) { + t.Errorf("got layers = %v, want %v", manifest.Layers, expectedLayers) } } -func Test_Pack_ImageRC4_WithOptions(t *testing.T) { +func Test_PackManifest_ImageV1_1_RC4_WithOptions(t *testing.T) { s := memory.New() // prepare test content @@ -613,6 +565,7 @@ func Test_Pack_ImageRC4_WithOptions(t *testing.T) { configAnnotations := map[string]string{"foo": "bar"} annotations := map[string]string{ ocispec.AnnotationCreated: "2000-01-01T00:00:00Z", + "foo": "bar", } artifactType := "application/vnd.test" subjectManifest := []byte(`{"layers":[]}`) @@ -622,19 +575,18 @@ func Test_Pack_ImageRC4_WithOptions(t *testing.T) { Size: int64(len(subjectManifest)), } - // test Pack with ConfigDescriptor + // test PackManifest with ConfigDescriptor ctx := context.Background() - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, + opts := PackManifestOptions{ Subject: &subjectDesc, + Layers: layers, ConfigDescriptor: &configDesc, ConfigAnnotations: configAnnotations, ManifestAnnotations: annotations, } - manifestDesc, err := Pack(ctx, s, artifactType, layers, opts) + manifestDesc, err := PackManifest(ctx, s, PackManifestVersion1_1_RC4, artifactType, opts) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) } expectedManifest := ocispec.Manifest{ @@ -674,21 +626,20 @@ func Test_Pack_ImageRC4_WithOptions(t *testing.T) { expectedManifestDesc.ArtifactType = expectedManifest.ArtifactType expectedManifestDesc.Annotations = expectedManifest.Annotations if !reflect.DeepEqual(manifestDesc, expectedManifestDesc) { - t.Errorf("Pack() = %v, want %v", manifestDesc, expectedManifestDesc) + t.Errorf("PackManifest() = %v, want %v", manifestDesc, expectedManifestDesc) } - // test Pack with ConfigDescriptor, but without artifactType - opts = PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, + // test PackManifest with ConfigDescriptor, but without artifactType + opts = PackManifestOptions{ Subject: &subjectDesc, + Layers: layers, ConfigDescriptor: &configDesc, ConfigAnnotations: configAnnotations, ManifestAnnotations: annotations, } - manifestDesc, err = Pack(ctx, s, "", layers, opts) + manifestDesc, err = PackManifest(ctx, s, PackManifestVersion1_1_RC4, "", opts) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) } expectedManifest = ocispec.Manifest{ @@ -727,20 +678,19 @@ func Test_Pack_ImageRC4_WithOptions(t *testing.T) { expectedManifestDesc.ArtifactType = expectedManifest.ArtifactType expectedManifestDesc.Annotations = expectedManifest.Annotations if !reflect.DeepEqual(manifestDesc, expectedManifestDesc) { - t.Errorf("Pack() = %v, want %v", manifestDesc, expectedManifestDesc) + t.Errorf("PackManifest() = %v, want %v", manifestDesc, expectedManifestDesc) } // test Pack without ConfigDescriptor - opts = PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, + opts = PackManifestOptions{ Subject: &subjectDesc, + Layers: layers, ConfigAnnotations: configAnnotations, ManifestAnnotations: annotations, } - manifestDesc, err = Pack(ctx, s, artifactType, layers, opts) + manifestDesc, err = PackManifest(ctx, s, PackManifestVersion1_1_RC4, artifactType, opts) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) } expectedConfigDesc := ocispec.DescriptorEmptyJSON @@ -782,50 +732,56 @@ func Test_Pack_ImageRC4_WithOptions(t *testing.T) { expectedManifestDesc.ArtifactType = expectedManifest.ArtifactType expectedManifestDesc.Annotations = expectedManifest.Annotations if !reflect.DeepEqual(manifestDesc, expectedManifestDesc) { - t.Errorf("Pack() = %v, want %v", manifestDesc, expectedManifestDesc) + t.Errorf("PackManifest() = %v, want %v", manifestDesc, expectedManifestDesc) } } -func Test_Pack_ImageRC4_NoArtifactType(t *testing.T) { +func Test_PackManifest_ImageV1_1_RC4_NoArtifactType(t *testing.T) { s := memory.New() ctx := context.Background() // test no artifact type and no config - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, - } - _, err := Pack(ctx, s, "", nil, opts) + _, err := PackManifest(ctx, s, PackManifestVersion1_1_RC4, "", PackManifestOptions{}) if wantErr := ErrMissingArtifactType; !errors.Is(err, wantErr) { - t.Errorf("Oras.Pack() error = %v, wantErr = %v", err, wantErr) + t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr) } // test no artifact type and config with media type empty - opts = PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, + opts := PackManifestOptions{ ConfigDescriptor: &ocispec.Descriptor{ MediaType: ocispec.DescriptorEmptyJSON.MediaType, }, } - _, err = Pack(ctx, s, "", nil, opts) + _, err = PackManifest(ctx, s, PackManifestVersion1_1_RC4, "", opts) if wantErr := ErrMissingArtifactType; !errors.Is(err, wantErr) { - t.Errorf("Oras.Pack() error = %v, wantErr = %v", err, wantErr) + t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr) } } -func Test_Pack_ImageRC4_NoLayer(t *testing.T) { +func Test_PackManifest_ImageV1_1_RC4_InvalidDateTimeFormat(t *testing.T) { s := memory.New() - // test Pack ctx := context.Background() - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, + opts := PackManifestOptions{ + ManifestAnnotations: map[string]string{ + ocispec.AnnotationCreated: "2000/01/01 00:00:00", + }, } - manifestDesc, err := Pack(ctx, s, "test", nil, opts) + _, err := PackManifest(ctx, s, PackManifestVersion1_1_RC4, "test", opts) + if wantErr := ErrInvalidDateTimeFormat; !errors.Is(err, wantErr) { + t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr) + } +} + +func Test_PackManifest_ImageV1_0(t *testing.T) { + s := memory.New() + + // test Pack + ctx := context.Background() + artifactType := "testconfig" + manifestDesc, err := PackManifest(ctx, s, PackManifestVersion1_0, artifactType, PackManifestOptions{}) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) } var manifest ocispec.Manifest @@ -840,31 +796,46 @@ func Test_Pack_ImageRC4_NoLayer(t *testing.T) { t.Fatal("Store.Fetch().Close() error =", err) } + // verify media type + got := manifest.MediaType + if got != ocispec.MediaTypeImageManifest { + t.Fatalf("got media type = %s, want %s", got, ocispec.MediaTypeImageManifest) + } + + // verify config + expectedConfigBytes := []byte("{}") + expectedConfig := ocispec.Descriptor{ + MediaType: artifactType, + Digest: digest.FromBytes(expectedConfigBytes), + Size: int64(len(expectedConfigBytes)), + } + if !reflect.DeepEqual(manifest.Config, expectedConfig) { + t.Errorf("got config = %v, want %v", manifest.Config, expectedConfig) + } + // verify layers - expectedLayers := []ocispec.Descriptor{ocispec.DescriptorEmptyJSON} + expectedLayers := []ocispec.Descriptor{} if !reflect.DeepEqual(manifest.Layers, expectedLayers) { t.Errorf("got layers = %v, want %v", manifest.Layers, expectedLayers) } -} -func Test_Pack_ImageRC4_InvalidDateTimeFormat(t *testing.T) { - s := memory.New() - - ctx := context.Background() - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: PackManifestTypeImageV1_1_0_RC4, - ManifestAnnotations: map[string]string{ - ocispec.AnnotationCreated: "2000/01/01 00:00:00", - }, + // verify created time annotation + createdTime, ok := manifest.Annotations[ocispec.AnnotationCreated] + if !ok { + t.Errorf("Annotation %s = %v, want %v", ocispec.AnnotationCreated, ok, true) } - _, err := Pack(ctx, s, "test", nil, opts) - if wantErr := ErrInvalidDateTimeFormat; !errors.Is(err, wantErr) { - t.Errorf("Oras.Pack() error = %v, wantErr = %v", err, wantErr) + _, err = time.Parse(time.RFC3339, createdTime) + if err != nil { + t.Errorf("error parsing created time: %s, error = %v", createdTime, err) + } + + // verify descriptor annotations + if want := manifest.Annotations; !reflect.DeepEqual(manifestDesc.Annotations, want) { + t.Errorf("got descriptor annotations = %v, want %v", manifestDesc.Annotations, want) } } -func Test_Pack_DefaultPackOptions(t *testing.T) { +func Test_PackManifest_ImageV1_0_WithOptions(t *testing.T) { s := memory.New() // prepare test content @@ -872,81 +843,192 @@ func Test_Pack_DefaultPackOptions(t *testing.T) { content.NewDescriptorFromBytes("test", []byte("hello world")), content.NewDescriptorFromBytes("test", []byte("goodbye world")), } + configBytes := []byte("{}") + configDesc := content.NewDescriptorFromBytes("testconfig", configBytes) + configAnnotations := map[string]string{"foo": "bar"} + annotations := map[string]string{ + ocispec.AnnotationCreated: "2000-01-01T00:00:00Z", + "foo": "bar", + } + artifactType := "application/vnd.test" - // verify Pack + // test PackManifest with ConfigDescriptor ctx := context.Background() - artifactType := "application/vnd.test" - manifestDesc, err := Pack(ctx, s, artifactType, layers, DefaultPackOptions) + opts := PackManifestOptions{ + Layers: layers, + ConfigDescriptor: &configDesc, + ConfigAnnotations: configAnnotations, + ManifestAnnotations: annotations, + } + manifestDesc, err := PackManifest(ctx, s, PackManifestVersion1_0, artifactType, opts) if err != nil { - t.Fatal("Oras.Pack() error =", err) + t.Fatal("Oras.PackManifest() error =", err) + } + + expectedManifest := ocispec.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageManifest, + Config: configDesc, + Layers: layers, + Annotations: annotations, + } + expectedManifestBytes, err := json.Marshal(expectedManifest) + if err != nil { + t.Fatal("failed to marshal manifest:", err) } - // verify manifest - var manifest ocispec.Manifest rc, err := s.Fetch(ctx, manifestDesc) if err != nil { t.Fatal("Store.Fetch() error =", err) } - if err := json.NewDecoder(rc).Decode(&manifest); err != nil { - t.Fatal("error decoding manifest, error =", err) + got, err := io.ReadAll(rc) + if err != nil { + t.Fatal("Store.Fetch().Read() error =", err) } - if err := rc.Close(); err != nil { - t.Fatal("Store.Fetch().Close() error =", err) + err = rc.Close() + if err != nil { + t.Error("Store.Fetch().Close() error =", err) + } + if !bytes.Equal(got, expectedManifestBytes) { + t.Errorf("Store.Fetch() = %v, want %v", string(got), string(expectedManifestBytes)) } - // verify media type - got := manifest.MediaType - if got != ocispec.MediaTypeImageManifest { - t.Fatalf("got media type = %s, want %s", got, ocispec.MediaTypeImageManifest) + // verify descriptor + expectedManifestDesc := content.NewDescriptorFromBytes(expectedManifest.MediaType, expectedManifestBytes) + expectedManifestDesc.ArtifactType = expectedManifest.Config.MediaType + expectedManifestDesc.Annotations = expectedManifest.Annotations + if !reflect.DeepEqual(manifestDesc, expectedManifestDesc) { + t.Errorf("Pack() = %v, want %v", manifestDesc, expectedManifestDesc) } - // verify config - expectedConfig := ocispec.DescriptorEmptyJSON - if !reflect.DeepEqual(manifest.Config, expectedConfig) { - t.Errorf("got config = %v, want %v", manifest.Config, expectedConfig) + // test PackManifest without ConfigDescriptor + opts = PackManifestOptions{ + Layers: layers, + ConfigAnnotations: configAnnotations, + ManifestAnnotations: annotations, + } + manifestDesc, err = PackManifest(ctx, s, PackManifestVersion1_0, artifactType, opts) + if err != nil { + t.Fatal("Oras.PackManifest() error =", err) } - // verify layers - if !reflect.DeepEqual(manifest.Layers, layers) { - t.Errorf("got layers = %v, want %v", manifest.Layers, layers) + expectedConfigDesc := content.NewDescriptorFromBytes(artifactType, configBytes) + expectedConfigDesc.Annotations = configAnnotations + expectedManifest = ocispec.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageManifest, + Config: expectedConfigDesc, + Layers: layers, + Annotations: annotations, + } + expectedManifestBytes, err = json.Marshal(expectedManifest) + if err != nil { + t.Fatal("failed to marshal manifest:", err) } - // verify created time annotation - createdTime, ok := manifest.Annotations[ocispec.AnnotationCreated] - if !ok { - t.Errorf("Annotation %s = %v, want %v", ocispec.AnnotationCreated, ok, true) + rc, err = s.Fetch(ctx, manifestDesc) + if err != nil { + t.Fatal("Store.Fetch() error =", err) } - _, err = time.Parse(time.RFC3339, createdTime) + got, err = io.ReadAll(rc) if err != nil { - t.Errorf("error parsing created time: %s, error = %v", createdTime, err) + t.Fatal("Store.Fetch().Read() error =", err) + } + err = rc.Close() + if err != nil { + t.Error("Store.Fetch().Close() error =", err) + } + if !bytes.Equal(got, expectedManifestBytes) { + t.Errorf("Store.Fetch() = %v, want %v", string(got), string(expectedManifestBytes)) } - // verify artifact type - if !reflect.DeepEqual(manifest.ArtifactType, artifactType) { - t.Errorf("got artifactType = %v, want %v", manifest.ArtifactType, artifactType) + // verify descriptor + expectedManifestDesc = content.NewDescriptorFromBytes(expectedManifest.MediaType, expectedManifestBytes) + expectedManifestDesc.ArtifactType = expectedManifest.Config.MediaType + expectedManifestDesc.Annotations = expectedManifest.Annotations + if !reflect.DeepEqual(manifestDesc, expectedManifestDesc) { + t.Errorf("PackManifest() = %v, want %v", manifestDesc, expectedManifestDesc) } +} - // verify descriptor artifact type - if want := manifest.ArtifactType; !reflect.DeepEqual(manifestDesc.ArtifactType, want) { - t.Errorf("got descriptor artifactType = %v, want %v", manifestDesc.ArtifactType, want) +func Test_PackManifest_ImageV1_0_SubjectUnsupported(t *testing.T) { + s := memory.New() + + // prepare test content + artifactType := "application/vnd.test" + subjectManifest := []byte(`{"layers":[]}`) + subjectDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageManifest, + Digest: digest.FromBytes(subjectManifest), + Size: int64(len(subjectManifest)), } - // verify descriptor annotations - if want := manifest.Annotations; !reflect.DeepEqual(manifestDesc.Annotations, want) { - t.Errorf("got descriptor annotations = %v, want %v", manifestDesc.Annotations, want) + // test Pack with ConfigDescriptor + ctx := context.Background() + opts := PackManifestOptions{ + Subject: &subjectDesc, + } + _, err := PackManifest(ctx, s, PackManifestVersion1_0, artifactType, opts) + if wantErr := errdef.ErrUnsupported; !errors.Is(err, wantErr) { + t.Errorf("Oras.PackManifest() error = %v, wantErr %v", err, wantErr) } } -func Test_Pack_UnsupportedPackManifestType(t *testing.T) { +func Test_PackManifest_ImageV1_0_NoArtifactType(t *testing.T) { s := memory.New() ctx := context.Background() - opts := PackOptions{ - PackImageManifest: true, - PackManifestType: -1, + manifestDesc, err := PackManifest(ctx, s, PackManifestVersion1_0, "", PackManifestOptions{}) + if err != nil { + t.Fatal("Oras.PackManifest() error =", err) } - _, err := Pack(ctx, s, "", nil, opts) + + var manifest ocispec.Manifest + rc, err := s.Fetch(ctx, manifestDesc) + if err != nil { + t.Fatal("Store.Fetch() error =", err) + } + if err := json.NewDecoder(rc).Decode(&manifest); err != nil { + t.Fatal("error decoding manifest, error =", err) + } + if err := rc.Close(); err != nil { + t.Fatal("Store.Fetch().Close() error =", err) + } + + // verify artifact type and config media type + if manifestDesc.ArtifactType != MediaTypeUnknownConfig { + t.Fatalf("got artifact type = %s, want %s", manifestDesc.ArtifactType, MediaTypeUnknownConfig) + } + if manifest.Config.MediaType != MediaTypeUnknownConfig { + t.Fatalf("got artifact type = %s, want %s", manifest.Config.MediaType, MediaTypeUnknownConfig) + } +} + +func Test_PackManifest_ImageV1_0_InvalidDateTimeFormat(t *testing.T) { + s := memory.New() + + ctx := context.Background() + opts := PackManifestOptions{ + ManifestAnnotations: map[string]string{ + ocispec.AnnotationCreated: "2000/01/01 00:00:00", + }, + } + _, err := PackManifest(ctx, s, PackManifestVersion1_0, "", opts) + if wantErr := ErrInvalidDateTimeFormat; !errors.Is(err, wantErr) { + t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr) + } +} + +func Test_PackManifest_UnsupportedPackManifestVersion(t *testing.T) { + s := memory.New() + + ctx := context.Background() + _, err := PackManifest(ctx, s, -1, "", PackManifestOptions{}) if wantErr := errdef.ErrUnsupported; !errors.Is(err, wantErr) { - t.Errorf("Oras.Pack() error = %v, wantErr = %v", err, wantErr) + t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr) } }