Merge pull request #3311 from quay/loosen-schema-requirements

Make command optional in schema 2 manifests (as per OCI spec) and pull out additional information
This commit is contained in:
Joseph Schorr 2018-12-12 10:16:30 -05:00 committed by GitHub
commit 1d57e1eb9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 53 additions and 16 deletions

View file

@ -37,6 +37,8 @@ def _label_dict(label):
def _layer_dict(manifest_layer, index): def _layer_dict(manifest_layer, index):
# NOTE: The `command` in the layer is either a JSON string of an array (schema 1) or # NOTE: The `command` in the layer is either a JSON string of an array (schema 1) or
# a single string (schema 2). The block below normalizes it to have the same format. # a single string (schema 2). The block below normalizes it to have the same format.
command = None
if manifest_layer.command:
try: try:
command = json.loads(manifest_layer.command) command = json.loads(manifest_layer.command)
except (TypeError, ValueError): except (TypeError, ValueError):
@ -48,6 +50,8 @@ def _layer_dict(manifest_layer, index):
'is_remote': manifest_layer.is_remote, 'is_remote': manifest_layer.is_remote,
'urls': manifest_layer.urls, 'urls': manifest_layer.urls,
'command': command, 'command': command,
'comment': manifest_layer.comment,
'author': manifest_layer.author,
'blob_digest': str(manifest_layer.blob_digest), 'blob_digest': str(manifest_layer.blob_digest),
'created_datetime': format_date(manifest_layer.created_datetime), 'created_datetime': format_date(manifest_layer.created_datetime),
} }

View file

@ -82,7 +82,8 @@ class Schema1Layer(namedtuple('Schema1Layer', ['digest', 'v1_metadata', 'raw_v1_
class Schema1V1Metadata(namedtuple('Schema1V1Metadata', ['image_id', 'parent_image_id', 'created', class Schema1V1Metadata(namedtuple('Schema1V1Metadata', ['image_id', 'parent_image_id', 'created',
'comment', 'command', 'labels'])): 'comment', 'command', 'author',
'labels'])):
""" """
Represents the necessary data extracted from the v1 compatibility string in a given layer of a Represents the necessary data extracted from the v1 compatibility string in a given layer of a
Manifest. Manifest.
@ -315,6 +316,8 @@ class DockerSchema1Manifest(ManifestInterface):
is_remote=False, is_remote=False,
urls=None, urls=None,
command=layer.v1_metadata.command, command=layer.v1_metadata.command,
comment=layer.v1_metadata.comment,
author=layer.v1_metadata.author,
blob_digest=layer.digest, blob_digest=layer.digest,
created_datetime=created_datetime, created_datetime=created_datetime,
internal_layer=layer) internal_layer=layer)
@ -372,9 +375,13 @@ class DockerSchema1Manifest(ManifestInterface):
raise MalformedSchema1Manifest('id field missing from v1Compatibility JSON') raise MalformedSchema1Manifest('id field missing from v1Compatibility JSON')
labels = v1_metadata.get('config', {}).get('Labels', {}) or {} labels = v1_metadata.get('config', {}).get('Labels', {}) or {}
extracted = Schema1V1Metadata(v1_metadata['id'], v1_metadata.get('parent'), extracted = Schema1V1Metadata(image_id=v1_metadata['id'],
v1_metadata.get('created'), v1_metadata.get('comment'), parent_image_id=v1_metadata.get('parent'),
command, labels) created=v1_metadata.get('created'),
comment=v1_metadata.get('comment'),
author=v1_metadata.get('author'),
command=command,
labels=labels)
compressed_size = v1_metadata.get('Size') compressed_size = v1_metadata.get('Size')
yield Schema1Layer(image_digest, extracted, metadata_string, compressed_size, False, None) yield Schema1Layer(image_digest, extracted, metadata_string, compressed_size, False, None)
@ -483,6 +490,7 @@ class DockerSchema1Manifest(ManifestInterface):
image_id=working_image_id, image_id=working_image_id,
created=extracted_v1_metadata.created, created=extracted_v1_metadata.created,
comment=extracted_v1_metadata.comment, comment=extracted_v1_metadata.comment,
author=extracted_v1_metadata.author,
command=extracted_v1_metadata.command, command=extracted_v1_metadata.command,
compat_json=v1_metadata_json, compat_json=v1_metadata_json,
parent_image_id=parent_image_id, parent_image_id=parent_image_id,

View file

@ -108,12 +108,14 @@ DOCKER_SCHEMA2_CONFIG_HISTORY_KEY = "history"
DOCKER_SCHEMA2_CONFIG_ROOTFS_KEY = "rootfs" DOCKER_SCHEMA2_CONFIG_ROOTFS_KEY = "rootfs"
DOCKER_SCHEMA2_CONFIG_CREATED_KEY = "created" DOCKER_SCHEMA2_CONFIG_CREATED_KEY = "created"
DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY = "created_by" DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY = "created_by"
DOCKER_SCHEMA2_CONFIG_COMMENT_KEY = "comment"
DOCKER_SCHEMA2_CONFIG_AUTHOR_KEY = "author"
DOCKER_SCHEMA2_CONFIG_EMPTY_LAYER_KEY = "empty_layer" DOCKER_SCHEMA2_CONFIG_EMPTY_LAYER_KEY = "empty_layer"
DOCKER_SCHEMA2_CONFIG_TYPE_KEY = "type" DOCKER_SCHEMA2_CONFIG_TYPE_KEY = "type"
LayerHistory = namedtuple('LayerHistory', ['created', 'created_datetime', 'command', 'is_empty', LayerHistory = namedtuple('LayerHistory', ['created', 'created_datetime', 'command', 'is_empty',
'raw_entry']) 'author', 'comment', 'raw_entry'])
class MalformedSchema2Config(ManifestException): class MalformedSchema2Config(ManifestException):
@ -151,8 +153,15 @@ class DockerSchema2Config(object):
'description': 'The command used to create the layer', 'description': 'The command used to create the layer',
'x-example': '\/bin\/sh -c #(nop) ADD file:somesha in /', 'x-example': '\/bin\/sh -c #(nop) ADD file:somesha in /',
}, },
DOCKER_SCHEMA2_CONFIG_COMMENT_KEY: {
'type': 'string',
'description': 'Comment describing the layer',
},
DOCKER_SCHEMA2_CONFIG_AUTHOR_KEY: {
'type': 'string',
'description': 'The author of the layer',
},
}, },
'required': [DOCKER_SCHEMA2_CONFIG_CREATED_KEY, DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY],
'additionalProperties': True, 'additionalProperties': True,
}, },
}, },
@ -220,8 +229,10 @@ class DockerSchema2Config(object):
for history_entry in self._parsed[DOCKER_SCHEMA2_CONFIG_HISTORY_KEY]: for history_entry in self._parsed[DOCKER_SCHEMA2_CONFIG_HISTORY_KEY]:
created_datetime = parse_date(history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_KEY]) created_datetime = parse_date(history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_KEY])
yield LayerHistory(created_datetime=created_datetime, yield LayerHistory(created_datetime=created_datetime,
created=history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_KEY], created=history_entry.get(DOCKER_SCHEMA2_CONFIG_CREATED_KEY),
command=history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY], command=history_entry.get(DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY),
author=history_entry.get(DOCKER_SCHEMA2_CONFIG_AUTHOR_KEY),
comment=history_entry.get(DOCKER_SCHEMA2_CONFIG_COMMENT_KEY),
is_empty=history_entry.get(DOCKER_SCHEMA2_CONFIG_EMPTY_LAYER_KEY, False), is_empty=history_entry.get(DOCKER_SCHEMA2_CONFIG_EMPTY_LAYER_KEY, False),
raw_entry=history_entry) raw_entry=history_entry)
@ -235,9 +246,18 @@ class DockerSchema2Config(object):
if v1_parent_id is not None: if v1_parent_id is not None:
v1_compatibility['parent'] = v1_parent_id v1_compatibility['parent'] = v1_parent_id
if 'created' not in v1_compatibility: if 'created' not in v1_compatibility and history.created:
v1_compatibility['created'] = history.created v1_compatibility['created'] = history.created
if 'author' not in v1_compatibility and history.author:
v1_compatibility['author'] = history.author
if 'comment' not in v1_compatibility and history.comment:
v1_compatibility['comment'] = history.comment
if 'throwaway' not in v1_compatibility and history.is_empty:
v1_compatibility['throwaway'] = True
if 'container_config' not in v1_compatibility: if 'container_config' not in v1_compatibility:
v1_compatibility['container_config'] = { v1_compatibility['container_config'] = {
'Cmd': [history.command], 'Cmd': [history.command],

View file

@ -221,6 +221,8 @@ class DockerSchema2Manifest(ManifestInterface):
command=image_layer.history.command, command=image_layer.history.command,
blob_digest=image_layer.blob_digest, blob_digest=image_layer.blob_digest,
created_datetime=image_layer.history.created_datetime, created_datetime=image_layer.history.created_datetime,
author=image_layer.history.author,
comment=image_layer.history.comment,
internal_layer=image_layer) internal_layer=image_layer)
@property @property

View file

@ -3,4 +3,5 @@ from collections import namedtuple
ManifestImageLayer = namedtuple('ManifestImageLayer', ['layer_id', 'compressed_size', ManifestImageLayer = namedtuple('ManifestImageLayer', ['layer_id', 'compressed_size',
'is_remote', 'urls', 'command', 'is_remote', 'urls', 'command',
'blob_digest', 'created_datetime', 'blob_digest', 'created_datetime',
'author', 'comment',
'internal_layer']) 'internal_layer'])

View file

@ -9,7 +9,7 @@ from collections import namedtuple
class DockerV1Metadata(namedtuple('DockerV1Metadata', class DockerV1Metadata(namedtuple('DockerV1Metadata',
['namespace_name', 'repo_name', 'image_id', 'checksum', ['namespace_name', 'repo_name', 'image_id', 'checksum',
'content_checksum', 'created', 'comment', 'command', 'content_checksum', 'created', 'comment', 'command',
'parent_image_id', 'compat_json'])): 'author', 'parent_image_id', 'compat_json'])):
""" """
DockerV1Metadata represents all of the metadata for a given Docker v1 Image. DockerV1Metadata represents all of the metadata for a given Docker v1 Image.
The original form of the metadata is stored in the compat_json field. The original form of the metadata is stored in the compat_json field.

View file

@ -1,6 +1,8 @@
<div class="manifest-view-layer-element" ng-class="getClass()"> <div class="manifest-view-layer-element" ng-class="getClass()">
<div class="image-command"> <div class="image-command">
<image-command command="layer.command"></image-command> <image-command command="layer.command" ng-if="layer.command"></image-command>
<i ng-if="!layer.command && layer.comment">{{ layer.comment }}</i>
<code ng-if="!layer.command && !layer.comment">{{ layer.blob_digest }}</code>
</div> </div>
<div class="image-layer-dot"></div> <div class="image-layer-dot"></div>
<div class="image-layer-line"></div> <div class="image-layer-line"></div>