Enhancements for Docker schema implementations in preparing for supporting schema 2 in the OCI model
This adds additional required properties and methods to the Docker schema interface to allow us to treat both schema1 and schema2 manifests and lists logically equivalent from the OCI mode perspective
This commit is contained in:
parent
6b86b87a16
commit
e344d4a5cf
12 changed files with 447 additions and 22 deletions
|
|
@ -12,6 +12,7 @@ from image.docker.schema2 import (DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE,
|
|||
DOCKER_SCHEMA2_CONFIG_CONTENT_TYPE,
|
||||
DOCKER_SCHEMA2_LAYER_CONTENT_TYPE,
|
||||
DOCKER_SCHEMA2_REMOTE_LAYER_CONTENT_TYPE)
|
||||
from image.docker.schema1 import DockerSchema1ManifestBuilder
|
||||
from image.docker.schema2.config import DockerSchema2Config
|
||||
|
||||
# Keys.
|
||||
|
|
@ -178,12 +179,26 @@ class DockerSchema2Manifest(ManifestInterface):
|
|||
|
||||
@property
|
||||
def blob_digests(self):
|
||||
return [str(layer.digest) for layer in self.layers]
|
||||
return [str(layer.digest) for layer in self.layers] + [str(self.config.digest)]
|
||||
|
||||
def get_manifest_labels(self, lookup_config_fn):
|
||||
return self._get_built_config(lookup_config_fn).labels
|
||||
|
||||
def _get_built_config(self, lookup_config_fn):
|
||||
config_bytes = lookup_config_fn(self.config.digest)
|
||||
if len(config_bytes) != self.config.size:
|
||||
raise MalformedSchema2Manifest('Size of config does not match that retrieved: %s vs %s',
|
||||
len(config_bytes), self.config.size)
|
||||
|
||||
return DockerSchema2Config(config_bytes)
|
||||
|
||||
@property
|
||||
def bytes(self):
|
||||
return self._payload
|
||||
|
||||
def child_manifests(self, lookup_manifest_fn):
|
||||
return None
|
||||
|
||||
def _generate_layers(self):
|
||||
for index, layer in enumerate(self._parsed[DOCKER_SCHEMA2_MANIFEST_LAYERS_KEY]):
|
||||
content_type = layer[DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY]
|
||||
|
|
@ -225,12 +240,7 @@ class DockerSchema2Manifest(ManifestInterface):
|
|||
this schema. The `lookup_config_fn` is a function that, when given the config
|
||||
digest SHA, returns the associated configuration JSON bytes for this schema.
|
||||
"""
|
||||
config_bytes = lookup_config_fn(self.config.digest)
|
||||
if len(config_bytes) != self.config.size:
|
||||
raise MalformedSchema2Manifest('Size of config does not match that retrieved: %s vs %s',
|
||||
len(config_bytes), self.config.size)
|
||||
|
||||
schema2_config = DockerSchema2Config(config_bytes)
|
||||
schema2_config = self._get_built_config(lookup_config_fn)
|
||||
|
||||
# Build the V1 IDs for the layers.
|
||||
layers = list(self.layers_with_v1_ids)
|
||||
|
|
@ -241,3 +251,77 @@ class DockerSchema2Manifest(ManifestInterface):
|
|||
v1_builder.add_layer(str(layer_with_ids.layer.digest), json.dumps(v1_compatibility))
|
||||
|
||||
return v1_builder
|
||||
|
||||
def generate_legacy_layers(self, images_map, lookup_config_fn):
|
||||
# NOTE: We use the DockerSchema1ManifestBuilder here because it already contains
|
||||
# the logic for generating the DockerV1Metadata. All of this will go away once we get
|
||||
# rid of legacy images in the database, so this is a temporary solution.
|
||||
v1_builder = DockerSchema1ManifestBuilder('', '', '')
|
||||
self.populate_schema1_builder(v1_builder, lookup_config_fn)
|
||||
return v1_builder.build().generate_legacy_layers(images_map, lookup_config_fn)
|
||||
|
||||
def unsigned(self):
|
||||
return self
|
||||
|
||||
|
||||
class DockerSchema2ManifestBuilder(object):
|
||||
"""
|
||||
A convenient abstraction around creating new DockerSchema2Manifests.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.config = None
|
||||
self.layers = []
|
||||
|
||||
def set_config(self, schema2_config):
|
||||
""" Sets the configuration for the manifest being built. """
|
||||
self.set_config_digest(schema2_config.digest, schema2_config.size)
|
||||
|
||||
def set_config_digest(self, config_digest, config_size):
|
||||
""" Sets the digest and size of the configuration layer. """
|
||||
self.config = DockerV2ManifestConfig(size=config_size, digest=config_digest)
|
||||
|
||||
def add_layer(self, digest, size, urls=None):
|
||||
""" Adds a layer to the manifest. """
|
||||
self.layers.append(DockerV2ManifestLayer(index=len(self.layers),
|
||||
digest=digest,
|
||||
compressed_size=size,
|
||||
urls=urls,
|
||||
is_remote=bool(urls)))
|
||||
|
||||
def build(self):
|
||||
""" Builds and returns the DockerSchema2Manifest. """
|
||||
assert self.layers
|
||||
assert self.config
|
||||
|
||||
def _build_layer(layer):
|
||||
if layer.urls:
|
||||
return {
|
||||
DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY: DOCKER_SCHEMA2_REMOTE_LAYER_CONTENT_TYPE,
|
||||
DOCKER_SCHEMA2_MANIFEST_SIZE_KEY: layer.compressed_size,
|
||||
DOCKER_SCHEMA2_MANIFEST_DIGEST_KEY: str(layer.digest),
|
||||
DOCKER_SCHEMA2_MANIFEST_URLS_KEY: layer.urls,
|
||||
}
|
||||
|
||||
return {
|
||||
DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY: DOCKER_SCHEMA2_LAYER_CONTENT_TYPE,
|
||||
DOCKER_SCHEMA2_MANIFEST_SIZE_KEY: layer.compressed_size,
|
||||
DOCKER_SCHEMA2_MANIFEST_DIGEST_KEY: str(layer.digest),
|
||||
}
|
||||
|
||||
manifest_dict = {
|
||||
DOCKER_SCHEMA2_MANIFEST_VERSION_KEY: 2,
|
||||
DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY: DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE,
|
||||
|
||||
# Config
|
||||
DOCKER_SCHEMA2_MANIFEST_CONFIG_KEY: {
|
||||
DOCKER_SCHEMA2_MANIFEST_MEDIATYPE_KEY: DOCKER_SCHEMA2_CONFIG_CONTENT_TYPE,
|
||||
DOCKER_SCHEMA2_MANIFEST_SIZE_KEY: self.config.size,
|
||||
DOCKER_SCHEMA2_MANIFEST_DIGEST_KEY: str(self.config.digest),
|
||||
},
|
||||
|
||||
# Layers
|
||||
DOCKER_SCHEMA2_MANIFEST_LAYERS_KEY: [
|
||||
_build_layer(layer) for layer in self.layers
|
||||
],
|
||||
}
|
||||
return DockerSchema2Manifest(json.dumps(manifest_dict, indent=3))
|
||||
|
|
|
|||
Reference in a new issue