194 lines
8.3 KiB
Python
194 lines
8.3 KiB
Python
import json
|
|
|
|
from cachetools import lru_cache
|
|
from jsonschema import validate as validate_schema, ValidationError
|
|
|
|
from image.docker.schema1 import DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE
|
|
from image.docker.schema1 import DockerSchema1Manifest
|
|
from image.docker.schema2 import (DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE,
|
|
DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE)
|
|
from image.docker.schema2.manifest import DockerSchema2Manifest
|
|
|
|
# Keys.
|
|
DOCKER_SCHEMA2_MANIFESTLIST_VERSION_KEY = 'schemaVersion'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY = 'mediaType'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_SIZE_KEY = 'size'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_DIGEST_KEY = 'digest'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY = 'manifests'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_PLATFORM_KEY = 'platform'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_ARCHITECTURE_KEY = 'architecture'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_KEY = 'os'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_VERSION_KEY = 'os.version'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_FEATURES_KEY = 'os.features'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_FEATURES_KEY = 'features'
|
|
DOCKER_SCHEMA2_MANIFESTLIST_VARIANT_KEY = 'variant'
|
|
|
|
|
|
class MalformedSchema2ManifestList(Exception):
|
|
"""
|
|
Raised when a manifest list fails an assertion that should be true according to the
|
|
Docker Manifest v2.2 Specification.
|
|
"""
|
|
pass
|
|
|
|
|
|
class LazyManifestLoader(object):
|
|
def __init__(self, manifest_data, lookup_manifest_fn):
|
|
self._manifest_data = manifest_data
|
|
self._lookup_manifest_fn = lookup_manifest_fn
|
|
self._loaded_manifest = None
|
|
|
|
@property
|
|
def manifest_obj(self):
|
|
if self._loaded_manifest is not None:
|
|
return self._loaded_manifest
|
|
|
|
self._loaded_manifest = self._load_manifest()
|
|
return self._loaded_manifest
|
|
|
|
def _load_manifest(self):
|
|
digest = self._manifest_data[DOCKER_SCHEMA2_MANIFESTLIST_DIGEST_KEY]
|
|
size = self._manifest_data[DOCKER_SCHEMA2_MANIFESTLIST_SIZE_KEY]
|
|
manifest_bytes = self._lookup_manifest_fn(digest)
|
|
if len(manifest_bytes) != size:
|
|
raise MalformedSchema2ManifestList('Size of manifest does not match that retrieved: %s vs %s',
|
|
len(manifest_bytes), size)
|
|
|
|
content_type = self._manifest_data[DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY]
|
|
if content_type == DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE:
|
|
return DockerSchema2Manifest(manifest_bytes)
|
|
|
|
if content_type == DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE:
|
|
return DockerSchema1Manifest(manifest_bytes, validate=False)
|
|
|
|
raise MalformedSchema2ManifestList('Unknown manifest content type')
|
|
|
|
|
|
class DockerSchema2ManifestList(object):
|
|
METASCHEMA = {
|
|
'type': 'object',
|
|
'properties': {
|
|
DOCKER_SCHEMA2_MANIFESTLIST_VERSION_KEY: {
|
|
'type': 'number',
|
|
'description': 'The version of the manifest list. Must always be `2`.',
|
|
'minimum': 2,
|
|
'maximum': 2,
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY: {
|
|
'type': 'string',
|
|
'description': 'The media type of the manifest list.',
|
|
'enum': [DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE],
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY: {
|
|
'type': 'array',
|
|
'description': 'The manifests field contains a list of manifests for specific platforms',
|
|
'items': {
|
|
'type': 'object',
|
|
'properties': {
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY: {
|
|
'type': 'string',
|
|
'description': 'The MIME type of the referenced object. This will generally be ' +
|
|
'application/vnd.docker.distribution.manifest.v2+json, but it ' +
|
|
'could also be application/vnd.docker.distribution.manifest.v1+json ' +
|
|
'if the manifest list references a legacy schema-1 manifest.',
|
|
'enum': [DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE, DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE],
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_SIZE_KEY: {
|
|
'type': 'number',
|
|
'description': 'The size in bytes of the object. This field exists so that a ' +
|
|
'client will have an expected size for the content before ' +
|
|
'validating. If the length of the retrieved content does not ' +
|
|
'match the specified length, the content should not be trusted.',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_DIGEST_KEY: {
|
|
'type': 'string',
|
|
'description': 'The content addressable digest of the manifest in the blob store',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_PLATFORM_KEY: {
|
|
'type': 'object',
|
|
'description': 'The platform object describes the platform which the image in ' +
|
|
'the manifest runs on',
|
|
'properties': {
|
|
DOCKER_SCHEMA2_MANIFESTLIST_ARCHITECTURE_KEY: {
|
|
'type': 'string',
|
|
'description': 'Specifies the CPU architecture, for example amd64 or ppc64le.',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_KEY: {
|
|
'type': 'string',
|
|
'description': 'Specifies the operating system, for example linux or windows',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_VERSION_KEY: {
|
|
'type': 'string',
|
|
'description': 'Specifies the operating system version, for example 10.0.10586',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_FEATURES_KEY: {
|
|
'type': 'array',
|
|
'description': 'specifies an array of strings, each listing a required OS ' +
|
|
'feature (for example on Windows win32k)',
|
|
'items': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_VARIANT_KEY: {
|
|
'type': 'string',
|
|
'description': 'Specifies a variant of the CPU, for example armv6l to specify ' +
|
|
'a particular CPU variant of the ARM CPU',
|
|
},
|
|
DOCKER_SCHEMA2_MANIFESTLIST_FEATURES_KEY: {
|
|
'type': 'array',
|
|
'description': 'specifies an array of strings, each listing a required CPU ' +
|
|
'feature (for example sse4 or aes).',
|
|
'items': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
},
|
|
'required': [DOCKER_SCHEMA2_MANIFESTLIST_ARCHITECTURE_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_OS_KEY],
|
|
},
|
|
},
|
|
'required': [DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_SIZE_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_DIGEST_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_PLATFORM_KEY],
|
|
},
|
|
},
|
|
},
|
|
'required': [DOCKER_SCHEMA2_MANIFESTLIST_VERSION_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY,
|
|
DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY],
|
|
}
|
|
|
|
def __init__(self, manifest_bytes):
|
|
self._layers = None
|
|
|
|
try:
|
|
self._parsed = json.loads(manifest_bytes)
|
|
except ValueError as ve:
|
|
raise MalformedSchema2ManifestList('malformed manifest data: %s' % ve)
|
|
|
|
try:
|
|
validate_schema(self._parsed, DockerSchema2ManifestList.METASCHEMA)
|
|
except ValidationError as ve:
|
|
raise MalformedSchema2ManifestList('manifest data does not match schema: %s' % ve)
|
|
|
|
@lru_cache(maxsize=1)
|
|
def manifests(self, lookup_manifest_fn):
|
|
""" Returns the manifests in the list. The `lookup_manifest_fn` is a function
|
|
that returns the manifest bytes for the specified digest.
|
|
"""
|
|
manifests = self._parsed[DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY]
|
|
return [LazyManifestLoader(m, lookup_manifest_fn) for m in manifests]
|
|
|
|
def get_v1_compatible_manifest(self, lookup_manifest_fn):
|
|
""" Returns the manifest that is compatible with V1, by virtue of being `amd64` and `linux`.
|
|
If none, returns None.
|
|
"""
|
|
for manifest in self.manifests(lookup_manifest_fn):
|
|
platform = manifest._manifest_data[DOCKER_SCHEMA2_MANIFESTLIST_PLATFORM_KEY]
|
|
architecture = platform[DOCKER_SCHEMA2_MANIFESTLIST_ARCHITECTURE_KEY]
|
|
os = platform[DOCKER_SCHEMA2_MANIFESTLIST_OS_KEY]
|
|
if architecture == 'amd64' and os == 'linux':
|
|
return manifest
|
|
|
|
return None
|