diff --git a/src/google/adk/cli/cli_deploy.py b/src/google/adk/cli/cli_deploy.py index 89c6d15b3a..8ef1b38e04 100644 --- a/src/google/adk/cli/cli_deploy.py +++ b/src/google/adk/cli/cli_deploy.py @@ -663,6 +663,7 @@ def to_cloud_run( memory_service_uri: Optional[str] = None, use_local_storage: bool = False, a2a: bool = False, + with_cloud_run_sandbox: bool = False, trigger_sources: Optional[str] = None, extra_gcloud_args: Optional[tuple[str, ...]] = None, ) -> None: @@ -785,8 +786,11 @@ def to_cloud_run( _validate_gcloud_extra_args(extra_gcloud_args, adk_managed_args) # Build the command with extra gcloud args - gcloud_cmd = [ - _GCLOUD_CMD, + 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, @@ -799,8 +803,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 = [] diff --git a/src/google/adk/cli/cli_tools_click.py b/src/google/adk/cli/cli_tools_click.py index 29820d0e01..2abecddc45 100644 --- a/src/google/adk/cli/cli_tools_click.py +++ b/src/google/adk/cli/cli_tools_click.py @@ -2266,6 +2266,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", @@ -2309,6 +2319,7 @@ def cli_deploy_cloud_run( memory_service_uri: Optional[str] = None, use_local_storage: bool = False, a2a: bool = False, + with_cloud_run_sandbox: bool = False, trigger_sources: str | None = None, ): """Deploys an agent to Cloud Run. @@ -2334,6 +2345,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, diff --git a/tests/unittests/cli/utils/test_cli_deploy_to_cloud_run.py b/tests/unittests/cli/utils/test_cli_deploy_to_cloud_run.py index cf11b285e7..a3e4e3be37 100644 --- a/tests/unittests/cli/utils/test_cli_deploy_to_cloud_run.py +++ b/tests/unittests/cli/utils/test_cli_deploy_to_cloud_run.py @@ -188,7 +188,6 @@ def test_to_cloud_run_happy_path( "8080", "--verbosity", "info", - "--sandbox-launcher", "--labels", "created-by=adk", ] @@ -275,6 +274,51 @@ 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 + + # Label merging tests @pytest.mark.parametrize( "extra_gcloud_args, expected_labels",