|  | # 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 list of layers.""" | 
|  | # This is the main program to create a docker image. It expect to be run with: | 
|  | # join_layers --output=output_file \ | 
|  | #             --layer=layer1 [--layer=layer2 ... --layer=layerN] \ | 
|  | #             --id=@identifier \ | 
|  | #             --name=myname --repository=repositoryName | 
|  | # See the gflags declaration about the flags argument details. | 
|  |  | 
|  | import json | 
|  | import os.path | 
|  | import sys | 
|  |  | 
|  | from tools.build_defs.docker import utils | 
|  | from tools.build_defs.pkg import archive | 
|  | from third_party.py import gflags | 
|  |  | 
|  | gflags.DEFINE_string('output', None, 'The output file, mandatory') | 
|  | gflags.MarkFlagAsRequired('output') | 
|  |  | 
|  | gflags.DEFINE_multistring('layer', [], 'The tar files for layers to join.') | 
|  |  | 
|  | gflags.DEFINE_multistring( | 
|  | 'tags', [], | 
|  | 'An associative list of fully qualified tag names and the layer they tag. ' | 
|  | 'e.g. ubuntu=deadbeef,gcr.io/blah/debian=baadf00d') | 
|  |  | 
|  | FLAGS = gflags.FLAGS | 
|  |  | 
|  |  | 
|  | def _layer_filter(name): | 
|  | basename = os.path.basename(name) | 
|  | return basename not in ('manifest.json', 'top', 'repositories') | 
|  |  | 
|  |  | 
|  | def _add_top(tar, repositories): | 
|  | # Don't add 'top' if there are multiple images in this bundle. | 
|  | if len(repositories) != 1: | 
|  | return | 
|  |  | 
|  | # Walk the single-item dictionary, and if there is a single tag | 
|  | # for the single repository, then emit a 'top' file pointing to | 
|  | # the single image in this bundle. | 
|  | for (unused_x, tags) in repositories.items(): | 
|  | if len(tags) != 1: | 
|  | continue | 
|  | for (unused_y, layer_id) in tags.items(): | 
|  | tar.add_file('top', content=layer_id) | 
|  |  | 
|  |  | 
|  | def _create_image(tar, layers, repositories=None): | 
|  | """Creates a Docker image from a list of layers. | 
|  |  | 
|  | Args: | 
|  | tar: archive.TarFileWriter object for the docker image file to create. | 
|  | layers: the layers (tar files) to join to the image. | 
|  | repositories: the repositories two-level dictionary, which is keyed by | 
|  | repo names at the top-level, and tag names at the second | 
|  | level pointing to layer ids. | 
|  | """ | 
|  | # Compute a map from layer tarball names to the tags that should apply to them | 
|  | layers_to_tag = {} | 
|  | for repo in repositories: | 
|  | tags = repositories[repo] | 
|  | for tag in tags: | 
|  | layer_name = tags[tag] + '/layer.tar' | 
|  | fq_name = '%s:%s' % (repo, tag) | 
|  | layer_tags = layers_to_tag.get(layer_name, []) | 
|  | layer_tags.append(fq_name) | 
|  | layers_to_tag[layer_name] = layer_tags | 
|  |  | 
|  | manifests = [] | 
|  | for layer in layers: | 
|  | tar.add_tar(layer, name_filter=_layer_filter) | 
|  | layer_manifests = utils.GetManifestFromTar(layer) | 
|  |  | 
|  | # Augment each manifest with any tags that should apply to their top layer. | 
|  | for manifest in layer_manifests: | 
|  | top_layer = manifest['Layers'][-1] | 
|  | manifest['RepoTags'] = list(sorted(set(manifest['RepoTags'] + | 
|  | layers_to_tag.get(top_layer, [])))) | 
|  |  | 
|  | manifests += layer_manifests | 
|  |  | 
|  | manifest_content = json.dumps(manifests, sort_keys=True) | 
|  | tar.add_file('manifest.json', content=manifest_content) | 
|  |  | 
|  | # In addition to N layers of the form described above, there might be | 
|  | # 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', | 
|  | #     ... | 
|  | #   }, | 
|  | #   ... | 
|  | # } | 
|  | # This is the exact structure we expect repositories to have. | 
|  | if repositories: | 
|  | # If the identifier is not provided, then the resulted layer will be | 
|  | # created without a 'top' file.  Docker doesn't needs that file nor | 
|  | # the repository to load the image and for intermediate layer, | 
|  | # docker_build store the name of the layer in a separate artifact so | 
|  | # this 'top' file is not needed. | 
|  | _add_top(tar, repositories) | 
|  | tar.add_file('repositories', | 
|  | content=json.dumps(repositories, sort_keys=True)) | 
|  |  | 
|  |  | 
|  | def create_image(output, layers, repositories=None): | 
|  | """Creates a Docker image from a list of layers. | 
|  |  | 
|  | Args: | 
|  | output: the name of the docker image file to create. | 
|  | layers: the layers (tar files) to join to the image. | 
|  | repositories: the repositories two-level dictionary, which is keyed by | 
|  | repo names at the top-level, and tag names at the second | 
|  | level pointing to layer ids. | 
|  | """ | 
|  | with archive.TarFileWriter(output) as tar: | 
|  | _create_image(tar, layers, repositories) | 
|  |  | 
|  |  | 
|  | def resolve_layer(identifier): | 
|  | if not identifier: | 
|  | # TODO(mattmoor): This should not happen. | 
|  | return None | 
|  |  | 
|  | if not identifier.startswith('@'): | 
|  | return identifier | 
|  |  | 
|  | with open(identifier[1:], 'r') as f: | 
|  | return f.read() | 
|  |  | 
|  |  | 
|  | def main(unused_argv): | 
|  | repositories = {} | 
|  | for entry in FLAGS.tags: | 
|  | elts = entry.split('=') | 
|  | if len(elts) != 2: | 
|  | raise Exception('Expected associative list key=value, got: %s' % entry) | 
|  | (fq_tag, layer_id) = elts | 
|  |  | 
|  | tag_parts = fq_tag.rsplit(':', 2) | 
|  | if len(tag_parts) != 2: | 
|  | raise Exception('Expected fully-qualified tag name (e.g. ubuntu:latest), ' | 
|  | 'got: %s' % fq_tag) | 
|  | (repository, tag) = tag_parts | 
|  |  | 
|  | others = repositories.get(repository, {}) | 
|  | others[tag] = resolve_layer(layer_id) | 
|  | repositories[repository] = others | 
|  |  | 
|  | create_image(FLAGS.output, FLAGS.layer, repositories) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main(FLAGS(sys.argv)) |