From 9c50a4a1705bbeef2697573302b690c26228508f Mon Sep 17 00:00:00 2001 From: Sahin Yort Date: Wed, 7 Aug 2024 08:53:34 -0700 Subject: [PATCH] fix: comma in entrypoint and cmd (#663) --- docs/image.md | 4 +- examples/assertion/BUILD.bazel | 88 ++++++++++++++++++++++++++++++++++ oci/defs.bzl | 35 ++++++++------ oci/private/image.bzl | 4 +- oci/private/image.sh | 4 +- 5 files changed, 115 insertions(+), 20 deletions(-) diff --git a/docs/image.md b/docs/image.md index 9011d8ab..9b7a03c3 100644 --- a/docs/image.md +++ b/docs/image.md @@ -74,8 +74,8 @@ oci_image( | annotations | A file containing a dictionary of annotations. Each line should be in the form name=value. | Label | optional | None | | 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. NOTE: Setting this attribute will reset the cmd attribute | Label | optional | None | +| cmd | A file containing a newline 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 newline 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/assertion/BUILD.bazel b/examples/assertion/BUILD.bazel index 0e43a186..b8135340 100644 --- a/examples/assertion/BUILD.bazel +++ b/examples/assertion/BUILD.bazel @@ -519,6 +519,94 @@ assert_oci_config( image = ":case16", ) +# Case 17: an entrypoint and cmd with , in it +oci_image( + name = "case17", + architecture = "arm64", + cmd = [ + '--permitted-system-keys="MY_HOST_V1,MY_HOST_V2"', + ], + entrypoint = [ + "/docker-entrypoint.sh", + '--permitted-system-keys="MY_HOST_V1,MY_HOST_V2"', + ], + os = "linux", +) + +assert_oci_config( + name = "test_case17", + cmd_eq = [ + '--permitted-system-keys="MY_HOST_V1,MY_HOST_V2"', + ], + entrypoint_eq = [ + "/docker-entrypoint.sh", + '--permitted-system-keys="MY_HOST_V1,MY_HOST_V2"', + ], + image = ":case17", +) + +# Case 18: an entrypoint and with \n in it +oci_image( + name = "case18", + architecture = "arm64", + cmd = ['--permitted-system-keys="MY_HOST_V1\nMY_HOST_V2"'], + entrypoint = ["/docker-entrypoint.sh"], + os = "linux", +) + +assert_oci_config( + name = "test_case18", + cmd_eq = ['--permitted-system-keys="MY_HOST_V1\nMY_HOST_V2"'], + entrypoint_eq = ["/docker-entrypoint.sh"], + image = ":case18", +) + +# Case 19: an entrypoint with escaped \n in it +oci_image( + name = "case19", + architecture = "arm64", + entrypoint = [ + "/docker-entrypoint.sh", + '--permitted-system-keys="MY_HOST_V1\\nMY_HOST_V2"', + ], + os = "linux", +) + +assert_oci_config( + name = "test_case19", + entrypoint_eq = [ + "/docker-entrypoint.sh", + '--permitted-system-keys="MY_HOST_V1\\nMY_HOST_V2"', + ], + image = ":case19", +) + +# Case 20: an entrypoint with \t and \n in it +case20_shell = ''' +#!/usr/bin/env bash +set -o pipefail -o errexit -o nounset +string="\t\t\n\n\\n" +''' + +oci_image( + name = "case20", + architecture = "arm64", + entrypoint = [ + "bash", + case20_shell, + ], + os = "linux", +) + +assert_oci_config( + name = "test_case20", + entrypoint_eq = [ + "bash", + case20_shell, + ], + image = ":case20", +) + # build them as test. build_test( name = "test", diff --git a/oci/defs.bzl b/oci/defs.bzl index 49467bcc..bee35038 100644 --- a/oci/defs.bzl +++ b/oci/defs.bzl @@ -22,6 +22,17 @@ oci_image_rule = _oci_image oci_image_index = _oci_image_index oci_push_rule = _oci_push +def _write_nl_seperated_file(name, kind, elems, forwarded_kwargs): + label = "_{}_write_{}".format(name, kind) + write_file( + name = label, + out = "_{}.{}.txt".format(name, kind), + # %5Cn is the uri escaped newline + content = [elem.replace("\n", "%5Cn") for elem in 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): """Macro wrapper around [oci_image_rule](#oci_image_rule). @@ -81,24 +92,20 @@ def oci_image(name, labels = None, annotations = None, env = None, cmd = None, e env = env_label if types.is_list(cmd): - cmd_label = "_{}_write_cmd".format(name) - write_file( - name = cmd_label, - out = "_{}.cmd.txt".format(name), - content = [",".join(cmd)], - **forwarded_kwargs + cmd = _write_nl_seperated_file( + name = name, + kind = "cmd", + elems = cmd, + forwarded_kwargs = forwarded_kwargs, ) - cmd = cmd_label if types.is_list(entrypoint): - entrypoint_label = "_{}_write_entrypoint".format(name) - write_file( - name = entrypoint_label, - out = "_{}.entrypoint.txt".format(name), - content = [",".join(entrypoint)], - **forwarded_kwargs + entrypoint = _write_nl_seperated_file( + name = name, + kind = "entrypoint", + elems = entrypoint, + forwarded_kwargs = forwarded_kwargs, ) - entrypoint = entrypoint_label if types.is_list(exposed_ports): exposed_ports_label = "_{}_write_exposed_ports".format(name) diff --git a/oci/private/image.bzl b/oci/private/image.bzl index 949427f3..99558e91 100644 --- a/oci/private/image.bzl +++ b/oci/private/image.bzl @@ -69,8 +69,8 @@ _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. 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), + "entrypoint": attr.label(doc = "A file containing a newline 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 newline 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. To merge entries with keys specified in the base, `${KEY}` or `$KEY` syntax may be used. diff --git a/oci/private/image.sh b/oci/private/image.sh index 78c637f4..13f1a480 100644 --- a/oci/private/image.sh +++ b/oci/private/image.sh @@ -146,13 +146,13 @@ for ARG in "$@"; do CONFIG=$(jq --argjson envs "${env}" '.config.Env = ($envs | map("\(.key)=\(.value)"))' <<<"$CONFIG") ;; --cmd=*) - CONFIG=$(jq --rawfile cmd "${ARG#--cmd=}" '.config.Cmd = ($cmd | split(",|\n"; "") | map(select(. | length > 0)))' <<<"$CONFIG") + CONFIG=$(jq --rawfile cmd "${ARG#--cmd=}" '.config.Cmd = ($cmd | split("\n") | map(select(. | length > 0)) | map(. | sub("%5Cn"; "\n"; "g")))' <<<"$CONFIG") ;; --entrypoint=*) # 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") + CONFIG=$(jq --rawfile entrypoint "${ARG#--entrypoint=}" '.config.Cmd = null | .config.Entrypoint = ($entrypoint | split("\n") | map(select(. | length > 0)) | map(. | sub("%5Cn"; "\n"; "g")))' <<<"$CONFIG") ;; --exposed-ports=*) CONFIG=$(jq --rawfile ep "${ARG#--exposed-ports=}" '.config.ExposedPorts = ($ep | split(",") | map({key: ., value: {}}) | from_entries)' <<<"$CONFIG")