diff --git a/src/google/adk/cli/cli_build.py b/src/google/adk/cli/cli_build.py new file mode 100644 index 0000000000..6148c3f5a6 --- /dev/null +++ b/src/google/adk/cli/cli_build.py @@ -0,0 +1,157 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Logic for the `adk build` command.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +import tempfile +from datetime import datetime +from typing import Optional + +import click +from .utils import build_utils +from .utils import gcp_utils + + +def build_image( + agent_folder: str, + project: Optional[str], + region: Optional[str], + repository: str, + image_name: Optional[str], + tag: str, + adk_version: str, + log_level: str = "INFO", +): + """Builds an agent image and pushes it to Artifact Registry. + + Args: + agent_folder: Path to the agent source code. + project: GCP project ID. + region: GCP region. + repository: Artifact Registry repository name. + image_name: Name of the image. Defaults to agent folder name. + tag: Image tag. + adk_version: ADK version to use in the image. + log_level: Gcloud logging verbosity. + """ + project = gcp_utils.resolve_project(project) + env_vars = {} + # Attempt to read the env variables from .env in the dir (if any). + env_file = os.path.join(agent_folder, '.env') + if os.path.exists(env_file): + from dotenv import dotenv_values + + click.echo(f'Reading environment variables from {env_file}') + env_vars = dotenv_values(env_file) + if 'GOOGLE_CLOUD_PROJECT' in env_vars: + env_project = env_vars.pop('GOOGLE_CLOUD_PROJECT') + if env_project: + if project: + click.secho( + 'Ignoring GOOGLE_CLOUD_PROJECT in .env as `--project` was' + ' explicitly passed and takes precedence', + fg='yellow', + ) + else: + project = env_project + click.echo(f'{project=} set by GOOGLE_CLOUD_PROJECT in {env_file}') + if 'GOOGLE_CLOUD_LOCATION' in env_vars: + env_region = env_vars.get('GOOGLE_CLOUD_LOCATION') + if env_region: + if region: + click.secho( + 'Ignoring GOOGLE_CLOUD_LOCATION in .env as `--region` was' + ' explicitly passed and takes precedence', + fg='yellow', + ) + else: + region = env_region + click.echo(f'{region=} set by GOOGLE_CLOUD_LOCATION in {env_file}') + + app_name = os.path.basename(agent_folder.rstrip("/")) + image_name = image_name or app_name + + temp_folder = os.path.join( + tempfile.gettempdir(), + "adk_build_src", + datetime.now().strftime("%Y%m%d_%H%M%S"), + ) + + try: + click.echo(f"Staging build files in {temp_folder}...") + agent_src_path = os.path.join(temp_folder, "agents", app_name) + shutil.copytree(agent_folder, agent_src_path) + + requirements_txt_path = os.path.join(agent_src_path, "requirements.txt") + install_agent_deps = ( + f'RUN pip install -r "/app/agents/{app_name}/requirements.txt"' + if os.path.exists(requirements_txt_path) + else "# No requirements.txt found." + ) + + dockerfile_content = build_utils.DOCKERFILE_TEMPLATE.format( + gcp_project_id=project, + gcp_region=region, + app_name=app_name, + port=8080, # Default port for container images + command="api_server", + install_agent_deps=install_agent_deps, + service_option=build_utils.get_service_option_by_adk_version( + adk_version, None, None, None, False + ), + trace_to_cloud_option="", + otel_to_cloud_option="", + allow_origins_option="", + adk_version=adk_version, + host_option="--host=0.0.0.0", + a2a_option="", + trigger_sources_option="", + ) + + dockerfile_path = os.path.join(temp_folder, "Dockerfile") + os.makedirs(temp_folder, exist_ok=True) + with open(dockerfile_path, "w", encoding="utf-8") as f: + f.write(dockerfile_content) + + # image URL format: [REGION]-docker.pkg.dev/[PROJECT]/[REPOSITORY]/[IMAGE]:[TAG] + full_image_url = ( + f"{region}-docker.pkg.dev/{project}/{repository}/{image_name}:{tag}" + ) + + click.secho(f"\nBuilding image: {full_image_url}", bold=True) + subprocess.run( + [ + gcp_utils.GCLOUD_CMD, + "builds", + "submit", + "--tag", + full_image_url, + "--project", + project, + "--verbosity", + log_level.lower(), + temp_folder, + ], + check=True, + ) + click.secho("\nāœ… Image built and pushed successfully.", fg="green") + + finally: + if os.path.exists(temp_folder): + shutil.rmtree(temp_folder) diff --git a/src/google/adk/cli/cli_deploy.py b/src/google/adk/cli/cli_deploy.py index e0c7f9b28c..db0bf96486 100644 --- a/src/google/adk/cli/cli_deploy.py +++ b/src/google/adk/cli/cli_deploy.py @@ -28,10 +28,9 @@ import click from packaging.version import parse +from google.adk.cli.utils import build_utils +from google.adk.cli.utils import gcp_utils -_IS_WINDOWS = os.name == 'nt' -_GCLOUD_CMD = 'gcloud.cmd' if _IS_WINDOWS else 'gcloud' -_LOCAL_STORAGE_FLAG_MIN_VERSION: Final[str] = '1.21.0' _AGENT_ENGINE_REQUIREMENT: Final[str] = ( 'google-cloud-aiplatform[adk,agent_engines]' ) @@ -63,45 +62,6 @@ def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None: f.write(_AGENT_ENGINE_REQUIREMENT + '\n') -_DOCKERFILE_TEMPLATE: Final[str] = """ -FROM python:3.11-slim -WORKDIR /app - -# Create a non-root user -RUN adduser --disabled-password --gecos "" myuser - -# Switch to the non-root user -USER myuser - -# Set up environment variables - Start -ENV PATH="/home/myuser/.local/bin:$PATH" - -ENV GOOGLE_GENAI_USE_VERTEXAI=1 -ENV GOOGLE_CLOUD_PROJECT={gcp_project_id} -ENV GOOGLE_CLOUD_LOCATION={gcp_region} - -# Set up environment variables - End - -# Install ADK - Start -RUN pip install google-adk=={adk_version} -# Install ADK - End - -# Copy agent - Start - -# Set permission -COPY --chown=myuser:myuser "agents/{app_name}/" "/app/agents/{app_name}/" - -# Copy agent - End - -# Install Agent Deps - Start -{install_agent_deps} -# Install Agent Deps - End - -EXPOSE {port} - -CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {otel_to_cloud_option} {allow_origins_option} {a2a_option} {trigger_sources_option} "/app/agents" -""" - _AGENT_ENGINE_APP_TEMPLATE: Final[str] = """ import os import vertexai @@ -409,17 +369,9 @@ def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None: def _resolve_project(project_in_option: Optional[str]) -> str: - if project_in_option: - return project_in_option - - result = subprocess.run( - [_GCLOUD_CMD, 'config', 'get-value', 'project'], - check=True, - capture_output=True, - text=True, - ) - project = result.stdout.strip() - click.echo(f'Use default project: {project}') + project = gcp_utils.resolve_project(project_in_option) + if not project_in_option: + click.echo(f'Use default project: {project}') return project @@ -585,45 +537,6 @@ def _validate_agent_import( sys.modules.pop(key, None) -def _get_service_option_by_adk_version( - adk_version: str, - session_uri: Optional[str], - artifact_uri: Optional[str], - memory_uri: Optional[str], - use_local_storage: Optional[bool] = None, -) -> str: - """Returns service option string based on adk_version.""" - parsed_version = parse(adk_version) - options: list[str] = [] - - if parsed_version >= parse('1.3.0'): - if session_uri: - options.append(f'--session_service_uri={session_uri}') - if artifact_uri: - options.append(f'--artifact_service_uri={artifact_uri}') - if memory_uri: - options.append(f'--memory_service_uri={memory_uri}') - else: - if session_uri: - options.append(f'--session_db_url={session_uri}') - if parsed_version >= parse('1.2.0') and artifact_uri: - options.append(f'--artifact_storage_uri={artifact_uri}') - - if use_local_storage is not None and parsed_version >= parse( - _LOCAL_STORAGE_FLAG_MIN_VERSION - ): - # Only valid when session/artifact URIs are unset; otherwise the CLI - # rejects the combination to avoid confusing precedence. - if session_uri is None and artifact_uri is None: - options.append(( - '--use_local_storage' - if use_local_storage - else '--no_use_local_storage' - )) - - return ' '.join(options) - - def to_cloud_run( *, agent_folder: str, @@ -719,14 +632,14 @@ def to_cloud_run( trigger_sources_option = ( f'--trigger_sources={trigger_sources}' if trigger_sources else '' ) - dockerfile_content = _DOCKERFILE_TEMPLATE.format( + dockerfile_content = build_utils.DOCKERFILE_TEMPLATE.format( gcp_project_id=project, gcp_region=region, app_name=app_name, port=port, command='web' if with_ui else 'api_server', install_agent_deps=install_agent_deps, - service_option=_get_service_option_by_adk_version( + service_option=build_utils.get_service_option_by_adk_version( adk_version, session_service_uri, artifact_service_uri, @@ -764,7 +677,7 @@ def to_cloud_run( # Build the command with extra gcloud args gcloud_cmd = [ - _GCLOUD_CMD, + gcp_utils.GCLOUD_CMD, 'run', 'deploy', service_name, @@ -828,6 +741,7 @@ def to_agent_engine( env_file: Optional[str] = None, agent_engine_config_file: Optional[str] = None, skip_agent_import_validation: bool = True, + image_uri: Optional[str] = None, ): """Deploys an agent to Vertex AI Agent Engine. @@ -895,7 +809,34 @@ def to_agent_engine( skip the pre-deployment import validation of `agent.py`. This can be useful when the local environment does not have the same dependencies as the deployment environment. + image_uri (str): Optional. The Artifact Registry Docker image URI (e.g., + us-central1-docker.pkg.dev/my-project/my-repo/my-image:tag) of the + container image to be deployed to Agent Engine. If specified, the + deployment will skip the build step and deploy the image directly to + Agent Engine, and the other source files will be ignored. """ + import vertexai + from ..utils._google_client_headers import get_tracking_headers + + if image_uri: + click.echo(f'Deploying agent engine from image: {image_uri}') + project = _resolve_project(project) + client = vertexai.Client( + project=project, + location=region, + http_options={'headers': get_tracking_headers()}, + ) + config = {'container_spec': {'image_uri': image_uri}, 'class_methods': []} + if display_name: + config['display_name'] = display_name + if description: + config['description'] = description + agent_engine = client.agent_engines.create(config=config) + click.secho( + f'āœ… Created agent engine: {agent_engine.api_resource.name}', + fg='green', + ) + return app_name = os.path.basename(agent_folder) display_name = display_name or app_name parent_folder = os.path.dirname(agent_folder) @@ -1072,10 +1013,6 @@ def to_agent_engine( # Set env_vars in agent_config to None if it is not set. agent_config['env_vars'] = agent_config.get('env_vars', env_vars) - import vertexai - - from ..utils._google_client_headers import get_tracking_headers - if project and region: click.echo('Initializing Vertex AI...') client = vertexai.Client( @@ -1261,14 +1198,14 @@ def to_gke( click.secho('\nSTEP 2: Generating deployment files...', bold=True) click.echo(' - Creating Dockerfile...') host_option = '--host=0.0.0.0' if adk_version > '0.5.0' else '' - dockerfile_content = _DOCKERFILE_TEMPLATE.format( + dockerfile_content = build_utils.DOCKERFILE_TEMPLATE.format( gcp_project_id=project, gcp_region=region, app_name=app_name, port=port, command='web' if with_ui else 'api_server', install_agent_deps=install_agent_deps, - service_option=_get_service_option_by_adk_version( + service_option=build_utils.get_service_option_by_adk_version( adk_version, session_service_uri, artifact_service_uri, diff --git a/src/google/adk/cli/cli_tools_click.py b/src/google/adk/cli/cli_tools_click.py index 07ccc15892..79fc18d6d4 100644 --- a/src/google/adk/cli/cli_tools_click.py +++ b/src/google/adk/cli/cli_tools_click.py @@ -33,6 +33,7 @@ import uvicorn from . import cli_create +from . import cli_build from . import cli_deploy from .. import version from ..agents.run_config import StreamingMode @@ -217,6 +218,12 @@ def deploy(): pass +@main.group() +def build(): + """Builds agent container images.""" + pass + + @main.group() def conformance(): """Conformance testing tools for ADK.""" @@ -1785,6 +1792,82 @@ def cli_api_server( server.run() +@build.command("image") +@click.option( + "--project", + type=str, + help=( + "Optional. Google Cloud project ID. If not specified, the project from" + " gcloud config will be used." + ), +) +@click.option( + "--region", + type=str, + help=( + "Optional. Google Cloud region. If not specified, the region from" + " gcloud config (compute/region) or 'us-central1' will be used." + ), +) +@click.option( + "--repository", + type=str, + required=True, + help="Required. Artifact Registry repository name.", +) +@click.option( + "--image_name", + type=str, + help="Optional. Name of the image. Defaults to the agent folder name.", +) +@click.option( + "--tag", + type=str, + default="latest", + show_default=True, + help="Optional. Image tag.", +) +@click.option( + "--log_level", + type=LOG_LEVELS, + default="INFO", + show_default=True, + help="Optional. The logging level for the gcloud command.", +) +@click.argument( + "agent", + type=click.Path( + exists=True, dir_okay=True, file_okay=False, resolve_path=True + ), +) +def cli_build_image( + agent: str, + project: Optional[str], + region: Optional[str], + repository: str, + image_name: Optional[str], + tag: str, + log_level: str, +): + """Builds an agent image and pushes it to Artifact Registry. + + AGENT: The path to the agent source code folder. + """ + try: + cli_build.build_image( + agent_folder=agent, + project=project, + region=region, + repository=repository, + image_name=image_name, + tag=tag, + adk_version=version.__version__, + log_level=log_level, + ) + except Exception as e: + click.secho(f"Build failed: {e}", fg="red", err=True) + + @deploy.command( "cloud_run", context_settings={ @@ -2252,14 +2335,27 @@ def cli_migrate_session( " the default; use --validate-agent-import to enable validation." ), ) +@click.option( + "--image_uri", + type=str, + default=None, + help=( + "Optional. The Artifact Registry Docker image URI (e.g.," + " us-central1-docker.pkg.dev/my-project/my-repo/my-image:tag) of the" + " container image to be deployed to Agent Engine. If specified, the" + " deployment will skip the build step and deploy the image directly to" + " Agent Engine, and the other source files will be ignored." + ), +) @click.argument( "agent", type=click.Path( exists=True, dir_okay=True, file_okay=False, resolve_path=True ), + default=None, ) def cli_deploy_agent_engine( - agent: str, + agent: Optional[str], project: Optional[str], region: Optional[str], staging_bucket: Optional[str], @@ -2278,6 +2374,7 @@ def cli_deploy_agent_engine( agent_engine_config_file: str, validate_agent_import: bool = False, skip_agent_import_validation_alias: bool = False, + image_uri: Optional[str] = None, ): """Deploys an agent to Agent Engine. @@ -2291,6 +2388,10 @@ def cli_deploy_agent_engine( # With Google Cloud Project and Region adk deploy agent_engine --project=[project] --region=[region] --display_name=[app_name] my_agent + \b + # With Google Cloud Project and Region (deploy from a container image) + adk deploy agent_engine --project=[project] --region=[region] + --display_name=[app_name] --image_uri=[image_uri] my_agent """ logging.getLogger("vertexai_genai.agentengines").setLevel(logging.INFO) try: @@ -2316,6 +2417,7 @@ def cli_deploy_agent_engine( requirements_file=requirements_file, absolutize_imports=absolutize_imports, agent_engine_config_file=agent_engine_config_file, + image_uri=image_uri, skip_agent_import_validation=not validate_agent_import, ) except Exception as e: diff --git a/src/google/adk/cli/utils/build_utils.py b/src/google/adk/cli/utils/build_utils.py new file mode 100644 index 0000000000..4af67cf84a --- /dev/null +++ b/src/google/adk/cli/utils/build_utils.py @@ -0,0 +1,102 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Shared build utilities for ADK CLI.""" + +from __future__ import annotations + +import os +from typing import Final, Optional + +from packaging.version import parse + +_LOCAL_STORAGE_FLAG_MIN_VERSION: Final[str] = '1.21.0' + +DOCKERFILE_TEMPLATE: Final[str] = """ +FROM python:3.11-slim +WORKDIR /app + +# Create a non-root user +RUN adduser --disabled-password --gecos "" myuser + +# Switch to the non-root user +USER myuser + +# Set up environment variables - Start +ENV PATH="/home/myuser/.local/bin:$PATH" + +ENV GOOGLE_GENAI_USE_VERTEXAI=1 +ENV GOOGLE_CLOUD_PROJECT={gcp_project_id} +ENV GOOGLE_CLOUD_LOCATION={gcp_region} + +# Set up environment variables - End + +# Install ADK - Start +RUN pip install google-adk=={adk_version} +# Install ADK - End + +# Copy agent - Start + +# Set permission +COPY --chown=myuser:myuser "agents/{app_name}/" "/app/agents/{app_name}/" + +# Copy agent - End + +# Install Agent Deps - Start +{install_agent_deps} +# Install Agent Deps - End + +EXPOSE {port} + +CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {otel_to_cloud_option} {allow_origins_option} {a2a_option} {trigger_sources_option} "/app/agents" +""" + + +def get_service_option_by_adk_version( + adk_version: str, + session_uri: Optional[str], + artifact_uri: Optional[str], + memory_uri: Optional[str], + use_local_storage: Optional[bool] = None, +) -> str: + """Returns service option string based on adk_version.""" + parsed_version = parse(adk_version) + options: list[str] = [] + + if parsed_version >= parse('1.3.0'): + if session_uri: + options.append(f'--session_service_uri={session_uri}') + if artifact_uri: + options.append(f'--artifact_service_uri={artifact_uri}') + if memory_uri: + options.append(f'--memory_service_uri={memory_uri}') + else: + if session_uri: + options.append(f'--session_db_url={session_uri}') + if parsed_version >= parse('1.2.0') and artifact_uri: + options.append(f'--artifact_storage_uri={artifact_uri}') + + if use_local_storage is not None and parsed_version >= parse( + _LOCAL_STORAGE_FLAG_MIN_VERSION + ): + # Only valid when session/artifact URIs are unset; otherwise the CLI + # rejects the combination to avoid confusing precedence. + if session_uri is None and artifact_uri is None: + options.append(( + '--use_local_storage' + if use_local_storage + else '--no_use_local_storage' + )) + + return ' '.join(options) diff --git a/src/google/adk/cli/utils/gcp_utils.py b/src/google/adk/cli/utils/gcp_utils.py index 70f603e4e9..2d9bf06f60 100644 --- a/src/google/adk/cli/utils/gcp_utils.py +++ b/src/google/adk/cli/utils/gcp_utils.py @@ -16,6 +16,7 @@ from __future__ import annotations +import os import subprocess from typing import Any from typing import Dict @@ -171,3 +172,29 @@ def list_gcp_projects(limit: int = 20) -> List[Tuple[str, str]]: return projects except Exception: return [] + + +IS_WINDOWS = os.name == "nt" +GCLOUD_CMD = "gcloud.cmd" if IS_WINDOWS else "gcloud" + + +def resolve_project(project_in_option: Optional[str]) -> str: + """Resolves the GCP project ID. + + Args: + project_in_option: The project ID provided in a CLI option. + + Returns: + The resolved project ID. + """ + if project_in_option: + return project_in_option + + result = subprocess.run( + [GCLOUD_CMD, "config", "get-value", "project"], + check=True, + capture_output=True, + text=True, + ) + project = result.stdout.strip() + return project diff --git a/tests/unittests/cli/utils/test_cli_build.py b/tests/unittests/cli/utils/test_cli_build.py new file mode 100644 index 0000000000..dc4e5f297c --- /dev/null +++ b/tests/unittests/cli/utils/test_cli_build.py @@ -0,0 +1,129 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for adk build command.""" + +from __future__ import annotations + +from pathlib import Path +from unittest import mock + +import click +from click.testing import CliRunner +from google.adk.cli import cli_build +from google.adk.cli import cli_tools_click +import pytest + + +@pytest.fixture(autouse=True) +def _mute_click(monkeypatch: pytest.MonkeyPatch) -> None: + """Suppress click output during tests.""" + monkeypatch.setattr(click, "echo", lambda *a, **k: None) + + +def test_cli_build_invokes_build_image( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """adk build image should forward arguments to cli_build.build_image.""" + mock_run = mock.Mock() + monkeypatch.setattr(cli_tools_click.cli_build, "build_image", mock_run) + + agent_dir = tmp_path / "my_agent" + agent_dir.mkdir() + + runner = CliRunner() + result = runner.invoke( + cli_tools_click.main, + [ + "build", + "image", + "--project", + "my-project", + "--region", + "us-central1", + "--repository", + "my-repo", + "--image_name", + "my-image", + "--tag", + "v1", + str(agent_dir), + ], + ) + + assert result.exit_code == 0 + mock_run.assert_called_once() + kwargs = mock_run.call_args.kwargs + assert kwargs["project"] == "my-project" + assert kwargs["region"] == "us-central1" + assert kwargs["repository"] == "my-repo" + assert kwargs["image_name"] == "my-image" + assert kwargs["tag"] == "v1" + assert kwargs["agent_folder"] == str(agent_dir) + + +def test_cli_build_directory_failure( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Exception from build_image should be caught and surfaced.""" + + def _boom(*args, **kwargs): + raise RuntimeError("build error") + + monkeypatch.setattr(cli_tools_click.cli_build, "build_image", _boom) + + agent_dir = tmp_path / "my_agent_fail" + agent_dir.mkdir() + + runner = CliRunner() + result = runner.invoke( + cli_tools_click.main, + ["build", "image", "--repository", "repo", str(agent_dir)], + ) + + assert result.exit_code == 0 + assert "Build failed: build error" in result.output + + +def test_build_image_success(tmp_path, monkeypatch): + """build_image should construct and run the correct gcloud command.""" + mock_subprocess = mock.Mock() + monkeypatch.setattr(cli_build.subprocess, "run", mock_subprocess) + + monkeypatch.setattr( + cli_build.gcp_utils, "resolve_project", lambda x: "test-project" + ) + + agent_dir = tmp_path / "my_agent" + agent_dir.mkdir() + (agent_dir / "agent.py").touch() + + cli_build.build_image( + agent_folder=str(agent_dir), + project="test-project", + region="us-east1", + repository="test-repo", + image_name="test-image", + tag="latest", + adk_version="1.3.0", + ) + + mock_subprocess.assert_called() + args = mock_subprocess.call_args[0][0] + assert "builds" in args + assert "submit" in args + assert "--tag" in args + assert ( + "us-east1-docker.pkg.dev/test-project/test-repo/test-image:latest" in args + ) diff --git a/tests/unittests/cli/utils/test_cli_deploy.py b/tests/unittests/cli/utils/test_cli_deploy.py index ed1aa8cc60..07ca5a0c81 100644 --- a/tests/unittests/cli/utils/test_cli_deploy.py +++ b/tests/unittests/cli/utils/test_cli_deploy.py @@ -32,7 +32,8 @@ import click import pytest -import src.google.adk.cli.cli_deploy as cli_deploy +from src.google.adk.cli import cli_deploy +from src.google.adk.cli.utils import build_utils # Helpers @@ -217,7 +218,7 @@ def test_get_service_option_by_adk_version( expected: str, ) -> None: """It should return the correct service URI flags for a given ADK version.""" - actual = cli_deploy._get_service_option_by_adk_version( + actual = build_utils.get_service_option_by_adk_version( adk_version=adk_version, session_uri=session_uri, artifact_uri=artifact_uri, 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 eae87889e6..e0a59e78c0 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 @@ -30,7 +30,8 @@ import click import pytest -import src.google.adk.cli.cli_deploy as cli_deploy +from src.google.adk.cli import cli_deploy +from src.google.adk.cli.utils import gcp_utils class AgentDirFixture(Protocol): @@ -174,7 +175,7 @@ def test_to_cloud_run_happy_path( gcloud_args = run_recorder.get_last_call_args()[0] expected_gcloud_command = [ - cli_deploy._GCLOUD_CMD, + gcp_utils.GCLOUD_CMD, "run", "deploy", "svc",