| """This module defines the skia_app_container macro.""" |
| |
| load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push") |
| load("@io_bazel_rules_docker//docker/util:run.bzl", "container_run_and_commit") |
| load("@rules_pkg//:pkg.bzl", "pkg_tar") |
| |
| def skia_app_container( |
| name, |
| repository, |
| dirs, |
| entrypoint = "", |
| run_commands_root = None, |
| run_commands_skia = None, |
| base_image = "@basealpine//image", |
| env = None, |
| default_user = "skia"): |
| """Builds a Docker container for a Skia app, and generates a target to push it to GCR. |
| |
| This macro produces the following: |
| * "<name>" target to build the Docker container with skia as default user. |
| * "<name>_run_root" target to execute run commands as root on the image. |
| root will be the default user here. Will be created only |
| if run_commands_root is specified. |
| * "<name>_run_skia" target to execute run commands as the "skia" user on the image. |
| Will be created only if run_commands_skia is specified. |
| * "push_<name>" target to push the container to GCR. |
| * "pushk_<name>" target to push the container to GCR, and deploy <name> to production via pushk. |
| |
| Example: |
| |
| ``` |
| # //myapp/BUILD.bazel |
| |
| load("//bazel:skia_app_container.bzl", "skia_app_container") |
| |
| skia_app_container( |
| name = "myapp", |
| dirs = { |
| "/usr/local/bin/myapp": [ |
| ["//myapp/go:mybinary", 755"], |
| ], |
| "/usr/local/share/myapp": [ |
| ["//myapp/config:config.cfg", "644"], |
| ["//myapp/data:data.json", "644"], |
| ], |
| }, |
| entrypoint = "/usr/local/bin/myapp/mybinary", |
| repository = "skia-public/myapp", |
| ) |
| ``` |
| |
| The above example will produce a Docker container based on gcr.io/skia-public/basealpine with |
| the following contents: |
| |
| - /usr/local/bin/myapp/mybinary (mode: 755) |
| - /usr/local/share/myapp/config.cfg (mode: 644) |
| - /usr/local/share/myapp/data.json (mode: 644) |
| |
| To build the container and load it into Docker: |
| |
| ``` |
| $ bazel run //myapp:myapp |
| ... |
| Loaded image ID: sha256:c0decafe |
| Tagging c0decafe as bazel/myapp:myapp |
| ``` |
| |
| To debug the container locally: |
| |
| ``` |
| $ docker run bazel/myapp:myapp |
| $ docker run -it --entrypoint /bin/sh bazel/myapp:myapp |
| ``` |
| |
| To push the container to GCR: |
| |
| ``` |
| $ bazel run //myapp:push_myapp |
| ... |
| Successfully pushed Docker image to gcr.io/skia-public/myapp:... |
| ``` |
| |
| To push the app to production (assuming the app is compatible with pushk): |
| |
| ``` |
| $ bazel run //myapp:pushk_myapp |
| ``` |
| |
| Which is equivalent to: |
| |
| ``` |
| $ bazel run //myapp:push_myapp |
| $ pushk myapp |
| ``` |
| |
| Args: |
| name: Name of the rule. |
| repository: Name of the repository under gcr.io. |
| dirs: Contents of the container, expressed as a dictionary where the keys are directory names |
| within the container (e.g. "/usr/local/share/myapp"), and the values are an array of |
| [Bazel label, mode] tuples indicating which files should be copied into the directory (e.g. |
| ["//myapp/go:mybinary", "755"]). |
| entrypoint: The entrypoint of the container, which can be a string or an array (e.g. |
| "/usr/local/share/myapp/mybinary", or ["/usr/local/share/myapp/mybinary", "--someflag"]). |
| Optional. |
| run_commands_root: The RUN commands that should be executed on the container by the root |
| user. Optional. |
| run_commands_skia: The RUN commands that should be executed on the container by the skia |
| user. Optional. |
| base_image: The image to base the container_image on. Optional. |
| env: A {"var": "val"} dictionary with the environment variables to use when building the |
| container. Optional. |
| default_user: The user the container will be run with. Defaults to "skia" but some apps |
| like skfe requires the default user to be "root". |
| """ |
| |
| # According to the container_image rule's docs[1], the recommended way to place files in |
| # specific directories is via the pkg_tar rule. |
| # |
| # The below loop creates one pkg_tar rule for each file in the container. |
| # |
| # [1] https://github.com/bazelbuild/rules_docker/blob/454981e65fa100d37b19210ee85fedb2f7af9626/README.md#container_image |
| pkg_tars = [] |
| i = 0 |
| for dir in dirs: |
| for file, mode in dirs[dir]: |
| pkg_tar_name = name + "_pkg_tar_" + str(i) |
| i += 1 |
| pkg_tars.append(pkg_tar_name) |
| |
| pkg_tar( |
| name = pkg_tar_name, |
| srcs = [file], |
| package_dir = dir, |
| mode = mode, |
| tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...". |
| ) |
| |
| image_name = (name + "_base") if (run_commands_root or run_commands_skia) else name |
| |
| container_image( |
| name = image_name, |
| base = base_image, |
| |
| # We cannot use an entrypoint with the container_run_and_commit rule |
| # required when run_commands_root or run_commands_skia is specified, |
| # because the commands we want to execute do not require a specific |
| # entrypoint. |
| # We will set the entrypoint back after the container_run_and_commit |
| # rule is executed. |
| entrypoint = None if (run_commands_root or run_commands_skia) else [entrypoint], |
| tars = pkg_tars, |
| user = default_user, |
| tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...". |
| env = env, |
| ) |
| |
| if run_commands_root: |
| rule_name = name + "_run_root" |
| container_run_and_commit( |
| name = rule_name, |
| commands = run_commands_root, |
| docker_run_flags = ["--user", "root"], |
| image = image_name + ".tar", |
| tags = [ |
| "manual", # Exclude it from wildcard queries, e.g. "bazel build //...". |
| # container_run_and_commit requires the docker daemon to be |
| # running. This is not possible inside RBE. |
| "no-remote", |
| ], |
| ) |
| image_name = ":" + rule_name + "_commit.tar" |
| |
| if run_commands_skia: |
| rule_name = name + "_run_skia" |
| container_run_and_commit( |
| name = rule_name, |
| commands = run_commands_skia, |
| docker_run_flags = ["--user", "skia"], |
| # If run_commands_root was specified then the image_name already contains |
| # ".tar" suffix. Make sure we do not add a double ".tar" suffix here. |
| image = image_name if image_name.endswith(".tar") else image_name + ".tar", |
| tags = [ |
| "manual", # Exclude it from wildcard queries, e.g. "bazel build //...". |
| # container_run_and_commit requires the docker daemon to be |
| # running. This is not possible inside RBE. |
| "no-remote", |
| ], |
| ) |
| image_name = ":" + rule_name + "_commit.tar" |
| |
| if run_commands_root or run_commands_skia: |
| # If run_commands_root was specified then it's container_run_and_commit |
| # sets root as the default user and overrides the entrypoint. |
| # If run_commands_skia was specified then it overrides the entrypoint. |
| # |
| # Now execute container_image using the previous image as base to set |
| # back skia as the default user and to set back the original entrypoint. |
| rule_name = name |
| container_image( |
| name = rule_name, |
| base = image_name, |
| entrypoint = [entrypoint], |
| user = default_user, |
| tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...". |
| env = env, |
| ) |
| image_name = ":" + rule_name |
| |
| container_push( |
| name = "push_" + name, |
| format = "Docker", |
| image = image_name, |
| registry = "gcr.io", |
| repository = repository, |
| stamp = "@io_bazel_rules_docker//stamp:always", |
| tag = "{STABLE_DOCKER_TAG}", |
| tags = [ |
| "manual", # Exclude it from wildcard queries, e.g. "bazel build //...". |
| # container_push requires the docker daemon to be |
| # running. This is not possible inside RBE. |
| "no-remote", |
| ], |
| ) |
| |
| # pushk expects the second half of the repository name as an argument. |
| pushk_image_name = repository.split("/")[1] |
| |
| # The container_push rule outputs two files: <name>, which is a script that uploads the |
| # container to GCR, and <name>.digest, which contains the SHA256 digest of the container. |
| # |
| # Because the container_push rule outputs multiple files, we cannot use $$(rootpath push_<name>) |
| # to get the path to <name>, so we use $$(rootpaths push_<name>), which returns the list of all |
| # output files, then take the base directory of an arbitrary file, and append <name> to it to |
| # get the path to the desired script. |
| pushk_script = "\n".join([ |
| "container_push_outputs=($(rootpaths push_%s))", |
| "container_push_base_dir=$$(dirname $${container_push_outputs[0]})", |
| "container_push_script=$${container_push_base_dir}/push_%s", |
| "", |
| "$$container_push_script && $(rootpath //kube/go/pushk) --use-temp-checkout %s", |
| ]) % (name, name, pushk_image_name) |
| |
| native.genrule( |
| name = "gen_pushk_" + name, |
| srcs = [ |
| "push_" + name, |
| "//kube/go/pushk", |
| ], |
| outs = ["pushk_%s.sh" % name], |
| cmd = "echo '%s' > $@" % pushk_script, |
| tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...". |
| ) |
| |
| native.sh_binary( |
| name = "pushk_" + name, |
| srcs = ["gen_pushk_" + name], |
| data = [ |
| "push_" + name, |
| "//kube/go/pushk", |
| ], |
| tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...". |
| ) |