Skip to content

Commit f816789

Browse files
test: auto-provision IAM role and Lambda for integration tests
1 parent 8ce79b1 commit f816789

1 file changed

Lines changed: 150 additions & 24 deletions

File tree

tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,23 @@
11
"""Integration tests for AgentCoreToolSearchPlugin.
22
3-
Requires environment variables:
3+
If GATEWAY_ROLE_ARN and GATEWAY_LAMBDA_ARN are set, uses those directly.
4+
Otherwise, automatically provisions the IAM role and Lambda function,
5+
and tears them down after the test run.
6+
7+
Environment variables (all optional):
48
BEDROCK_TEST_REGION: AWS region (default: us-west-2)
59
GATEWAY_ROLE_ARN: IAM role ARN with AgentCore gateway trust policy
6-
GATEWAY_LAMBDA_ARN: Lambda ARN for the gateway target (must implement MCP tool handler)
7-
8-
Prerequisites:
9-
1. Deploy the Lambda in tests_integ/gateway/integrations/lambda_function/lambda_function.py
10-
(Python 3.10+ runtime, handler: lambda_function.lambda_handler)
11-
12-
2. The GATEWAY_ROLE_ARN must have:
13-
- Trust policy for bedrock-agentcore.amazonaws.com
14-
- lambda:InvokeFunction permission on the GATEWAY_LAMBDA_ARN
15-
16-
Example inline policy:
17-
{
18-
"Version": "2012-10-17",
19-
"Statement": [{
20-
"Effect": "Allow",
21-
"Action": "lambda:InvokeFunction",
22-
"Resource": "<GATEWAY_LAMBDA_ARN>"
23-
}]
24-
}
25-
26-
3. Install mcp-proxy-for-aws: uv pip install mcp-proxy-for-aws
27-
10+
GATEWAY_LAMBDA_ARN: Lambda ARN for the gateway target
2811
"""
2912

13+
import io
14+
import json
3015
import logging
3116
import os
3217
import time
18+
import zipfile
3319

20+
import boto3
3421
import pytest
3522

3623
from bedrock_agentcore.gateway.client import GatewayClient
@@ -43,6 +30,109 @@
4330
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
4431
logger = logging.getLogger(__name__)
4532

33+
# Infrastructure constants
34+
_ROLE_NAME = "integ-test-gateway-role"
35+
_LAMBDA_NAME = "integ-test-lambda"
36+
_LAMBDA_HANDLER = "lambda_function.lambda_handler"
37+
_LAMBDA_RUNTIME = "python3.10"
38+
39+
_TRUST_POLICY = {
40+
"Version": "2012-10-17",
41+
"Statement": [
42+
{
43+
"Effect": "Allow",
44+
"Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
45+
"Action": "sts:AssumeRole",
46+
},
47+
{
48+
"Effect": "Allow",
49+
"Principal": {"Service": "lambda.amazonaws.com"},
50+
"Action": "sts:AssumeRole",
51+
},
52+
],
53+
}
54+
55+
_LAMBDA_INVOKE_POLICY_TEMPLATE = {
56+
"Version": "2012-10-17",
57+
"Statement": [
58+
{
59+
"Effect": "Allow",
60+
"Action": "lambda:InvokeFunction",
61+
"Resource": None, # filled in after Lambda creation
62+
}
63+
],
64+
}
65+
66+
67+
def _get_lambda_zip() -> bytes:
68+
"""Package lambda_function.py into a zip archive."""
69+
lambda_path = os.path.join(os.path.dirname(__file__), "lambda_function", "lambda_function.py")
70+
buf = io.BytesIO()
71+
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
72+
zf.write(lambda_path, "lambda_function.py")
73+
return buf.getvalue()
74+
75+
76+
def _ensure_role(iam_client) -> str:
77+
"""Create the gateway IAM role if it doesn't exist, return its ARN."""
78+
try:
79+
response = iam_client.get_role(RoleName=_ROLE_NAME)
80+
return response["Role"]["Arn"]
81+
except iam_client.exceptions.NoSuchEntityException:
82+
pass
83+
84+
response = iam_client.create_role(
85+
RoleName=_ROLE_NAME,
86+
AssumeRolePolicyDocument=json.dumps(_TRUST_POLICY),
87+
Description="Integration test role for AgentCore gateway tests",
88+
)
89+
role_arn = response["Role"]["Arn"]
90+
91+
iam_client.attach_role_policy(
92+
RoleName=_ROLE_NAME,
93+
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
94+
)
95+
# Wait for IAM propagation
96+
time.sleep(10)
97+
logger.info("Created role: %s", role_arn)
98+
return role_arn
99+
100+
101+
def _attach_lambda_invoke_policy(iam_client, lambda_arn: str):
102+
"""Attach a scoped lambda:InvokeFunction policy to the gateway role."""
103+
policy = _LAMBDA_INVOKE_POLICY_TEMPLATE.copy()
104+
policy["Statement"] = [{"Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": lambda_arn}]
105+
iam_client.put_role_policy(
106+
RoleName=_ROLE_NAME,
107+
PolicyName="lambda-invoke",
108+
PolicyDocument=json.dumps(policy),
109+
)
110+
111+
112+
def _ensure_lambda(lambda_client, role_arn: str) -> str:
113+
"""Create or update the test Lambda, return its ARN."""
114+
zip_bytes = _get_lambda_zip()
115+
try:
116+
response = lambda_client.get_function(FunctionName=_LAMBDA_NAME)
117+
lambda_client.update_function_code(FunctionName=_LAMBDA_NAME, ZipFile=zip_bytes)
118+
return response["Configuration"]["FunctionArn"]
119+
except lambda_client.exceptions.ResourceNotFoundException:
120+
pass
121+
122+
response = lambda_client.create_function(
123+
FunctionName=_LAMBDA_NAME,
124+
Runtime=_LAMBDA_RUNTIME,
125+
Role=role_arn,
126+
Handler=_LAMBDA_HANDLER,
127+
Code={"ZipFile": zip_bytes},
128+
Timeout=30,
129+
Description="MCP test Lambda for AgentCore gateway integration tests",
130+
)
131+
waiter = lambda_client.get_waiter("function_active_v2")
132+
waiter.wait(FunctionName=_LAMBDA_NAME)
133+
logger.info("Created Lambda: %s", response["FunctionArn"])
134+
return response["FunctionArn"]
135+
46136

47137
class FixedIntentProvider(IntentProvider):
48138
"""Intent provider that returns a fixed string for deterministic testing."""
@@ -67,9 +157,18 @@ def setup_class(cls):
67157
cls.region = os.environ.get("BEDROCK_TEST_REGION", "us-west-2")
68158
cls.role_arn = os.environ.get("GATEWAY_ROLE_ARN")
69159
cls.lambda_arn = os.environ.get("GATEWAY_LAMBDA_ARN")
160+
cls._provisioned_infra = False
70161

71162
if not cls.role_arn or not cls.lambda_arn:
72-
pytest.fail("GATEWAY_ROLE_ARN and GATEWAY_LAMBDA_ARN must be set")
163+
# Auto-provision infrastructure
164+
session = boto3.Session(region_name=cls.region)
165+
iam_client = session.client("iam")
166+
lambda_client = session.client("lambda", region_name=cls.region)
167+
cls.role_arn = _ensure_role(iam_client)
168+
cls.lambda_arn = _ensure_lambda(lambda_client, cls.role_arn)
169+
_attach_lambda_invoke_policy(iam_client, cls.lambda_arn)
170+
cls._provisioned_infra = True
171+
logger.info("Auto-provisioned infrastructure: role=%s, lambda=%s", cls.role_arn, cls.lambda_arn)
73172

74173
cls.gw_client = GatewayClient(region_name=cls.region)
75174
cls.test_prefix = f"sdk-integ-plugin-{int(time.time())}"
@@ -158,6 +257,33 @@ def teardown_class(cls):
158257
except Exception as e:
159258
logger.warning("Failed to delete gateway %s: %s", cls.gateway_id, e)
160259

260+
# Clean up auto-provisioned infrastructure
261+
if cls._provisioned_infra:
262+
session = boto3.Session(region_name=cls.region)
263+
lambda_client = session.client("lambda", region_name=cls.region)
264+
iam_client = session.client("iam")
265+
try:
266+
lambda_client.delete_function(FunctionName=_LAMBDA_NAME)
267+
logger.info("Deleted Lambda: %s", _LAMBDA_NAME)
268+
except Exception as e:
269+
logger.warning("Failed to delete Lambda: %s", e)
270+
try:
271+
iam_client.delete_role_policy(RoleName=_ROLE_NAME, PolicyName="lambda-invoke")
272+
except Exception:
273+
pass
274+
try:
275+
iam_client.detach_role_policy(
276+
RoleName=_ROLE_NAME,
277+
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
278+
)
279+
except Exception:
280+
pass
281+
try:
282+
iam_client.delete_role(RoleName=_ROLE_NAME)
283+
logger.info("Deleted role: %s", _ROLE_NAME)
284+
except Exception as e:
285+
logger.warning("Failed to delete role: %s", e)
286+
161287
def _make_mcp_client(self):
162288
"""Create an MCPClient connected to the test gateway via Streamable HTTP with IAM auth."""
163289
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client

0 commit comments

Comments
 (0)