diff --git a/README.md b/README.md index bf8ddf84b..59d829de4 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,12 @@ like: Unable to load package for //:WORKSPACE: BUILD file not found in any of the following directories. ``` +* rules_docker uses transitions to build your containers using toolchains the correct +architecture and operating system. If you run into issues with toolchain resolutions, +you can disable this behaviour, by adding this to your .bazelrc: +``` +build --@io_bazel_rules_docker//transitions:enable=false +``` ## Using with Docker locally. Suppose you have a `container_image` target `//my/image:helloworld`: @@ -635,7 +641,7 @@ nodejs_image( name = "nodejs_image", entry_point = "@your_workspace//path/to:file.js", # npm deps will be put into their own layer - data = [":file.js", "@npm//some-npm-dep"], + data = [":file.js", "@npm//some-npm-dep"], ... ) ``` @@ -1150,7 +1156,7 @@ load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl", docker_toolchain_configure( name = "docker_config", - # Replace this with a Bazel label to the config.json file. Note absolute or relative + # Replace this with a Bazel label to the config.json file. Note absolute or relative # paths are not supported. Docker allows you to specify custom authentication credentials # in the client configuration JSON file. # See https://docs.docker.com/engine/reference/commandline/cli/#configuration-files diff --git a/container/image.bzl b/container/image.bzl index fd2b72962..ab798dfa4 100644 --- a/container/image.bzl +++ b/container/image.bzl @@ -770,7 +770,15 @@ _outputs["config_digest"] = "%{name}.json.sha256" _outputs["build_script"] = "%{name}.executable" def _image_transition_impl(settings, attr): - return dicts.add(settings, { + if not settings["//transitions:enable"]: + # Once bazel < 5.0 is not supported we can return an empty dict here + return { + "//command_line_option:platforms": settings["//command_line_option:platforms"], + "@io_bazel_rules_docker//platforms:image_transition_cpu": "//plaftorms:image_transition_cpu_unset", + "@io_bazel_rules_docker//platforms:image_transition_os": "//plaftorms:image_transition_os_unset", + } + + return { "//command_line_option:platforms": "@io_bazel_rules_docker//platforms:image_transition", "@io_bazel_rules_docker//platforms:image_transition_cpu": "@platforms//cpu:" + { # Architecture aliases. @@ -779,11 +787,14 @@ def _image_transition_impl(settings, attr): "ppc64le": "ppc", }.get(attr.architecture, attr.architecture), "@io_bazel_rules_docker//platforms:image_transition_os": "@platforms//os:" + attr.operating_system, - }) + } _image_transition = transition( implementation = _image_transition_impl, - inputs = [], + inputs = [ + "//transitions:enable", + "//command_line_option:platforms", + ], outputs = [ "//command_line_option:platforms", "@io_bazel_rules_docker//platforms:image_transition_cpu", diff --git a/tests/container/BUILD b/tests/container/BUILD index e26637493..21bb013d7 100644 --- a/tests/container/BUILD +++ b/tests/container/BUILD @@ -30,6 +30,7 @@ load("//contrib:test.bzl", "container_test") load(":apple.bzl", "create_banana_directory") load(":empty_layers.bzl", "empty_layers") load(":pull_info_validation_test.bzl", "pull_info_validation_test") +load(":transitions.bzl", "templated_file", "transition_test") package(default_visibility = ["//visibility:public"]) @@ -999,3 +1000,84 @@ container_bundle( "localhost:5000/image4:latest": "//testdata:with_double_label", }, ) + +genrule( + name = "got_os", + outs = ["got_os.txt"], + cmd = select({ + "@platforms//os:windows": "echo windows > \"$@\"", + "@platforms//os:linux": "echo linux > \"$@\"", + "@platforms//os:macos": "echo macos > \"$@\"", + }), +) + +genrule( + name = "got_arch", + outs = ["got_arch.txt"], + cmd = select({ + "@platforms//cpu:arm64": "echo arm64 > \"$@\"", + "@platforms//cpu:aarch64": "echo arm64 > \"$@\"", + "@platforms//cpu:x86_64": "echo x86_64 > \"$@\"", + "@platforms//cpu:arm": "echo arm > \"$@\"", + }), +) + +container_image( + name = "transitioned_image", + architecture = "arm64", + files = [ + ":got_arch", + ":got_os", + ], + operating_system = "windows", +) + +templated_file( + name = "transitions_on", + out = "transitions_on.yaml", + substitutions = [ + "%WANT_OS%=windows", + "%WANT_ARCH%=arm64", + ], + template = "//tests/container/configs:transitions.yaml.tpl", +) + +templated_file( + name = "transitions_off", + out = "transitions_off.yaml", + substitutions = + select({ + "@platforms//os:linux": ["%WANT_OS%=linux"], + "@platforms//os:macos": ["%WANT_OS%=macos"], + }) + select({ + "@platforms//cpu:arm64": ["%WANT_ARCH%=arm64"], + "@platforms//cpu:aarch64": ["%WANT_ARCH%=arm64"], + "@platforms//cpu:x86_64": ["%WANT_ARCH%=x86_64"], + }), + template = "//tests/container/configs:transitions.yaml.tpl", +) + +container_test( + name = "transition_test", + configs = select({ + "//transitions:enabled": [":transitions_on"], + "//transitions:disabled": [":transitions_off"], + }), + driver = "tar", + image = ":transitioned_image", + # marked manual, because we are interested in running it with transitions + # enabled or disabled + tags = ["manual"], +) + +transition_test( + name = "transitions_on_test", + actual = ":transition_test", + transitions_enabled = True, +) + +transition_test( + name = "transitions_off_test", + actual = ":transition_test", + transitions_enabled = False, +) diff --git a/tests/container/configs/BUILD b/tests/container/configs/BUILD index a31bc75c9..cf70f71e6 100644 --- a/tests/container/configs/BUILD +++ b/tests/container/configs/BUILD @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -exports_files(glob(["*.yaml"])) +exports_files(glob(["*.yaml"]) + glob(["*.yaml.tpl"])) diff --git a/tests/container/configs/transitions.yaml.tpl b/tests/container/configs/transitions.yaml.tpl new file mode 100644 index 000000000..60a80f7ac --- /dev/null +++ b/tests/container/configs/transitions.yaml.tpl @@ -0,0 +1,9 @@ +schemaVersion: 2.0.0 + +fileContentTests: + - name: "validate architecture" + path: "/Files/got_arch.txt" + expectedContents: ["%WANT_ARCH%"] + - name: "validate os" + path: "/Files/got_os.txt" + expectedContents: ["%WANT_OS%"] diff --git a/tests/container/transitions.bzl b/tests/container/transitions.bzl new file mode 100644 index 000000000..64acef5dc --- /dev/null +++ b/tests/container/transitions.bzl @@ -0,0 +1,77 @@ +""" +Rules for running tests with a specific value of the //transitions:enabled flag +""" + +def _templated_file_impl(ctx): + out = ctx.outputs.out + ctx.actions.expand_template( + template = ctx.file.template, + output = out, + substitutions = dict([s.split("=", 1) for s in ctx.attr.substitutions]), + ) + return DefaultInfo( + files = depset([out]), + ) + +# Replaces substitutions split on `=` in the template. Substitution is a list +# so that is usable with multiple selects, +templated_file = rule( + attrs = { + "template": attr.label( + mandatory = True, + allow_single_file = True, + ), + "substitutions": attr.string_list(), + "out": attr.output(), + }, + implementation = _templated_file_impl, +) + +def _enable_transition_impl(settings, attr): + _ = settings + return {"//transitions:enable": attr.transitions_enabled} + +_enable_transition = transition( + implementation = _enable_transition_impl, + inputs = [], + outputs = [ + "//transitions:enable", + ], +) + +def _transitioned_test(ctx): + source_info = ctx.attr.actual[DefaultInfo] + + # Bazel wants the executable to be generated by this rule, let's oblige by + # just copying the actual runner. + executable = None + if source_info.files_to_run and source_info.files_to_run.executable: + executable = ctx.actions.declare_file("{}_{}".format(ctx.file.actual.basename, "on" if ctx.attr.transitions_enabled else "off")) + ctx.actions.run_shell( + command = "cp {} {}".format(source_info.files_to_run.executable.path, executable.path), + inputs = [source_info.files_to_run.executable], + outputs = [executable], + ) + return [DefaultInfo( + files = depset(ctx.files.actual), + runfiles = source_info.default_runfiles.merge(source_info.data_runfiles), + executable = executable, + )] + +# Defines a test that runs with a specific value of the //transitions:enable +# flag. +transition_test = rule( + attrs = { + "actual": attr.label( + mandatory = True, + allow_single_file = True, + ), + "transitions_enabled": attr.bool(), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + cfg = _enable_transition, + test = True, + implementation = _transitioned_test, +) diff --git a/transitions/BUILD b/transitions/BUILD new file mode 100644 index 000000000..e0dae81db --- /dev/null +++ b/transitions/BUILD @@ -0,0 +1,31 @@ +# Copyright 2022 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("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +package( + default_visibility = ["//visibility:public"], +) + +bool_flag(name = "enable", build_setting_default=True) + +config_setting( + name = "enabled", + flag_values = {"//transitions:enable": "true"} +) + +config_setting( + name = "disabled", + flag_values = {"//transitions:enable": "false"} +)