# Copyright 2017 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. """Rule for bundling Docker images into a tarball.""" load(":label.bzl", _string_to_label = "string_to_label") load( ":layers.bzl", _assemble_image = "assemble", _get_layers = "get_from_target", _incr_load = "incremental_load", _layer_tools = "tools", ) load(":list.bzl", "reverse") def _docker_bundle_impl(ctx): """Implementation for the docker_bundle rule.""" # Compute the set of layers from the image_targes. image_target_dict = _string_to_label( ctx.attr.image_targets, ctx.attr.image_target_strings, ) seen_names = [] layers = [] for image in ctx.attr.image_targets: # TODO(mattmoor): Add support for naked tarballs. for layer in _get_layers(ctx, image): if layer["name"].path in seen_names: continue seen_names.append(layer["name"].path) layers.append(layer) images = dict() for unresolved_tag in ctx.attr.images: # Allow users to put make variables into the tag name. tag = ctx.expand_make_variables("images", unresolved_tag, {}) target = ctx.attr.images[unresolved_tag] target = image_target_dict[target] images[tag] = _get_layers(ctx, target)[0] _incr_load(ctx, layers, images, ctx.outputs.executable) _assemble_image(ctx, reverse(layers), { # Create a new dictionary with the same keyspace that # points to the name of the layer. k: images[k]["name"] for k in images }, ctx.outputs.out) runfiles = ctx.runfiles( files = ([l["name"] for l in layers] + [l["id"] for l in layers] + [l["layer"] for l in layers]), ) return struct( runfiles = runfiles, files = depset(), ) docker_bundle_ = rule( implementation = _docker_bundle_impl, attrs = dict({ "images": attr.string_dict(), # Implicit dependencies. "image_targets": attr.label_list(allow_files = True), "image_target_strings": attr.string_list(), }.items() + _layer_tools.items()), outputs = { "out": "%{name}.tar", }, executable = True, ) # Produces a new docker image tarball compatible with 'docker load', which # contains the N listed 'images', each aliased with their key. # # Example: # docker_bundle( # name = "foo", # images = { # "ubuntu:latest": ":blah", # "foo.io/bar:canary": "//baz:asdf", # } # ) def docker_bundle(**kwargs): """Package several docker images into a single tarball. Args: **kwargs: See above. """ for reserved in ["image_targets", "image_target_strings"]: if reserved in kwargs: fail("reserved for internal use by docker_bundle macro", attr = reserved) if "images" in kwargs: kwargs["image_targets"] = kwargs["images"].values() kwargs["image_target_strings"] = kwargs["images"].values() docker_bundle_(**kwargs)