fix(deploy): Add back installing requirements.txt to Dockerfile template for cloud run

PiperOrigin-RevId: 800595427
This commit is contained in:
Wei Sun (Jack)
2025-08-28 13:43:03 -07:00
committed by Copybara-Service
parent b92b288c97
commit 8e43f0dd83
3 changed files with 352 additions and 250 deletions
@@ -22,7 +22,6 @@ from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import types
from typing import Any
from typing import Callable
@@ -217,179 +216,6 @@ def test_get_service_option_by_adk_version(
assert actual.rstrip() == expected.rstrip()
@pytest.mark.parametrize("include_requirements", [True, False])
@pytest.mark.parametrize("with_ui", [True, False])
def test_to_cloud_run_happy_path(
monkeypatch: pytest.MonkeyPatch,
agent_dir: Callable[[bool, bool], Path],
tmp_path: Path,
include_requirements: bool,
with_ui: bool,
) -> None:
"""
End-to-end execution test for `to_cloud_run`.
"""
src_dir = agent_dir(include_requirements, False)
run_recorder = _Recorder()
monkeypatch.setattr(subprocess, "run", run_recorder)
rmtree_recorder = _Recorder()
monkeypatch.setattr(shutil, "rmtree", rmtree_recorder)
cli_deploy.to_cloud_run(
agent_folder=str(src_dir),
project="proj",
region="asia-northeast1",
service_name="svc",
app_name="agent",
temp_folder=str(tmp_path),
port=8080,
trace_to_cloud=True,
with_ui=with_ui,
log_level="info",
verbosity="info",
allow_origins=["http://localhost:3000", "https://my-app.com"],
session_service_uri="sqlite://",
artifact_service_uri="gs://bucket",
memory_service_uri="rag://",
adk_version="1.3.0",
)
agent_dest_path = tmp_path / "agents" / "agent"
assert (agent_dest_path / "agent.py").is_file()
assert (agent_dest_path / "__init__.py").is_file()
assert (
agent_dest_path / "requirements.txt"
).is_file() == include_requirements
dockerfile_path = tmp_path / "Dockerfile"
assert dockerfile_path.is_file()
dockerfile_content = dockerfile_path.read_text()
expected_command = "web" if with_ui else "api_server"
assert f"CMD adk {expected_command} --port=8080" in dockerfile_content
assert "FROM python:3.11-slim" in dockerfile_content
assert (
'RUN adduser --disabled-password --gecos "" myuser' in dockerfile_content
)
assert "USER myuser" in dockerfile_content
assert "ENV GOOGLE_CLOUD_PROJECT=proj" in dockerfile_content
assert "ENV GOOGLE_CLOUD_LOCATION=asia-northeast1" in dockerfile_content
assert "RUN pip install google-adk==1.3.0" in dockerfile_content
assert "--trace_to_cloud" in dockerfile_content
# --- FIX IS HERE ---
# This assertion is changed to reflect that the new Dockerfile template
# intentionally does NOT install agent dependencies.
assert "RUN pip install -r" not in dockerfile_content
assert (
"--allow_origins=http://localhost:3000,https://my-app.com"
in dockerfile_content
)
assert len(run_recorder.calls) == 1
gcloud_args = run_recorder.get_last_call_args()[0]
expected_gcloud_command = [
"gcloud",
"run",
"deploy",
"svc",
"--source",
str(tmp_path),
"--project",
"proj",
"--region",
"asia-northeast1",
"--port",
"8080",
"--verbosity",
"info",
"--labels",
"created-by=adk",
]
assert gcloud_args == expected_gcloud_command
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path)
def test_to_cloud_run_cleans_temp_dir(
monkeypatch: pytest.MonkeyPatch,
agent_dir: Callable[[bool, bool], Path],
) -> None:
"""`to_cloud_run` should always delete the temporary folder on exit."""
tmp_dir = Path(tempfile.mkdtemp())
src_dir = agent_dir(False, False)
deleted: Dict[str, Path] = {}
def _fake_rmtree(path: str | Path, *a: Any, **k: Any) -> None:
deleted["path"] = Path(path)
monkeypatch.setattr(shutil, "rmtree", _fake_rmtree)
monkeypatch.setattr(subprocess, "run", _Recorder())
cli_deploy.to_cloud_run(
agent_folder=str(src_dir),
project="proj",
region=None,
service_name="svc",
app_name="app",
temp_folder=str(tmp_dir),
port=8080,
trace_to_cloud=False,
with_ui=False,
log_level="info",
verbosity="info",
adk_version="1.0.0",
session_service_uri=None,
artifact_service_uri=None,
memory_service_uri=None,
)
assert deleted["path"] == tmp_dir
def test_to_cloud_run_cleans_temp_dir_on_failure(
monkeypatch: pytest.MonkeyPatch,
agent_dir: Callable[[bool, bool], Path],
) -> None:
"""`to_cloud_run` should delete the temp folder on exit, even if gcloud fails."""
tmp_dir = Path(tempfile.mkdtemp())
src_dir = agent_dir(False, False)
rmtree_recorder = _Recorder()
monkeypatch.setattr(shutil, "rmtree", rmtree_recorder)
monkeypatch.setattr(
subprocess,
"run",
mock.Mock(side_effect=subprocess.CalledProcessError(1, "gcloud")),
)
with pytest.raises(subprocess.CalledProcessError):
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_dir),
port=8080,
trace_to_cloud=False,
with_ui=False,
log_level="info",
verbosity="info",
adk_version="1.0.0",
session_service_uri=None,
artifact_service_uri=None,
memory_service_uri=None,
)
assert rmtree_recorder.calls, "shutil.rmtree should have been called"
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_dir)
@pytest.mark.usefixtures("mock_vertex_ai")
@pytest.mark.parametrize("has_reqs", [True, False])
@pytest.mark.parametrize("has_env", [True, False])
@@ -565,76 +391,3 @@ def test_to_gke_happy_path(
# 4. Verify cleanup
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path)
# Label merging tests
@pytest.mark.parametrize(
"extra_gcloud_args, expected_labels",
[
# No user labels - should only have default ADK label
(None, "created-by=adk"),
([], "created-by=adk"),
# Single user label
(["--labels=env=test"], "created-by=adk,env=test"),
# Multiple user labels in same argument
(
["--labels=env=test,team=myteam"],
"created-by=adk,env=test,team=myteam",
),
# User labels mixed with other args
(
["--memory=1Gi", "--labels=env=test", "--cpu=1"],
"created-by=adk,env=test",
),
# Multiple --labels arguments
(
["--labels=env=test", "--labels=team=myteam"],
"created-by=adk,env=test,team=myteam",
),
# Labels with other passthrough args
(
["--timeout=300", "--labels=env=prod", "--max-instances=10"],
"created-by=adk,env=prod",
),
],
)
def test_cloud_run_label_merging(
monkeypatch: pytest.MonkeyPatch,
agent_dir: Callable[[bool, bool], Path],
tmp_path: Path,
extra_gcloud_args: list[str] | None,
expected_labels: str,
) -> None:
"""Test that user labels are properly merged with the default ADK label."""
src_dir = agent_dir(False, False)
run_recorder = _Recorder()
monkeypatch.setattr(subprocess, "run", run_recorder)
monkeypatch.setattr(shutil, "rmtree", lambda x: None)
# Execute the function under test
cli_deploy.to_cloud_run(
agent_folder=str(src_dir),
project="test-project",
region="us-central1",
service_name="test-service",
app_name="test-app",
temp_folder=str(tmp_path),
port=8080,
trace_to_cloud=False,
with_ui=False,
log_level="info",
verbosity="info",
adk_version="1.0.0",
extra_gcloud_args=tuple(extra_gcloud_args) if extra_gcloud_args else None,
)
# Verify that the gcloud command was called
assert len(run_recorder.calls) == 1
gcloud_args = run_recorder.get_last_call_args()[0]
# Find the labels argument
labels_idx = gcloud_args.index("--labels")
actual_labels = gcloud_args[labels_idx + 1]
assert actual_labels == expected_labels