From 41b50fdd620c24557bac18df84a0408c14a8fa97 Mon Sep 17 00:00:00 2001 From: Dan Duvall Date: Fri, 30 Aug 2024 16:04:00 -0700 Subject: [PATCH] Add mediaType, artifactType, and subject to Index per the specification Define these optional properties according to the latest v1 specification. See https://github.com/opencontainers/image-spec/blob/3c3d71753a024375415026b918ba470714383a81/image-index.md --- opencontainers/image/v1/descriptor.py | 4 +-- opencontainers/image/v1/index.py | 39 ++++++++++++++++++++++--- opencontainers/mediatype.py | 2 ++ opencontainers/tests/test_imageindex.py | 31 ++++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 opencontainers/mediatype.py diff --git a/opencontainers/image/v1/descriptor.py b/opencontainers/image/v1/descriptor.py index ea3a0bb..0b14c37 100644 --- a/opencontainers/image/v1/descriptor.py +++ b/opencontainers/image/v1/descriptor.py @@ -6,6 +6,7 @@ from opencontainers.struct import Struct from opencontainers.digest import Digest +from opencontainers.mediatype import RFC6838 class Descriptor(Struct): @@ -28,12 +29,11 @@ def __init__( super().__init__() # MediaType is the media type of the object this schema refers to. - regexp = "^[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}$" self.newAttr( name="MediaType", attType=str, jsonName="mediaType", - regexp=regexp, + regexp=RFC6838, required=True, ) diff --git a/opencontainers/image/v1/index.py b/opencontainers/image/v1/index.py index 861271d..2a07975 100644 --- a/opencontainers/image/v1/index.py +++ b/opencontainers/image/v1/index.py @@ -7,6 +7,7 @@ from opencontainers.struct import Struct from opencontainers.image.specs import Versioned from opencontainers.logger import bot +from opencontainers.mediatype import RFC6838 from .mediatype import MediaTypeImageIndex, MediaTypeImageManifest from .descriptor import Descriptor import re @@ -20,22 +21,44 @@ class Index(Struct): mediatype when marshalled to JSON. """ - def __init__(self, manifests=None, schemaVersion=None, annotations=None): + def __init__(self, manifests=None, schemaVersion=None, annotations=None, + mediaType=None, artifactType=None, subject=None): super().__init__() self.newAttr(name="schemaVersion", attType=Versioned, required=True) + # MediaType must be "application/vnd.oci.image.index.v1+json" if given + self.newAttr( + name="MediaType", + attType=str, + jsonName="mediaType", + ) + + # ArtifactType must be a valid media type according to RFC6838 + self.newAttr( + name="ArtifactType", + attType=str, + jsonName="artifactType", + regexp=RFC6838, + ) + # Manifests references platform specific manifests. self.newAttr( name="Manifests", attType=[Descriptor], jsonName="manifests", required=True ) + # Subject is a descriptor of another manifest + self.newAttr(name="Subject", attType=Descriptor, jsonName="subject") + # Annotations contains arbitrary metadata for the image index. self.newAttr(name="Annotations", attType=dict, jsonName="annotations") + self.add("schemaVersion", schemaVersion) + self.add("MediaType", mediaType) + self.add("ArtifactType", artifactType) self.add("Manifests", manifests) + self.add("Subject", subject) self.add("Annotations", annotations) - self.add("schemaVersion", schemaVersion) def _validate(self): """ @@ -44,8 +67,16 @@ def _validate(self): custom validation function to ensure that Manifests mediaTypes are valid. """ + valid = True + valid_types = [MediaTypeImageManifest, MediaTypeImageIndex] + mediaType = self.attrs.get("MediaType") + if mediaType.value and mediaType.value != MediaTypeImageIndex: + bot.error("%s must be %s" % (mediaType, MediaTypeImageIndex)) + valid = False + + manifests = self.attrs.get("Manifests").value if manifests: for manifest in manifests: @@ -61,6 +92,6 @@ def _validate(self): # Case 2: not valid and doesn't match regular expression else: bot.error("%s is not valid for index manifest." % mediaType) - return False + valid = False - return True + return valid diff --git a/opencontainers/mediatype.py b/opencontainers/mediatype.py new file mode 100644 index 0000000..b2857af --- /dev/null +++ b/opencontainers/mediatype.py @@ -0,0 +1,2 @@ +# RFC6838 specifies a regexp string for validating media types. +RFC6838 = "^[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}$" diff --git a/opencontainers/tests/test_imageindex.py b/opencontainers/tests/test_imageindex.py index 84a38ba..eec17a0 100644 --- a/opencontainers/tests/test_imageindex.py +++ b/opencontainers/tests/test_imageindex.py @@ -125,6 +125,30 @@ ], } +root_mediatype_valid = { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7143, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + } + ], +} + +root_mediatype_invalid = { + "schemaVersion": 2, + "mediaType": "something/else", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7143, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + } + ], +} + def test_imageindex(tmp_path): """test creation of an opencontainers Index""" @@ -162,3 +186,10 @@ def test_imageindex(tmp_path): # valid image index, with customized media type of referenced manifest index.load(index_with_custom) + + # expected failure: invalid root media type + with pytest.raises(SystemExit): + index.load(root_mediatype_invalid) + + # valid root media type + index.load(root_mediatype_valid)