diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..df5119e --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.7.0 diff --git a/MODULE.bazel b/MODULE.bazel index 85e3fcc..9176b04 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -17,5 +17,6 @@ module( version = "0.1.0", ) +bazel_dep(name = "rules_python", version = "1.9.1") bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "cel-spec", version = "0.25.1", repo_name = "cel_spec") diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel new file mode 100644 index 0000000..64520aa --- /dev/null +++ b/bazel/BUILD.bazel @@ -0,0 +1,37 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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("@rules_python//python:py_binary.bzl", "py_binary") +load("@rules_python//python:py_library.bzl", "py_library") +load("@rules_python//python:py_test.bzl", "py_test") +load(":test_bundle.bzl", "cel_policy_test_bundle") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//:__subpackages__"], +) + +exports_files(["test_bundle.bzl"]) + +py_library( + name = "bundle_lib", + srcs = ["bundle.py"], +) + +py_binary( + name = "bundle", + srcs = ["bundle.py"], + visibility = ["//visibility:public"], + deps = [":bundle_lib"], +) diff --git a/bazel/bundle.py b/bazel/bundle.py new file mode 100644 index 0000000..8027d20 --- /dev/null +++ b/bazel/bundle.py @@ -0,0 +1,85 @@ +r"""Script to bundle CEL policy conformance tests. + +Each CEL policy conformance test is a self-contained directory containing an +optional environment, a policy, and a test suite. This script +bundles them all together into a single multidocument YAML file to simplify file +loading. + +Intended to be called via bazel rule. + +Format: + +``` +# config.yaml +{environment} +--- +# policy.yaml +{policy} +--- +# tests.yaml +{test} +``` + +Usage: + bazel run //third_party/cel/policy/bazel:bundle -- \ + --environment= \ + --policy= \ + --test= \ + --output= +""" + +import argparse +from collections.abc import Sequence +import os +import sys + + +def _read_and_format_section(path: str) -> bytes: + with open(path, "rb") as f: + content = f.read().rstrip() + comment = f"# {os.path.basename(path)}".encode("utf-8") + return comment + b"\n" + content + + +def bundle(environment: str | None, policy: str, test: str) -> bytes: + sections = [] + if environment: + sections.append(_read_and_format_section(environment)) + sections.append(_read_and_format_section(policy)) + sections.append(_read_and_format_section(test)) + return b"\n---\n".join(sections) + b"\n" + + +def main(argv: Sequence[str]) -> None: + parser = argparse.ArgumentParser( + description="Bundle CEL policy conformance tests." + ) + parser.add_argument( + "--environment", + help="Path to the environment file.", + required=False, + default=None, + ) + parser.add_argument( + "--policy", + help="Path to the policy file.", + required=True, + ) + parser.add_argument( + "--test", + help="Path to the test file.", + required=True, + ) + parser.add_argument( + "--output", + help="Path to the output file.", + required=True, + ) + args = parser.parse_args(argv[1:]) + bundled_content = bundle(args.environment, args.policy, args.test) + with open(args.output, "wb") as f: + f.write(bundled_content) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/bazel/test_bundle.bzl b/bazel/test_bundle.bzl new file mode 100644 index 0000000..64e0c72 --- /dev/null +++ b/bazel/test_bundle.bzl @@ -0,0 +1,62 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +"""Defines a bazel rule for bundling CEL policy conformance tests.""" + +def _cel_policy_test_bundle_impl(ctx): + inputs = [] + args = ctx.actions.args() + if ctx.file.environment: + inputs.append(ctx.file.environment) + args.add("--environment", ctx.file.environment.path) + if ctx.file.policy: + inputs.append(ctx.file.policy) + args.add("--policy", ctx.file.policy.path) + if ctx.file.test: + inputs.append(ctx.file.test) + args.add("--test", ctx.file.test.path) + + args.add("--output", ctx.outputs.out.path) + + ctx.actions.run( + inputs = inputs, + outputs = [ctx.outputs.out], + executable = ctx.executable._bundle_tool, + arguments = [args], + mnemonic = "CelPolicyBundle", + progress_message = "Bundling CEL policy test %s" % ctx.label, + ) + return [DefaultInfo(files = depset([ctx.outputs.out]))] + +cel_policy_test_bundle = rule( + doc = """Bundles CEL policy conformance tests into a single YAML file. + + Output file is a YAML file with three documents: environment, policy, and + test. + + If there is no environment file, the environment document will be omitted. + """, + implementation = _cel_policy_test_bundle_impl, + outputs = {"out": "%{name}_bundle.yaml"}, + attrs = { + "environment": attr.label(allow_single_file = [".yaml"]), + "policy": attr.label(allow_single_file = [".yaml"], mandatory = True), + "test": attr.label(allow_single_file = [".yaml"], mandatory = True), + "_bundle_tool": attr.label( + default = "//bazel:bundle", + executable = True, + cfg = "exec", + ), + }, +) diff --git a/conformance/BUILD.bazel b/conformance/BUILD.bazel index e212464..c9aa959 100644 --- a/conformance/BUILD.bazel +++ b/conformance/BUILD.bazel @@ -1,3 +1,5 @@ +load("//bazel:test_bundle.bzl", "cel_policy_test_bundle") + package( default_applicable_licenses = ["//:license"], default_testonly = True, @@ -20,3 +22,68 @@ exports_files( "//visibility:public", ], ) + +_TEST_DIRS = [ + "context_pb", + "k8s", + "limits", + "nested_rule", + "nested_rule2", + "nested_rule3", + "nested_rule4", + "nested_rule5", + "nested_rule6", + "nested_rule7", + "nested_rules_variable_shadowing", + "pb", + "required_labels", + "restricted_destinations", + "unconditional_rules", + "unnest", + "variable_type_propagation", +] + +_TEST_ERROR_DIRS_WITH_ENV = [ + "compose_conflicting_output", + "compose_conflicting_subrule", + "unreachable", +] + +_TEST_ERROR_DIRS = [ + "duplicate_variable", + "import", + "incompatible_outputs", + "syntax", + "undeclared_reference", +] + +[cel_policy_test_bundle( + name = dir_name, + environment = "testdata/%s/config.yaml" % dir_name, + policy = "testdata/%s/policy.yaml" % dir_name, + test = "testdata/%s/tests.yaml" % dir_name, +) for dir_name in _TEST_DIRS] + +[cel_policy_test_bundle( + name = "compile_error_" + dir_name, + environment = "testdata/compile_errors/%s/config.yaml" % dir_name, + policy = "testdata/compile_errors/%s/policy.yaml" % dir_name, + test = "testdata/compile_errors/%s/tests.yaml" % dir_name, +) for dir_name in _TEST_ERROR_DIRS_WITH_ENV] + +[cel_policy_test_bundle( + name = "compile_error_" + dir_name, + policy = "testdata/compile_errors/%s/policy.yaml" % dir_name, + test = "testdata/compile_errors/%s/tests.yaml" % dir_name, +) for dir_name in _TEST_ERROR_DIRS] + +filegroup( + name = "test_bundles", + srcs = [ + ":%s" % dir_name + for dir_name in _TEST_DIRS + ] + [ + ":compile_error_" + dir_name + for dir_name in _TEST_ERROR_DIRS_WITH_ENV + _TEST_ERROR_DIRS + ], +)