diff --git a/src/python/pants/backend/docker/goals/run_image.py b/src/python/pants/backend/docker/goals/run_image.py index 3522d4d3e89..c9075681647 100644 --- a/src/python/pants/backend/docker/goals/run_image.py +++ b/src/python/pants/backend/docker/goals/run_image.py @@ -8,7 +8,11 @@ from pants.backend.docker.goals.package_image import BuiltDockerImage, DockerPackageFieldSet from pants.backend.docker.subsystems.docker_options import DockerOptions -from pants.backend.docker.target_types import DockerImageRegistriesField, DockerImageSourceField +from pants.backend.docker.target_types import ( + DockerImageRegistriesField, + DockerImageRunExtraArgsField, + DockerImageSourceField, +) from pants.backend.docker.util_rules.docker_binary import DockerBinary from pants.core.goals.package import BuiltPackage, PackageFieldSet from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior, RunRequest @@ -22,6 +26,11 @@ class DockerRunFieldSet(RunFieldSet): required_fields = (DockerImageSourceField,) run_in_sandbox_behavior = RunInSandboxBehavior.RUN_REQUEST_HERMETIC + extra_run_args: DockerImageRunExtraArgsField + + def get_run_args(self, options: DockerOptions) -> tuple[str, ...]: + return tuple(options.run_args + (self.extra_run_args.value or ())) + @rule async def docker_image_run_request( @@ -49,8 +58,13 @@ async def docker_image_run_request( Get(EnvironmentVars, EnvironmentVarsRequest(options_env_aware.env_vars)), Get(BuiltPackage, PackageFieldSet, build_request), ) + tag = cast(BuiltDockerImage, image.artifacts[0]).tags[0] - run = docker.run_image(tag, docker_run_args=options.run_args, env=env) + run = docker.run_image( + tag, + docker_run_args=field_set.get_run_args(options), + env=env, + ) return RunRequest( digest=image.digest, diff --git a/src/python/pants/backend/docker/goals/run_image_integration_test.py b/src/python/pants/backend/docker/goals/run_image_integration_test.py index 2722a6a4aa2..d69f7959014 100644 --- a/src/python/pants/backend/docker/goals/run_image_integration_test.py +++ b/src/python/pants/backend/docker/goals/run_image_integration_test.py @@ -5,6 +5,9 @@ from textwrap import dedent +import pytest + +from pants.backend.docker.target_types import DockerImageRunExtraArgsField from pants.testutil.pants_integration_test import PantsResult, run_pants, setup_tmpdir @@ -20,22 +23,37 @@ def run_pants_with_sources(sources: dict[str, str], *args: str) -> PantsResult: ) -def test_docker_run() -> None: +@pytest.mark.parametrize("override_run_args", [True, False]) +def test_docker_run(override_run_args) -> None: + """This test exercises overriding the run args at the target level. + + It works by setting a default value for an envvar in the Dockerfile with the `ENV` statement, + and then overriding that envvar with the `-e` option on the target level. + """ + + expected_value = "override" if override_run_args else "build" + docker_run_args_attribute = ( + f'{DockerImageRunExtraArgsField.alias}=["-e", "mykey=override"]' + if override_run_args + else "" + ) + sources = { - "BUILD": "docker_image(name='run-image')", + "BUILD": f"docker_image(name='run-image', {docker_run_args_attribute})", "Dockerfile": dedent( """\ FROM alpine - CMD echo "Hello from Docker image" + ENV mykey=build + CMD echo "Hello from Docker image with mykey=$mykey" """ ), } + result = run_pants_with_sources( sources, "run", "{tmpdir}:run-image", ) - print("pants stderr\n", result.stderr) - assert "Hello from Docker image\n" == result.stdout + assert f"Hello from Docker image with mykey={expected_value}\n" == result.stdout result.assert_success() diff --git a/src/python/pants/backend/docker/target_types.py b/src/python/pants/backend/docker/target_types.py index 1bfdfdd788a..276f1458a45 100644 --- a/src/python/pants/backend/docker/target_types.py +++ b/src/python/pants/backend/docker/target_types.py @@ -12,6 +12,7 @@ from typing_extensions import final from pants.backend.docker.registries import ALL_DEFAULT_REGISTRIES +from pants.backend.docker.subsystems.docker_options import DockerOptions from pants.base.build_environment import get_buildroot from pants.core.goals.package import OutputPathField from pants.core.goals.run import RestartableField @@ -559,6 +560,14 @@ class DockerImageBuildPlatformOptionField( docker_build_option = "--platform" +class DockerImageRunExtraArgsField(StringSequenceField): + alias: ClassVar[str] = "extra_run_args" + default = () + help = help_text( + lambda: f"Extra arguments to pass into the invocation of `docker run`. These are in addition to those at the `[{DockerOptions.options_scope}].run_args`" + ) + + class DockerImageTarget(Target): alias = "docker_image" core_fields = ( @@ -584,6 +593,7 @@ class DockerImageTarget(Target): DockerImageBuildImageCacheToField, DockerImageBuildImageCacheFromField, DockerImageBuildImageOutputField, + DockerImageRunExtraArgsField, OutputPathField, RestartableField, )