Skip to content

Commit 8854f66

Browse files
yeesiancopybara-github
authored andcommitted
feat: Enable building of images from ADK CLI
Co-authored-by: Yeesian Ng <ysian@google.com> PiperOrigin-RevId: 897874029
1 parent abcf14c commit 8854f66

File tree

8 files changed

+593
-106
lines changed

8 files changed

+593
-106
lines changed

src/google/adk/cli/cli_build.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Logic for the `adk build` command."""
16+
17+
from __future__ import annotations
18+
19+
import os
20+
import shutil
21+
import subprocess
22+
import tempfile
23+
from datetime import datetime
24+
from typing import Optional
25+
26+
import click
27+
from .utils import build_utils
28+
from .utils import gcp_utils
29+
30+
31+
def build_image(
32+
agent_folder: str,
33+
project: Optional[str],
34+
region: Optional[str],
35+
repository: str,
36+
image_name: Optional[str],
37+
tag: str,
38+
adk_version: str,
39+
log_level: str = "INFO",
40+
):
41+
"""Builds an agent image and pushes it to Artifact Registry.
42+
43+
Args:
44+
agent_folder: Path to the agent source code.
45+
project: GCP project ID.
46+
region: GCP region.
47+
repository: Artifact Registry repository name.
48+
image_name: Name of the image. Defaults to agent folder name.
49+
tag: Image tag.
50+
adk_version: ADK version to use in the image.
51+
log_level: Gcloud logging verbosity.
52+
"""
53+
project = gcp_utils.resolve_project(project)
54+
env_vars = {}
55+
# Attempt to read the env variables from .env in the dir (if any).
56+
env_file = os.path.join(agent_folder, '.env')
57+
if os.path.exists(env_file):
58+
from dotenv import dotenv_values
59+
60+
click.echo(f'Reading environment variables from {env_file}')
61+
env_vars = dotenv_values(env_file)
62+
if 'GOOGLE_CLOUD_PROJECT' in env_vars:
63+
env_project = env_vars.pop('GOOGLE_CLOUD_PROJECT')
64+
if env_project:
65+
if project:
66+
click.secho(
67+
'Ignoring GOOGLE_CLOUD_PROJECT in .env as `--project` was'
68+
' explicitly passed and takes precedence',
69+
fg='yellow',
70+
)
71+
else:
72+
project = env_project
73+
click.echo(f'{project=} set by GOOGLE_CLOUD_PROJECT in {env_file}')
74+
if 'GOOGLE_CLOUD_LOCATION' in env_vars:
75+
env_region = env_vars.get('GOOGLE_CLOUD_LOCATION')
76+
if env_region:
77+
if region:
78+
click.secho(
79+
'Ignoring GOOGLE_CLOUD_LOCATION in .env as `--region` was'
80+
' explicitly passed and takes precedence',
81+
fg='yellow',
82+
)
83+
else:
84+
region = env_region
85+
click.echo(f'{region=} set by GOOGLE_CLOUD_LOCATION in {env_file}')
86+
87+
app_name = os.path.basename(agent_folder.rstrip("/"))
88+
image_name = image_name or app_name
89+
90+
temp_folder = os.path.join(
91+
tempfile.gettempdir(),
92+
"adk_build_src",
93+
datetime.now().strftime("%Y%m%d_%H%M%S"),
94+
)
95+
96+
try:
97+
click.echo(f"Staging build files in {temp_folder}...")
98+
agent_src_path = os.path.join(temp_folder, "agents", app_name)
99+
shutil.copytree(agent_folder, agent_src_path)
100+
101+
requirements_txt_path = os.path.join(agent_src_path, "requirements.txt")
102+
install_agent_deps = (
103+
f'RUN pip install -r "/app/agents/{app_name}/requirements.txt"'
104+
if os.path.exists(requirements_txt_path)
105+
else "# No requirements.txt found."
106+
)
107+
108+
dockerfile_content = build_utils.DOCKERFILE_TEMPLATE.format(
109+
gcp_project_id=project,
110+
gcp_region=region,
111+
app_name=app_name,
112+
port=8080, # Default port for container images
113+
command="api_server",
114+
install_agent_deps=install_agent_deps,
115+
service_option=build_utils.get_service_option_by_adk_version(
116+
adk_version, None, None, None, False
117+
),
118+
trace_to_cloud_option="",
119+
otel_to_cloud_option="",
120+
allow_origins_option="",
121+
adk_version=adk_version,
122+
host_option="--host=0.0.0.0",
123+
a2a_option="",
124+
trigger_sources_option="",
125+
)
126+
127+
dockerfile_path = os.path.join(temp_folder, "Dockerfile")
128+
os.makedirs(temp_folder, exist_ok=True)
129+
with open(dockerfile_path, "w", encoding="utf-8") as f:
130+
f.write(dockerfile_content)
131+
132+
# image URL format: [REGION]-docker.pkg.dev/[PROJECT]/[REPOSITORY]/[IMAGE]:[TAG]
133+
full_image_url = (
134+
f"{region}-docker.pkg.dev/{project}/{repository}/{image_name}:{tag}"
135+
)
136+
137+
click.secho(f"\nBuilding image: {full_image_url}", bold=True)
138+
subprocess.run(
139+
[
140+
gcp_utils.GCLOUD_CMD,
141+
"builds",
142+
"submit",
143+
"--tag",
144+
full_image_url,
145+
"--project",
146+
project,
147+
"--verbosity",
148+
log_level.lower(),
149+
temp_folder,
150+
],
151+
check=True,
152+
)
153+
click.secho("\n✅ Image built and pushed successfully.", fg="green")
154+
155+
finally:
156+
if os.path.exists(temp_folder):
157+
shutil.rmtree(temp_folder)

0 commit comments

Comments
 (0)