diff --git a/docs/image.md b/docs/image.md index e5e0cc01..9011d8ab 100644 --- a/docs/image.md +++ b/docs/image.md @@ -75,7 +75,7 @@ oci_image( | architecture | The CPU architecture which the binaries in this image are built to run on. eg: arm64, arm, amd64, s390x. See $GOARCH documentation for possible values: https://go.dev/doc/install/source#environment | String | optional | "" | | base | Label to an oci_image target to use as the base. | Label | optional | None | | cmd | A file containing a comma separated list to be used as the command & args of the container. These values act as defaults and may be replaced by any specified when creating a container. | Label | optional | None | -| entrypoint | A file containing a comma separated list to be used as the entrypoint to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container. | Label | optional | None | +| entrypoint | A file containing a comma separated list to be used as the entrypoint to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container. NOTE: Setting this attribute will reset the cmd attribute | Label | optional | None | | env | A file containing the default values for the environment variables of the container. These values act as defaults and are merged with any specified when creating a container. Entries replace the base environment variables if any of the entries has conflicting keys. To merge entries with keys specified in the base, ${KEY} or $KEY syntax may be used. | Label | optional | None | | exposed_ports | A file containing a comma separated list of exposed ports. (e.g. 2000/tcp, 3000/udp or 4000. No protocol defaults to tcp). | Label | optional | None | | labels | A file containing a dictionary of labels. Each line should be in the form name=value. | Label | optional | None | diff --git a/examples/assert.bzl b/examples/assert.bzl index f68dab94..ba4161ac 100644 --- a/examples/assert.bzl +++ b/examples/assert.bzl @@ -16,12 +16,14 @@ config_digest=$$($(JQ_BIN) -r '.config.digest | sub(":"; "/")' $$image_path/blob $(JQ_BIN) 'def pick(p): . as $$v | reduce path(p) as $$p ({{}}; setpath($$p; $$v | getpath($$p))); pick({keys})' "$$image_path/blobs/$$config_digest" > $@ """ +_DEFAULT_ = {"____I_WILL_NOT_MATCH_ANYTHING__": True} + # buildifier: disable=function-docstring-args def assert_oci_config( name, image, - entrypoint_eq = None, - cmd_eq = None, + entrypoint_eq = _DEFAULT_, + cmd_eq = _DEFAULT_, env_eq = None, exposed_ports_eq = None, volumes_eq = None, @@ -37,9 +39,9 @@ def assert_oci_config( config = {} # .config - if entrypoint_eq: + if entrypoint_eq != _DEFAULT_: config["Entrypoint"] = entrypoint_eq - if cmd_eq: + if cmd_eq != _DEFAULT_: config["Cmd"] = cmd_eq if env_eq: config["Env"] = ["=".join(e) for e in env_eq.items()] diff --git a/examples/assertion/BUILD.bazel b/examples/assertion/BUILD.bazel index 6ed05a5a..a79014c9 100644 --- a/examples/assertion/BUILD.bazel +++ b/examples/assertion/BUILD.bazel @@ -385,6 +385,55 @@ sh_test( data = [":case12"], ) +# Case 15: Setting entrypoint resets cmd +oci_image( + name = "case15_base", + architecture = "arm64", + cmd = [ + "-c", + "test", + ], + os = "linux", +) + +assert_oci_config( + name = "test_case15_base", + cmd_eq = [ + "-c", + "test", + ], + image = ":case15_base", +) + +oci_image( + name = "case15", + base = ":case15_base", + entrypoint = ["/custom_bin"], +) + +assert_oci_config( + name = "test_case15", + cmd_eq = None, # cmd should not exist + entrypoint_eq = [ + "/custom_bin", + ], + image = ":case15", +) + +oci_image( + name = "case15_cmd", + base = ":case15_base", + cmd = ["--arg"], + entrypoint = ["/custom_bin"], +) + +assert_oci_config( + name = "test_case15_cmd", + cmd_eq = ["--arg"], + entrypoint_eq = ["/custom_bin"], + image = ":case15_cmd", +) + # build them as test. build_test( name = "test", diff --git a/examples/dockerfile/BUILD.bazel b/examples/dockerfile/BUILD.bazel index 1dc00b1f..887476d4 100644 --- a/examples/dockerfile/BUILD.bazel +++ b/examples/dockerfile/BUILD.bazel @@ -40,7 +40,7 @@ oci_image( assert_oci_config( name = "assert_metadata", cmd_eq = ["/app/say.py"], - entrypoint_eq = [], + entrypoint_eq = None, image = ":image", ) @@ -50,8 +50,8 @@ assert_oci_image_command( "jq", "--version", ], - image = ":image", exit_code_eq = 0, + image = ":image", output_eq = "jq-1.6\n", ) @@ -61,8 +61,8 @@ assert_oci_image_command( "file", "/var/lib/apt/lists", ], - image = ":image", exit_code_eq = 0, + image = ":image", output_eq = "/var/lib/apt/lists: directory\n", ) @@ -72,8 +72,8 @@ assert_oci_image_command( "python", "/app/say.py", ], - image = ":image", exit_code_eq = 0, + image = ":image", output_eq = """\ ____ | moo! | diff --git a/oci/private/image.bzl b/oci/private/image.bzl index 19b58431..29ee0ec6 100644 --- a/oci/private/image.bzl +++ b/oci/private/image.bzl @@ -69,7 +69,7 @@ _attrs = { The authors recommend [dive](https://github.com/wagoodman/dive) to explore the layering of the resulting image. """), # See: https://github.com/opencontainers/image-spec/blob/main/config.md#properties - "entrypoint": attr.label(doc = "A file containing a comma separated list to be used as the `entrypoint` to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container.", allow_single_file = True), + "entrypoint": attr.label(doc = "A file containing a comma separated list to be used as the `entrypoint` to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container. NOTE: Setting this attribute will reset the `cmd` attribute", allow_single_file = True), "cmd": attr.label(doc = "A file containing a comma separated list to be used as the `command & args` of the container. These values act as defaults and may be replaced by any specified when creating a container.", allow_single_file = True), "env": attr.label(doc = """\ A file containing the default values for the environment variables of the container. These values act as defaults and are merged with any specified when creating a container. Entries replace the base environment variables if any of the entries has conflicting keys. @@ -191,10 +191,17 @@ def _oci_image_impl(ctx): # tars are already added as input above. args.add_joined([layer, descriptor], join_with = "=", format_joined = "--layer=%s") + # WARNING: entrypoint should always be added before the cmd argument. + # This due to implicit behavior which setting entrypoint deletes `cmd`. + # See: https://github.com/bazel-contrib/rules_oci/issues/649 if ctx.attr.entrypoint: args.add(ctx.file.entrypoint.path, format = "--entrypoint=%s") inputs.append(ctx.file.entrypoint) + if ctx.attr.cmd: + args.add(ctx.file.cmd.path, format = "--cmd=%s") + inputs.append(ctx.file.cmd) + if ctx.attr.exposed_ports: args.add(ctx.file.exposed_ports.path, format = "--exposed-ports=%s") inputs.append(ctx.file.exposed_ports) @@ -203,10 +210,6 @@ def _oci_image_impl(ctx): args.add(ctx.file.volumes.path, format = "--volumes=%s") inputs.append(ctx.file.volumes) - if ctx.attr.cmd: - args.add(ctx.file.cmd.path, format = "--cmd=%s") - inputs.append(ctx.file.cmd) - if ctx.attr.env: args.add(ctx.file.env.path, format = "--env=%s") inputs.append(ctx.file.env) diff --git a/oci/private/image.sh b/oci/private/image.sh index c5397399..adb60e56 100644 --- a/oci/private/image.sh +++ b/oci/private/image.sh @@ -149,7 +149,10 @@ for ARG in "$@"; do CONFIG=$(jq --rawfile cmd "${ARG#--cmd=}" '.config.Cmd = ($cmd | split(",|\n"; "") | map(select(. | length > 0)))' <<<"$CONFIG") ;; --entrypoint=*) - CONFIG=$(jq --rawfile entrypoint "${ARG#--entrypoint=}" '.config.Entrypoint = ($entrypoint | split(",|\n"; "") | map(select(. | length > 0)))' <<<"$CONFIG") + # NOTE: setting entrypoint deletes `.config.Cmd` which is consistent with crane and Dockerfile behavior. + # See: https://github.com/bazel-contrib/rules_oci/issues/649 + # See: https://github.com/google/go-containerregistry/blob/c3d1dcc932076c15b65b8b9acfff1d47ded2ebf9/cmd/crane/cmd/mutate.go#L107 + CONFIG=$(jq --rawfile entrypoint "${ARG#--entrypoint=}" '.config.Cmd = null | .config.Entrypoint = ($entrypoint | split(",|\n"; "") | map(select(. | length > 0)))' <<<"$CONFIG") ;; --exposed-ports=*) CONFIG=$(jq --rawfile ep "${ARG#--exposed-ports=}" '.config.ExposedPorts = ($ep | split(",") | map({key: ., value: {}}) | from_entries)' <<<"$CONFIG") @@ -179,6 +182,6 @@ for ARG in "$@"; do esac done -get_config | jq --argjson config "$CONFIG" '. *= $config' | update_config >/dev/null +get_config | jq --argjson config "$CONFIG" '. *= $config | del(.config.Cmd|nulls)' | update_config >/dev/null ## TODO: container structure is broken (JSON="$(coreutils cat "$OUTPUT/index.json")" && jq "del(.manifests[].annotations)" >"$OUTPUT/index.json" <<<"$JSON")