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:
commit
1d57e1eb9f
7 changed files with 53 additions and 16 deletions
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'])
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue