diff --git a/cmd/oras/root/manifest/index/create.go b/cmd/oras/root/manifest/index/create.go index d87fef9f5..47cb06c2b 100644 --- a/cmd/oras/root/manifest/index/create.go +++ b/cmd/oras/root/manifest/index/create.go @@ -245,6 +245,18 @@ func enrichDescriptor(ctx context.Context, target oras.ReadOnlyTarget, desc ocis return ocispec.Descriptor{}, err } desc.ArtifactType = manifest.ArtifactType + if desc.ArtifactType == "" { + // Old-style artifacts predate the manifest artifactType field and + // convey their type through config.mediaType instead. Per the + // image-spec descriptor guidance, fall back to config.mediaType so + // the enriched descriptor still advertises an artifactType. + switch manifest.Config.MediaType { + case ocispec.MediaTypeImageConfig, ocispec.MediaTypeEmptyJSON, docker.MediaTypeConfig: + // standard image / empty config: not an artifact type, leave empty + default: + desc.ArtifactType = manifest.Config.MediaType + } + } } else if descriptor.IsIndex(desc) { var index ocispec.Index if err := json.Unmarshal(manifestBytes, &index); err != nil { diff --git a/cmd/oras/root/manifest/index/create_test.go b/cmd/oras/root/manifest/index/create_test.go index c3f4ee604..7f6f98490 100644 --- a/cmd/oras/root/manifest/index/create_test.go +++ b/cmd/oras/root/manifest/index/create_test.go @@ -292,6 +292,102 @@ func Test_enrichDescriptor(t *testing.T) { }, wantErr: false, }, + { + name: "old-style artifact, artifactType falls back to config mediaType", + target: NewTestReadOnlyTarget(`intentionally not valid JSON`), + manifestBytes: []byte(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.example.config", + "digest": "sha256:dc889043956f34871cc04ae96e03efc29dfe2f582c26195a72dd4827f4dd830d", + "size": 28 + }, + "layers": [] + } + `), + manifestMediaType: "application/vnd.oci.image.manifest.v1+json", + checkDesc: func(t *testing.T, gotDesc, _ ocispec.Descriptor) { + t.Helper() + if got, want := gotDesc.ArtifactType, "application/vnd.example.config"; got != want { + t.Errorf("ArtifactType = %s, want %s", got, want) + } + }, + wantErr: false, + }, + { + name: "empty artifactType with OCI image config stays empty", + target: NewTestReadOnlyTarget(`intentionally not valid JSON`), + manifestBytes: []byte(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:dc889043956f34871cc04ae96e03efc29dfe2f582c26195a72dd4827f4dd830d", + "size": 28 + }, + "layers": [] + } + `), + manifestMediaType: "application/vnd.oci.image.manifest.v1+json", + checkDesc: func(t *testing.T, gotDesc, _ ocispec.Descriptor) { + t.Helper() + if got := gotDesc.ArtifactType; got != "" { + t.Errorf("ArtifactType = %q, want empty", got) + } + }, + wantErr: false, + }, + { + name: "empty artifactType with empty JSON config stays empty", + target: NewTestReadOnlyTarget(`intentionally not valid JSON`), + manifestBytes: []byte(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:dc889043956f34871cc04ae96e03efc29dfe2f582c26195a72dd4827f4dd830d", + "size": 28 + }, + "layers": [] + } + `), + manifestMediaType: "application/vnd.oci.image.manifest.v1+json", + checkDesc: func(t *testing.T, gotDesc, _ ocispec.Descriptor) { + t.Helper() + if got := gotDesc.ArtifactType; got != "" { + t.Errorf("ArtifactType = %q, want empty", got) + } + }, + wantErr: false, + }, + { + name: "empty artifactType with docker image config stays empty", + target: NewTestReadOnlyTarget(`intentionally not valid JSON`), + manifestBytes: []byte(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "digest": "sha256:dc889043956f34871cc04ae96e03efc29dfe2f582c26195a72dd4827f4dd830d", + "size": 28 + }, + "layers": [] + } + `), + manifestMediaType: "application/vnd.oci.image.manifest.v1+json", + checkDesc: func(t *testing.T, gotDesc, _ ocispec.Descriptor) { + t.Helper() + if got := gotDesc.ArtifactType; got != "" { + t.Errorf("ArtifactType = %q, want empty", got) + } + }, + wantErr: false, + }, { name: "child of unrecognized type", target: NewTestReadOnlyTarget("(unused)"), diff --git a/test/e2e/internal/testdata/nonjson_config/const.go b/test/e2e/internal/testdata/nonjson_config/const.go index 576848327..be5de9f5f 100644 --- a/test/e2e/internal/testdata/nonjson_config/const.go +++ b/test/e2e/internal/testdata/nonjson_config/const.go @@ -21,8 +21,9 @@ import ( var ( Descriptor = ocispec.Descriptor{ - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:9d16f5505246424aed7116cb21216704ba8c919997d0f1f37e154c11d509e1d2", - Size: 529, + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: "sha256:9d16f5505246424aed7116cb21216704ba8c919997d0f1f37e154c11d509e1d2", + Size: 529, + ArtifactType: "application/vnd.unknown.config.v1+json", } )