Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make oci_image() accept a file for workdir #673

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions docs/image.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions examples/assertion/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -619,3 +619,22 @@ build_test(
":case10_run",
],
)

# Case 21: accept workdir param as a target label (string input is tested in case8)
genrule(
name = "case21_workdir_target",
outs = ["case21_workdir_target.txt"],
cmd = "echo -n /workdir > $@",
)

oci_image(
name = "case21",
base = ":case8",
workdir = "case21_workdir_target",
)

assert_oci_config(
name = "test_case21",
image = ":case21",
workdir_eq = "/workdir",
)
82 changes: 69 additions & 13 deletions oci/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,73 @@ def _write_nl_seperated_file(name, kind, elems, forwarded_kwargs):
)
return label

def oci_image(name, labels = None, annotations = None, env = None, cmd = None, entrypoint = None, exposed_ports = None, volumes = None, **kwargs):

# There's no "is this a label or just a string?" check in macro land, so
# approximate it with some silly string heuristics. See
# https://bazel.build/concepts/labels for label name rules
def _is_a_workdir_label(input):
if input[0] in (":", "@") or input[:2] in ("@@", "//") or '/' not in input:
return True
return False


def test_is_a_workdir_label():
testdata = {
# Corner case: "foo" could be either a string or a target. This
# implementation chooses target. If you want a relative workdir "foo"
# use "./foo"
"foo": True,
"//foo": True,
"@@foo//bar": True,
":foo": True,
# These are all not labels
"/foo": False,
"./foo": False,
"foo/bar": False,
"../foo": False,
}

for input, expected in testdata.items():
value = _is_a_workdir_label(input)
if value != expected:
fail("_is_a_workdir_label(%s) returned %s, expected %s" % (input, value, expected))


def oci_image(name, labels = None, annotations = None, env = None, cmd = None, entrypoint = None, workdir = None, exposed_ports = None, volumes = None, **kwargs):
"""Macro wrapper around [oci_image_rule](#oci_image_rule).

Allows labels and annotations to be provided as a dictionary, in addition to a text file.
See https://github.com/opencontainers/image-spec/blob/main/annotations.md
This wrapper allows (some) parameters to be specified as a list or dict, or
as a target text file containing the contents. This accomodes rules to auto
generate some of these items.

Produces a target `[name].digest`, whose default output is a file containing the sha256 digest of the resulting image.
This is similar to the same-named target created by rules_docker's `container_image` macro.

**DICT_OR_LABEL**: `label`, `annotation`, `env`

Label/annotation/env can by configured using either dict(key->value) or a file that contains key=value pairs
Can by configured using either dict(key->value) or a file that contains key=value pairs
(one per line). The file can be preprocessed using (e.g. using `jq`) to supply external (potentially not
deterministic) information when running with `--stamp` flag. See the example in
[/examples/labels/BUILD.bazel](https://github.com/bazel-contrib/rules_oci/blob/main/examples/labels/BUILD.bazel).

Produces a target `[name].digest`, whose default output is a file containing the sha256 digest of the resulting image.
This is similar to the same-named target created by rules_docker's `container_image` macro.
**LIST_OR_LABEL**: `cmd`, `entrypoint`, `exposed_ports`, `volumes`

Can be a list of strings, or a file with newlines separating entries.

**STRING_OR_LABEL**: `workdir`

A string, or a target text file whose output contains a single line

Args:
name: name of resulting oci_image_rule
labels: Labels for the image config. See documentation above.
annotations: Annotations for the image config. See documentation above.
env: Environment variables provisioned by default to the running container. See documentation above.
cmd: Command & argument configured by default in the running container. See documentation above.
entrypoint: Entrypoint configured by default in the running container. See documentation above.
exposed_ports: Exposed ports in the running container. See documentation above.
volumes: Volumes for the container. See documentation above.
labels: `DICT_OR_LABEL` Labels for the image config.
annotations: `DICT_OR_LABEL` Annotations for the image config.
env: `DICT_OR_LABEL` Environment variables provisioned by default to the running container.
cmd: `LIST_OR_LABEL` Command & argument configured by default in the running container.
entrypoint: `LIST_OR_LABEL` Entrypoint configured by default in the running container.
workdir: `STRING_OR_LABEL` Workdir configured by default in the running container.
exposed_ports: `LIST_OR_LABEL` Exposed ports in the running container.
volumes: `LIST_OR_LABEL` Volumes for the container.
**kwargs: other named arguments to [oci_image_rule](#oci_image_rule) and
[common rule attributes](https://bazel.build/reference/be/common-definitions#common-attributes).
"""
Expand Down Expand Up @@ -107,6 +151,17 @@ def oci_image(name, labels = None, annotations = None, env = None, cmd = None, e
forwarded_kwargs = forwarded_kwargs,
)

# Support a string for convenience. Create a label on the fly.
if workdir != None and not _is_a_workdir_label(workdir):
workdir_label = "_{}_write_workdir".format(name)
write_file(
name = workdir_label,
out = "_{}.workdir.txt".format(name),
content = [workdir],
**forwarded_kwargs
)
workdir = workdir_label

if types.is_list(exposed_ports):
exposed_ports_label = "_{}_write_exposed_ports".format(name)
write_file(
Expand Down Expand Up @@ -134,6 +189,7 @@ def oci_image(name, labels = None, annotations = None, env = None, cmd = None, e
env = env,
cmd = cmd,
entrypoint = entrypoint,
workdir = workdir,
exposed_ports = exposed_ports,
volumes = volumes,
**kwargs
Expand Down
5 changes: 3 additions & 2 deletions oci/private/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ This acts as a default value to use when the value is not specified when creatin
For Linux based systems, all of the following are valid: `user`, `uid`, `user:group`, `uid:gid`, `uid:group`, `user:gid`.
If `group/gid` is not specified, the default group and supplementary groups of the given `user/uid` in `/etc/passwd` from the container are applied.
"""),
"workdir": attr.string(doc = "Sets the current working directory of the `entrypoint` process in the container. This value acts as a default and may be replaced by a working directory specified when creating a container."),
"workdir": attr.label(doc = "A file containing the path to the current working directory of the `entrypoint` process in the container. This value acts as a default and may be replaced by a working directory specified when runninng the container.", allow_single_file = True),
"exposed_ports": attr.label(doc = "A file containing a comma separated list of exposed ports. (e.g. 2000/tcp, 3000/udp or 4000. No protocol defaults to tcp).", allow_single_file = True),
"volumes": attr.label(doc = "A file containing a comma separated list of volumes. (e.g. /srv/data,/srv/other-data)", allow_single_file = True),
"os": attr.string(doc = "The name of the operating system which the image is built to run on. eg: `linux`, `windows`. See $GOOS documentation for possible values: https://go.dev/doc/install/source#environment"),
Expand Down Expand Up @@ -227,7 +227,8 @@ def _oci_image_impl(ctx):
args.add(ctx.attr.user, format = "--user=%s")

if ctx.attr.workdir:
args.add(ctx.attr.workdir, format = "--workdir=%s")
args.add(ctx.file.workdir.path, format = "--workdir=%s")
inputs.append(ctx.file.workdir)

action_env = {}

Expand Down
2 changes: 1 addition & 1 deletion oci/private/image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ for ARG in "$@"; do
CONFIG=$(jq --arg user "${ARG#--user=}" '.config.User = $user' <<<"$CONFIG")
;;
--workdir=*)
CONFIG=$(jq --arg workdir "${ARG#--workdir=}" '.config.WorkingDir = $workdir' <<<"$CONFIG")
CONFIG=$(jq --rawfile workdir "${ARG#--workdir=}" '.config.WorkingDir = $workdir' <<<"$CONFIG")
;;
--labels=*)
CONFIG=$(jq --rawfile labels "${ARG#--labels=}" '.config.Labels += ($labels | split("\n") | map(select(. | length > 0)) | map(. | split("=")) | map({key: .[0], value: .[1:] | join("=")}) | from_entries)' <<<"$CONFIG")
Expand Down
3 changes: 3 additions & 0 deletions oci/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ load("@aspect_bazel_lib//lib:diff_test.bzl", "diff_test")
load("@aspect_bazel_lib//lib:run_binary.bzl", "run_binary")
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load(":pull_tests.bzl", "parse_image_test")
load("//oci:defs.bzl", "test_is_a_workdir_label")

IMAGES_TO_TEST = {
"linux/amd64": {
Expand Down Expand Up @@ -83,3 +84,5 @@ build_test(
)

parse_image_test(name = "parse_image_test")

test_is_a_workdir_label()
Loading