Skip to content

Commit 8e43f0d

Browse files
Jacksunweicopybara-github
authored andcommitted
fix(deploy): Add back installing requirements.txt to Dockerfile template for cloud run
PiperOrigin-RevId: 800595427
1 parent b92b288 commit 8e43f0d

File tree

3 files changed

+352
-250
lines changed

3 files changed

+352
-250
lines changed

src/google/adk/cli/cli_deploy.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
import os
1818
import shutil
1919
import subprocess
20+
from typing import Final
2021
from typing import Optional
2122

2223
import click
2324
from packaging.version import parse
2425

25-
_DOCKERFILE_TEMPLATE = """
26+
_DOCKERFILE_TEMPLATE: Final[str] = """
2627
FROM python:3.11-slim
2728
WORKDIR /app
2829
@@ -52,12 +53,16 @@
5253
5354
# Copy agent - End
5455
56+
# Install Agent Deps - Start
57+
{install_agent_deps}
58+
# Install Agent Deps - End
59+
5560
EXPOSE {port}
5661
5762
CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {allow_origins_option} {a2a_option} "/app/agents"
5863
"""
5964

60-
_AGENT_ENGINE_APP_TEMPLATE = """
65+
_AGENT_ENGINE_APP_TEMPLATE: Final[str] = """
6166
from vertexai.preview.reasoning_engines import AdkApp
6267
6368
if {is_config_agent}:
@@ -237,7 +242,7 @@ def to_cloud_run(
237242
install_agent_deps = (
238243
f'RUN pip install -r "/app/agents/{app_name}/requirements.txt"'
239244
if os.path.exists(requirements_txt_path)
240-
else ''
245+
else '# No requirements.txt found.'
241246
)
242247
click.echo('Copying agent source code completed.')
243248

tests/unittests/cli/utils/test_cli_deploy.py

Lines changed: 0 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import shutil
2323
import subprocess
2424
import sys
25-
import tempfile
2625
import types
2726
from typing import Any
2827
from typing import Callable
@@ -217,179 +216,6 @@ def test_get_service_option_by_adk_version(
217216
assert actual.rstrip() == expected.rstrip()
218217

219218

220-
@pytest.mark.parametrize("include_requirements", [True, False])
221-
@pytest.mark.parametrize("with_ui", [True, False])
222-
def test_to_cloud_run_happy_path(
223-
monkeypatch: pytest.MonkeyPatch,
224-
agent_dir: Callable[[bool, bool], Path],
225-
tmp_path: Path,
226-
include_requirements: bool,
227-
with_ui: bool,
228-
) -> None:
229-
"""
230-
End-to-end execution test for `to_cloud_run`.
231-
"""
232-
src_dir = agent_dir(include_requirements, False)
233-
run_recorder = _Recorder()
234-
235-
monkeypatch.setattr(subprocess, "run", run_recorder)
236-
rmtree_recorder = _Recorder()
237-
monkeypatch.setattr(shutil, "rmtree", rmtree_recorder)
238-
239-
cli_deploy.to_cloud_run(
240-
agent_folder=str(src_dir),
241-
project="proj",
242-
region="asia-northeast1",
243-
service_name="svc",
244-
app_name="agent",
245-
temp_folder=str(tmp_path),
246-
port=8080,
247-
trace_to_cloud=True,
248-
with_ui=with_ui,
249-
log_level="info",
250-
verbosity="info",
251-
allow_origins=["http://localhost:3000", "https://my-app.com"],
252-
session_service_uri="sqlite://",
253-
artifact_service_uri="gs://bucket",
254-
memory_service_uri="rag://",
255-
adk_version="1.3.0",
256-
)
257-
258-
agent_dest_path = tmp_path / "agents" / "agent"
259-
assert (agent_dest_path / "agent.py").is_file()
260-
assert (agent_dest_path / "__init__.py").is_file()
261-
assert (
262-
agent_dest_path / "requirements.txt"
263-
).is_file() == include_requirements
264-
265-
dockerfile_path = tmp_path / "Dockerfile"
266-
assert dockerfile_path.is_file()
267-
dockerfile_content = dockerfile_path.read_text()
268-
269-
expected_command = "web" if with_ui else "api_server"
270-
assert f"CMD adk {expected_command} --port=8080" in dockerfile_content
271-
assert "FROM python:3.11-slim" in dockerfile_content
272-
assert (
273-
'RUN adduser --disabled-password --gecos "" myuser' in dockerfile_content
274-
)
275-
assert "USER myuser" in dockerfile_content
276-
assert "ENV GOOGLE_CLOUD_PROJECT=proj" in dockerfile_content
277-
assert "ENV GOOGLE_CLOUD_LOCATION=asia-northeast1" in dockerfile_content
278-
assert "RUN pip install google-adk==1.3.0" in dockerfile_content
279-
assert "--trace_to_cloud" in dockerfile_content
280-
281-
# --- FIX IS HERE ---
282-
# This assertion is changed to reflect that the new Dockerfile template
283-
# intentionally does NOT install agent dependencies.
284-
assert "RUN pip install -r" not in dockerfile_content
285-
286-
assert (
287-
"--allow_origins=http://localhost:3000,https://my-app.com"
288-
in dockerfile_content
289-
)
290-
291-
assert len(run_recorder.calls) == 1
292-
gcloud_args = run_recorder.get_last_call_args()[0]
293-
294-
expected_gcloud_command = [
295-
"gcloud",
296-
"run",
297-
"deploy",
298-
"svc",
299-
"--source",
300-
str(tmp_path),
301-
"--project",
302-
"proj",
303-
"--region",
304-
"asia-northeast1",
305-
"--port",
306-
"8080",
307-
"--verbosity",
308-
"info",
309-
"--labels",
310-
"created-by=adk",
311-
]
312-
assert gcloud_args == expected_gcloud_command
313-
314-
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path)
315-
316-
317-
def test_to_cloud_run_cleans_temp_dir(
318-
monkeypatch: pytest.MonkeyPatch,
319-
agent_dir: Callable[[bool, bool], Path],
320-
) -> None:
321-
"""`to_cloud_run` should always delete the temporary folder on exit."""
322-
tmp_dir = Path(tempfile.mkdtemp())
323-
src_dir = agent_dir(False, False)
324-
325-
deleted: Dict[str, Path] = {}
326-
327-
def _fake_rmtree(path: str | Path, *a: Any, **k: Any) -> None:
328-
deleted["path"] = Path(path)
329-
330-
monkeypatch.setattr(shutil, "rmtree", _fake_rmtree)
331-
monkeypatch.setattr(subprocess, "run", _Recorder())
332-
333-
cli_deploy.to_cloud_run(
334-
agent_folder=str(src_dir),
335-
project="proj",
336-
region=None,
337-
service_name="svc",
338-
app_name="app",
339-
temp_folder=str(tmp_dir),
340-
port=8080,
341-
trace_to_cloud=False,
342-
with_ui=False,
343-
log_level="info",
344-
verbosity="info",
345-
adk_version="1.0.0",
346-
session_service_uri=None,
347-
artifact_service_uri=None,
348-
memory_service_uri=None,
349-
)
350-
351-
assert deleted["path"] == tmp_dir
352-
353-
354-
def test_to_cloud_run_cleans_temp_dir_on_failure(
355-
monkeypatch: pytest.MonkeyPatch,
356-
agent_dir: Callable[[bool, bool], Path],
357-
) -> None:
358-
"""`to_cloud_run` should delete the temp folder on exit, even if gcloud fails."""
359-
tmp_dir = Path(tempfile.mkdtemp())
360-
src_dir = agent_dir(False, False)
361-
362-
rmtree_recorder = _Recorder()
363-
monkeypatch.setattr(shutil, "rmtree", rmtree_recorder)
364-
monkeypatch.setattr(
365-
subprocess,
366-
"run",
367-
mock.Mock(side_effect=subprocess.CalledProcessError(1, "gcloud")),
368-
)
369-
370-
with pytest.raises(subprocess.CalledProcessError):
371-
cli_deploy.to_cloud_run(
372-
agent_folder=str(src_dir),
373-
project="proj",
374-
region="us-central1",
375-
service_name="svc",
376-
app_name="app",
377-
temp_folder=str(tmp_dir),
378-
port=8080,
379-
trace_to_cloud=False,
380-
with_ui=False,
381-
log_level="info",
382-
verbosity="info",
383-
adk_version="1.0.0",
384-
session_service_uri=None,
385-
artifact_service_uri=None,
386-
memory_service_uri=None,
387-
)
388-
389-
assert rmtree_recorder.calls, "shutil.rmtree should have been called"
390-
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_dir)
391-
392-
393219
@pytest.mark.usefixtures("mock_vertex_ai")
394220
@pytest.mark.parametrize("has_reqs", [True, False])
395221
@pytest.mark.parametrize("has_env", [True, False])
@@ -565,76 +391,3 @@ def mock_subprocess_run(*args, **kwargs):
565391

566392
# 4. Verify cleanup
567393
assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path)
568-
569-
570-
# Label merging tests
571-
@pytest.mark.parametrize(
572-
"extra_gcloud_args, expected_labels",
573-
[
574-
# No user labels - should only have default ADK label
575-
(None, "created-by=adk"),
576-
([], "created-by=adk"),
577-
# Single user label
578-
(["--labels=env=test"], "created-by=adk,env=test"),
579-
# Multiple user labels in same argument
580-
(
581-
["--labels=env=test,team=myteam"],
582-
"created-by=adk,env=test,team=myteam",
583-
),
584-
# User labels mixed with other args
585-
(
586-
["--memory=1Gi", "--labels=env=test", "--cpu=1"],
587-
"created-by=adk,env=test",
588-
),
589-
# Multiple --labels arguments
590-
(
591-
["--labels=env=test", "--labels=team=myteam"],
592-
"created-by=adk,env=test,team=myteam",
593-
),
594-
# Labels with other passthrough args
595-
(
596-
["--timeout=300", "--labels=env=prod", "--max-instances=10"],
597-
"created-by=adk,env=prod",
598-
),
599-
],
600-
)
601-
def test_cloud_run_label_merging(
602-
monkeypatch: pytest.MonkeyPatch,
603-
agent_dir: Callable[[bool, bool], Path],
604-
tmp_path: Path,
605-
extra_gcloud_args: list[str] | None,
606-
expected_labels: str,
607-
) -> None:
608-
"""Test that user labels are properly merged with the default ADK label."""
609-
src_dir = agent_dir(False, False)
610-
run_recorder = _Recorder()
611-
612-
monkeypatch.setattr(subprocess, "run", run_recorder)
613-
monkeypatch.setattr(shutil, "rmtree", lambda x: None)
614-
615-
# Execute the function under test
616-
cli_deploy.to_cloud_run(
617-
agent_folder=str(src_dir),
618-
project="test-project",
619-
region="us-central1",
620-
service_name="test-service",
621-
app_name="test-app",
622-
temp_folder=str(tmp_path),
623-
port=8080,
624-
trace_to_cloud=False,
625-
with_ui=False,
626-
log_level="info",
627-
verbosity="info",
628-
adk_version="1.0.0",
629-
extra_gcloud_args=tuple(extra_gcloud_args) if extra_gcloud_args else None,
630-
)
631-
632-
# Verify that the gcloud command was called
633-
assert len(run_recorder.calls) == 1
634-
gcloud_args = run_recorder.get_last_call_args()[0]
635-
636-
# Find the labels argument
637-
labels_idx = gcloud_args.index("--labels")
638-
actual_labels = gcloud_args[labels_idx + 1]
639-
640-
assert actual_labels == expected_labels

0 commit comments

Comments
 (0)