fix: gate --sandbox-launcher behind gcloud beta run deploy

Merge https://github.com/google/adk-python/pull/6514

Closes #6511

PiperOrigin-RevId: 960588457
This commit is contained in:
abhiramArise
2026-08-06 17:20:11 -07:00
committed by adk-bot
parent 18e94c9803
commit 8120292dd1
4 changed files with 126 additions and 6 deletions
+12 -4
View File
@@ -665,6 +665,7 @@ def to_cloud_run(
a2a: bool = False,
trigger_sources: Optional[str] = None,
extra_gcloud_args: Optional[tuple[str, ...]] = None,
with_cloud_run_sandbox: bool = False,
) -> None:
"""Deploys an agent to Google Cloud Run.
@@ -701,6 +702,8 @@ def to_cloud_run(
artifact_service_uri: The URI of the artifact service.
memory_service_uri: The URI of the memory service.
use_local_storage: Whether to use local .adk storage in the container.
with_cloud_run_sandbox: Whether to enable the Cloud Run sandbox for code
execution.
"""
app_name = app_name or os.path.basename(agent_folder)
if parse(adk_version) >= parse('1.3.0') and not use_local_storage:
@@ -780,14 +783,18 @@ def to_cloud_run(
adk_managed_args = {'--source', '--project', '--port', '--verbosity'}
if region:
adk_managed_args.add('--region')
if with_cloud_run_sandbox:
adk_managed_args.add('--sandbox-launcher')
# Validate that extra gcloud args don't conflict with ADK-managed args
_validate_gcloud_extra_args(extra_gcloud_args, adk_managed_args)
# Build the command with extra gcloud args
gcloud_cmd = [
_GCLOUD_CMD,
'beta',
gcloud_cmd = [_GCLOUD_CMD]
if with_cloud_run_sandbox:
# --sandbox-launcher is only supported on the beta release track.
gcloud_cmd.append('beta')
gcloud_cmd += [
'run',
'deploy',
service_name,
@@ -800,8 +807,9 @@ def to_cloud_run(
str(port),
'--verbosity',
log_level.lower() if log_level else verbosity,
'--sandbox-launcher',
]
if with_cloud_run_sandbox:
gcloud_cmd.append('--sandbox-launcher')
# Handle labels specially - merge user labels with ADK label
user_labels = []
+12
View File
@@ -2315,6 +2315,16 @@ def cli_api_server(
default=False,
help="Optional. Whether to enable A2A endpoint.",
)
@click.option(
"--with_cloud_run_sandbox",
is_flag=True,
show_default=True,
default=False,
help=(
"Optional. Whether to enable the Cloud Run sandbox for code"
" execution. Requires the 'gcloud beta run deploy' release track."
),
)
# Kept as raw str (not parsed to list) — interpolated directly into Dockerfile CMD.
@click.option(
"--trigger_sources",
@@ -2359,6 +2369,7 @@ def cli_deploy_cloud_run(
use_local_storage: bool = False,
a2a: bool = False,
trigger_sources: str | None = None,
with_cloud_run_sandbox: bool = False,
):
"""Deploys an agent to Cloud Run.
@@ -2383,6 +2394,7 @@ def cli_deploy_cloud_run(
cli_deploy.to_cloud_run(
agent_folder=agent,
with_cloud_run_sandbox=with_cloud_run_sandbox,
project=project,
region=region,
service_name=service_name,
@@ -175,7 +175,6 @@ def test_to_cloud_run_happy_path(
expected_gcloud_command = [
cli_deploy._GCLOUD_CMD,
"beta",
"run",
"deploy",
"svc",
@@ -189,7 +188,6 @@ def test_to_cloud_run_happy_path(
"8080",
"--verbosity",
"info",
"--sandbox-launcher",
"--labels",
"created-by=adk",
]
@@ -276,6 +274,85 @@ def test_to_cloud_run_cleans_temp_dir_on_failure(
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_dir)
@pytest.mark.parametrize("with_cloud_run_sandbox", [True, False])
def test_to_cloud_run_with_sandbox(
monkeypatch: pytest.MonkeyPatch,
agent_dir: AgentDirFixture,
tmp_path: Path,
with_cloud_run_sandbox: bool,
) -> None:
"""Verify --sandbox-launcher and beta release track based on with_cloud_run_sandbox."""
src_dir = agent_dir(include_requirements=False, include_env=False)
run_recorder = _Recorder()
monkeypatch.setattr(subprocess, "run", run_recorder)
monkeypatch.setattr(shutil, "rmtree", lambda _x: None)
cli_deploy.to_cloud_run(
agent_folder=str(src_dir),
project="proj",
region="us-central1",
service_name="svc",
app_name="app",
temp_folder=str(tmp_path),
port=8080,
trace_to_cloud=False,
otel_to_cloud=False,
with_ui=False,
log_level="info",
verbosity="info",
adk_version="1.0.0",
with_cloud_run_sandbox=with_cloud_run_sandbox,
)
assert len(run_recorder.calls) == 1
gcloud_cmd = run_recorder.get_last_call_args()[0]
if with_cloud_run_sandbox:
# 'beta' is inserted right after the gcloud command
assert gcloud_cmd[1] == "beta"
assert gcloud_cmd[2] == "run"
assert "--sandbox-launcher" in gcloud_cmd
else:
assert gcloud_cmd[1] == "run"
assert "--sandbox-launcher" not in gcloud_cmd
assert "beta" not in gcloud_cmd
def test_to_cloud_run_sandbox_conflict(
monkeypatch: pytest.MonkeyPatch,
agent_dir: AgentDirFixture,
tmp_path: Path,
) -> None:
"""Verify that --sandbox-launcher in extra_gcloud_args raises an error when with_cloud_run_sandbox is True."""
src_dir = agent_dir(include_requirements=False, include_env=False)
run_recorder = _Recorder()
monkeypatch.setattr(subprocess, "run", run_recorder)
monkeypatch.setattr(shutil, "rmtree", lambda _x: None)
with pytest.raises(click.ClickException) as exc_info:
cli_deploy.to_cloud_run(
agent_folder=str(src_dir),
project="proj",
region="us-central1",
service_name="svc",
app_name="app",
temp_folder=str(tmp_path),
port=8080,
trace_to_cloud=False,
otel_to_cloud=False,
with_ui=False,
log_level="info",
verbosity="info",
adk_version="1.0.0",
with_cloud_run_sandbox=True,
extra_gcloud_args=("--sandbox-launcher",),
)
assert "conflicts with ADK's automatic configuration" in str(exc_info.value)
# Label merging tests
@pytest.mark.parametrize(
"extra_gcloud_args, expected_labels",
@@ -1090,6 +1090,29 @@ def test_cli_deploy_cloud_run_allows_empty_gcloud_args(
assert extra_args == ()
@pytest.mark.parametrize("with_sandbox", [True, False])
def test_cli_deploy_cloud_run_sandbox(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, with_sandbox: bool
) -> None:
"""Verify --with_cloud_run_sandbox parameter gets forwarded to to_cloud_run."""
rec = _Recorder()
monkeypatch.setattr("google.adk.cli.cli_deploy.to_cloud_run", rec)
agent_dir = tmp_path / "agent_sandbox"
agent_dir.mkdir()
runner = CliRunner()
args = ["deploy", "cloud_run", str(agent_dir)]
if with_sandbox:
args.append("--with_cloud_run_sandbox")
result = runner.invoke(
cli_tools_click.main,
args,
)
assert result.exit_code == 0
assert rec.calls, "cli_deploy.to_cloud_run must be invoked"
assert rec.calls[0][1].get("with_cloud_run_sandbox") == with_sandbox
def test_cli_deploy_cloud_run_interspersed_options(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None: