Skip to content

Commit 4549390

Browse files
authored
feat: expose a Bazel rule (#341)
* feat: expose a Bazel rule Previously we documented that users should use the container_test rule from rules_docker: https://github.com/bazelbuild/rules_docker/blob/8c3a8110a0c519929a7e79c39ac345a0f8c74d04/contrib/test.bzl#L125 However, rules_docker is no longer maintained. rules_oci means to be a replacement, but doesn't want to add a rule for a binary that is published elsewhere. It's easier to understand if this repo publishes both the binary, and the bazel rule that wraps it, so that any changes can be made to them together.
1 parent d3c64ed commit 4549390

22 files changed

+486
-48
lines changed

.github/workflows/bazel.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Bazel
2+
3+
# Controls when the action will run.
4+
on:
5+
# Triggers the workflow on push or pull request events but only for the main branch
6+
push:
7+
branches: [main]
8+
pull_request:
9+
branches: [main]
10+
11+
jobs:
12+
test:
13+
uses: bazel-contrib/.github/.github/workflows/bazel.yaml@cb7908519b9a71d7531b13cafce759eb260575d3
14+
with:
15+
folders: '["bazel/test"]'
16+
exclude_windows: true

.github/workflows/ci.bazelrc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file contains Bazel settings to apply on CI only.
2+
# It is referenced with a --bazelrc option in the call to bazel in ci.yaml
3+
4+
# Debug where options came from
5+
build --announce_rc
6+
# This directory is configured in GitHub actions to be persisted between runs.
7+
build --disk_cache=~/.cache/bazel
8+
build --repository_cache=~/.cache/bazel-repo
9+
# Don't rely on test logs being easily accessible from the test runner,
10+
# though it makes the log noisier.
11+
test --test_output=errors
12+
# Allows tests to run bazelisk-in-bazel, since this is the cache folder used
13+
test --test_env=XDG_CACHE_HOME

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ structure-test
33
structure_tests.test
44
.vscode
55
out/
6-
**/bazel-*
6+
bazel-*

BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Marker that this is the root of a Bazel package

MODULE.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"Bazel module definition, see https://bazel.build/external/overview#bzlmod"
2+
module(
3+
name = "container_structure_test",
4+
compatibility_level = 1,
5+
# Replaced dynamically when published
6+
version = "0.0.0",
7+
)
8+
9+
# To run yq
10+
bazel_dep(name = "aspect_bazel_lib", version = "1.28.0")
11+
bazel_dep(name = "bazel_skylib", version = "1.4.1")
12+
bazel_dep(name = "platforms", version = "0.0.5")
13+
14+
ext = use_extension("//:repositories.bzl", "extension")
15+
use_repo(ext, "structure_test_toolchains")
16+
17+
register_toolchains("@structure_test_toolchains//:all")

README.md

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -359,62 +359,22 @@ Does *not* support command tests.
359359

360360
### Running Structure Tests Through Bazel
361361
Structure tests can also be run through `bazel`.
362-
To do so, load the rule and its dependencies in your `WORKSPACE`:
363-
```BUILD
364-
git_repository(
365-
name = "io_bazel_rules_docker",
366-
commit = "8aeab63328a82fdb8e8eb12f677a4e5ce6b183b1",
367-
remote = "https://github.com/bazelbuild/rules_docker.git",
368-
)
369-
370-
load(
371-
"@io_bazel_rules_docker//container:container.bzl",
372-
"repositories",
373-
)
374-
repositories()
375-
376362

377-
load(
378-
"@io_bazel_rules_docker//contrib:test.bzl",
379-
"container_test",
380-
)
381-
382-
# io_bazel_rules_go is the dependency of container_test rules.
383-
http_archive(
384-
name = "io_bazel_rules_go",
385-
url = "https://github.com/bazelbuild/rules_go/releases/download/0.9.0/rules_go-0.9.0.tar.gz",
386-
sha256 = "4d8d6244320dd751590f9100cf39fd7a4b75cd901e1f3ffdfd6f048328883695",
387-
)
388-
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
389-
go_rules_dependencies()
390-
go_register_toolchains()
363+
With Bazel 6 and bzlmod, just see https://registry.bazel.build/modules/container_structure_test.
364+
Otherwise, load the rule and its dependencies in your `WORKSPACE`, see bazel/test/WORKSPACE.bazel in this repo.
391365

392-
```
366+
Load the rule definition in your BUILD file and declare a `container_structure_test` target, passing in your image and config file as parameters:
393367

394-
and then include the rule definition in your `BUILD` file:
368+
```bazel
369+
load("@container_structure_test//:defs.bzl", "container_structure_test")
395370
396-
```BUILD
397-
load("@io_bazel_rules_docker//contrib:test.bzl", "container_test")
398-
```
399-
400-
Then, create a `container_test` rule, passing in your image and config
401-
file as parameters:
402-
403-
```BUILD
404-
container_build(
405-
name = "hello",
406-
base = "//java:java8",
407-
cmd = ["/HelloJava_deploy.jar"],
408-
files = [":HelloJava_deploy.jar"],
409-
)
410-
411-
412-
container_test(
371+
container_structure_test(
413372
name = "hello_test",
414373
configs = ["testdata/hello.yaml"],
415374
image = ":hello",
416375
)
417376
```
377+
418378
### Flags:
419379
`container-structure-test test -h`
420380
```

WORKSPACE.bazel

Whitespace-only changes.

bazel/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
toolchain_type(
2+
name = "structure_test_toolchain_type",
3+
visibility = ["//visibility:public"],
4+
)

bazel/container_structure_test.bzl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"Implementation details for container_structure_test rule."
2+
3+
load("@aspect_bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path")
4+
load("@aspect_bazel_lib//lib:windows_utils.bzl", "create_windows_native_launcher_script")
5+
6+
_attrs = {
7+
"image": attr.label(
8+
allow_single_file = True,
9+
doc = "Label of an oci_image or oci_tarball target.",
10+
),
11+
"configs": attr.label_list(allow_files = True, mandatory = True),
12+
"driver": attr.string(
13+
default = "docker",
14+
# https://github.com/GoogleContainerTools/container-structure-test/blob/5e347b66fcd06325e3caac75ef7dc999f1a9b614/pkg/drivers/driver.go#L26-L28
15+
values = ["docker", "tar", "host"],
16+
doc = "See https://github.com/GoogleContainerTools/container-structure-test#running-file-tests-without-docker",
17+
),
18+
"_runfiles": attr.label(default = "@bazel_tools//tools/bash/runfiles"),
19+
"_windows_constraint": attr.label(default = "@platforms//os:windows"),
20+
}
21+
22+
CMD = """\
23+
#!/usr/bin/env bash
24+
25+
{BASH_RLOCATION_FUNCTION}
26+
27+
readonly st=$(rlocation {st_path})
28+
readonly yq=$(rlocation {yq_path})
29+
readonly image=$(rlocation {image_path})
30+
31+
# When the image points to a folder, we can read the index.json file inside
32+
if [[ -d "$image" ]]; then
33+
readonly DIGEST=$("$yq" eval '.manifests[0].digest | sub(":"; "-")' "${{image}}/index.json")
34+
fi
35+
36+
exec "$st" test {fixed_args} $@
37+
"""
38+
39+
def _structure_test_impl(ctx):
40+
fixed_args = ["--driver", ctx.attr.driver]
41+
test_bin = ctx.toolchains["@container_structure_test//bazel:structure_test_toolchain_type"].st_info.binary
42+
yq_bin = ctx.toolchains["@aspect_bazel_lib//lib:yq_toolchain_type"].yqinfo.bin
43+
44+
image_path = to_rlocation_path(ctx, ctx.file.image)
45+
# Prefer to use a tarball if we are given one, as it works with more 'driver' types.
46+
if image_path.endswith(".tar"):
47+
fixed_args.extend(["--image", "$(rlocation %s)" % image_path])
48+
else:
49+
# https://github.com/GoogleContainerTools/container-structure-test/blob/5e347b66fcd06325e3caac75ef7dc999f1a9b614/cmd/container-structure-test/app/cmd/test.go#L110
50+
if ctx.attr.driver != "docker":
51+
fail("when the 'driver' attribute is not 'docker', then the image must be a .tar file")
52+
fixed_args.extend(["--image-from-oci-layout", "$(rlocation %s)" % image_path])
53+
fixed_args.extend(["--default-image-tag", "registry.structure_test.oci.local/image:$DIGEST"])
54+
55+
for arg in ctx.files.configs:
56+
fixed_args.extend(["--config", "$(rlocation %s)" % to_rlocation_path(ctx, arg)])
57+
58+
bash_launcher = ctx.actions.declare_file("%s.sh" % ctx.label.name)
59+
ctx.actions.write(
60+
bash_launcher,
61+
content = CMD.format(
62+
BASH_RLOCATION_FUNCTION = BASH_RLOCATION_FUNCTION,
63+
st_path = to_rlocation_path(ctx, test_bin),
64+
yq_path = to_rlocation_path(ctx, yq_bin),
65+
image_path = image_path,
66+
fixed_args = " ".join(fixed_args),
67+
),
68+
is_executable = True,
69+
)
70+
71+
is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
72+
launcher = create_windows_native_launcher_script(ctx, bash_launcher) if is_windows else bash_launcher
73+
74+
runfiles = ctx.runfiles(
75+
files = ctx.files.image + ctx.files.configs + [
76+
bash_launcher, test_bin, yq_bin
77+
]).merge(ctx.attr._runfiles.default_runfiles)
78+
79+
return DefaultInfo(runfiles = runfiles, executable = launcher)
80+
81+
lib = struct(
82+
attrs = _attrs,
83+
implementation = _structure_test_impl,
84+
)

bazel/test/.bazelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Bazel options go here

0 commit comments

Comments
 (0)