Add modules to create and download debian packages (#154)

* Add modules to get debian packages

1. Extract a new rule to download debian packages and create a tarball
2. Create a new file for the new rule aggregate_debian_pkgs
3. Move the use of new rule into language_tool_layer
diff --git a/container/rules/debian_pkg_tar.bzl b/container/rules/debian_pkg_tar.bzl
new file mode 100644
index 0000000..d76fb38
--- /dev/null
+++ b/container/rules/debian_pkg_tar.bzl
@@ -0,0 +1,188 @@
+# 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.
+
+load("@base_images_docker//package_managers:download_pkgs.bzl", _download_deb_pkgs = "download")
+load("@base_images_docker//package_managers:apt_key.bzl", _apt_key = "key")
+
+def _input_validation(kwargs):
+    allowed_attribues = ["name", "base", "language_layers"]
+
+    for key in kwargs.keys():
+        if key not in allowed_attribues:
+            fail("Attribute " + key + " is not supported.")
+
+container = [
+    ".tar.gz",
+    ".tgz",
+    ".tar",
+    ".tar.xz",
+]
+
+generate_deb_tar_attrs = _download_deb_pkgs.attrs + {
+    "base": attr.label(allow_files = container),
+    "packages": attr.string_list(),
+    "keys": attr.label_list(
+        allow_files = True,
+    ),
+}
+
+aggregate_debian_pkgs_attrs = {
+    "base": attr.label(allow_files = container),
+    "language_layers": attr.label_list(),
+
+    # Declare the following attributes since _download_deb_pkgs.implementation
+    # need access those attribute if their overrides are None
+    "additional_repos": attr.string_list(),
+    "_image_id_extractor": attr.label(
+        default = "@io_bazel_rules_docker//contrib:extract_image_id.py",
+        allow_files = True,
+        single_file = True,
+    ),
+}
+
+InstallableTarInfo = provider(fields = [
+    "installables_tar",
+])
+
+def _generate_deb_tar(
+        ctx,
+        packages = None,
+        additional_repos = None,
+        keys = None,
+        download_pkgs_output_tar = None,
+        download_pkgs_output_script = None):
+    """A function for producing a tarball for a set of debian packages.
+
+    Args:
+      ctx: ctx has either generate_deb_tar_attrs or aggregate_debian_pkgs_attrs,
+        this depends on the rule that uses this function
+      packages: list of packages to fetch and install in the base image.
+      additional_repos: list of additional debian package repos to use,
+        in sources.list format.
+      keys: list of labels of additional gpg keys to use while downloading
+        packages.
+      download_pkgs_output_tar: output tar file generated by download_pkgs rule to
+        override default output_tar
+      download_pkgs_output_script: output script generated by download_pkgs rule to
+        override default output_script
+    """
+
+    # Prepare base image for the download_pkgs rule.
+    download_base = ctx.files.base[0]
+
+    # Create an intermediate image with additional gpg keys used to download packages.
+    if keys != []:
+        image_with_keys = "%s_with_keys" % ctx.attr.name
+
+        # Declare intermediate output file generated by add_apt_key rule.
+        image_with_keys_output_executable = ctx.actions.declare_file(image_with_keys)
+        image_with_keys_output_tarball = ctx.actions.declare_file(image_with_keys + ".tar")
+        image_with_keys_output_layer = ctx.actions.declare_file(image_with_keys + "-layer.tar")
+
+        _apt_key.implementation(
+            ctx,
+            name = image_with_keys,
+            image_tar = ctx.files.base[0],
+            keys = keys,
+            output_executable = image_with_keys_output_executable,
+            output_tarball = image_with_keys_output_tarball,
+            output_layer = image_with_keys_output_layer,
+        )
+        download_base = image_with_keys_output_tarball
+
+    # Declare intermediate output file generated by download_pkgs rule.
+    output_executable = ctx.actions.declare_file(ctx.attr.name + "-output_executable.sh")
+    download_pkgs_output_tar = download_pkgs_output_tar or ctx.attr.name + ".tar"
+    download_pkgs_output_script = download_pkgs_output_script or ctx.attr.name + ".sh"
+    output_tar = ctx.actions.declare_file(download_pkgs_output_tar)
+    output_script = ctx.actions.declare_file(download_pkgs_output_script)
+
+    # download_pkgs rule consumes 'packages' and 'additional_repos'.
+    _download_deb_pkgs.implementation(
+        ctx,
+        image_tar = download_base,
+        packages = packages,
+        additional_repos = additional_repos,
+        output_executable = output_executable,
+        output_tar = output_tar,
+        output_script = output_script,
+    )
+
+    return struct(
+        providers = [
+            InstallableTarInfo(
+                installables_tar = output_tar,
+            ),
+        ],
+    )
+
+def _aggregate_debian_pkgs_impl(ctx):
+    """Implementation for the aggregate_debian_pkgs rule.
+
+    aggregate_debian_pkgs rule produces a tarball with all debian packages declared
+    in the language_tool_layer(s) this rule depends on.
+
+    Args:
+      ctx: ctx only has name, base, and language_layers attributes
+    """
+
+    packages = []
+    additional_repos = []
+    keys = []
+
+    for layer in ctx.attr.language_layers:
+        packages.extend(layer.packages)
+        additional_repos.extend(layer.additional_repos)
+        keys.extend(layer.keys)
+
+    packages = depset(packages).to_list()
+    additional_repos = depset(additional_repos).to_list()
+    keys = depset(keys).to_list()
+
+    return _generate_deb_tar(
+        ctx,
+        packages = packages,
+        additional_repos = additional_repos,
+        keys = keys,
+    )
+
+# Export _generate_deb_tar function for other bazel rules to use.
+generate = struct(
+    attrs = generate_deb_tar_attrs,
+    outputs = _download_deb_pkgs.outputs,
+    implementation = _generate_deb_tar,
+)
+
+aggregate_debian_pkgs_ = rule(
+    attrs = aggregate_debian_pkgs_attrs,
+    outputs = _download_deb_pkgs.outputs,
+    implementation = _aggregate_debian_pkgs_impl,
+)
+
+def aggregate_debian_pkgs(**kwargs):
+    """Aggregate debian packages from multiple language_tool_layers into a tarball.
+
+    Args:
+      name: a unique name for this rule.
+      base: base os image used for this rule.
+      language_layers: a list of language_tool_layer targets.
+
+    Only name, base, and language_layers attributes are allowed in this rule.
+
+    Experimental rule.
+    """
+
+    _input_validation(kwargs)
+
+    aggregate_debian_pkgs_(**kwargs)
diff --git a/container/rules/docker_toolchains.bzl b/container/rules/docker_toolchains.bzl
index 2fba04e..b6e8964 100644
--- a/container/rules/docker_toolchains.bzl
+++ b/container/rules/docker_toolchains.bzl
@@ -18,6 +18,7 @@
 load("@base_images_docker//package_managers:download_pkgs.bzl", _download = "download")
 load("@base_images_docker//package_managers:install_pkgs.bzl", _install = "install")
 load("@base_images_docker//package_managers:apt_key.bzl", _key = "key")
+load(":debian_pkg_tar.bzl", _generate_deb_tar = "generate")
 
 def _input_validation(kwargs):
     if "debs" in kwargs:
@@ -91,45 +92,19 @@
         # debs to installables_tars.
 
     elif packages != []:
-        # Prepare base image for the download_pkgs rule.
-        download_base = ctx.files.base[0]
+        download_pkgs_output_tar = ctx.attr.name + "-download_pkgs_output_tar.tar"
+        download_pkgs_output_script = ctx.attr.name + "-download_pkgs_output_script.sh"
 
-        # Create an intermediate image with additional gpg keys used to download packages.
-        if keys != []:
-            image_with_keys = "%s_with_keys" % ctx.attr.name
-
-            # Declare intermediate output file generated by add_apt_key rule.
-            image_with_keys_output_executable = ctx.actions.declare_file(image_with_keys)
-            image_with_keys_output_tarball = ctx.actions.declare_file(image_with_keys + ".tar")
-            image_with_keys_output_layer = ctx.actions.declare_file(image_with_keys + "-layer.tar")
-
-            _key.implementation(
-                ctx,
-                name = image_with_keys,
-                image_tar = ctx.files.base[0],
-                keys = keys,
-                output_executable = image_with_keys_output_executable,
-                output_tarball = image_with_keys_output_tarball,
-                output_layer = image_with_keys_output_layer,
-            )
-            download_base = image_with_keys_output_tarball
-
-        # Declare intermediate output file generated by download_pkgs rule.
-        download_pkgs_output_executable = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_executable.sh")
-        download_pkgs_output_tar = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_tar.tar")
-        download_pkgs_output_script = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_script.sh")
-
-        # download_pkgs rule consumes 'packages' and 'additional_repos'.
-        _download.implementation(
+        aggregated_debian_tar = _generate_deb_tar.implementation(
             ctx,
-            image_tar = download_base,
             packages = packages,
             additional_repos = additional_repos,
-            output_executable = download_pkgs_output_executable,
-            output_tar = download_pkgs_output_tar,
-            output_script = download_pkgs_output_script,
+            keys = keys,
+            download_pkgs_output_tar = download_pkgs_output_tar,
+            download_pkgs_output_script = download_pkgs_output_script,
         )
-        installables_tars.append(download_pkgs_output_tar)
+
+        installables_tars.append(aggregated_debian_tar.providers[0].installables_tar)
 
     # Prepare new base image for the container_image rule.
     new_base = ctx.files.base[0]
diff --git a/container/ubuntu16_04/debian_pkgs/BUILD b/container/ubuntu16_04/debian_pkgs/BUILD
new file mode 100644
index 0000000..5ae66cc
--- /dev/null
+++ b/container/ubuntu16_04/debian_pkgs/BUILD
@@ -0,0 +1,35 @@
+# 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.
+
+licenses(["notice"])  # Apache 2.0
+
+package(default_visibility = ["//visibility:public"])
+
+load(
+    "//container/rules:debian_pkg_tar.bzl",
+    "aggregate_debian_pkgs",
+)
+
+aggregate_debian_pkgs(
+    name = "ubuntu16-04-debian_pkgs",
+    base = "@ubuntu16_04//image",
+    language_layers = [
+        "//container/ubuntu16_04/builders/rbe-ubuntu16_04:base-ltl",
+        "//container/ubuntu16_04/layers/clang:clang-ltl",
+        "//container/ubuntu16_04/layers/go:go-ltl",
+        "//container/ubuntu16_04/layers/java:java-ltl",
+        "//container/ubuntu16_04/layers/java:java10-ltl",
+        "//container/ubuntu16_04/layers/python:python-ltl",
+    ],
+)