diff --git a/endpoints/api/manifest.py b/endpoints/api/manifest.py index fa2d54e0d..08dec2dc8 100644 --- a/endpoints/api/manifest.py +++ b/endpoints/api/manifest.py @@ -37,10 +37,12 @@ def _label_dict(label): def _layer_dict(manifest_layer, index): # 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. - try: - command = json.loads(manifest_layer.command) - except (TypeError, ValueError): - command = [manifest_layer.command] + command = None + if manifest_layer.command: + try: + command = json.loads(manifest_layer.command) + except (TypeError, ValueError): + command = [manifest_layer.command] return { 'index': index, @@ -48,6 +50,8 @@ def _layer_dict(manifest_layer, index): 'is_remote': manifest_layer.is_remote, 'urls': manifest_layer.urls, 'command': command, + 'comment': manifest_layer.comment, + 'author': manifest_layer.author, 'blob_digest': str(manifest_layer.blob_digest), 'created_datetime': format_date(manifest_layer.created_datetime), } diff --git a/image/docker/schema1.py b/image/docker/schema1.py index c5dc1db22..a216d6fb9 100644 --- a/image/docker/schema1.py +++ b/image/docker/schema1.py @@ -82,7 +82,8 @@ class Schema1Layer(namedtuple('Schema1Layer', ['digest', 'v1_metadata', 'raw_v1_ 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 Manifest. @@ -315,6 +316,8 @@ class DockerSchema1Manifest(ManifestInterface): is_remote=False, urls=None, command=layer.v1_metadata.command, + comment=layer.v1_metadata.comment, + author=layer.v1_metadata.author, blob_digest=layer.digest, created_datetime=created_datetime, internal_layer=layer) @@ -372,9 +375,13 @@ class DockerSchema1Manifest(ManifestInterface): raise MalformedSchema1Manifest('id field missing from v1Compatibility JSON') labels = v1_metadata.get('config', {}).get('Labels', {}) or {} - extracted = Schema1V1Metadata(v1_metadata['id'], v1_metadata.get('parent'), - v1_metadata.get('created'), v1_metadata.get('comment'), - command, labels) + extracted = Schema1V1Metadata(image_id=v1_metadata['id'], + parent_image_id=v1_metadata.get('parent'), + 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') yield Schema1Layer(image_digest, extracted, metadata_string, compressed_size, False, None) @@ -483,6 +490,7 @@ class DockerSchema1Manifest(ManifestInterface): image_id=working_image_id, created=extracted_v1_metadata.created, comment=extracted_v1_metadata.comment, + author=extracted_v1_metadata.author, command=extracted_v1_metadata.command, compat_json=v1_metadata_json, parent_image_id=parent_image_id, diff --git a/image/docker/schema2/config.py b/image/docker/schema2/config.py index d6a6bf81b..f46c96a71 100644 --- a/image/docker/schema2/config.py +++ b/image/docker/schema2/config.py @@ -108,12 +108,14 @@ DOCKER_SCHEMA2_CONFIG_HISTORY_KEY = "history" DOCKER_SCHEMA2_CONFIG_ROOTFS_KEY = "rootfs" DOCKER_SCHEMA2_CONFIG_CREATED_KEY = "created" 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_TYPE_KEY = "type" LayerHistory = namedtuple('LayerHistory', ['created', 'created_datetime', 'command', 'is_empty', - 'raw_entry']) + 'author', 'comment', 'raw_entry']) class MalformedSchema2Config(ManifestException): @@ -151,8 +153,15 @@ class DockerSchema2Config(object): 'description': 'The command used to create the layer', '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, }, }, @@ -211,7 +220,7 @@ class DockerSchema2Config(object): for history_entry in self._parsed[DOCKER_SCHEMA2_CONFIG_HISTORY_KEY]: if history_entry.get(DOCKER_SCHEMA2_CONFIG_EMPTY_LAYER_KEY, False): return True - + return False @property @@ -220,8 +229,10 @@ class DockerSchema2Config(object): for history_entry in self._parsed[DOCKER_SCHEMA2_CONFIG_HISTORY_KEY]: created_datetime = parse_date(history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_KEY]) yield LayerHistory(created_datetime=created_datetime, - created=history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_KEY], - command=history_entry[DOCKER_SCHEMA2_CONFIG_CREATED_BY_KEY], + created=history_entry.get(DOCKER_SCHEMA2_CONFIG_CREATED_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), raw_entry=history_entry) @@ -235,9 +246,18 @@ class DockerSchema2Config(object): if v1_parent_id is not None: 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 + 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: v1_compatibility['container_config'] = { 'Cmd': [history.command], diff --git a/image/docker/schema2/manifest.py b/image/docker/schema2/manifest.py index d5717a7bf..53d3f3268 100644 --- a/image/docker/schema2/manifest.py +++ b/image/docker/schema2/manifest.py @@ -221,6 +221,8 @@ class DockerSchema2Manifest(ManifestInterface): command=image_layer.history.command, blob_digest=image_layer.blob_digest, created_datetime=image_layer.history.created_datetime, + author=image_layer.history.author, + comment=image_layer.history.comment, internal_layer=image_layer) @property diff --git a/image/docker/types.py b/image/docker/types.py index 088ff9887..69ca6d3be 100644 --- a/image/docker/types.py +++ b/image/docker/types.py @@ -3,4 +3,5 @@ from collections import namedtuple ManifestImageLayer = namedtuple('ManifestImageLayer', ['layer_id', 'compressed_size', 'is_remote', 'urls', 'command', 'blob_digest', 'created_datetime', + 'author', 'comment', 'internal_layer']) diff --git a/image/docker/v1.py b/image/docker/v1.py index b6df9f21a..b6700cdad 100644 --- a/image/docker/v1.py +++ b/image/docker/v1.py @@ -9,7 +9,7 @@ from collections import namedtuple class DockerV1Metadata(namedtuple('DockerV1Metadata', ['namespace_name', 'repo_name', 'image_id', 'checksum', '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. The original form of the metadata is stored in the compat_json field. diff --git a/static/directives/manifest-view-layer.html b/static/directives/manifest-view-layer.html index b58005250..a527c7427 100644 --- a/static/directives/manifest-view-layer.html +++ b/static/directives/manifest-view-layer.html @@ -1,6 +1,8 @@
- + + {{ layer.comment }} + {{ layer.blob_digest }}