From 1760050388337c94abe807f9d21ee64d228f7c17 Mon Sep 17 00:00:00 2001 From: Sajay Antony Date: Wed, 21 Sep 2022 17:04:03 +0000 Subject: [PATCH] schema: Add schema and test for artifact manifest Signed-off-by: Sajay Antony --- schema/artifact-manifest-schema.json | 29 ++++++ schema/artifact_test.go | 148 +++++++++++++++++++++++++++ schema/schema.go | 2 + schema/spec_test.go | 4 + schema/validator.go | 20 ++++ 5 files changed, 203 insertions(+) create mode 100644 schema/artifact-manifest-schema.json create mode 100644 schema/artifact_test.go diff --git a/schema/artifact-manifest-schema.json b/schema/artifact-manifest-schema.json new file mode 100644 index 000000000..cdd1dd6f6 --- /dev/null +++ b/schema/artifact-manifest-schema.json @@ -0,0 +1,29 @@ +{ + "description": "OpenContainer Artifact Manifest Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/artifact", + "type": "object", + "properties": { + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, + "artifactType": { + "description": "The IANA mediatype of the referenced objects properties", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, + "blobs": { + "type": "array", + "items": { + "$ref": "content-descriptor.json" + } + }, + "subject": { + "$ref": "content-descriptor.json" + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/manifest/annotations", + "$ref": "defs-descriptor.json#/definitions/annotations" + } + } +} diff --git a/schema/artifact_test.go b/schema/artifact_test.go new file mode 100644 index 000000000..a02352760 --- /dev/null +++ b/schema/artifact_test.go @@ -0,0 +1,148 @@ +// Copyright 2016 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 schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/opencontainers/image-spec/schema" +) + +func TestArtifact(t *testing.T) { + for i, tt := range []struct { + manifest string + fail bool + }{ + // expected failure: mediaType does not match pattern + { + manifest: ` +{ + "mediaType": "invalid", + "artifactType": "application/example", + "blobs": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 148, + "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" + } + ] +} +`, + fail: true, + }, + + // expected failure: invalid artifact mediaType + { + manifest: ` +{ + "mediaType": "application/vnd.oci.artifact.manifest.v1+json", + "artifactType": "invalid", + "blobs": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": "148", + "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" + } + ] +} +`, + fail: true, + }, + + // expected failure: blob[0].size is a string, expected integer + { + manifest: ` +{ + "mediaType": "application/vnd.oci.artifact.manifest.v1+json", + "artifactType": "application/example", + "blobs": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": "148", + "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" + } + ] +} +`, + fail: true, + }, + + // expected failure: subject: size is required + { + manifest: ` +{ + "mediaType": "application/vnd.oci.artifact.manifest.v1+json", + "artifactType": "application/example", + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" + }, + "annotations": { + "key1": "value1", + "key2": "value2" + } +} +`, + fail: true, + }, + + // valid manifest with optional fields + { + manifest: ` +{ + "mediaType": "application/vnd.oci.artifact.manifest.v1+json", + "artifactType": "application/example", + "blobs": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 675598, + "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 156, + "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 148, + "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" + } + ], + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 1470, + "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" + }, + "annotations": { + "key1": "value1", + "key2": "value2" + } +} +`, + fail: false, + }, + } { + r := strings.NewReader(tt.manifest) + err := schema.ValidatorMediaTypeArtifact.Validate(r) + + if got := err != nil; tt.fail != got { + t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) + fmt.Println(tt.manifest) + } + } +} diff --git a/schema/schema.go b/schema/schema.go index 7a338d8ee..34f10de3b 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -29,6 +29,7 @@ const ( ValidatorMediaTypeImageIndex Validator = v1.MediaTypeImageIndex ValidatorMediaTypeImageConfig Validator = v1.MediaTypeImageConfig ValidatorMediaTypeImageLayer unimplemented = v1.MediaTypeImageLayer + ValidatorMediaTypeArtifact Validator = v1.MediaTypeArtifactManifest ) var ( @@ -68,6 +69,7 @@ var ( ValidatorMediaTypeManifest: "https://opencontainers.org/schema/image/image-manifest-schema.json", ValidatorMediaTypeImageIndex: "https://opencontainers.org/schema/image/image-index-schema.json", ValidatorMediaTypeImageConfig: "https://opencontainers.org/schema/image/config-schema.json", + ValidatorMediaTypeArtifact: "https://opencontainers.org/schema/artifact-manifest-schema.json", } ) diff --git a/schema/spec_test.go b/schema/spec_test.go index e8dde99f0..b7d98db94 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -54,6 +54,10 @@ func TestValidateConfig(t *testing.T) { validate(t, "../config.md") } +func TestValidateArtifactManifest(t *testing.T) { + validate(t, "../artifact.md") +} + func TestSchemaFS(t *testing.T) { expectedSchemaFileNames, err := filepath.Glob("*.json") if err != nil { diff --git a/schema/validator.go b/schema/validator.go index e219d38c2..586a2f32b 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -38,6 +38,7 @@ var mapValidate = map[Validator]validateFunc{ ValidatorMediaTypeDescriptor: validateDescriptor, ValidatorMediaTypeImageIndex: validateIndex, ValidatorMediaTypeManifest: validateManifest, + ValidatorMediaTypeArtifact: validateArtifact, } // ValidationError contains all the errors that happened during validation. @@ -250,3 +251,22 @@ func checkPlatform(OS string, Architecture string) { } fmt.Printf("warning: operating system %q of the bundle is not supported yet.\n", OS) } + +func validateArtifact(r io.Reader) error { + header := v1.Artifact{} + + buf, err := io.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "error reading the io stream") + } + + err = json.Unmarshal(buf, &header) + if err != nil { + return errors.Wrap(err, "manifest format mismatch") + } + + if header.MediaType != string(v1.MediaTypeArtifactManifest) { + fmt.Printf("warning: Artifact has an unknown media type: %s\n", header.MediaType) + } + return nil +}