diff --git a/src/bentoml/_internal/container/frontend/dockerfile/templates/base.j2 b/src/bentoml/_internal/container/frontend/dockerfile/templates/base.j2 index e4ec1bbb0a9..1ec11464557 100644 --- a/src/bentoml/_internal/container/frontend/dockerfile/templates/base.j2 +++ b/src/bentoml/_internal/container/frontend/dockerfile/templates/base.j2 @@ -44,8 +44,8 @@ RUN groupadd -g $BENTO_USER_GID -o $BENTO_USER && useradd -m -u $BENTO_USER_UID {% block SETUP_BENTO_ENVARS %} {% if __options__env is not none %} {% for key, value in __options__env.items() -%} -ARG {{ key }}={{ value }} -ENV {{ key }}=${{ key }} +ARG {{ key|normalize_line }}={{ value|normalize_line|bash_quote }} +ENV {{ key|normalize_line }}=${{ key|normalize_line }} {% endfor -%} {% endif -%} diff --git a/src/bentoml/_internal/container/frontend/dockerfile/templates/base_v2.j2 b/src/bentoml/_internal/container/frontend/dockerfile/templates/base_v2.j2 index b50a8b3f87b..fc750e7b32c 100644 --- a/src/bentoml/_internal/container/frontend/dockerfile/templates/base_v2.j2 +++ b/src/bentoml/_internal/container/frontend/dockerfile/templates/base_v2.j2 @@ -67,7 +67,7 @@ ENV BENTOML_CONTAINERIZED=true {% for env in __bento_envs__ %} {% set stage = env.stage | default("all") -%} {% if stage != "runtime" -%} -ARG {{ env.name|normalize_line }}{% if env.value %}={{ env.value | bash_quote }}{% endif %} +ARG {{ env.name|normalize_line }}{% if env.value %}={{ env.value | normalize_line | bash_quote }}{% endif %} ENV {{ env.name|normalize_line }}=${{ env.name|normalize_line }} {% endif -%} diff --git a/tests/unit/_internal/container/test_generate.py b/tests/unit/_internal/container/test_generate.py index 84c53492b9a..2f9bc2020f8 100644 --- a/tests/unit/_internal/container/test_generate.py +++ b/tests/unit/_internal/container/test_generate.py @@ -1,5 +1,8 @@ from __future__ import annotations +from _bentoml_impl.docker import generate_dockerfile as generate_v2_dockerfile +from bentoml._internal.bento.bento import ImageInfo +from bentoml._internal.bento.build_config import BentoEnvSchema from bentoml._internal.bento.build_config import CondaOptions from bentoml._internal.bento.build_config import DockerOptions from bentoml._internal.container.generate import build_environment @@ -21,6 +24,51 @@ def test_build_environment_registers_normalize_line_filter() -> None: ) +def test_generate_containerfile_env_dict_collapses_newlines(tmp_path) -> None: + dockerfile = generate_containerfile( + DockerOptions( + distro="debian", + python_version="3.11", + env={"X": "a\nRUN echo PWNED\n"}, + ), + str(tmp_path), + conda=CondaOptions(), + bento_fs=tmp_path, + ) + + # the value stays on the quoted ARG line instead of injecting an instruction + assert "ARG X='a RUN echo PWNED'" in dockerfile + assert "\nRUN echo PWNED" not in dockerfile + + +def test_generate_v2_containerfile_env_collapses_newlines(tmp_path) -> None: + dockerfile = generate_v2_dockerfile( + ImageInfo(base_image="python:3.11-slim", python_version="3.11"), + tmp_path, + envs=[BentoEnvSchema(name="X", value="a\nRUN echo PWNED\n")], + command="true", + ) + + assert "ARG X='a RUN echo PWNED'" in dockerfile + assert "\nRUN echo PWNED" not in dockerfile + + +def test_docker_options_env_preserves_value_whitespace() -> None: + options = DockerOptions( + env={ + "JAVA_OPTS": "-Xmx1g -Xms512m", + "PEM": " indented value ", + "TABBED": "a\tb", + }, + ) + + assert options.env == { + "JAVA_OPTS": "-Xmx1g -Xms512m", + "PEM": " indented value ", + "TABBED": "a\tb", + } + + def test_generate_containerfile_quotes_system_packages(tmp_path) -> None: dockerfile = generate_containerfile( DockerOptions(