diff --git a/src/google/adk/cli/cli_deploy.py b/src/google/adk/cli/cli_deploy.py index 4888b2958..3bc2f0699 100644 --- a/src/google/adk/cli/cli_deploy.py +++ b/src/google/adk/cli/cli_deploy.py @@ -131,6 +131,7 @@ def to_cloud_run( artifact_service_uri: Optional[str] = None, memory_service_uri: Optional[str] = None, a2a: bool = False, + extra_gcloud_args: Optional[tuple[str, ...]] = None, ): """Deploys an agent to Google Cloud Run. @@ -224,26 +225,48 @@ def to_cloud_run( click.echo('Deploying to Cloud Run...') region_options = ['--region', region] if region else [] project = _resolve_project(project) - subprocess.run( - [ - 'gcloud', - 'run', - 'deploy', - service_name, - '--source', - temp_folder, - '--project', - project, - *region_options, - '--port', - str(port), - '--verbosity', - log_level.lower() if log_level else verbosity, - '--labels', - 'created-by=adk', - ], - check=True, - ) + + # Build the command with extra gcloud args + gcloud_cmd = [ + 'gcloud', + 'run', + 'deploy', + service_name, + '--source', + temp_folder, + '--project', + project, + *region_options, + '--port', + str(port), + '--verbosity', + log_level.lower() if log_level else verbosity, + ] + + # Handle labels specially - merge user labels with ADK label + user_labels = [] + extra_args_without_labels = [] + + if extra_gcloud_args: + for arg in extra_gcloud_args: + if arg.startswith('--labels='): + # Extract user-provided labels + user_labels_value = arg[9:] # Remove '--labels=' prefix + user_labels.append(user_labels_value) + else: + extra_args_without_labels.append(arg) + + # Combine ADK label with user labels + all_labels = ['created-by=adk'] + all_labels.extend(user_labels) + labels_arg = ','.join(all_labels) + + gcloud_cmd.extend(['--labels', labels_arg]) + + # Add any remaining extra passthrough args + gcloud_cmd.extend(extra_args_without_labels) + + subprocess.run(gcloud_cmd, check=True) finally: click.echo(f'Cleaning up the temp folder: {temp_folder}') shutil.rmtree(temp_folder) diff --git a/src/google/adk/cli/cli_tools_click.py b/src/google/adk/cli/cli_tools_click.py index 3000edf78..3645a059b 100644 --- a/src/google/adk/cli/cli_tools_click.py +++ b/src/google/adk/cli/cli_tools_click.py @@ -858,7 +858,13 @@ def cli_api_server( server.run() -@deploy.command("cloud_run") +@deploy.command( + "cloud_run", + context_settings={ + "allow_extra_args": True, + "allow_interspersed_args": False, + }, +) @click.option( "--project", type=str, @@ -971,7 +977,9 @@ def cli_api_server( # TODO: Add eval_storage_uri option back when evals are supported in Cloud Run. @adk_services_options() @deprecated_adk_services_options() +@click.pass_context def cli_deploy_cloud_run( + ctx, agent: str, project: Optional[str], region: Optional[str], @@ -1029,6 +1037,7 @@ def cli_deploy_cloud_run( artifact_service_uri=artifact_service_uri, memory_service_uri=memory_service_uri, a2a=a2a, + extra_gcloud_args=ctx.args, ) except Exception as e: click.secho(f"Deploy failed: {e}", fg="red", err=True) diff --git a/tests/unittests/cli/test_cli_tools_click_option_mismatch.py b/tests/unittests/cli/test_cli_tools_click_option_mismatch.py index 3a01c4694..346fd421d 100644 --- a/tests/unittests/cli/test_cli_tools_click_option_mismatch.py +++ b/tests/unittests/cli/test_cli_tools_click_option_mismatch.py @@ -137,7 +137,7 @@ def test_adk_deploy_cloud_run(): cloud_run_command, cli_deploy_cloud_run.callback, "deploy cloud_run", - ignore_params={"verbose"}, + ignore_params={"verbose", "ctx"}, ) diff --git a/tests/unittests/cli/utils/test_cli_tools_click.py b/tests/unittests/cli/utils/test_cli_tools_click.py index b57097ab0..7c58e16b4 100644 --- a/tests/unittests/cli/utils/test_cli_tools_click.py +++ b/tests/unittests/cli/utils/test_cli_tools_click.py @@ -196,6 +196,49 @@ def _boom(*_a: Any, **_k: Any) -> None: # noqa: D401 assert "Deploy failed: boom" in result.output +def test_cli_deploy_cloud_run_passthrough_args( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Extra args should be passed through to the gcloud command.""" + rec = _Recorder() + monkeypatch.setattr(cli_tools_click.cli_deploy, "to_cloud_run", rec) + + agent_dir = tmp_path / "agent_passthrough" + agent_dir.mkdir() + runner = CliRunner() + result = runner.invoke( + cli_tools_click.main, + [ + "deploy", + "cloud_run", + "--project", + "test-project", + "--region", + "us-central1", + str(agent_dir), + "--labels=test-label=test", + "--memory=1Gi", + "--cpu=1", + ], + ) + # Print debug information if the test fails + if result.exit_code != 0: + print(f"Exit code: {result.exit_code}") + print(f"Output: {result.output}") + print(f"Exception: {result.exception}") + + assert result.exit_code == 0 + assert rec.calls, "cli_deploy.to_cloud_run must be invoked" + + # Check that extra_gcloud_args were passed correctly + called_kwargs = rec.calls[0][1] + extra_args = called_kwargs.get("extra_gcloud_args") + assert extra_args is not None + assert "--labels=test-label=test" in extra_args + assert "--memory=1Gi" in extra_args + assert "--cpu=1" in extra_args + + # cli deploy agent_engine def test_cli_deploy_agent_engine_success( tmp_path: Path, monkeypatch: pytest.MonkeyPatch