Skip to content

Commit ddf7dcb

Browse files
amitmodakAmit Modak
andauthored
feat: add ADK Secret Manager sample agents with global and regional support and pytest integration (#14101)
Co-authored-by: Amit Modak <amitmodak@google.com>
1 parent d4bd17f commit ddf7dcb

11 files changed

Lines changed: 504 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# ADK Secret Manager Samples
2+
3+
This directory contains samples demonstrating how to use the Agent Development Kit (ADK) with Google Cloud Secret Manager.
4+
5+
## Folders
6+
* `agent_global`: Sample for accessing secrets from a global Secret Manager instance.
7+
* `agent_regional`: Sample for accessing secrets from a regional Secret Manager endpoint.
8+
9+
## Prerequisites
10+
11+
1. **Create and activate a virtual environment**:
12+
```bash
13+
python3 -m venv .venv
14+
source .venv/bin/activate
15+
```
16+
17+
2. **Set up Application Default Credentials**:
18+
```bash
19+
gcloud auth application-default login
20+
```
21+
22+
3. **Install dependencies**:
23+
You need to install dependencies for the specific sample or test you want to run.
24+
25+
For global agent samples and tests:
26+
```bash
27+
pip install -r agent_global/requirements.txt
28+
```
29+
30+
For regional agent samples and tests:
31+
```bash
32+
pip install -r agent_regional/requirements.txt
33+
```
34+
35+
4. **Set up environment variables**:
36+
* `GOOGLE_CLOUD_PROJECT`: Your Google Cloud Project ID. (Required for both samples and tests).
37+
38+
The following environment variables are **only required when running the samples manually**. The tests will generate and use their own temporary secrets automatically.
39+
40+
* `ADK_TEST_SECRET_ID`: The ID of the secret to access.
41+
* `ADK_TEST_SECRET_VERSION` (Optional): The version of the secret (defaults to `latest`).
42+
* `GOOGLE_CLOUD_PROJECT_LOCATION` (Required for regional samples): The region where the secret is located (e.g., `us-central1`).
43+
44+
## Running the Samples
45+
46+
The samples are designed to be run from this directory (`snippets-adk`) using the `adk run` command.
47+
48+
### Global Secret Manager Agent
49+
50+
1. **Create a `.env` file** alongside `agent_global.py` in the `agent_global` directory:
51+
```env
52+
GOOGLE_GENAI_USE_VERTEXAI=1
53+
GOOGLE_CLOUD_PROJECT=your-project-id
54+
GOOGLE_CLOUD_LOCATION=your-region
55+
```
56+
*Note: Replace `your-project-id` with your GCP project ID and `your-region` with your desired region.*
57+
58+
2. **Run the agent**:
59+
```bash
60+
export GOOGLE_CLOUD_PROJECT="your-project-id"
61+
export ADK_TEST_SECRET_ID="your-secret-id"
62+
adk run agent_global
63+
```
64+
65+
### Regional Secret Manager Agent
66+
67+
1. **Create a `.env` file** alongside `agent_regional.py` in the `agent_regional` directory:
68+
```env
69+
GOOGLE_GENAI_USE_VERTEXAI=1
70+
GOOGLE_CLOUD_PROJECT=your-project-id
71+
GOOGLE_CLOUD_LOCATION=your-region
72+
```
73+
*Note: Replace `your-project-id` with your GCP project ID and `your-region` with your desired region.*
74+
75+
2. **Run the agent**:
76+
```bash
77+
export GOOGLE_CLOUD_PROJECT="your-project-id"
78+
export GOOGLE_CLOUD_PROJECT_LOCATION="your-region"
79+
export ADK_TEST_SECRET_ID="your-secret-id"
80+
adk run agent_regional
81+
```
82+
83+
## Running the Tests
84+
85+
The tests are located within the specific agent folders and run the samples using `adk run`.
86+
87+
To run tests for the global agent:
88+
```bash
89+
pip install -r agent_global/requirements.txt
90+
pip install -r agent_global/requirements-test.txt
91+
pytest agent_global/snippets_adk_test.py
92+
```
93+
94+
To run tests for the regional agent:
95+
```bash
96+
pip install -r agent_regional/requirements.txt
97+
pip install -r agent_regional/requirements-test.txt
98+
pytest agent_regional/snippets_adk_test.py
99+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2026 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
"""
16+
ADK agent for accessing secrets from global Secret Manager.
17+
"""
18+
19+
import os
20+
21+
from google.adk import Agent
22+
from google.adk.integrations.secret_manager.secret_client import SecretManagerClient
23+
24+
# Fetch secret from global Secret Manager
25+
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
26+
secret_id = os.environ.get("ADK_TEST_SECRET_ID")
27+
secret_version = os.environ.get("ADK_TEST_SECRET_VERSION", "latest")
28+
29+
if not project_id or not secret_id:
30+
raise ValueError("GOOGLE_CLOUD_PROJECT and ADK_TEST_SECRET_ID environment variables must be set.")
31+
32+
resource_name = f"projects/{project_id}/secrets/{secret_id}/versions/{secret_version}"
33+
34+
print("Fetching secret from global Secret Manager...")
35+
# Initialize Secret Manager Client (Global)
36+
client = SecretManagerClient()
37+
38+
# Fetch secret
39+
try:
40+
secret_payload = client.get_secret(resource_name)
41+
print("Successfully fetched secret.")
42+
# The secret_payload can now be used by the agent or its tools as required.
43+
except Exception as e:
44+
print(f"Error fetching secret: {e}")
45+
raise e
46+
47+
# Initialize Agent
48+
root_agent = Agent(
49+
model='gemini-2.5-flash',
50+
name='root_agent',
51+
description='A helpful assistant for user questions.',
52+
instruction='Answer user questions to the best of your knowledge',
53+
)
54+
55+
print("Agent initialized successfully.")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2020 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+
# Default TEST_CONFIG_OVERRIDE for python repos.
16+
17+
# You can copy this file into your directory, then it will be inported from
18+
# the noxfile.py.
19+
20+
# The source of truth:
21+
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py
22+
23+
TEST_CONFIG_OVERRIDE = {
24+
# You can opt out from the test for specific Python versions.
25+
"ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"],
26+
# Old samples are opted out of enforcing Python type hints
27+
# All new samples should feature them
28+
"enforce_type_hints": True,
29+
# An envvar key for determining the project id to use. Change it
30+
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
31+
# build specific Cloud project. You can also use your own string
32+
# to use your own Cloud project.
33+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
34+
# "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT",
35+
# A dictionary you want to inject into your test. Don't put any
36+
# secrets here. These values will override predefined values.
37+
"envs": {},
38+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==8.2.0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
google-adk>=1.29
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
14+
import os
15+
import subprocess
16+
import time
17+
from typing import Iterator
18+
import uuid
19+
20+
from google.api_core import exceptions
21+
from google.cloud import secretmanager
22+
import pytest
23+
24+
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
25+
PARENT_DIR = os.path.dirname(CURRENT_DIR)
26+
27+
28+
@pytest.fixture()
29+
def client() -> secretmanager.SecretManagerServiceClient:
30+
return secretmanager.SecretManagerServiceClient()
31+
32+
33+
@pytest.fixture()
34+
def project_id() -> str:
35+
return os.environ["GOOGLE_CLOUD_PROJECT"]
36+
37+
38+
@pytest.fixture()
39+
def secret_id(
40+
client: secretmanager.SecretManagerServiceClient, project_id: str
41+
) -> Iterator[str]:
42+
secret_id = f"python-adk-test-{uuid.uuid4()}"
43+
yield secret_id
44+
45+
# Teardown: delete the secret
46+
secret_path = client.secret_path(project_id, secret_id)
47+
print(f"Deleting secret {secret_id}...")
48+
try:
49+
# Wait a bit to avoid conflicts if operations are still pending
50+
time.sleep(2)
51+
client.delete_secret(request={"name": secret_path})
52+
except exceptions.NotFound:
53+
pass
54+
55+
56+
def test_agent_global(
57+
client: secretmanager.SecretManagerServiceClient, project_id: str, secret_id: str
58+
) -> None:
59+
# Create the secret
60+
parent = f"projects/{project_id}"
61+
print(f"Creating secret {secret_id}...")
62+
client.create_secret(
63+
request={
64+
"parent": parent,
65+
"secret_id": secret_id,
66+
"secret": {"replication": {"automatic": {}}},
67+
}
68+
)
69+
70+
# Add a version
71+
secret_path = client.secret_path(project_id, secret_id)
72+
print(f"Adding version to {secret_id}...")
73+
client.add_secret_version(
74+
request={
75+
"parent": secret_path,
76+
"payload": {"data": b"test-payload"},
77+
}
78+
)
79+
80+
# Set environment variables required by the agent
81+
# GOOGLE_CLOUD_PROJECT is already set in the environment (required to run this test)
82+
os.environ["ADK_TEST_SECRET_ID"] = secret_id
83+
os.environ["ADK_TEST_SECRET_VERSION"] = "latest"
84+
85+
print(f"Running adk run agent_global from {PARENT_DIR}...")
86+
# Run the sample using adk run
87+
result = subprocess.run(
88+
["adk", "run", "agent_global"],
89+
input="exit\n",
90+
capture_output=True,
91+
text=True,
92+
cwd=PARENT_DIR,
93+
)
94+
95+
print("STDOUT:")
96+
print(result.stdout)
97+
print("STDERR:")
98+
print(result.stderr)
99+
100+
assert result.returncode == 0
101+
assert "Successfully fetched secret" in result.stdout
102+
assert "Agent initialized successfully" in result.stdout
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2026 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
"""
16+
ADK agent for accessing secrets from regional Secret Manager.
17+
"""
18+
19+
import os
20+
21+
from google.adk import Agent
22+
from google.adk.integrations.secret_manager.secret_client import SecretManagerClient
23+
24+
# Fetch secret from regional Secret Manager
25+
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
26+
location = os.environ.get("GOOGLE_CLOUD_PROJECT_LOCATION")
27+
secret_id = os.environ.get("ADK_TEST_SECRET_ID")
28+
secret_version = os.environ.get("ADK_TEST_SECRET_VERSION", "latest")
29+
30+
if not project_id or not location or not secret_id:
31+
raise ValueError("GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_PROJECT_LOCATION, and ADK_TEST_SECRET_ID environment variables must be set.")
32+
33+
resource_name = f"projects/{project_id}/locations/{location}/secrets/{secret_id}/versions/{secret_version}"
34+
35+
print(f"Fetching secret from regional Secret Manager ({location})...")
36+
# Initialize Secret Manager Client (Regional)
37+
client = SecretManagerClient(location=location)
38+
39+
# Fetch secret
40+
try:
41+
secret_payload = client.get_secret(resource_name)
42+
print("Successfully fetched secret.")
43+
# The secret_payload can now be used by the agent or its tools as required.
44+
except Exception as e:
45+
print(f"Error fetching secret: {e}")
46+
raise e
47+
48+
# Initialize the agent
49+
root_agent = Agent(
50+
model='gemini-2.5-flash',
51+
name='root_agent',
52+
description='A helpful assistant for user questions.',
53+
instruction='Answer user questions to the best of your knowledge',
54+
)
55+
56+
print("Agent initialized successfully.")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2020 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+
# Default TEST_CONFIG_OVERRIDE for python repos.
16+
17+
# You can copy this file into your directory, then it will be inported from
18+
# the noxfile.py.
19+
20+
# The source of truth:
21+
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py
22+
23+
TEST_CONFIG_OVERRIDE = {
24+
# You can opt out from the test for specific Python versions.
25+
"ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"],
26+
# Old samples are opted out of enforcing Python type hints
27+
# All new samples should feature them
28+
"enforce_type_hints": True,
29+
# An envvar key for determining the project id to use. Change it
30+
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
31+
# build specific Cloud project. You can also use your own string
32+
# to use your own Cloud project.
33+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
34+
# "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT",
35+
# A dictionary you want to inject into your test. Don't put any
36+
# secrets here. These values will override predefined values.
37+
"envs": {},
38+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==8.2.0

0 commit comments

Comments
 (0)