Changes for OCI 1.1RC3
Artifact Manifest was deleted ¯\_(ツ)_/¯, Image manifest keeps the subject field, gains an artifactType field, and the rules for what the artifactType actually is change. Keeping a lot of the framework that came of the Artifact Manifest because I think it lead to some worthwhile abstractions. Signed-off-by: Bracken Dawson <abdawson@gmail.com>
This commit is contained in:
parent
3b1947a38d
commit
27a96320b9
25 changed files with 372 additions and 679 deletions
2
go.mod
2
go.mod
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/mitchellh/mapstructure v1.1.2
|
github.com/mitchellh/mapstructure v1.1.2
|
||||||
github.com/ncw/swift v1.0.47
|
github.com/ncw/swift v1.0.47
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2
|
github.com/opencontainers/image-spec v1.1.0-rc3
|
||||||
github.com/prometheus/client_golang v1.12.1 // indirect; updated to latest
|
github.com/prometheus/client_golang v1.12.1 // indirect; updated to latest
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -227,8 +227,8 @@ github.com/ncw/swift v1.0.47 h1:4DQRPj35Y41WogBxyhOXlrI37nzGlyEcsforeudyYPQ=
|
||||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
|
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
||||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.artifact.manifest.v1+json",
|
|
||||||
"artifactType": "application/vnd.example.sbom.v1",
|
|
||||||
"blobs": [
|
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
"digest": "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
"size": 29876998
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subject": {
|
|
||||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
"size": 343,
|
|
||||||
"digest": "sha256:dcfff5eb40423f055a4cd0a8d7ed39ff6cb9816868f5766b4088b9e9906961b9"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.artifact.manifest.v1+json",
|
|
||||||
"artifactType": "application/vnd.example.sbom.v1",
|
|
||||||
"blobs": [
|
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
"digest": "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
"size": 29876998
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subject": {
|
|
||||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
"size": 549,
|
|
||||||
"digest": "sha256:f756842dc7541130d3a327a870a38aa9521233fc076d0ee2cea895c8c0a1e388"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"artifactType": "application/vnd.example.sbom.v1",
|
|
||||||
"blobs": [
|
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
"digest": "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
"size": 29876998
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subject": {
|
|
||||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
"size": 549,
|
|
||||||
"digest": "sha256:f756842dc7541130d3a327a870a38aa9521233fc076d0ee2cea895c8c0a1e388"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.artifact.manifest.v1+json",
|
|
||||||
"artifactType": "application/vnd.example.sbom.v1",
|
|
||||||
"blobs": [
|
|
||||||
{
|
|
||||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
"digest": "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
"size": 29876998
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"annotations": {
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package ociartifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
|
||||||
_ "github.com/distribution/distribution/v3/manifest/ocischema"
|
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ManifestBytes is an example of a valid artifact manifest
|
|
||||||
//go:embed fixtures/manifest.json
|
|
||||||
ManifestBytes []byte
|
|
||||||
ManifestDescriptor = distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
Size: 647,
|
|
||||||
Digest: "sha256:dadc6d0e6ccdcb629c78d1d13ee4e857b0fad468371e67839e777f2e1b9e33c4",
|
|
||||||
}
|
|
||||||
ManifestDeserialized = &DeserializedManifest{
|
|
||||||
Manifest: Manifest{
|
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
},
|
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
|
||||||
Blobs: []distribution.Descriptor{
|
|
||||||
{
|
|
||||||
MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
Digest: "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
Size: 29876998,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Subject: &distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeImageManifest,
|
|
||||||
Digest: "sha256:f756842dc7541130d3a327a870a38aa9521233fc076d0ee2cea895c8c0a1e388",
|
|
||||||
Size: 549,
|
|
||||||
},
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
canonical: ManifestBytes,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ManifestNoMediaType is an invalid artifact manifest because there is no
|
|
||||||
// mediaType field, unlike image manifests the fils is mandatory.
|
|
||||||
//go:embed fixtures/no-media-type.json
|
|
||||||
ManifestNoMediaType []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ManifestNoSubjectBytes is an example of a valid artifact manifest that has no
|
|
||||||
// subject.
|
|
||||||
//go:embed fixtures/no-subject.json
|
|
||||||
ManifestNoSubjectBytes []byte
|
|
||||||
ManifestNoSubjectDescriptor = distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
Size: 459,
|
|
||||||
Digest: "sha256:89d65c08998b0b94515414eb060b6c28e15f76b2c3a51efe0b2b541a53babe55",
|
|
||||||
}
|
|
||||||
ManifestNoSubjectDeserialized = &DeserializedManifest{
|
|
||||||
Manifest: Manifest{
|
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
},
|
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
|
||||||
Blobs: []distribution.Descriptor{
|
|
||||||
{
|
|
||||||
MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
|
|
||||||
Digest: "sha256:b093528a5eabd2ce6c954c0ecad0509f95536744c176f181c2640a8b126cdbcf",
|
|
||||||
Size: 29876998,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"org.opencontainers.artifact.created": "2023-01-16T14:40:01Z",
|
|
||||||
"org.example.sbom.format": "json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
canonical: ManifestNoSubjectBytes,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ManifestBlobSubjectBytes is an example of an invalid artifact manifest
|
|
||||||
// because the subject must be another manifest.
|
|
||||||
//go:embed fixtures/blob-subject.json
|
|
||||||
ManifestBlobSubjectBytes []byte
|
|
||||||
)
|
|
|
@ -1,129 +0,0 @@
|
||||||
package ociartifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
artifactFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
|
|
||||||
m := &DeserializedManifest{}
|
|
||||||
err := m.UnmarshalJSON(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, distribution.Descriptor{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dgst := digest.FromBytes(b)
|
|
||||||
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeArtifactManifest}, nil
|
|
||||||
}
|
|
||||||
if err := distribution.RegisterManifestSchema(v1.MediaTypeArtifactManifest, artifactFunc); err != nil {
|
|
||||||
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest defines an oci artifact manifest.
|
|
||||||
type Manifest struct {
|
|
||||||
manifest.Unversioned
|
|
||||||
|
|
||||||
// ArtifactType is the media type of the artifact referenced by this
|
|
||||||
// artifact manifest.
|
|
||||||
ArtifactType string `json:"artifactType,omitempty"`
|
|
||||||
|
|
||||||
// Blobs lists the descriptors for the files making up the artifact
|
|
||||||
// referenced by this this artifact manifest.
|
|
||||||
Blobs []distribution.Descriptor `json:"blobs,omitempty"`
|
|
||||||
|
|
||||||
// Subject is the descriptor of a manifest referred to by this artifact.
|
|
||||||
Subject *distribution.Descriptor `json:"subject,omitempty"`
|
|
||||||
|
|
||||||
// Annotations contain arbitrary metadata for the image manifest
|
|
||||||
Annotations map[string]string `json:"annotations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromStruct takes a Manifest structure, marshals it to JSON, and returns a
|
|
||||||
// DeserializedManifest which contains the manifest and its JSON representation.
|
|
||||||
func FromStruct(m Manifest) (*DeserializedManifest, error) {
|
|
||||||
var deserialized DeserializedManifest
|
|
||||||
deserialized.Manifest = m
|
|
||||||
|
|
||||||
var err error
|
|
||||||
deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
|
|
||||||
return &deserialized, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeserializedManifest wraps Manifest with a copy of the original JSON.
|
|
||||||
// It satisfies the distribution.Manifest interface.
|
|
||||||
type DeserializedManifest struct {
|
|
||||||
Manifest
|
|
||||||
|
|
||||||
// canonical is the canonical byte representation of the Manifest.
|
|
||||||
canonical []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
|
|
||||||
m.canonical = make([]byte, len(b))
|
|
||||||
copy(m.canonical, b)
|
|
||||||
|
|
||||||
var manifest Manifest
|
|
||||||
if err := json.Unmarshal(m.canonical, &manifest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if manifest.MediaType != v1.MediaTypeArtifactManifest {
|
|
||||||
return fmt.Errorf("mediaType in manifest must be '%q' not '%s'",
|
|
||||||
v1.MediaTypeArtifactManifest, manifest.MediaType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The subject if specified must be a must be a manifest. This is validated
|
|
||||||
// here rather then in the storage manifest Put handler because the subject
|
|
||||||
// does not have to exist, so there is nothing to validate in the manifest
|
|
||||||
// store. If a non-compliant client provided the digest of a blob then the
|
|
||||||
// registry would still indicate that the referred manifest does not exist.
|
|
||||||
if manifest.Subject != nil {
|
|
||||||
if !distribution.ManifestMediaTypeSupported(manifest.Subject.MediaType) {
|
|
||||||
return fmt.Errorf("subject.mediaType must be a manifest, not '%s'", manifest.Subject.MediaType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Manifest = manifest
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON returns the contents of canonical. If canonical is empty,
|
|
||||||
// returns an error.
|
|
||||||
func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(m.canonical) > 0 {
|
|
||||||
return m.canonical, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("JSON representation not initialized in DeserializedManifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payload returns the raw content of the manifest. The contents can be used to
|
|
||||||
// calculate the content identifier.
|
|
||||||
func (m *DeserializedManifest) Payload() (string, []byte, error) {
|
|
||||||
return v1.MediaTypeArtifactManifest, m.canonical, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// References returns the descriptors of this manifest's blobs only.
|
|
||||||
func (m *DeserializedManifest) References() []distribution.Descriptor {
|
|
||||||
return m.Blobs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subject returns a pointer to the subject of this manifest or nil if there is
|
|
||||||
// none
|
|
||||||
func (m *DeserializedManifest) Subject() *distribution.Descriptor {
|
|
||||||
return m.Manifest.Subject
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the artifactType of this manifest if there is one, otherwise it
|
|
||||||
// returns empty string.
|
|
||||||
func (m *DeserializedManifest) Type() string {
|
|
||||||
return m.Manifest.ArtifactType
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
package ociartifact_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifactFunc(t *testing.T) {
|
|
||||||
for name, test := range map[string]struct {
|
|
||||||
rawManifest []byte
|
|
||||||
expectError bool
|
|
||||||
expectedDescriptor distribution.Descriptor
|
|
||||||
expectedManifest distribution.Manifest
|
|
||||||
}{
|
|
||||||
"valid_artifact": {
|
|
||||||
rawManifest: ociartifact.ManifestBytes,
|
|
||||||
expectedDescriptor: ociartifact.ManifestDescriptor,
|
|
||||||
expectedManifest: ociartifact.ManifestDeserialized,
|
|
||||||
},
|
|
||||||
"artifact_must_have_mediaType": {
|
|
||||||
rawManifest: ociartifact.ManifestNoMediaType,
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
"artifact_can_have_no_subject": {
|
|
||||||
rawManifest: ociartifact.ManifestNoSubjectBytes,
|
|
||||||
expectedDescriptor: ociartifact.ManifestNoSubjectDescriptor,
|
|
||||||
expectedManifest: ociartifact.ManifestNoSubjectDeserialized,
|
|
||||||
},
|
|
||||||
"artifact_subject_must_be_manifest": {
|
|
||||||
rawManifest: ociartifact.ManifestBlobSubjectBytes,
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
test := test
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
manifest, descriptor, err := distribution.UnmarshalManifest(v1.MediaTypeArtifactManifest, test.rawManifest)
|
|
||||||
|
|
||||||
if err != nil != test.expectError {
|
|
||||||
t.Fatalf("Unexpected error value: %s, expected error=%t", err, test.expectError)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(descriptor, test.expectedDescriptor) {
|
|
||||||
t.Errorf("Descriptor incorrect:\n%v\nexpected:\n%v", descriptor, test.expectedDescriptor)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(manifest, test.expectedManifest) {
|
|
||||||
t.Errorf("Manifest incorrect:\n%v\nexpected:\n%v", manifest, test.expectedManifest)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPayload(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeArtifactManifest, ociartifact.ManifestBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to unmarshal manifest: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaType, payload, err := manifest.Payload()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
if mediaType != v1.MediaTypeArtifactManifest {
|
|
||||||
t.Errorf("Unexpected mediaType %q, should be %q", mediaType, v1.MediaTypeArtifactManifest)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(payload, ociartifact.ManifestBytes) {
|
|
||||||
t.Errorf("Unexpected payload, should exactly match inputted manifest.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReferences(t *testing.T) {
|
|
||||||
// References only returns the blobs of this artifact and not also it's
|
|
||||||
// subject because the subject does not have to exist when the artifact is
|
|
||||||
// pushed. Unlike when an image index returns the references image manifests
|
|
||||||
// because those do have to exist before the image index is pushed.
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeArtifactManifest, ociartifact.ManifestBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to unmarshal manifest: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
references := manifest.References()
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(references, ociartifact.ManifestDeserialized.Blobs) {
|
|
||||||
t.Errorf("Unexpected references:\n%v\nexpected:\n%v", references, ociartifact.ManifestDeserialized.Blobs)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,6 +45,10 @@ type Manifest struct {
|
||||||
// Config references the image configuration as a blob.
|
// Config references the image configuration as a blob.
|
||||||
Config distribution.Descriptor `json:"config"`
|
Config distribution.Descriptor `json:"config"`
|
||||||
|
|
||||||
|
// ArtifactType is the type of an artifact when the manifest is used for an
|
||||||
|
// artifact.
|
||||||
|
ArtifactType string `json:"artifactType,omitempty"`
|
||||||
|
|
||||||
// Layers lists descriptors for the layers referenced by the
|
// Layers lists descriptors for the layers referenced by the
|
||||||
// configuration.
|
// configuration.
|
||||||
Layers []distribution.Descriptor `json:"layers"`
|
Layers []distribution.Descriptor `json:"layers"`
|
||||||
|
@ -106,6 +110,21 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
|
||||||
v1.MediaTypeImageManifest, mfst.MediaType)
|
v1.MediaTypeImageManifest, mfst.MediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mfst.Config.MediaType == v1.MediaTypeScratch && mfst.ArtifactType == "" {
|
||||||
|
return fmt.Errorf("if config.mediaType is '%s' then artifactType must be set", v1.MediaTypeScratch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The subject if specified must be a must be a manifest. This is validated
|
||||||
|
// here rather than in the storage manifest Put handler because the subject
|
||||||
|
// does not have to exist, so there is nothing to validate in the manifest
|
||||||
|
// store. If a non-compliant client provided the digest of a blob then this
|
||||||
|
// registry would still indicate that the referred manifest does not exist.
|
||||||
|
if mfst.Subject != nil {
|
||||||
|
if !distribution.ManifestMediaTypeSupported(mfst.Subject.MediaType) {
|
||||||
|
return fmt.Errorf("subject.mediaType must be a manifest, not '%s'", mfst.Subject.MediaType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.Manifest = mfst
|
m.Manifest = mfst
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -133,10 +152,12 @@ func (m *DeserializedManifest) Subject() *distribution.Descriptor {
|
||||||
return m.Manifest.Subject
|
return m.Manifest.Subject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns empty string because image manifests with subjects cannot have
|
// Type returns the artifactType of the manifest
|
||||||
// an artifact type.
|
|
||||||
func (m *DeserializedManifest) Type() string {
|
func (m *DeserializedManifest) Type() string {
|
||||||
return ""
|
if m.ArtifactType == "" {
|
||||||
|
return m.Config.MediaType
|
||||||
|
}
|
||||||
|
return m.ArtifactType
|
||||||
}
|
}
|
||||||
|
|
||||||
// unknownDocument represents a manifest, manifest list, or index that has not
|
// unknownDocument represents a manifest, manifest list, or index that has not
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/distribution/distribution/v3"
|
"github.com/distribution/distribution/v3"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
"github.com/distribution/distribution/v3/manifest"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
|
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
@ -39,6 +40,14 @@ const expectedManifestSerialization = `{
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
var (
|
||||||
|
scratchDescriptor = distribution.Descriptor{
|
||||||
|
MediaType: v1.ScratchDescriptor.MediaType,
|
||||||
|
Size: v1.ScratchDescriptor.Size,
|
||||||
|
Digest: v1.ScratchDescriptor.Digest,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func makeTestManifest(mediaType string) Manifest {
|
func makeTestManifest(mediaType string) Manifest {
|
||||||
return Manifest{
|
return Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: manifest.Versioned{
|
||||||
|
@ -214,3 +223,191 @@ func TestValidateManifest(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArtifactManifest(t *testing.T) {
|
||||||
|
for name, test := range map[string]struct {
|
||||||
|
manifest Manifest
|
||||||
|
expectValid bool
|
||||||
|
expectedArtifactType string
|
||||||
|
}{
|
||||||
|
"not_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
Config: distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageConfig,
|
||||||
|
Size: 200,
|
||||||
|
Digest: "sha256:4de6702c739d8c9ed907f4c031fd0abc54ee1bf372603a585e139730772cc0b8",
|
||||||
|
},
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: v1.MediaTypeImageLayerGzip,
|
||||||
|
Size: 23423,
|
||||||
|
Digest: "sha256:ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: v1.MediaTypeImageConfig,
|
||||||
|
},
|
||||||
|
"typical_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
Config: distribution.Descriptor{
|
||||||
|
MediaType: "application/vnd.example.thing",
|
||||||
|
Size: 200,
|
||||||
|
Digest: "sha256:4de6702c739d8c9ed907f4c031fd0abc54ee1bf372603a585e139730772cc0b8",
|
||||||
|
},
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: v1.MediaTypeImageLayerGzip,
|
||||||
|
Size: 23423,
|
||||||
|
Digest: "sha256:ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.thing",
|
||||||
|
},
|
||||||
|
"also_typical_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.sbom",
|
||||||
|
Config: distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageConfig,
|
||||||
|
Size: 200,
|
||||||
|
Digest: "sha256:4de6702c739d8c9ed907f4c031fd0abc54ee1bf372603a585e139730772cc0b8",
|
||||||
|
},
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: v1.MediaTypeImageLayerGzip,
|
||||||
|
Size: 23423,
|
||||||
|
Digest: "sha256:ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.sbom",
|
||||||
|
},
|
||||||
|
"configless_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.catgif",
|
||||||
|
Config: scratchDescriptor,
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "image/gif",
|
||||||
|
Size: 23423,
|
||||||
|
Digest: "sha256:ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.catgif",
|
||||||
|
},
|
||||||
|
"invalid_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
Config: scratchDescriptor, // This MUST have an artifactType
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "image/gif",
|
||||||
|
Size: 23423,
|
||||||
|
Digest: "sha256:ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: false,
|
||||||
|
},
|
||||||
|
"annotation_artifact": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.comment",
|
||||||
|
Config: scratchDescriptor,
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
scratchDescriptor,
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.example.data": "payload",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.comment",
|
||||||
|
},
|
||||||
|
"valid_subject": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.comment",
|
||||||
|
Config: scratchDescriptor,
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
scratchDescriptor,
|
||||||
|
},
|
||||||
|
Subject: &distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageManifest,
|
||||||
|
Size: 365,
|
||||||
|
Digest: "sha256:05b3abf2579a5eb66403cd78be557fd860633a1fe2103c7642030defe32c657f",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.example.data": "payload",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.comment",
|
||||||
|
},
|
||||||
|
"invalid_subject": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.comment",
|
||||||
|
Config: scratchDescriptor,
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
scratchDescriptor,
|
||||||
|
},
|
||||||
|
Subject: &distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageLayerGzip, // The subject is a manifest
|
||||||
|
Size: 365,
|
||||||
|
Digest: "sha256:05b3abf2579a5eb66403cd78be557fd860633a1fe2103c7642030defe32c657f",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.example.data": "payload",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: false,
|
||||||
|
},
|
||||||
|
"docker_manifest_valid_as_subject": {
|
||||||
|
manifest: Manifest{
|
||||||
|
Versioned: SchemaVersion,
|
||||||
|
ArtifactType: "application/vnd.example.comment",
|
||||||
|
Config: scratchDescriptor,
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
scratchDescriptor,
|
||||||
|
},
|
||||||
|
Subject: &distribution.Descriptor{
|
||||||
|
MediaType: schema2.MediaTypeManifest,
|
||||||
|
Size: 365,
|
||||||
|
Digest: "sha256:05b3abf2579a5eb66403cd78be557fd860633a1fe2103c7642030defe32c657f",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.example.data": "payload",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectValid: true,
|
||||||
|
expectedArtifactType: "application/vnd.example.comment",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
dm, err := FromStruct(test.manifest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error making DeserializedManifest from struct: %s", err)
|
||||||
|
}
|
||||||
|
m, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, dm.canonical)
|
||||||
|
if test.expectValid != (nil == err) {
|
||||||
|
t.Fatalf("expectValid=%t but got err=%v", test.expectValid, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if artifactType := m.(distribution.Referrer).Type(); artifactType != test.expectedArtifactType {
|
||||||
|
t.Errorf("Expected artifactType to be %q but got %q", test.expectedArtifactType, artifactType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,10 +10,3 @@ type Versioned struct {
|
||||||
// MediaType is the media type of this schema.
|
// MediaType is the media type of this schema.
|
||||||
MediaType string `json:"mediaType,omitempty"`
|
MediaType string `json:"mediaType,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unversioned provides a struct with the mediaType only. Incoming content with
|
|
||||||
// unknown mediaType can be decoded against this struct to check the type.
|
|
||||||
type Unversioned struct {
|
|
||||||
// MediaType is the media type of this schema.
|
|
||||||
MediaType string `json:"mediaType,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
"github.com/distribution/distribution/v3/manifest"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
||||||
"github.com/distribution/distribution/v3/manifest/schema2"
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
|
@ -40,9 +39,16 @@ import (
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var headerConfig = http.Header{
|
var (
|
||||||
|
headerConfig = http.Header{
|
||||||
"X-Content-Type-Options": []string{"nosniff"},
|
"X-Content-Type-Options": []string{"nosniff"},
|
||||||
}
|
}
|
||||||
|
scratchDescriptor = distribution.Descriptor{
|
||||||
|
MediaType: v1.ScratchDescriptor.MediaType,
|
||||||
|
Size: v1.ScratchDescriptor.Size,
|
||||||
|
Digest: v1.ScratchDescriptor.Digest,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// digestSha256EmptyTar is the canonical sha256 digest of empty data
|
// digestSha256EmptyTar is the canonical sha256 digest of empty data
|
||||||
|
@ -3163,50 +3169,8 @@ func TestArtifactManifest(t *testing.T) {
|
||||||
manifest func(*testing.T, *testEnv, reference.Named) distribution.Manifest
|
manifest func(*testing.T, *testEnv, reference.Named) distribution.Manifest
|
||||||
deleteSubject bool
|
deleteSubject bool
|
||||||
}{
|
}{
|
||||||
// When an OCI artifact is PUT before its subject, the subject's referrers
|
// The link is made when the subject already exists and is kept if the
|
||||||
// link will be made in advance.
|
// subject is deleted
|
||||||
"valid": {
|
|
||||||
manifest: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Manifest {
|
|
||||||
manifest, err := ociartifact.FromStruct(ociartifact.Manifest{
|
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
},
|
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
|
||||||
Subject: &distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeImageManifest,
|
|
||||||
Digest: "sha256:ebe054f08821294feee7bc442014fdd38b4836d83781d8ba99d38eb50d0c9d85",
|
|
||||||
Size: 99,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create manifest: %s", err)
|
|
||||||
}
|
|
||||||
return manifest
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// An artifact can have no artifactType
|
|
||||||
"no_type": {
|
|
||||||
manifest: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Manifest {
|
|
||||||
manifest, err := ociartifact.FromStruct(ociartifact.Manifest{
|
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
},
|
|
||||||
ArtifactType: "",
|
|
||||||
Subject: &distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeImageManifest,
|
|
||||||
Digest: "sha256:ebe054f08821294feee7bc442014fdd38b4836d83781d8ba99d38eb50d0c9d85",
|
|
||||||
Size: 99,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create manifest: %s", err)
|
|
||||||
}
|
|
||||||
return manifest
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// The link is also made when the subject already exists and is kept if
|
|
||||||
// the subject is deleted
|
|
||||||
"subject_exists": {
|
"subject_exists": {
|
||||||
manifest: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Manifest {
|
manifest: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Manifest {
|
||||||
args := testManifestAPISchema2(t, testEnv, repo)
|
args := testManifestAPISchema2(t, testEnv, repo)
|
||||||
|
@ -3215,11 +3179,12 @@ func TestArtifactManifest(t *testing.T) {
|
||||||
t.Fatalf("Failed to get subject payload: %s", err)
|
t.Fatalf("Failed to get subject payload: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, err := ociartifact.FromStruct(ociartifact.Manifest{
|
pushScratch(t, testEnv, repo)
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
manifest, err := ocischema.FromStruct(ocischema.Manifest{
|
||||||
},
|
Versioned: ocischema.SchemaVersion,
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
ArtifactType: "application/vnd.example.sbom.v1",
|
||||||
|
Config: scratchDescriptor,
|
||||||
Subject: &distribution.Descriptor{
|
Subject: &distribution.Descriptor{
|
||||||
MediaType: args.mediaType,
|
MediaType: args.mediaType,
|
||||||
Digest: args.dgst,
|
Digest: args.dgst,
|
||||||
|
@ -3244,10 +3209,7 @@ func TestArtifactManifest(t *testing.T) {
|
||||||
url, _ := startPushLayer(t, testEnv, repo)
|
url, _ := startPushLayer(t, testEnv, repo)
|
||||||
pushLayer(t, testEnv.builder, repo, configDigest, url, config)
|
pushLayer(t, testEnv.builder, repo, configDigest, url, config)
|
||||||
manifest, err := ocischema.FromStruct(ocischema.Manifest{
|
manifest, err := ocischema.FromStruct(ocischema.Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: ocischema.SchemaVersion,
|
||||||
SchemaVersion: 2,
|
|
||||||
MediaType: v1.MediaTypeImageManifest,
|
|
||||||
},
|
|
||||||
Config: distribution.Descriptor{
|
Config: distribution.Descriptor{
|
||||||
MediaType: v1.MediaTypeImageConfig,
|
MediaType: v1.MediaTypeImageConfig,
|
||||||
Digest: configDigest,
|
Digest: configDigest,
|
||||||
|
@ -3412,7 +3374,7 @@ func TestArtifactManifest(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create artifact GET request: %s", err)
|
t.Fatalf("Failed to create artifact GET request: %s", err)
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", v1.MediaTypeArtifactManifest)
|
req.Header.Set("Accept", v1.MediaTypeImageManifest)
|
||||||
res, err = http.DefaultClient.Do(req)
|
res, err = http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to GET manifest: %s", err)
|
t.Fatalf("Failed to GET manifest: %s", err)
|
||||||
|
@ -3512,12 +3474,17 @@ func TestDockerManifestWithSubject(t *testing.T) {
|
||||||
|
|
||||||
func TestArtifactManifestValidation(t *testing.T) {
|
func TestArtifactManifestValidation(t *testing.T) {
|
||||||
for name, test := range map[string]struct {
|
for name, test := range map[string]struct {
|
||||||
blobs func(*testing.T, *testEnv, reference.Named) []distribution.Descriptor
|
config func(*testing.T, *testEnv, reference.Named) distribution.Descriptor
|
||||||
|
layers func(*testing.T, *testEnv, reference.Named) []distribution.Descriptor
|
||||||
wantCode int
|
wantCode int
|
||||||
}{
|
}{
|
||||||
"blobs_must_exist": {
|
"layers_must_exist": {
|
||||||
blobs: func(t *testing.T, te *testEnv, n reference.Named) []distribution.Descriptor {
|
config: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Descriptor {
|
||||||
// a blob which has not been uploaded
|
pushScratch(t, testEnv, repo)
|
||||||
|
return scratchDescriptor
|
||||||
|
},
|
||||||
|
layers: func(t *testing.T, te *testEnv, n reference.Named) []distribution.Descriptor {
|
||||||
|
// a layer which has not been uploaded
|
||||||
return []distribution.Descriptor{
|
return []distribution.Descriptor{
|
||||||
{
|
{
|
||||||
MediaType: v1.MediaTypeImageLayer,
|
MediaType: v1.MediaTypeImageLayer,
|
||||||
|
@ -3528,8 +3495,60 @@ func TestArtifactManifestValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantCode: 400,
|
wantCode: 400,
|
||||||
},
|
},
|
||||||
|
"config_must_be_set": {
|
||||||
|
config: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Descriptor {
|
||||||
|
return distribution.Descriptor{} // zero value
|
||||||
|
},
|
||||||
|
layers: func(t *testing.T, testEnv *testEnv, repo reference.Named) []distribution.Descriptor {
|
||||||
|
layers := int64(10)
|
||||||
|
digests := make([]distribution.Descriptor, layers)
|
||||||
|
for i := int64(0); i < layers; i++ {
|
||||||
|
rs, digest, err := testutil.CreateRandomTarFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test blob: %s", err)
|
||||||
|
}
|
||||||
|
url, _ := startPushLayer(t, testEnv, repo)
|
||||||
|
pushLayer(t, testEnv.builder, repo, digest, url, rs)
|
||||||
|
digests[i] = distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageLayer,
|
||||||
|
Digest: digest,
|
||||||
|
Size: testutil.MustSeekerLen(rs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digests
|
||||||
|
},
|
||||||
|
wantCode: 400,
|
||||||
|
},
|
||||||
|
"config_must_exist": {
|
||||||
|
config: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Descriptor {
|
||||||
|
return scratchDescriptor // not uploaded
|
||||||
|
},
|
||||||
|
layers: func(t *testing.T, testEnv *testEnv, repo reference.Named) []distribution.Descriptor {
|
||||||
|
layers := int64(10)
|
||||||
|
digests := make([]distribution.Descriptor, layers)
|
||||||
|
for i := int64(0); i < layers; i++ {
|
||||||
|
rs, digest, err := testutil.CreateRandomTarFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test blob: %s", err)
|
||||||
|
}
|
||||||
|
url, _ := startPushLayer(t, testEnv, repo)
|
||||||
|
pushLayer(t, testEnv.builder, repo, digest, url, rs)
|
||||||
|
digests[i] = distribution.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageLayer,
|
||||||
|
Digest: digest,
|
||||||
|
Size: testutil.MustSeekerLen(rs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digests
|
||||||
|
},
|
||||||
|
wantCode: 400,
|
||||||
|
},
|
||||||
"valid_blobs": {
|
"valid_blobs": {
|
||||||
blobs: func(t *testing.T, testEnv *testEnv, repo reference.Named) []distribution.Descriptor {
|
config: func(t *testing.T, testEnv *testEnv, repo reference.Named) distribution.Descriptor {
|
||||||
|
pushScratch(t, testEnv, repo)
|
||||||
|
return scratchDescriptor
|
||||||
|
},
|
||||||
|
layers: func(t *testing.T, testEnv *testEnv, repo reference.Named) []distribution.Descriptor {
|
||||||
layers := int64(10)
|
layers := int64(10)
|
||||||
digests := make([]distribution.Descriptor, layers)
|
digests := make([]distribution.Descriptor, layers)
|
||||||
for i := int64(0); i < layers; i++ {
|
for i := int64(0); i < layers; i++ {
|
||||||
|
@ -3559,12 +3578,11 @@ func TestArtifactManifestValidation(t *testing.T) {
|
||||||
t.Fatalf("failed to make repo: %s", err)
|
t.Fatalf("failed to make repo: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, err := ociartifact.FromStruct(ociartifact.Manifest{
|
manifest, err := ocischema.FromStruct(ocischema.Manifest{
|
||||||
Unversioned: manifest.Unversioned{
|
Versioned: ocischema.SchemaVersion,
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
Config: test.config(t, testEnv, repo),
|
||||||
},
|
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
ArtifactType: "application/vnd.example.sbom.v1",
|
||||||
Blobs: test.blobs(t, testEnv, repo),
|
Layers: test.layers(t, testEnv, repo),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to make manifest: %s", err)
|
t.Fatalf("Failed to make manifest: %s", err)
|
||||||
|
@ -3600,3 +3618,8 @@ func TestArtifactManifestValidation(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pushScratch(t *testing.T, testEnv *testEnv, repo reference.Named) {
|
||||||
|
url, _ := startPushLayer(t, testEnv, repo)
|
||||||
|
pushLayer(t, testEnv.builder, repo, v1.ScratchDescriptor.Digest, url, bytes.NewBuffer(v1.ScratchDescriptor.Data))
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/distribution/distribution/v3"
|
"github.com/distribution/distribution/v3"
|
||||||
dcontext "github.com/distribution/distribution/v3/context"
|
dcontext "github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
||||||
"github.com/distribution/distribution/v3/manifest/schema2"
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
|
@ -41,8 +40,7 @@ const (
|
||||||
manifestlistSchema // 2
|
manifestlistSchema // 2
|
||||||
ociSchema // 3
|
ociSchema // 3
|
||||||
ociImageIndexSchema // 4
|
ociImageIndexSchema // 4
|
||||||
ociArtifact // 5
|
numStorageTypes // 5
|
||||||
numStorageTypes // 6
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// manifestDispatcher takes the request context and builds the
|
// manifestDispatcher takes the request context and builds the
|
||||||
|
@ -117,9 +115,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
if mediaType == v1.MediaTypeImageIndex {
|
if mediaType == v1.MediaTypeImageIndex {
|
||||||
supports[ociImageIndexSchema] = true
|
supports[ociImageIndexSchema] = true
|
||||||
}
|
}
|
||||||
if mediaType == v1.MediaTypeArtifactManifest {
|
|
||||||
supports[ociArtifact] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +158,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
manifestType = manifestSchema2
|
manifestType = manifestSchema2
|
||||||
} else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
|
} else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
|
||||||
manifestType = ociSchema
|
manifestType = ociSchema
|
||||||
} else if _, isOCIArtifact := manifest.(*ociartifact.DeserializedManifest); isOCIArtifact {
|
|
||||||
manifestType = ociArtifact
|
|
||||||
} else if isManifestList {
|
} else if isManifestList {
|
||||||
if manifestList.MediaType == manifestlist.MediaTypeManifestList {
|
if manifestList.MediaType == manifestlist.MediaTypeManifestList {
|
||||||
manifestType = manifestlistSchema
|
manifestType = manifestlistSchema
|
||||||
|
@ -181,10 +174,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestNotAcceptable.WithMessage("OCI index found, but accept header does not support OCI indexes"))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestNotAcceptable.WithMessage("OCI index found, but accept header does not support OCI indexes"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if manifestType == ociArtifact && !supports[ociArtifact] {
|
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestNotAcceptable.WithMessage("OCI artifact found, but accept header does not support OCI artifacts"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Only rewrite schema2 manifests when they are being fetched by tag.
|
// Only rewrite schema2 manifests when they are being fetched by tag.
|
||||||
// If they are being fetched by digest, we can't return something not
|
// If they are being fetched by digest, we can't return something not
|
||||||
// matching the digest.
|
// matching the digest.
|
||||||
|
@ -333,8 +322,7 @@ func (imh *manifestHandler) PutManifest(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex ||
|
isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex
|
||||||
mediaType == v1.MediaTypeArtifactManifest
|
|
||||||
|
|
||||||
if isAnOCIManifest {
|
if isAnOCIManifest {
|
||||||
dcontext.GetLogger(imh).Debug("Putting an OCI Manifest!")
|
dcontext.GetLogger(imh).Debug("Putting an OCI Manifest!")
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"schemaVersion": "this is allowed, I can do this if I want",
|
|
||||||
"mediaType": "application/vnd.oci.artifact.manifest.v1+json"
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
dcontext "github.com/distribution/distribution/v3/context"
|
dcontext "github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
"github.com/distribution/distribution/v3/manifest"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
||||||
"github.com/distribution/distribution/v3/manifest/schema2"
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
|
@ -90,21 +89,6 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ..
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The content is unmarshalled into Unversioned first because it's possible
|
|
||||||
// for a spec compliant OCI artifact manifest to fail to unmarshal into
|
|
||||||
// Versioned due to the schemaVersion property being unknown. We MUST ignore
|
|
||||||
// this unknown property, and unmarshalling into Versioned would implicitly
|
|
||||||
// constrain that property to a number type.
|
|
||||||
var unversioned manifest.Unversioned
|
|
||||||
if err = json.Unmarshal(content, &unversioned); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch unversioned.MediaType {
|
|
||||||
case v1.MediaTypeArtifactManifest:
|
|
||||||
return ms.ocischemaHandler.Unmarshal(ctx, dgst, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
var versioned manifest.Versioned
|
var versioned manifest.Versioned
|
||||||
if err = json.Unmarshal(content, &versioned); err != nil {
|
if err = json.Unmarshal(content, &versioned); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -150,7 +134,7 @@ func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest
|
||||||
return ms.schema1Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
return ms.schema1Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
||||||
case *schema2.DeserializedManifest:
|
case *schema2.DeserializedManifest:
|
||||||
return ms.schema2Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
return ms.schema2Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
||||||
case *ocischema.DeserializedManifest, *ociartifact.DeserializedManifest:
|
case *ocischema.DeserializedManifest:
|
||||||
return ms.ocischemaHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
return ms.ocischemaHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
||||||
case *manifestlist.DeserializedManifestList:
|
case *manifestlist.DeserializedManifestList:
|
||||||
return ms.manifestListHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
return ms.manifestListHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
||||||
|
|
|
@ -2,14 +2,11 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
"github.com/distribution/distribution/v3"
|
||||||
dcontext "github.com/distribution/distribution/v3/context"
|
dcontext "github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
@ -30,25 +27,8 @@ var _ ManifestHandler = &ocischemaManifestHandler{}
|
||||||
func (ms *ocischemaManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
|
func (ms *ocischemaManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
|
||||||
dcontext.GetLogger(ms.ctx).Debug("(*ocischemaManifestHandler).Unmarshal")
|
dcontext.GetLogger(ms.ctx).Debug("(*ocischemaManifestHandler).Unmarshal")
|
||||||
|
|
||||||
var unversioned manifest.Unversioned
|
m := &ocischema.DeserializedManifest{}
|
||||||
if err := json.Unmarshal(content, &unversioned); err != nil {
|
if err := m.UnmarshalJSON(content); err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var m distribution.Manifest
|
|
||||||
switch unversioned.MediaType {
|
|
||||||
case "", v1.MediaTypeImageManifest:
|
|
||||||
m = &ocischema.DeserializedManifest{}
|
|
||||||
case v1.MediaTypeArtifactManifest:
|
|
||||||
m = &ociartifact.DeserializedManifest{}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("if present, mediaType should be '%s' or '%s', not '%s'",
|
|
||||||
v1.MediaTypeImageManifest,
|
|
||||||
v1.MediaTypeArtifactManifest,
|
|
||||||
unversioned.MediaType,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.(json.Unmarshaler).UnmarshalJSON(content); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +38,12 @@ func (ms *ocischemaManifestHandler) Unmarshal(ctx context.Context, dgst digest.D
|
||||||
func (ms *ocischemaManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
|
func (ms *ocischemaManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
|
||||||
dcontext.GetLogger(ms.ctx).Debug("(*ocischemaManifestHandler).Put")
|
dcontext.GetLogger(ms.ctx).Debug("(*ocischemaManifestHandler).Put")
|
||||||
|
|
||||||
if err := ms.verifyManifest(ms.ctx, manifest, skipDependencyVerification); err != nil {
|
m, ok := manifest.(*ocischema.DeserializedManifest)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("non-ocischema manifest put to ocischemaManifestHandler: %T", manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ms.verifyManifest(ms.ctx, *m, skipDependencyVerification); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,17 +72,11 @@ func (ms *ocischemaManifestHandler) Put(ctx context.Context, manifest distributi
|
||||||
// verifyManifest ensures that the manifest content is valid from the
|
// verifyManifest ensures that the manifest content is valid from the
|
||||||
// perspective of the registry. As a policy, the registry only tries to store
|
// perspective of the registry. As a policy, the registry only tries to store
|
||||||
// valid content, leaving trust policies of that content up to consumers.
|
// valid content, leaving trust policies of that content up to consumers.
|
||||||
func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst distribution.Manifest, skipDependencyVerification bool) error {
|
func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst ocischema.DeserializedManifest, skipDependencyVerification bool) error {
|
||||||
var errs distribution.ErrManifestVerification
|
var errs distribution.ErrManifestVerification
|
||||||
|
|
||||||
switch m := mnfst.(type) {
|
if mnfst.Manifest.SchemaVersion != 2 {
|
||||||
case *ocischema.DeserializedManifest:
|
return fmt.Errorf("unrecognized manifest schema version %d", mnfst.Manifest.SchemaVersion)
|
||||||
if m.Manifest.SchemaVersion != 2 {
|
|
||||||
return fmt.Errorf("unrecognized manifest schema version %d", m.Manifest.SchemaVersion)
|
|
||||||
}
|
|
||||||
case *ociartifact.DeserializedManifest:
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unrecognized manifest type: %T", mnfst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipDependencyVerification {
|
if skipDependencyVerification {
|
||||||
|
|
|
@ -2,24 +2,18 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3"
|
"github.com/distribution/distribution/v3"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
"github.com/distribution/distribution/v3/manifest"
|
||||||
"github.com/distribution/distribution/v3/manifest/ociartifact"
|
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed fixtures/naughtiest-artifact.json
|
|
||||||
var naughtiestArtifact []byte
|
|
||||||
|
|
||||||
func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
|
func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
inmemoryDriver := inmemory.New()
|
inmemoryDriver := inmemory.New()
|
||||||
|
@ -338,81 +332,3 @@ func TestVerifyOCIManifestBlobLayerAndConfig(t *testing.T) {
|
||||||
checkFn(m, c.Err)
|
checkFn(m, c.Err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutArtifact(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
driver := inmemory.New()
|
|
||||||
registry := createRegistry(t, driver)
|
|
||||||
repo := makeRepository(t, registry, "test")
|
|
||||||
manifestService := makeManifestService(t, repo)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
blob, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageLayerGzip, []byte("I am an artifact"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
template := ociartifact.Manifest{
|
|
||||||
Unversioned: manifest.Unversioned{
|
|
||||||
MediaType: v1.MediaTypeArtifactManifest,
|
|
||||||
},
|
|
||||||
ArtifactType: "application/vnd.example.sbom.v1",
|
|
||||||
Blobs: []distribution.Descriptor{
|
|
||||||
blob,
|
|
||||||
},
|
|
||||||
Subject: &distribution.Descriptor{
|
|
||||||
MediaType: v1.MediaTypeImageManifest,
|
|
||||||
Digest: "sha256:195ce2d6ff471aa95e91f3ea1e95a27d474a452f040d4c18f6eb29f3ca42a821",
|
|
||||||
Size: 21,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest, err := ociartifact.FromStruct(template)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to construct artifact manifest: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = manifestService.Put(ctx, manifest)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetArtifact(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
driver := inmemory.New()
|
|
||||||
registry := createRegistry(t, driver)
|
|
||||||
repo := makeRepository(t, registry, "test")
|
|
||||||
manifestService := makeManifestService(t, repo)
|
|
||||||
|
|
||||||
manifest := &ociartifact.DeserializedManifest{}
|
|
||||||
err := manifest.UnmarshalJSON(naughtiestArtifact)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse valid artifact manifest: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
dgst, err := manifestService.Put(ctx, manifest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to Put valid artifact manifest: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gotManifest, err := manifestService.Get(ctx, dgst)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get valid artifact manifest: %s", err)
|
|
||||||
}
|
|
||||||
_, gotPayload, err := gotManifest.Payload()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed ot get returned manifest payload: %s", err)
|
|
||||||
}
|
|
||||||
_, expectedPayload, err := manifest.Payload()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed ot get valid manifest payload: %s", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(gotPayload, expectedPayload) {
|
|
||||||
t.Errorf("Pulled manifest does not match put manifest, got:\n%s\nexpected:\n%s", gotPayload, expectedPayload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
3
vendor/github.com/opencontainers/image-spec/specs-go/v1/annotations.go
generated
vendored
3
vendor/github.com/opencontainers/image-spec/specs-go/v1/annotations.go
generated
vendored
|
@ -65,7 +65,4 @@ const (
|
||||||
|
|
||||||
// AnnotationArtifactDescription is the annotation key for the human readable description for the artifact.
|
// AnnotationArtifactDescription is the annotation key for the human readable description for the artifact.
|
||||||
AnnotationArtifactDescription = "org.opencontainers.artifact.description"
|
AnnotationArtifactDescription = "org.opencontainers.artifact.description"
|
||||||
|
|
||||||
// AnnotationReferrersFiltersApplied is the annotation key for the comma separated list of filters applied by the registry in the referrers listing.
|
|
||||||
AnnotationReferrersFiltersApplied = "org.opencontainers.referrers.filtersApplied"
|
|
||||||
)
|
)
|
||||||
|
|
34
vendor/github.com/opencontainers/image-spec/specs-go/v1/artifact.go
generated
vendored
34
vendor/github.com/opencontainers/image-spec/specs-go/v1/artifact.go
generated
vendored
|
@ -1,34 +0,0 @@
|
||||||
// Copyright 2022 The Linux Foundation
|
|
||||||
//
|
|
||||||
// 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 v1
|
|
||||||
|
|
||||||
// Artifact describes an artifact manifest.
|
|
||||||
// This structure provides `application/vnd.oci.artifact.manifest.v1+json` mediatype when marshalled to JSON.
|
|
||||||
type Artifact struct {
|
|
||||||
// MediaType is the media type of the object this schema refers to.
|
|
||||||
MediaType string `json:"mediaType"`
|
|
||||||
|
|
||||||
// ArtifactType is the IANA media type of the artifact this schema refers to.
|
|
||||||
ArtifactType string `json:"artifactType"`
|
|
||||||
|
|
||||||
// Blobs is a collection of blobs referenced by this manifest.
|
|
||||||
Blobs []Descriptor `json:"blobs,omitempty"`
|
|
||||||
|
|
||||||
// Subject (reference) is an optional link from the artifact to another manifest forming an association between the artifact and the other manifest.
|
|
||||||
Subject *Descriptor `json:"subject,omitempty"`
|
|
||||||
|
|
||||||
// Annotations contains arbitrary metadata for the artifact manifest.
|
|
||||||
Annotations map[string]string `json:"annotations,omitempty"`
|
|
||||||
}
|
|
29
vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go
generated
vendored
29
vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go
generated
vendored
|
@ -48,6 +48,17 @@ type ImageConfig struct {
|
||||||
|
|
||||||
// StopSignal contains the system call signal that will be sent to the container to exit.
|
// StopSignal contains the system call signal that will be sent to the container to exit.
|
||||||
StopSignal string `json:"StopSignal,omitempty"`
|
StopSignal string `json:"StopSignal,omitempty"`
|
||||||
|
|
||||||
|
// ArgsEscaped
|
||||||
|
//
|
||||||
|
// Deprecated: This field is present only for legacy compatibility with
|
||||||
|
// Docker and should not be used by new image builders. It is used by Docker
|
||||||
|
// for Windows images to indicate that the `Entrypoint` or `Cmd` or both,
|
||||||
|
// contains only a single element array, that is a pre-escaped, and combined
|
||||||
|
// into a single string `CommandLine`. If `true` the value in `Entrypoint` or
|
||||||
|
// `Cmd` should be used as-is to avoid double escaping.
|
||||||
|
// https://github.com/opencontainers/image-spec/pull/892
|
||||||
|
ArgsEscaped bool `json:"ArgsEscaped,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootFS describes a layer content addresses
|
// RootFS describes a layer content addresses
|
||||||
|
@ -86,22 +97,8 @@ type Image struct {
|
||||||
// Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
|
// Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
|
||||||
Author string `json:"author,omitempty"`
|
Author string `json:"author,omitempty"`
|
||||||
|
|
||||||
// Architecture is the CPU architecture which the binaries in this image are built to run on.
|
// Platform describes the platform which the image in the manifest runs on.
|
||||||
Architecture string `json:"architecture"`
|
Platform
|
||||||
|
|
||||||
// Variant is the variant of the specified CPU architecture which image binaries are intended to run on.
|
|
||||||
Variant string `json:"variant,omitempty"`
|
|
||||||
|
|
||||||
// OS is the name of the operating system which the image is built to run on.
|
|
||||||
OS string `json:"os"`
|
|
||||||
|
|
||||||
// OSVersion is an optional field specifying the operating system
|
|
||||||
// version, for example on Windows `10.0.14393.1066`.
|
|
||||||
OSVersion string `json:"os.version,omitempty"`
|
|
||||||
|
|
||||||
// OSFeatures is an optional field specifying an array of strings,
|
|
||||||
// each listing a required OS feature (for example on Windows `win32k`).
|
|
||||||
OSFeatures []string `json:"os.features,omitempty"`
|
|
||||||
|
|
||||||
// Config defines the execution parameters which should be used as a base when running a container using the image.
|
// Config defines the execution parameters which should be used as a base when running a container using the image.
|
||||||
Config ImageConfig `json:"config,omitempty"`
|
Config ImageConfig `json:"config,omitempty"`
|
||||||
|
|
11
vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go
generated
vendored
11
vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go
generated
vendored
|
@ -23,6 +23,9 @@ type Manifest struct {
|
||||||
// MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json`
|
// MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json`
|
||||||
MediaType string `json:"mediaType,omitempty"`
|
MediaType string `json:"mediaType,omitempty"`
|
||||||
|
|
||||||
|
// ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact.
|
||||||
|
ArtifactType string `json:"artifactType,omitempty"`
|
||||||
|
|
||||||
// Config references a configuration object for a container, by digest.
|
// Config references a configuration object for a container, by digest.
|
||||||
// The referenced configuration object is a JSON blob that the runtime uses to set up the container.
|
// The referenced configuration object is a JSON blob that the runtime uses to set up the container.
|
||||||
Config Descriptor `json:"config"`
|
Config Descriptor `json:"config"`
|
||||||
|
@ -36,3 +39,11 @@ type Manifest struct {
|
||||||
// Annotations contains arbitrary metadata for the image manifest.
|
// Annotations contains arbitrary metadata for the image manifest.
|
||||||
Annotations map[string]string `json:"annotations,omitempty"`
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScratchDescriptor is the descriptor of a blob with content of `{}`.
|
||||||
|
var ScratchDescriptor = Descriptor{
|
||||||
|
MediaType: MediaTypeScratch,
|
||||||
|
Digest: `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`,
|
||||||
|
Size: 2,
|
||||||
|
Data: []byte(`{}`),
|
||||||
|
}
|
||||||
|
|
19
vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go
generated
vendored
19
vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go
generated
vendored
|
@ -40,21 +40,36 @@ const (
|
||||||
|
|
||||||
// MediaTypeImageLayerNonDistributable is the media type for layers referenced by
|
// MediaTypeImageLayerNonDistributable is the media type for layers referenced by
|
||||||
// the manifest but with distribution restrictions.
|
// the manifest but with distribution restrictions.
|
||||||
|
//
|
||||||
|
// Deprecated: Non-distributable layers are deprecated, and not recommended
|
||||||
|
// for future use. Implementations SHOULD NOT produce new non-distributable
|
||||||
|
// layers.
|
||||||
|
// https://github.com/opencontainers/image-spec/pull/965
|
||||||
MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar"
|
MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar"
|
||||||
|
|
||||||
// MediaTypeImageLayerNonDistributableGzip is the media type for
|
// MediaTypeImageLayerNonDistributableGzip is the media type for
|
||||||
// gzipped layers referenced by the manifest but with distribution
|
// gzipped layers referenced by the manifest but with distribution
|
||||||
// restrictions.
|
// restrictions.
|
||||||
|
//
|
||||||
|
// Deprecated: Non-distributable layers are deprecated, and not recommended
|
||||||
|
// for future use. Implementations SHOULD NOT produce new non-distributable
|
||||||
|
// layers.
|
||||||
|
// https://github.com/opencontainers/image-spec/pull/965
|
||||||
MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
|
MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
|
||||||
|
|
||||||
// MediaTypeImageLayerNonDistributableZstd is the media type for zstd
|
// MediaTypeImageLayerNonDistributableZstd is the media type for zstd
|
||||||
// compressed layers referenced by the manifest but with distribution
|
// compressed layers referenced by the manifest but with distribution
|
||||||
// restrictions.
|
// restrictions.
|
||||||
|
//
|
||||||
|
// Deprecated: Non-distributable layers are deprecated, and not recommended
|
||||||
|
// for future use. Implementations SHOULD NOT produce new non-distributable
|
||||||
|
// layers.
|
||||||
|
// https://github.com/opencontainers/image-spec/pull/965
|
||||||
MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
|
MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
|
||||||
|
|
||||||
// MediaTypeImageConfig specifies the media type for the image configuration.
|
// MediaTypeImageConfig specifies the media type for the image configuration.
|
||||||
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
|
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
|
||||||
|
|
||||||
// MediaTypeArtifactManifest specifies the media type for a content descriptor.
|
// MediaTypeScratch specifies the media type for an unused blob containing the value `{}`
|
||||||
MediaTypeArtifactManifest = "application/vnd.oci.artifact.manifest.v1+json"
|
MediaTypeScratch = "application/vnd.oci.scratch.v1+json"
|
||||||
)
|
)
|
||||||
|
|
2
vendor/github.com/opencontainers/image-spec/specs-go/version.go
generated
vendored
2
vendor/github.com/opencontainers/image-spec/specs-go/version.go
generated
vendored
|
@ -25,7 +25,7 @@ const (
|
||||||
VersionPatch = 0
|
VersionPatch = 0
|
||||||
|
|
||||||
// VersionDev indicates development branch. Releases will be empty string.
|
// VersionDev indicates development branch. Releases will be empty string.
|
||||||
VersionDev = "-rc2"
|
VersionDev = "-rc.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the specification version that the package types support.
|
// Version is the specification version that the package types support.
|
||||||
|
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
|
@ -231,8 +231,8 @@ github.com/ncw/swift/swifttest
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/opencontainers/go-digest
|
github.com/opencontainers/go-digest
|
||||||
github.com/opencontainers/go-digest/digestset
|
github.com/opencontainers/go-digest/digestset
|
||||||
# github.com/opencontainers/image-spec v1.1.0-rc2
|
# github.com/opencontainers/image-spec v1.1.0-rc3
|
||||||
## explicit; go 1.17
|
## explicit; go 1.18
|
||||||
github.com/opencontainers/image-spec/specs-go
|
github.com/opencontainers/image-spec/specs-go
|
||||||
github.com/opencontainers/image-spec/specs-go/v1
|
github.com/opencontainers/image-spec/specs-go/v1
|
||||||
# github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
|
# github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
|
||||||
|
|
Loading…
Reference in a new issue