|  | # Copyright 2015 The Bazel Authors. All rights reserved. | 
|  | # | 
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | # you may not use this file except in compliance with the License. | 
|  | # You may obtain a copy of the License at | 
|  | # | 
|  | #    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, software | 
|  | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | # See the License for the specific language governing permissions and | 
|  | # limitations under the License. | 
|  | """This tool creates a docker image from a layer and the various metadata.""" | 
|  |  | 
|  | import json | 
|  | import re | 
|  | import sys | 
|  | import tarfile | 
|  |  | 
|  | from tools.build_defs.docker import utils | 
|  | from tools.build_defs.pkg import archive | 
|  | from third_party.py import gflags | 
|  |  | 
|  | # Hardcoded docker versions that we are claiming to be. | 
|  | DATA_FORMAT_VERSION = '1.0' | 
|  |  | 
|  | gflags.DEFINE_string( | 
|  | 'output', None, | 
|  | 'The output file, mandatory') | 
|  | gflags.MarkFlagAsRequired('output') | 
|  |  | 
|  | gflags.DEFINE_multistring( | 
|  | 'layer', [], | 
|  | 'Layer tar files and their identifiers that make up this image') | 
|  |  | 
|  | gflags.DEFINE_string( | 
|  | 'id', None, | 
|  | 'The hex identifier of this image (hexstring or @filename), mandatory.') | 
|  | gflags.MarkFlagAsRequired('id') | 
|  |  | 
|  | gflags.DEFINE_string('config', None, | 
|  | 'The JSON configuration file for this image, mandatory.') | 
|  | gflags.MarkFlagAsRequired('config') | 
|  |  | 
|  | gflags.DEFINE_string('base', None, 'The base image file for this image.') | 
|  |  | 
|  | gflags.DEFINE_string( | 
|  | 'legacy_id', None, | 
|  | 'The legacy hex identifier of this layer (hexstring or @filename).') | 
|  |  | 
|  | gflags.DEFINE_string('metadata', None, | 
|  | 'The legacy JSON metadata file for this layer.') | 
|  |  | 
|  | gflags.DEFINE_string('legacy_base', None, | 
|  | 'The legacy base image file for this image.') | 
|  |  | 
|  | gflags.DEFINE_string( | 
|  | 'repository', None, | 
|  | 'The name of the repository to add this image.') | 
|  |  | 
|  | gflags.DEFINE_string( | 
|  | 'name', None, | 
|  | 'The symbolic name of this image.') | 
|  |  | 
|  | gflags.DEFINE_multistring('tag', None, | 
|  | 'The repository tags to apply to the image') | 
|  |  | 
|  | FLAGS = gflags.FLAGS | 
|  |  | 
|  |  | 
|  | def _base_name_filter(name): | 
|  | """Do not add multiple times 'top' and 'repositories' when merging images.""" | 
|  | filter_names = ['top', 'repositories', 'manifest.json'] | 
|  | return all([not name.endswith(s) for s in filter_names]) | 
|  |  | 
|  |  | 
|  | def create_image(output, | 
|  | identifier, | 
|  | layers, | 
|  | config, | 
|  | tags=None, | 
|  | base=None, | 
|  | legacy_base=None, | 
|  | metadata_id=None, | 
|  | metadata=None, | 
|  | name=None, | 
|  | repository=None): | 
|  | """Creates a Docker image. | 
|  |  | 
|  | Args: | 
|  | output: the name of the docker image file to create. | 
|  | identifier: the identifier for this image (sha256 of the metadata). | 
|  | layers: the layer content (a sha256 and a tar file). | 
|  | config: the configuration file for the image. | 
|  | tags: tags that apply to this image. | 
|  | base: a base layer (optional) to build on top of. | 
|  | legacy_base: a base layer (optional) to build on top of. | 
|  | metadata_id: the identifier of the top layer for this image. | 
|  | metadata: the json metadata file for the top layer. | 
|  | name: symbolic name for this docker image. | 
|  | repository: repository name for this docker image. | 
|  | """ | 
|  | tar = archive.TarFileWriter(output) | 
|  |  | 
|  | # add the image config referenced by the Config section in the manifest | 
|  | # the name can be anything but docker uses the format below | 
|  | config_file_name = identifier + '.json' | 
|  | tar.add_file(config_file_name, file_content=config) | 
|  |  | 
|  | layer_file_names = [] | 
|  |  | 
|  | if metadata_id: | 
|  | # Write our id to 'top' as we are now the topmost layer. | 
|  | tar.add_file('top', content=metadata_id) | 
|  |  | 
|  | # Each layer is encoded as a directory in the larger tarball of the form: | 
|  | #  {id}\ | 
|  | #    layer.tar | 
|  | #    VERSION | 
|  | #    json | 
|  | # Create the directory for us to now fill in. | 
|  | tar.add_file(metadata_id + '/', tarfile.DIRTYPE) | 
|  | # VERSION generally seems to contain 1.0, not entirely sure | 
|  | # what the point of this is. | 
|  | tar.add_file(metadata_id + '/VERSION', content=DATA_FORMAT_VERSION) | 
|  | # Add the layer file | 
|  | layer_file_name = metadata_id + '/layer.tar' | 
|  | layer_file_names.append(layer_file_name) | 
|  | tar.add_file(layer_file_name, file_content=layers[0]['layer']) | 
|  | # Now the json metadata | 
|  | tar.add_file(metadata_id + '/json', file_content=metadata) | 
|  |  | 
|  | # Merge the base if any | 
|  | if legacy_base: | 
|  | tar.add_tar(legacy_base, name_filter=_base_name_filter) | 
|  | else: | 
|  | for layer in layers: | 
|  | # layers can be called anything, so just name them by their sha256 | 
|  | layer_file_name = identifier + '/' + layer['name'] + '.tar' | 
|  | layer_file_names.append(layer_file_name) | 
|  | tar.add_file(layer_file_name, file_content=layer['layer']) | 
|  |  | 
|  | base_layer_file_names = [] | 
|  | parent = None | 
|  | if base: | 
|  | latest_item = utils.GetLatestManifestFromTar(base) | 
|  | if latest_item: | 
|  | base_layer_file_names = latest_item.get('Layers', []) | 
|  | config_file = latest_item['Config'] | 
|  | parent_search = re.search('^(.+)\\.json$', config_file) | 
|  | if parent_search: | 
|  | parent = parent_search.group(1) | 
|  |  | 
|  | manifest_item = { | 
|  | 'Config': config_file_name, | 
|  | 'Layers': base_layer_file_names + layer_file_names, | 
|  | 'RepoTags': tags or [] | 
|  | } | 
|  | if parent: | 
|  | manifest_item['Parent'] = 'sha256:' + parent | 
|  |  | 
|  | manifest = [manifest_item] | 
|  |  | 
|  | manifest_content = json.dumps(manifest, sort_keys=True) | 
|  | tar.add_file('manifest.json', content=manifest_content) | 
|  |  | 
|  | # In addition to N layers of the form described above, there is | 
|  | # a single file at the top of the image called repositories. | 
|  | # This file contains a JSON blob of the form: | 
|  | # { | 
|  | #   'repo':{ | 
|  | #     'tag-name': 'top-most layer hex', | 
|  | #     ... | 
|  | #   }, | 
|  | #   ... | 
|  | # } | 
|  | if repository: | 
|  | tar.add_file('repositories', content='\n'.join([ | 
|  | '{', | 
|  | '  "%s": {' % repository, | 
|  | '    "%s": "%s"' % (name, identifier), | 
|  | '  }', | 
|  | '}'])) | 
|  |  | 
|  |  | 
|  | # Main program to create a docker image. It expect to be run with: | 
|  | # create_image --output=output_file \ | 
|  | #              --id=@identifier \ | 
|  | #              [--base=base] \ | 
|  | #              --layer=@identifier=layer.tar \ | 
|  | #              --metadata=metadata.json \ | 
|  | #              --name=myname --repository=repositoryName \ | 
|  | #              --tag=repo/image:tag | 
|  | # See the gflags declaration about the flags argument details. | 
|  | def main(unused_argv): | 
|  | identifier = utils.ExtractValue(FLAGS.id) | 
|  | legacy_id = utils.ExtractValue(FLAGS.legacy_id) | 
|  |  | 
|  | layers = [] | 
|  | for kv in FLAGS.layer: | 
|  | (k, v) = kv.split('=', 1) | 
|  | layers.append({ | 
|  | 'name': utils.ExtractValue(k), | 
|  | 'layer': v, | 
|  | }) | 
|  |  | 
|  | create_image(FLAGS.output, identifier, layers, FLAGS.config, FLAGS.tag, | 
|  | FLAGS.base, FLAGS.legacy_base, legacy_id, FLAGS.metadata, | 
|  | FLAGS.name, FLAGS.repository) | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main(FLAGS(sys.argv)) |