Define a formal manifest interface and implement in the schema1 and schema2 manifests

This will allow us to pass arbitrary manifests to the model
This commit is contained in:
Joseph Schorr 2018-07-31 15:41:30 -04:00
parent cf5a6e1adc
commit 36c7482385
4 changed files with 94 additions and 5 deletions

View file

@ -0,0 +1,41 @@
from abc import ABCMeta, abstractproperty
from six import add_metaclass
@add_metaclass(ABCMeta)
class ManifestInterface(object):
""" Defines the interface for the various manifests types supported. """
@abstractproperty
def digest(self):
""" The digest of the manifest, including type prefix. """
pass
@abstractproperty
def media_type(self):
""" The media type of the schema. """
pass
@abstractproperty
def manifest_dict(self):
""" Returns the manifest as a dictionary ready to be serialized to JSON. """
pass
@abstractproperty
def bytes(self):
""" Returns the bytes of the manifest. """
pass
@abstractproperty
def layers(self):
""" Returns the layers of this manifest, from base to leaf. """
pass
@abstractproperty
def leaf_layer_v1_image_id(self):
""" Returns the Docker V1 image ID for the leaf (top) layer, if any, or None if none. """
pass
@abstractproperty
def blob_digests(self):
""" Returns an iterator over all the blob digests referenced by this manifest,
from base to leaf. The blob digests are strings with prefixes.
"""

View file

@ -18,9 +18,9 @@ from jwt.utils import base64url_encode, base64url_decode
from digest import digest_tools
from image.docker import ManifestException
from image.docker.interfaces import ManifestInterface
from image.docker.v1 import DockerV1Metadata
logger = logging.getLogger(__name__)
@ -85,7 +85,7 @@ class Schema1V1Metadata(namedtuple('Schema1V1Metadata', ['image_id', 'parent_ima
"""
class DockerSchema1Manifest(object):
class DockerSchema1Manifest(ManifestInterface):
METASCHEMA = {
'type': 'object',
'properties': {
@ -235,6 +235,10 @@ class DockerSchema1Manifest(object):
def manifest_json(self):
return self._parsed
@property
def manifest_dict(self):
return self._parsed
@property
def digest(self):
return digest_tools.sha256_digest(self.payload)
@ -252,6 +256,10 @@ class DockerSchema1Manifest(object):
def checksums(self):
return list({str(mdata.digest) for mdata in self.layers})
@property
def leaf_layer_v1_image_id(self):
return self.layers[-1].v1_metadata.image_id
@property
def leaf_layer(self):
return self.layers[-1]
@ -262,6 +270,10 @@ class DockerSchema1Manifest(object):
self._layers = list(self._generate_layers())
return self._layers
@property
def blob_digests(self):
return [str(layer.digest) for layer in self.layers]
def _generate_layers(self):
"""
Returns a generator of objects that have the blobSum and v1Compatibility keys in them,

View file

@ -7,6 +7,7 @@ from jsonschema import validate as validate_schema, ValidationError
from digest import digest_tools
from image.docker import ManifestException
from image.docker.interfaces import ManifestInterface
from image.docker.schema2 import (DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE,
DOCKER_SCHEMA2_CONFIG_CONTENT_TYPE,
DOCKER_SCHEMA2_LAYER_CONTENT_TYPE,
@ -39,7 +40,7 @@ class MalformedSchema2Manifest(ManifestException):
pass
class DockerSchema2Manifest(object):
class DockerSchema2Manifest(ManifestInterface):
METASCHEMA = {
'type': 'object',
'properties': {
@ -121,6 +122,7 @@ class DockerSchema2Manifest(object):
def __init__(self, manifest_bytes):
self._layers = None
self._payload = manifest_bytes
try:
self._parsed = json.loads(manifest_bytes)
@ -136,6 +138,18 @@ class DockerSchema2Manifest(object):
def schema_version(self):
return 2
@property
def manifest_dict(self):
return self._parsed
@property
def media_type(self):
return self._parsed[DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY]
@property
def digest(self):
return digest_tools.sha256_digest(self._payload)
@property
def config(self):
config = self._parsed[DOCKER_SCHEMA2_MANIFEST_CONFIG_KEY]
@ -153,6 +167,18 @@ class DockerSchema2Manifest(object):
def leaf_layer(self):
return self.layers[-1]
@property
def leaf_layer_v1_image_id(self):
return list(self.layers_with_v1_ids)[-1].v1_id
@property
def blob_digests(self):
return [str(layer.digest) for layer in self.layers]
@property
def bytes(self):
return self._payload
def _generate_layers(self):
for index, layer in enumerate(self._parsed[DOCKER_SCHEMA2_MANIFEST_LAYERS_KEY]):
content_type = layer[DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY]

View file

@ -2,7 +2,8 @@ import json
import pytest
from app import docker_v2_signing_key
from image.docker.schema1 import DockerSchema1ManifestBuilder
from image.docker.schema1 import (DockerSchema1ManifestBuilder,
DOCKER_SCHEMA1_SIGNED_MANIFEST_CONTENT_TYPE)
from image.docker.schema2.manifest import MalformedSchema2Manifest, DockerSchema2Manifest
from image.docker.schema2.test.test_config import CONFIG_BYTES
@ -57,7 +58,8 @@ def test_valid_manifest():
manifest = DockerSchema2Manifest(MANIFEST_BYTES)
assert manifest.config.size == 1885
assert str(manifest.config.digest) == 'sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7'
assert manifest.media_type == "application/vnd.docker.distribution.manifest.v2+json"
assert len(manifest.layers) == 4
assert manifest.layers[0].is_remote
assert manifest.layers[0].size == 1234
@ -68,6 +70,10 @@ def test_valid_manifest():
assert not manifest.leaf_layer.is_remote
assert manifest.leaf_layer.size == 73109
blob_digests = list(manifest.blob_digests)
assert len(blob_digests) == len(manifest.layers)
assert blob_digests == [str(layer.digest) for layer in manifest.layers]
def test_build_schema1():
manifest = DockerSchema2Manifest(MANIFEST_BYTES)
@ -76,6 +82,7 @@ def test_build_schema1():
manifest.populate_schema1_builder(builder, lambda digest: CONFIG_BYTES)
schema1 = builder.build(docker_v2_signing_key)
assert schema1.media_type == DOCKER_SCHEMA1_SIGNED_MANIFEST_CONTENT_TYPE
assert len(schema1.layers) == len(manifest.layers)
assert set(schema1.image_ids) == set([l.v1_id for l in manifest.layers_with_v1_ids])
assert set(schema1.parent_image_ids) == set([l.v1_parent_id for l in manifest.layers_with_v1_ids if l.v1_parent_id])
@ -85,3 +92,6 @@ def test_build_schema1():
assert layer.digest == manifest_layers[index].layer.digest
assert layer.v1_metadata.image_id == manifest_layers[index].v1_id
assert layer.v1_metadata.parent_image_id == manifest_layers[index].v1_parent_id
for index, digest in enumerate(schema1.blob_digests):
assert digest == str(list(manifest.blob_digests)[index])