Add Terraform Infrastructure-as-Code (IaC) patterns for AgentCore deployment (#654)
* feat: Add Terraform basic-runtime pattern * Modified the Terraform basic-runtime with test script, README, default region * feat: Add Terraform mcp-server-runtime pattern * feat: Add Terraform multi-agent-runtime pattern * feat: Add Terraform end-to-end-weather-agent runtime pattern * Added Terraform main README * Fixed basic runtime test script * docs: add Terraform support to IaC README * Replaced resources to intuitive names, removed hardcoded values, cleaned README * Enhanced Terraform READMEs * Removed unused imports --------- Co-authored-by: Tesfagabir Meharizghi <mehariz@amazon.com>
This commit is contained in:
committed by
GitHub
parent
62965534d8
commit
83b72e1dda
@@ -1,6 +1,6 @@
|
||||
# Infrastructure as Code Samples for Amazon Bedrock AgentCore
|
||||
|
||||
Deploy Amazon Bedrock AgentCore resources using CloudFormation templates or AWS CDK.
|
||||
Deploy Amazon Bedrock AgentCore resources using CloudFormation templates, AWS CDK, or Terraform.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -13,6 +13,7 @@ These Infrastructure as Code samples enable you to:
|
||||
Choose your preferred approach:
|
||||
- **[CloudFormation](./cloudformation/)** - YAML/JSON templates for declarative infrastructure
|
||||
- **[CDK](./cdk/)** - Python code for programmatic infrastructure
|
||||
- **[Terraform](./terraform/)** - HCL code for declarative infrastructure with state management
|
||||
|
||||
## Samples
|
||||
|
||||
@@ -28,7 +29,7 @@ Deploy a simple AgentCore Runtime with a basic Strands agent - no additional too
|
||||
**Deployment time:** ~5-15 minutes
|
||||
**Estimated cost:** ~$50-100/month
|
||||
|
||||
**Implementation:** [CloudFormation](./cloudformation/basic-runtime/) | [CDK](./cdk/basic-runtime/)
|
||||
**Implementation:** [CloudFormation](./cloudformation/basic-runtime/) | [CDK](./cdk/basic-runtime/) | [Terraform](./terraform/basic-runtime/)
|
||||
|
||||
### 2. MCP Server on AgentCore Runtime
|
||||
Deploy a complete MCP (Model Context Protocol) server with automated Docker building and JWT authentication.
|
||||
@@ -42,7 +43,7 @@ Deploy a complete MCP (Model Context Protocol) server with automated Docker buil
|
||||
**Deployment time:** ~10-15 minutes
|
||||
**Estimated cost:** ~$50-100/month
|
||||
|
||||
**Implementation:** [CloudFormation](./cloudformation/mcp-server-agentcore-runtime/) | [CDK](./cdk/mcp-server-agentcore-runtime/)
|
||||
**Implementation:** [CloudFormation](./cloudformation/mcp-server-agentcore-runtime/) | [CDK](./cdk/mcp-server-agentcore-runtime/) | [Terraform](./terraform/mcp-server-agentcore-runtime/)
|
||||
|
||||
### 3. Multi-Agent Runtime
|
||||
Deploy a multi-agent system where Agent1 (orchestrator) can invoke Agent2 (specialist) for complex tasks.
|
||||
@@ -56,7 +57,7 @@ Deploy a multi-agent system where Agent1 (orchestrator) can invoke Agent2 (speci
|
||||
**Deployment time:** ~15-20 minutes
|
||||
**Estimated cost:** ~$100-200/month
|
||||
|
||||
**Implementation:** [CloudFormation](./cloudformation/multi-agent-runtime/) | [CDK](./cdk/multi-agent-runtime/)
|
||||
**Implementation:** [CloudFormation](./cloudformation/multi-agent-runtime/) | [CDK](./cdk/multi-agent-runtime/) | [Terraform](./terraform/multi-agent-runtime/)
|
||||
|
||||
### 4. End-to-End Weather Agent with Tools and Memory
|
||||
Deploy a complete weather-based activity planning agent with browser automation, code interpreter, and memory.
|
||||
@@ -72,7 +73,7 @@ Deploy a complete weather-based activity planning agent with browser automation,
|
||||
**Deployment time:** ~15-20 minutes
|
||||
**Estimated cost:** ~$100-150/month
|
||||
|
||||
**Implementation:** [CloudFormation](./cloudformation/end-to-end-weather-agent/) | [CDK](./cdk/end-to-end-weather-agent/)
|
||||
**Implementation:** [CloudFormation](./cloudformation/end-to-end-weather-agent/) | [CDK](./cdk/end-to-end-weather-agent/) | [Terraform](./terraform/end-to-end-weather-agent/)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -93,6 +94,10 @@ For CDK samples, also install:
|
||||
- Python 3.8+
|
||||
- AWS CDK v2.218.0 or later
|
||||
|
||||
For Terraform samples, also install:
|
||||
- Terraform >= 1.6 (recommend [tfenv](https://github.com/tfutils/tfenv) for version management)
|
||||
- Note: `brew install terraform` provides v1.5.7 which is deprecated
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
@@ -104,8 +109,14 @@ For CDK samples, also install:
|
||||
│ ├── mcp-server-agentcore-runtime/
|
||||
│ ├── multi-agent-runtime/
|
||||
│ └── end-to-end-weather-agent/
|
||||
└── cdk/ # CDK samples
|
||||
├── README.md # CDK-specific guide
|
||||
├── cdk/ # CDK samples
|
||||
│ ├── README.md # CDK-specific guide
|
||||
│ ├── basic-runtime/
|
||||
│ ├── mcp-server-agentcore-runtime/
|
||||
│ ├── multi-agent-runtime/
|
||||
│ └── end-to-end-weather-agent/
|
||||
└── terraform/ # Terraform samples
|
||||
├── README.md # Terraform-specific guide
|
||||
├── basic-runtime/
|
||||
├── mcp-server-agentcore-runtime/
|
||||
├── multi-agent-runtime/
|
||||
@@ -117,4 +128,5 @@ For CDK samples, also install:
|
||||
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
|
||||
- [AWS CloudFormation Documentation](https://docs.aws.amazon.com/cloudformation/)
|
||||
- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/)
|
||||
- [Terraform Documentation](https://www.terraform.io/docs)
|
||||
- [Original Tutorials](../01-tutorials/)
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
# Terraform Samples
|
||||
|
||||
Deploy Amazon Bedrock AgentCore resources using Terraform.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Terraform >= 1.6**
|
||||
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
|
||||
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
|
||||
- **Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6
|
||||
- **AWS CLI** configured with credentials
|
||||
- **Python 3.11+** (for testing scripts)
|
||||
- **Docker** (optional, for local testing)
|
||||
- Access to Amazon Bedrock AgentCore (preview)
|
||||
|
||||
## State Management Options
|
||||
|
||||
Terraform tracks deployed resources in a state file. Choose the approach that fits your needs:
|
||||
|
||||
### Option A: Local State (Quickstart)
|
||||
|
||||
Perfect for testing, learning, and solo development:
|
||||
|
||||
```bash
|
||||
cd <sample-directory>
|
||||
terraform init
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- State stored in local `terraform.tfstate` file
|
||||
- Simple setup, no additional configuration
|
||||
- Best for individual experimentation
|
||||
- Not suitable for team collaboration
|
||||
|
||||
### Option B: Remote State (Teams/Production)
|
||||
|
||||
Recommended for team collaboration and production environments:
|
||||
|
||||
```bash
|
||||
cd <sample-directory>
|
||||
|
||||
# 1. Setup (one-time per pattern)
|
||||
cp backend.tf.example backend.tf
|
||||
# Edit backend.tf with your S3 bucket and DynamoDB table
|
||||
|
||||
# 2. Initialize with backend
|
||||
terraform init
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- State stored in S3 with DynamoDB locking
|
||||
- Enables team collaboration
|
||||
- Provides state versioning and backup
|
||||
- Prevents concurrent modifications
|
||||
|
||||
**Setup Requirements:**
|
||||
- S3 bucket for state storage
|
||||
- DynamoDB table for state locking
|
||||
- See `backend.tf.example` in each pattern for details
|
||||
|
||||
💡 **Note**: You must create the S3 bucket and DynamoDB table before running `terraform init` with remote state. See `backend.tf.example` in each pattern directory for setup instructions.
|
||||
|
||||
## General Deployment Pattern
|
||||
|
||||
### Option 1: Using Automation Scripts (Recommended)
|
||||
|
||||
```bash
|
||||
cd <sample-directory>
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The deployment script will:
|
||||
- Validate your environment
|
||||
- Initialize Terraform
|
||||
- Create and review the plan
|
||||
- Deploy all resources
|
||||
- Display outputs and next steps
|
||||
|
||||
### Option 2: Manual Terraform Commands
|
||||
|
||||
```bash
|
||||
cd <sample-directory>
|
||||
|
||||
# 1. Configure variables
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
# Edit terraform.tfvars with your values
|
||||
|
||||
# 2. Choose state management (see State Management Options above)
|
||||
terraform init
|
||||
|
||||
# 3. Review the plan
|
||||
terraform plan
|
||||
|
||||
# 4. Deploy
|
||||
terraform apply
|
||||
|
||||
# 5. View outputs
|
||||
terraform output
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
All patterns include Python test scripts to verify your deployment.
|
||||
|
||||
### Setup Test Environment
|
||||
|
||||
**Option 1: Using uv (Recommended)**
|
||||
|
||||
```bash
|
||||
# Install uv if not already installed
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Create virtual environment
|
||||
uv venv
|
||||
|
||||
# Activate virtual environment
|
||||
source .venv/bin/activate # On macOS/Linux
|
||||
# .venv\Scripts\activate # On Windows
|
||||
|
||||
# Install boto3
|
||||
uv pip install boto3
|
||||
```
|
||||
|
||||
**Option 2: Using pip**
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python3 -m venv .venv
|
||||
|
||||
# Activate virtual environment
|
||||
source .venv/bin/activate # On macOS/Linux
|
||||
# .venv\Scripts\activate # On Windows
|
||||
|
||||
# Install boto3
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
# Get the agent ARN from Terraform outputs
|
||||
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
|
||||
|
||||
# Run the test script
|
||||
python test_*.py $AGENT_ARN
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
```bash
|
||||
# Using automation script
|
||||
./destroy.sh
|
||||
|
||||
# Or using Terraform directly
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
## Samples
|
||||
|
||||
- **[basic-runtime/](./basic-runtime/)** - Simple agent deployment with container runtime
|
||||
- **[mcp-server-agentcore-runtime/](./mcp-server-agentcore-runtime/)** - MCP Server with JWT authentication and API Gateway
|
||||
- **[multi-agent-runtime/](./multi-agent-runtime/)** - Multi-agent system with Agent-to-Agent (A2A) communication
|
||||
- **[end-to-end-weather-agent/](./end-to-end-weather-agent/)** - Weather agent with Browser, Code Interpreter, and Memory tools
|
||||
|
||||
## Terraform Advantages
|
||||
|
||||
- **Infrastructure as Code**: Define resources declaratively with HCL
|
||||
- **State Management**: Track and manage infrastructure state
|
||||
- **Module Reusability**: Create reusable infrastructure components
|
||||
- **Plan Before Apply**: Preview changes before deployment
|
||||
- **Automated Image Building**: Uses CodeBuild for Docker image creation
|
||||
- **Provider Ecosystem**: Access to thousands of providers and resources
|
||||
- **Automation Scripts**: Included deploy.sh and destroy.sh for easy deployment
|
||||
|
||||
## Pattern Comparison
|
||||
|
||||
| Pattern | Agent Runtimes | Tools | A2A | MCP Server | Use Case |
|
||||
|---------|----------------|-------|-----|------------|----------|
|
||||
| basic-runtime | 1 | - | ❌ | ❌ | Simple agent deployment |
|
||||
| mcp-server | 1 | - | ❌ | ✅ | API integration with JWT auth |
|
||||
| multi-agent | 2 | - | ✅ | ❌ | Orchestrator + Specialist pattern |
|
||||
| weather-agent | 1 | Browser, Code Interpreter, Memory | ❌ | ❌ | Full-featured agent with tools |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Terraform Version Issues
|
||||
|
||||
If you encounter provider compatibility issues:
|
||||
|
||||
```bash
|
||||
# Install specific Terraform version with tfenv
|
||||
tfenv install 1.6.0
|
||||
tfenv use 1.6.0
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```bash
|
||||
# View current state
|
||||
terraform show
|
||||
|
||||
# List all resources in state
|
||||
terraform state list
|
||||
|
||||
# Remove a resource from state (if needed)
|
||||
terraform state rm <resource_address>
|
||||
```
|
||||
|
||||
### Provider Errors
|
||||
|
||||
If you see provider version conflicts:
|
||||
|
||||
```bash
|
||||
# Upgrade providers to latest compatible versions
|
||||
terraform init -upgrade
|
||||
|
||||
# Lock provider versions
|
||||
terraform providers lock
|
||||
```
|
||||
|
||||
### CodeBuild Failures
|
||||
|
||||
Check build logs:
|
||||
|
||||
```bash
|
||||
# Get project name from outputs
|
||||
PROJECT_NAME=$(terraform output -raw codebuild_project_name)
|
||||
|
||||
# View recent build logs
|
||||
aws codebuild list-builds-for-project \
|
||||
--project-name $PROJECT_NAME \
|
||||
--region <region>
|
||||
|
||||
# Get specific build details
|
||||
aws codebuild batch-get-builds \
|
||||
--ids <build-id> \
|
||||
--region <region>
|
||||
```
|
||||
|
||||
### Deployment Stuck
|
||||
|
||||
If deployment appears stuck:
|
||||
|
||||
```bash
|
||||
# Check CloudWatch Logs for the agent runtime
|
||||
aws logs tail /aws/bedrock-agentcore/<runtime-name> --follow
|
||||
|
||||
# Check CodeBuild progress
|
||||
aws codebuild list-builds-for-project \
|
||||
--project-name <project-name> \
|
||||
--max-items 5
|
||||
```
|
||||
|
||||
### Resource Already Exists
|
||||
|
||||
If you encounter "resource already exists" errors:
|
||||
|
||||
```bash
|
||||
# Import existing resource into state
|
||||
terraform import <resource_type>.<resource_name> <resource_id>
|
||||
|
||||
# Example for S3 bucket
|
||||
terraform import aws_s3_bucket.example my-bucket-name
|
||||
```
|
||||
|
||||
### Cleanup Issues
|
||||
|
||||
If `terraform destroy` fails:
|
||||
|
||||
```bash
|
||||
# Manually empty S3 buckets first
|
||||
aws s3 rm s3://<bucket-name> --recursive
|
||||
|
||||
# Force destroy (use with caution)
|
||||
terraform destroy -auto-approve
|
||||
|
||||
# Check for remaining resources
|
||||
aws resourcegroupstaggingapi get-resources \
|
||||
--tag-filters Key=ManagedBy,Values=Terraform
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### State Management
|
||||
|
||||
Terraform tracks all deployed resources in a state file. For team collaboration:
|
||||
|
||||
```bash
|
||||
# Setup remote state (example with S3)
|
||||
cp backend.tf.example backend.tf
|
||||
# Edit backend.tf with your S3 bucket details
|
||||
terraform init -migrate-state
|
||||
```
|
||||
|
||||
### Automated Docker Builds
|
||||
|
||||
Each pattern uses AWS CodeBuild to automatically build ARM64 Docker images:
|
||||
- Triggered on source code changes (MD5 hash detection)
|
||||
- No local Docker daemon required
|
||||
- Optimized for AWS Graviton processors
|
||||
|
||||
### Testing Scripts
|
||||
|
||||
All patterns include infrastructure-agnostic Python test scripts:
|
||||
|
||||
```bash
|
||||
# Get the agent ARN from Terraform outputs
|
||||
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
|
||||
|
||||
# Run tests
|
||||
python test_*.py $AGENT_ARN
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Terraform Documentation](https://www.terraform.io/docs)
|
||||
- [AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
|
||||
- [Terraform Best Practices](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT-0 license. See the [LICENSE](../../LICENSE) file for details.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Terraform files
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
!terraform.tfvars.example
|
||||
tfplan
|
||||
|
||||
# Generated archives
|
||||
*.zip
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,403 @@
|
||||
# Basic AgentCore Runtime - Terraform
|
||||
|
||||
This pattern demonstrates the simplest deployment of an AgentCore Runtime using Terraform. It creates a basic agent without additional tools like Memory, Code Interpreter, or Browser.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Testing the Agent](#testing-the-agent)
|
||||
- [Sample Queries](#sample-queries)
|
||||
- [Customization](#customization)
|
||||
- [File Structure](#file-structure)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Pricing](#pricing)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Resources](#resources)
|
||||
- [🤝 Contributing](#-contributing)
|
||||
- [📄 License](#-license)
|
||||
|
||||
## Overview
|
||||
|
||||
This Terraform configuration creates a minimal AgentCore deployment that includes:
|
||||
|
||||
- **AgentCore Runtime**: Hosts a simple Strands agent
|
||||
- **ECR Repository**: Stores the Docker container image
|
||||
- **IAM Roles**: Provides necessary permissions
|
||||
- **CodeBuild Project**: Automatically builds the ARM64 Docker image
|
||||
|
||||
This makes it ideal for:
|
||||
- Learning AgentCore basics with Terraform
|
||||
- Quick prototyping and experimentation
|
||||
- Understanding the core deployment pattern
|
||||
- Building a foundation before adding complexity
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## What's Included
|
||||
|
||||
This Terraform configuration creates:
|
||||
|
||||
- **S3 Bucket**: Stores agent source code for version-controlled builds
|
||||
- **ECR Repository**: Container registry for the agent Docker image
|
||||
- **CodeBuild Project**: Automated Docker image building and pushing
|
||||
- **IAM Roles**: Execution roles for the agent and CodeBuild
|
||||
- **AgentCore Runtime**: Serverless agent runtime with the deployed container
|
||||
|
||||
### Agent Code Management
|
||||
|
||||
The `agent-code/` directory contains your agent's source files:
|
||||
- `basic_agent.py` - Agent implementation
|
||||
- `Dockerfile` - Container configuration
|
||||
- `requirements.txt` - Python dependencies
|
||||
|
||||
**Automatic Change Detection**:
|
||||
- Terraform archives the `agent-code/` directory
|
||||
- Uploads to S3 with MD5-based versioning
|
||||
- CodeBuild pulls from S3 and builds the Docker image
|
||||
- Any changes to files trigger automatic rebuild (new files, modifications, deletions)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Tools
|
||||
|
||||
1. **Terraform** (>= 1.6)
|
||||
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
|
||||
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
|
||||
|
||||
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
|
||||
|
||||
2. **AWS CLI** (configured with credentials)
|
||||
```bash
|
||||
aws configure
|
||||
```
|
||||
|
||||
3. **Python 3.11+** (for testing scripts)
|
||||
```bash
|
||||
python --version # Verify Python 3.11 or later
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
4. **Docker** (for local testing, optional)
|
||||
|
||||
### AWS Account Requirements
|
||||
|
||||
- AWS Account with appropriate permissions
|
||||
- Access to Amazon Bedrock models
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Configure Variables
|
||||
|
||||
Copy the example variables file and customize:
|
||||
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
```
|
||||
|
||||
Edit `terraform.tfvars` with your preferred values.
|
||||
|
||||
### 2. Initialize Terraform
|
||||
|
||||
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
|
||||
|
||||
**Quick start with local state:**
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
|
||||
|
||||
### 3. Review the Plan
|
||||
|
||||
```bash
|
||||
terraform plan
|
||||
```
|
||||
|
||||
### 4. Deploy
|
||||
|
||||
**Method 1: Using Deploy Script (Recommended)**
|
||||
|
||||
Make the script executable (first-time only):
|
||||
```bash
|
||||
chmod +x deploy.sh
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The deploy script:
|
||||
- Validates Terraform configuration
|
||||
- Shows deployment plan
|
||||
- Prompts for confirmation
|
||||
- Applies changes
|
||||
|
||||
**Method 2: Direct Terraform Commands**
|
||||
|
||||
```bash
|
||||
terraform apply
|
||||
```
|
||||
|
||||
When prompted, type `yes` to confirm the deployment.
|
||||
|
||||
**Note**: The deployment process includes:
|
||||
1. Creating ECR repository
|
||||
2. Building Docker image via CodeBuild
|
||||
3. Creating AgentCore Runtime
|
||||
|
||||
Total deployment time: **~3-5 minutes**
|
||||
|
||||
### 5. Get Outputs
|
||||
|
||||
After deployment completes:
|
||||
|
||||
```bash
|
||||
terraform output
|
||||
```
|
||||
|
||||
Example output:
|
||||
```
|
||||
agent_runtime_id = "AGENT1234567890"
|
||||
agent_runtime_arn = "arn:aws:bedrock-agentcore:<us-west-2>:123456789012:agent-runtime/AGENT1234567890"
|
||||
ecr_repository_url = "123456789012.dkr.ecr.us-west-2.amazonaws.com/agentcore-basic-basic-agent"
|
||||
```
|
||||
|
||||
## Testing the Agent
|
||||
|
||||
### Prerequisites for Testing
|
||||
|
||||
Before testing, ensure you have the required packages installed:
|
||||
|
||||
**Option A: Using uv (Recommended)**
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Option B: System-wide installation**
|
||||
```bash
|
||||
pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Note**: `boto3` is required for the test script to invoke the agent runtime via AWS API.
|
||||
|
||||
### Option 1: Using Test Script (Recommended)
|
||||
|
||||
```bash
|
||||
# Run the test suite
|
||||
python test_basic_agent.py $(terraform output -raw agent_runtime_arn)
|
||||
```
|
||||
|
||||
### Option 2: Using AWS CLI
|
||||
|
||||
```bash
|
||||
# Get the runtime ARN from outputs
|
||||
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn)
|
||||
|
||||
# Invoke the agent
|
||||
aws bedrock-agentcore invoke-agent-runtime \
|
||||
--agent-runtime-arn $RUNTIME_ARN \
|
||||
--qualifier DEFAULT \
|
||||
--payload $(echo '{"prompt": "Hello, introduce yourself"}' | base64) \
|
||||
response.json
|
||||
|
||||
# View the response
|
||||
cat response.json | jq -r '.response'
|
||||
```
|
||||
|
||||
### Option 3: Using AWS Console
|
||||
|
||||
1. Navigate to Amazon Bedrock console
|
||||
2. Go to AgentCore → Runtimes
|
||||
3. Select your runtime
|
||||
4. Use the "Test" feature to send queries
|
||||
|
||||
## Sample Queries
|
||||
|
||||
Try these queries to test your basic agent:
|
||||
|
||||
1. **Simple Math**:
|
||||
```json
|
||||
{"prompt": "What is 2+2?"}
|
||||
```
|
||||
|
||||
2. **General Knowledge**:
|
||||
```json
|
||||
{"prompt": "What is the capital of France?"}
|
||||
```
|
||||
|
||||
3. **Explanation Request**:
|
||||
```json
|
||||
{"prompt": "Explain what Amazon Bedrock is in simple terms"}
|
||||
```
|
||||
|
||||
4. **Creative Task**:
|
||||
```json
|
||||
{"prompt": "Write a haiku about cloud computing"}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify Agent Code
|
||||
|
||||
Edit files in `agent-code/` and deploy:
|
||||
- `basic_agent.py` - Agent logic and system prompt
|
||||
- `Dockerfile` - Container configuration
|
||||
- `requirements.txt` - Python dependencies
|
||||
|
||||
Changes are automatically detected and trigger rebuild. Run `terraform apply` to deploy.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add to `terraform.tfvars`:
|
||||
```hcl
|
||||
environment_variables = {
|
||||
LOG_LEVEL = "DEBUG"
|
||||
}
|
||||
```
|
||||
|
||||
### Network Mode
|
||||
|
||||
Set `network_mode = "PRIVATE"` for VPC deployment (requires additional VPC configuration).
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
basic-runtime/
|
||||
├── main.tf # AgentCore runtime resource
|
||||
├── variables.tf # Input variables
|
||||
├── outputs.tf # Output values
|
||||
├── versions.tf # Provider configuration
|
||||
├── iam.tf # IAM roles and policies
|
||||
├── s3.tf # S3 bucket for source code
|
||||
├── ecr.tf # ECR repository
|
||||
├── codebuild.tf # Docker build automation
|
||||
├── buildspec.yml # CodeBuild build specification
|
||||
├── terraform.tfvars.example # Example configuration
|
||||
├── backend.tf.example # Remote state example
|
||||
├── test_basic_agent.py # Automated test script
|
||||
├── agent-code/ # Agent source code
|
||||
│ ├── basic_agent.py # Agent implementation
|
||||
│ ├── Dockerfile # Container configuration
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── scripts/ # Build automation scripts
|
||||
│ └── build-image.sh # CodeBuild trigger & verification
|
||||
├── deploy.sh # Deployment helper script
|
||||
├── destroy.sh # Cleanup helper script
|
||||
├── .gitignore # Git ignore patterns
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CodeBuild Fails
|
||||
|
||||
If the Docker build fails:
|
||||
|
||||
1. Check CodeBuild logs:
|
||||
```bash
|
||||
aws codebuild batch-get-builds \
|
||||
--ids $(terraform output -raw codebuild_project_name) \
|
||||
--region us-west-2
|
||||
```
|
||||
|
||||
2. Common issues:
|
||||
- Network connectivity issues
|
||||
- ECR authentication problems
|
||||
- Python dependency conflicts
|
||||
|
||||
### Runtime Creation Fails
|
||||
|
||||
If the runtime creation fails:
|
||||
|
||||
1. Verify the Docker image exists:
|
||||
```bash
|
||||
aws ecr describe-images \
|
||||
--repository-name $(terraform output -raw ecr_repository_url | cut -d'/' -f2) \
|
||||
--region us-west-2
|
||||
```
|
||||
|
||||
2. Check IAM role permissions
|
||||
3. Verify Bedrock AgentCore service quotas
|
||||
|
||||
### Agent Invocation Fails
|
||||
|
||||
If invoking the agent fails:
|
||||
|
||||
1. Check runtime status in AWS Console
|
||||
2. Review CloudWatch Logs for the runtime
|
||||
3. Verify Bedrock model access permissions
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Destroy All Resources
|
||||
|
||||
Make the script executable (first-time only):
|
||||
```bash
|
||||
chmod +x destroy.sh
|
||||
```
|
||||
|
||||
Then cleanup:
|
||||
```bash
|
||||
./destroy.sh
|
||||
```
|
||||
|
||||
Or use Terraform directly:
|
||||
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
### Verify Cleanup
|
||||
|
||||
Confirm all resources are deleted:
|
||||
|
||||
```bash
|
||||
# Check ECR repositories
|
||||
aws ecr describe-repositories --region us-west-2 | grep agentcore-basic
|
||||
|
||||
# Check AgentCore runtimes
|
||||
aws bedrock-agentcore list-agent-runtimes --region us-west-2
|
||||
```
|
||||
|
||||
## Pricing
|
||||
|
||||
For current pricing information, please refer to:
|
||||
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
|
||||
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
|
||||
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
|
||||
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
|
||||
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
||||
|
||||
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Explore Other Patterns
|
||||
|
||||
- [MCP Server Runtime](../mcp-server-agentcore-runtime/) - Add MCP protocol support
|
||||
- [Multi-Agent Runtime](../multi-agent-runtime/) - Deploy multiple coordinating agents
|
||||
- [End-to-End Weather Agent](../end-to-end-weather-agent/) - Full-featured agent with tools
|
||||
|
||||
## Resources
|
||||
|
||||
- [Terraform AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
|
||||
- [Strands Agents Documentation](https://strands-agents.readthedocs.io/)
|
||||
- [AgentCore Samples Repository](https://github.com/aws-samples/amazon-bedrock-agentcore-samples)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](../../../LICENSE) file for details.
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt && \
|
||||
pip install --no-cache-dir aws-opentelemetry-distro==0.10.1
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 bedrock_agentcore
|
||||
USER bedrock_agentcore
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8000
|
||||
|
||||
COPY . .
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/ping || exit 1
|
||||
|
||||
CMD ["opentelemetry-instrument", "python", "-m", "basic_agent"]
|
||||
@@ -0,0 +1,38 @@
|
||||
from strands import Agent
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
||||
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
def create_basic_agent() -> Agent:
|
||||
"""Create a basic agent with simple functionality"""
|
||||
system_prompt = """You are a helpful assistant. Answer questions clearly and concisely."""
|
||||
|
||||
return Agent(
|
||||
system_prompt=system_prompt,
|
||||
name="BasicAgent"
|
||||
)
|
||||
|
||||
@app.entrypoint
|
||||
async def invoke(payload=None):
|
||||
"""Main entrypoint for the agent"""
|
||||
try:
|
||||
# Get the query from payload
|
||||
query = payload.get("prompt", "Hello, how are you?") if payload else "Hello, how are you?"
|
||||
|
||||
# Create and use the agent
|
||||
agent = create_basic_agent()
|
||||
response = agent(query)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"response": response.message['content'][0]['text']
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -0,0 +1,3 @@
|
||||
strands-agents
|
||||
boto3
|
||||
bedrock-agentcore
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# Terraform Backend Configuration - Remote State Storage
|
||||
# ============================================================================
|
||||
# This is an example backend configuration for storing Terraform state remotely
|
||||
# Copy this file to backend.tf and customize with your backend settings
|
||||
# Example: cp backend.tf.example backend.tf
|
||||
|
||||
# IMPORTANT: Uncomment and configure ONE of the backend options below
|
||||
|
||||
# Option 1: S3 Backend (Recommended for AWS)
|
||||
# terraform {
|
||||
# backend "s3" {
|
||||
# bucket = "your-terraform-state-bucket"
|
||||
# key = "agentcore/basic-runtime/terraform.tfstate"
|
||||
# region = "us-west-2"
|
||||
# encrypt = true
|
||||
# dynamodb_table = "terraform-state-lock"
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 2: Terraform Cloud
|
||||
# terraform {
|
||||
# cloud {
|
||||
# organization = "your-organization"
|
||||
# workspaces {
|
||||
# name = "agentcore-basic-runtime"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 3: Local Backend (Default - No configuration needed)
|
||||
# State will be stored locally in terraform.tfstate
|
||||
# Not recommended for team environments or production use
|
||||
@@ -0,0 +1,29 @@
|
||||
version: 0.2
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Installing dependencies...
|
||||
- yum install -y unzip
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Source code already extracted by CodeBuild...
|
||||
- cd $CODEBUILD_SRC_DIR
|
||||
- ls -la
|
||||
- echo Logging in to Amazon ECR...
|
||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image for basic agent ARM64 from agent-code/...
|
||||
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
||||
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Build completed on `date`
|
||||
- echo Pushing the Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
- echo ARM64 Docker image pushed successfully
|
||||
@@ -0,0 +1,90 @@
|
||||
# ============================================================================
|
||||
# CodeBuild Project - Build and Push Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_codebuild_project" "agent_image" {
|
||||
name = "${var.stack_name}-basic-agent-build"
|
||||
description = "Build basic agent Docker image for ${var.stack_name}"
|
||||
service_role = aws_iam_role.image_build.arn
|
||||
build_timeout = 60
|
||||
|
||||
artifacts {
|
||||
type = "NO_ARTIFACTS"
|
||||
}
|
||||
|
||||
environment {
|
||||
compute_type = "BUILD_GENERAL1_LARGE"
|
||||
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
|
||||
type = "ARM_CONTAINER"
|
||||
privileged_mode = true
|
||||
image_pull_credentials_type = "CODEBUILD"
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_DEFAULT_REGION"
|
||||
value = data.aws_region.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_ACCOUNT_ID"
|
||||
value = data.aws_caller_identity.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_REPO_NAME"
|
||||
value = aws_ecr_repository.agent_ecr.name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_TAG"
|
||||
value = var.image_tag
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "STACK_NAME"
|
||||
value = var.stack_name
|
||||
}
|
||||
}
|
||||
|
||||
source {
|
||||
type = "S3"
|
||||
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
|
||||
buildspec = file("${path.module}/buildspec.yml")
|
||||
}
|
||||
|
||||
logs_config {
|
||||
cloudwatch_logs {
|
||||
group_name = "/aws/codebuild/${var.stack_name}-basic-agent-build"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-basic-build"
|
||||
Module = "CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Trigger CodeBuild - Build Image Before Creating Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "null_resource" "trigger_build" {
|
||||
triggers = {
|
||||
build_project = aws_codebuild_project.agent_image.id
|
||||
image_tag = var.image_tag
|
||||
# Trigger rebuild if ECR repository changes
|
||||
ecr_repository = aws_ecr_repository.agent_ecr.id
|
||||
# Trigger rebuild when source code changes (MD5 hash)
|
||||
source_code_md5 = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.agent_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.agent_ecr.repository_url}\""
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_codebuild_project.agent_image,
|
||||
aws_ecr_repository.agent_ecr,
|
||||
aws_iam_role_policy.image_build,
|
||||
aws_s3_object.agent_source
|
||||
]
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Script for Basic AgentCore Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script automates the deployment process for the Terraform configuration
|
||||
# Usage: ./deploy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting Basic AgentCore Runtime Deployment..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed. Please install Terraform >= 1.6"
|
||||
print_info "Visit: https://www.terraform.io/downloads"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Terraform version
|
||||
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
|
||||
print_success "Terraform version: $TERRAFORM_VERSION"
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
|
||||
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "AWS CLI is installed"
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
print_info "Run: aws configure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_success "AWS Account: $AWS_ACCOUNT"
|
||||
print_success "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking configuration files..."
|
||||
|
||||
# Check if terraform.tfvars exists
|
||||
if [ ! -f "terraform.tfvars" ]; then
|
||||
print_warning "terraform.tfvars not found"
|
||||
print_info "Creating terraform.tfvars from example..."
|
||||
|
||||
if [ -f "terraform.tfvars.example" ]; then
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
print_success "Created terraform.tfvars"
|
||||
print_warning "Please review and update terraform.tfvars with your settings"
|
||||
print_info "Then run this script again"
|
||||
exit 0
|
||||
else
|
||||
print_error "terraform.tfvars.example not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Configuration file found: terraform.tfvars"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Initialization
|
||||
# ============================================================================
|
||||
|
||||
print_info "Initializing Terraform..."
|
||||
if terraform init; then
|
||||
print_success "Terraform initialized successfully"
|
||||
else
|
||||
print_error "Terraform initialization failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Validation
|
||||
# ============================================================================
|
||||
|
||||
print_info "Validating Terraform configuration..."
|
||||
if terraform validate; then
|
||||
print_success "Terraform configuration is valid"
|
||||
else
|
||||
print_error "Terraform validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Format Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking Terraform formatting..."
|
||||
if terraform fmt -check -recursive > /dev/null 2>&1; then
|
||||
print_success "Terraform files are properly formatted"
|
||||
else
|
||||
print_warning "Some files need formatting. Running terraform fmt..."
|
||||
terraform fmt -recursive
|
||||
print_success "Files formatted"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating Terraform execution plan..."
|
||||
print_warning "This may take a few moments..."
|
||||
echo ""
|
||||
|
||||
if terraform plan -out=tfplan; then
|
||||
print_success "Terraform plan created successfully"
|
||||
else
|
||||
print_error "Terraform plan failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "DEPLOYMENT CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_info "This will deploy the following resources:"
|
||||
print_info " - S3 Bucket (source code storage)"
|
||||
print_info " - ECR Repository"
|
||||
print_info " - CodeBuild Project"
|
||||
print_info " - IAM Roles and Policies"
|
||||
print_info " - AgentCore Runtime"
|
||||
echo ""
|
||||
print_info "The deployment includes:"
|
||||
print_info " - Building Docker image"
|
||||
print_info " - Creating AWS resources"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to proceed with deployment? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Deployment cancelled by user"
|
||||
rm -f tfplan
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Apply
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting deployment..."
|
||||
echo ""
|
||||
|
||||
if terraform apply tfplan; then
|
||||
print_success "Deployment completed successfully!"
|
||||
else
|
||||
print_error "Deployment failed"
|
||||
rm -f tfplan
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up plan file
|
||||
rm -f tfplan
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "DEPLOYMENT COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Retrieving deployment outputs..."
|
||||
echo ""
|
||||
|
||||
# Get outputs
|
||||
RUNTIME_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
|
||||
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
|
||||
ECR_URL=$(terraform output -raw ecr_repository_url 2>/dev/null || echo "N/A")
|
||||
|
||||
print_success "Agent Runtime ID: $RUNTIME_ID"
|
||||
print_success "Agent Runtime ARN: $RUNTIME_ARN"
|
||||
print_success "ECR Repository URL: $ECR_URL"
|
||||
|
||||
echo ""
|
||||
print_info "Next Steps:"
|
||||
print_info "1. Test your agent:"
|
||||
print_info " aws bedrock-agentcore invoke-agent-runtime \\"
|
||||
print_info " --agent-runtime-arn $RUNTIME_ARN \\"
|
||||
print_info " --qualifier DEFAULT \\"
|
||||
print_info " --payload \$(echo '{\"prompt\": \"Hello, how are you?\"}' | base64) \\"
|
||||
print_info " response.json"
|
||||
echo ""
|
||||
print_info " Then view the response:"
|
||||
print_info " cat response.json | jq -r '.response'"
|
||||
echo ""
|
||||
print_info "2. View all outputs:"
|
||||
print_info " terraform output"
|
||||
echo ""
|
||||
print_info "3. Monitor in AWS Console:"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_success "Deployment completed successfully!"
|
||||
@@ -0,0 +1,262 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Destroy Script for Basic AgentCore Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script safely destroys all resources created by this Terraform configuration
|
||||
# Usage: ./destroy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting Resource Cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_info "AWS Account: $AWS_ACCOUNT"
|
||||
print_info "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Check for Terraform State
|
||||
# ============================================================================
|
||||
|
||||
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
|
||||
print_warning "No Terraform state found"
|
||||
print_info "Either no resources have been deployed, or state is stored remotely"
|
||||
|
||||
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Initializing Terraform to fetch remote state..."
|
||||
terraform init
|
||||
else
|
||||
print_info "Cleanup cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Show Destruction Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating destruction plan..."
|
||||
echo ""
|
||||
|
||||
if ! terraform plan -destroy; then
|
||||
print_error "Failed to create destruction plan"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Destruction Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_warning "This will permanently delete the following resources:"
|
||||
print_warning " - AgentCore Runtime"
|
||||
print_warning " - S3 Bucket (source code storage)"
|
||||
print_warning " - ECR Repository (including all images)"
|
||||
print_warning " - CodeBuild Project"
|
||||
print_warning " - IAM Roles and Policies"
|
||||
print_warning " - CloudWatch Log Groups"
|
||||
echo ""
|
||||
print_warning "THIS ACTION CANNOT BE UNDONE!"
|
||||
echo ""
|
||||
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
|
||||
echo ""
|
||||
|
||||
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Destruction cancelled by user"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Double confirmation for safety
|
||||
print_warning "Second confirmation required..."
|
||||
read -p "Type 'DESTROY' to confirm: " -r
|
||||
echo ""
|
||||
|
||||
if [ "$REPLY" != "DESTROY" ]; then
|
||||
print_info "Destruction cancelled - confirmation text did not match"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Execute Destruction
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting resource destruction..."
|
||||
echo ""
|
||||
|
||||
if terraform destroy -auto-approve; then
|
||||
print_success "All resources destroyed successfully"
|
||||
else
|
||||
print_error "Destruction failed"
|
||||
print_warning "Some resources may still exist. Please check AWS Console"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Cleanup Local Files
|
||||
# ============================================================================
|
||||
|
||||
print_info "Cleaning up local Terraform files..."
|
||||
|
||||
# Ask about state file cleanup
|
||||
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -f terraform.tfstate
|
||||
rm -f terraform.tfstate.backup
|
||||
rm -f tfplan
|
||||
print_success "Local state files removed"
|
||||
fi
|
||||
|
||||
# Ask about .terraform directory
|
||||
read -p "Do you want to remove .terraform directory? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -rf .terraform
|
||||
rm -f .terraform.lock.hcl
|
||||
print_success ".terraform directory removed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verification
|
||||
# ============================================================================
|
||||
|
||||
print_info "Verifying resource cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check for ECR repositories
|
||||
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
|
||||
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
|
||||
|
||||
if [ "$ECR_REPOS" -eq 0 ]; then
|
||||
print_success "ECR repositories cleaned up"
|
||||
else
|
||||
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for AgentCore runtimes
|
||||
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
|
||||
|
||||
if [ "$RUNTIME_COUNT" -eq 0 ]; then
|
||||
print_success "AgentCore runtimes cleaned up"
|
||||
else
|
||||
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for S3 buckets
|
||||
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep -c "$STACK_NAME" || echo "0")
|
||||
|
||||
if [ "$S3_BUCKETS" -eq 0 ]; then
|
||||
print_success "S3 buckets cleaned up"
|
||||
else
|
||||
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Completion Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "CLEANUP COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Cleanup Summary:"
|
||||
print_success " ✓ Terraform resources destroyed"
|
||||
print_success " ✓ Local state files cleaned (if selected)"
|
||||
echo ""
|
||||
|
||||
print_info "What to verify in AWS Console:"
|
||||
print_info "1. Bedrock AgentCore - No runtimes remaining"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_info "2. S3 - No buckets remaining"
|
||||
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "3. ECR - No repositories remaining"
|
||||
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "4. CodeBuild - No projects remaining"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "5. CloudWatch Logs - Check for orphaned log groups"
|
||||
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
|
||||
echo ""
|
||||
|
||||
print_success "Cleanup completed successfully!"
|
||||
print_info "You can safely re-deploy by running: ./deploy.sh"
|
||||
@@ -0,0 +1,63 @@
|
||||
# ============================================================================
|
||||
# ECR Repository - Container Registry for Agent Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_ecr_repository" "agent_ecr" {
|
||||
name = "${var.stack_name}-${var.ecr_repository_name}"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
force_delete = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-ecr-repository"
|
||||
Module = "ECR"
|
||||
}
|
||||
}
|
||||
|
||||
# ECR Repository Policy
|
||||
resource "aws_ecr_repository_policy" "agent_ecr" {
|
||||
repository = aws_ecr_repository.agent_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowPullFromAccount"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
|
||||
}
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Lifecycle Policy - Keep last 5 images
|
||||
resource "aws_ecr_lifecycle_policy" "agent_ecr" {
|
||||
repository = aws_ecr_repository.agent_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep last 5 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
# Data sources
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# ============================================================================
|
||||
# Agent Execution Role - For AgentCore Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "agent_execution" {
|
||||
name = "${var.stack_name}-agent-execution-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AssumeRolePolicy"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "bedrock-agentcore.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.id
|
||||
}
|
||||
ArnLike = {
|
||||
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-execution-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Attach AWS managed policy for AgentCore
|
||||
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
|
||||
role = aws_iam_role.agent_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
|
||||
}
|
||||
|
||||
# Inline policy for agent execution
|
||||
resource "aws_iam_role_policy" "agent_execution" {
|
||||
name = "AgentCoreExecutionPolicy"
|
||||
role = aws_iam_role.agent_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRImageAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
Resource = aws_ecr_repository.agent_ecr.arn
|
||||
},
|
||||
{
|
||||
Sid = "ECRTokenAccess"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:GetAuthorizationToken"]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
||||
},
|
||||
# X-Ray Tracing
|
||||
{
|
||||
Sid = "XRayTracing"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Metrics
|
||||
{
|
||||
Sid = "CloudWatchMetrics"
|
||||
Effect = "Allow"
|
||||
Action = ["cloudwatch:PutMetricData"]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cloudwatch:namespace" = "bedrock-agentcore"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Bedrock Model Invocation
|
||||
{
|
||||
Sid = "BedrockModelInvocation"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# Workload Access Tokens
|
||||
{
|
||||
Sid = "GetAgentAccessToken"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CodeBuild Service Role - For Docker Image Building
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "image_build" {
|
||||
name = "${var.stack_name}-codebuild-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "codebuild.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-codebuild-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Inline policy for CodeBuild
|
||||
resource "aws_iam_role_policy" "image_build" {
|
||||
name = "CodeBuildPolicy"
|
||||
role = aws_iam_role.image_build.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
|
||||
},
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetAuthorizationToken",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload"
|
||||
]
|
||||
Resource = [
|
||||
aws_ecr_repository.agent_ecr.arn,
|
||||
"*"
|
||||
]
|
||||
},
|
||||
# S3 Source Access (for agent-code)
|
||||
{
|
||||
Sid = "S3SourceAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion"
|
||||
]
|
||||
Resource = "${aws_s3_bucket.agent_source.arn}/*"
|
||||
},
|
||||
{
|
||||
Sid = "S3BucketAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:ListBucket",
|
||||
"s3:GetBucketLocation"
|
||||
]
|
||||
Resource = aws_s3_bucket.agent_source.arn
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# AgentCore Runtime - Main Agent Runtime Resource
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "basic_agent" {
|
||||
agent_runtime_name = replace("${var.stack_name}_${var.agent_name}", "-", "_")
|
||||
description = var.description
|
||||
role_arn = aws_iam_role.agent_execution.arn
|
||||
|
||||
agent_runtime_artifact {
|
||||
container_configuration {
|
||||
container_uri = "${aws_ecr_repository.agent_ecr.repository_url}:${var.image_tag}"
|
||||
}
|
||||
}
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
environment_variables = merge(
|
||||
{
|
||||
AWS_REGION = var.aws_region
|
||||
AWS_DEFAULT_REGION = var.aws_region
|
||||
},
|
||||
var.environment_variables
|
||||
)
|
||||
|
||||
depends_on = [
|
||||
null_resource.trigger_build,
|
||||
aws_iam_role_policy.agent_execution,
|
||||
aws_iam_role_policy_attachment.agent_execution_managed
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
output "agent_runtime_id" {
|
||||
description = "ID of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_id
|
||||
}
|
||||
|
||||
output "agent_runtime_arn" {
|
||||
description = "ARN of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "agent_runtime_version" {
|
||||
description = "Version of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.basic_agent.agent_runtime_version
|
||||
}
|
||||
|
||||
output "ecr_repository_url" {
|
||||
description = "URL of the ECR repository"
|
||||
value = aws_ecr_repository.agent_ecr.repository_url
|
||||
}
|
||||
|
||||
output "ecr_repository_arn" {
|
||||
description = "ARN of the ECR repository"
|
||||
value = aws_ecr_repository.agent_ecr.arn
|
||||
}
|
||||
|
||||
output "agent_execution_role_arn" {
|
||||
description = "ARN of the agent execution role"
|
||||
value = aws_iam_role.agent_execution.arn
|
||||
}
|
||||
|
||||
output "codebuild_project_name" {
|
||||
description = "Name of the CodeBuild project"
|
||||
value = aws_codebuild_project.agent_image.name
|
||||
}
|
||||
|
||||
output "codebuild_project_arn" {
|
||||
description = "ARN of the CodeBuild project"
|
||||
value = aws_codebuild_project.agent_image.arn
|
||||
}
|
||||
|
||||
output "source_bucket_name" {
|
||||
description = "S3 bucket containing agent source code"
|
||||
value = aws_s3_bucket.agent_source.id
|
||||
}
|
||||
|
||||
output "source_bucket_arn" {
|
||||
description = "ARN of the S3 bucket containing agent source code"
|
||||
value = aws_s3_bucket.agent_source.arn
|
||||
}
|
||||
|
||||
output "source_object_key" {
|
||||
description = "S3 object key for the agent source code archive"
|
||||
value = aws_s3_object.agent_source.key
|
||||
}
|
||||
|
||||
output "source_code_md5" {
|
||||
description = "MD5 hash of the agent source code (triggers rebuild when changed)"
|
||||
value = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# ============================================================================
|
||||
# S3 Bucket for Agent Source Code (CDK Asset Equivalent)
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_s3_bucket" "agent_source" {
|
||||
bucket_prefix = "${var.stack_name}-agent-source-"
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-source"
|
||||
Purpose = "Store agent source code for CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# Block public access
|
||||
resource "aws_s3_bucket_public_access_block" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Enable versioning for source code tracking
|
||||
resource "aws_s3_bucket_versioning" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Archive and Upload Agent Source Code
|
||||
# ============================================================================
|
||||
|
||||
# Archive agent-code/ directory
|
||||
# This automatically detects ALL files including new ones
|
||||
data "archive_file" "agent_source" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/agent-code"
|
||||
output_path = "${path.module}/.terraform/agent-code.zip"
|
||||
}
|
||||
|
||||
# Upload to S3 (re-uploads when MD5 changes)
|
||||
resource "aws_s3_object" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
key = "agent-code-${data.archive_file.agent_source.output_md5}.zip"
|
||||
source = data.archive_file.agent_source.output_path
|
||||
etag = data.archive_file.agent_source.output_md5
|
||||
|
||||
tags = {
|
||||
Name = "agent-source-code"
|
||||
MD5 = data.archive_file.agent_source.output_md5
|
||||
Timestamp = timestamp()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Build and Verify Docker Image for AgentCore Runtime
|
||||
# ============================================================================
|
||||
# This script is called by Terraform during deployment to:
|
||||
# 1. Trigger CodeBuild to build the Docker image
|
||||
# 2. Wait for the build to complete
|
||||
# 3. Verify the image was successfully pushed to ECR
|
||||
#
|
||||
# Parameters:
|
||||
# $1 - CodeBuild project name
|
||||
# $2 - AWS region
|
||||
# $3 - ECR repository name
|
||||
# $4 - Image tag
|
||||
# $5 - ECR repository URL
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parameters
|
||||
PROJECT_NAME="$1"
|
||||
REGION="$2"
|
||||
REPO_NAME="$3"
|
||||
IMAGE_TAG="$4"
|
||||
REPO_URL="$5"
|
||||
|
||||
# ============================================================================
|
||||
# Print functions
|
||||
# ============================================================================
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[✓]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[⚠]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[✗]${NC} $1"
|
||||
}
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Start Build Process
|
||||
# ============================================================================
|
||||
|
||||
print_header "Building Docker Image for AgentCore Runtime"
|
||||
|
||||
print_info "CodeBuild Project: $PROJECT_NAME"
|
||||
print_info "Region: $REGION"
|
||||
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
|
||||
echo ""
|
||||
|
||||
# Start CodeBuild
|
||||
print_info "Starting CodeBuild project..."
|
||||
|
||||
BUILD_ID=$(aws codebuild start-build \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--region "$REGION" \
|
||||
--query 'build.id' \
|
||||
--output text 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
print_error "Failed to start CodeBuild"
|
||||
echo "$BUILD_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build started: $BUILD_ID"
|
||||
print_info "Waiting for build to complete (typically 5-10 minutes)..."
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Monitor Build Progress
|
||||
# ============================================================================
|
||||
|
||||
ATTEMPT=0
|
||||
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
|
||||
|
||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
|
||||
STATUS=$(aws codebuild batch-get-builds \
|
||||
--ids "$BUILD_ID" \
|
||||
--region "$REGION" \
|
||||
--query 'builds[0].buildStatus' \
|
||||
--output text 2>/dev/null)
|
||||
|
||||
if [ "$STATUS" != "IN_PROGRESS" ]; then
|
||||
print_info "Build process completed with status: $STATUS"
|
||||
break
|
||||
fi
|
||||
|
||||
# Progress indicator
|
||||
if [ $((ATTEMPT % 6)) -eq 0 ]; then
|
||||
MINUTES=$((ATTEMPT / 6))
|
||||
print_info "Build in progress... (${MINUTES} minutes elapsed)"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
||||
print_error "Build timeout after 10 minutes"
|
||||
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verify Image in ECR
|
||||
# ============================================================================
|
||||
|
||||
print_header "Verifying Docker Image in ECR"
|
||||
|
||||
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
|
||||
print_info "Waiting for ECR propagation..."
|
||||
echo ""
|
||||
|
||||
sleep 5 # Brief wait for ECR to register the push
|
||||
|
||||
VERIFY_ATTEMPT=0
|
||||
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
|
||||
|
||||
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
|
||||
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
|
||||
|
||||
if aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" >/dev/null 2>&1; then
|
||||
|
||||
print_success "Docker image successfully verified in ECR!"
|
||||
echo ""
|
||||
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
|
||||
|
||||
# Get image details
|
||||
IMAGE_SIZE=$(aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" \
|
||||
--query 'imageDetails[0].imageSizeInBytes' \
|
||||
--output text 2>/dev/null || echo "Unknown")
|
||||
|
||||
if [ "$IMAGE_SIZE" != "Unknown" ]; then
|
||||
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
|
||||
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Build and verification completed successfully!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
|
||||
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Error: Image Not Found
|
||||
# ============================================================================
|
||||
|
||||
print_error "Docker image not found in ECR after build completion"
|
||||
echo ""
|
||||
print_warning "This indicates the build or push step failed."
|
||||
print_info "Troubleshooting steps:"
|
||||
print_info " 1. Check CodeBuild logs:"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
print_info ""
|
||||
print_info " 2. Verify ECR repository:"
|
||||
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
|
||||
print_info ""
|
||||
print_info " 3. Check IAM permissions for CodeBuild role"
|
||||
|
||||
exit 1
|
||||
@@ -0,0 +1,27 @@
|
||||
# ============================================================================
|
||||
# Basic AgentCore Runtime - Example Configuration
|
||||
# ============================================================================
|
||||
# Copy this file to terraform.tfvars and customize the values
|
||||
# Example: cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Agent Configuration
|
||||
agent_name = "BasicAgent"
|
||||
stack_name = "agentcore-basic"
|
||||
description = "Basic agent runtime without memory, code interpreter, or browser"
|
||||
|
||||
# Network Configuration
|
||||
network_mode = "PUBLIC" # Options: PUBLIC, PRIVATE
|
||||
|
||||
# Container Configuration
|
||||
ecr_repository_name = "basic-agent"
|
||||
image_tag = "latest"
|
||||
|
||||
# AWS Configuration
|
||||
aws_region = "us-west-2"
|
||||
environment = "dev"
|
||||
|
||||
# Optional: Environment Variables for the Agent Runtime
|
||||
# environment_variables = {
|
||||
# LOG_LEVEL = "INFO"
|
||||
# CUSTOM_VAR = "value"
|
||||
# }
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic Agent Test Script
|
||||
|
||||
This script tests the Basic Agent with simple conversational prompts.
|
||||
The basic agent has no additional tools - just core Q&A capabilities.
|
||||
|
||||
Usage:
|
||||
python test_basic_agent.py <agent_arn>
|
||||
|
||||
agent_arn: ARN of the basic agent runtime (required)
|
||||
|
||||
Examples:
|
||||
# Test basic agent
|
||||
python test_basic_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/basic-agent-id
|
||||
|
||||
# From Terraform outputs
|
||||
python test_basic_agent.py $(terraform output -raw agent_runtime_arn)
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def extract_region_from_arn(arn):
|
||||
"""Extract AWS region from agent runtime ARN.
|
||||
|
||||
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
|
||||
|
||||
Args:
|
||||
arn: Agent runtime ARN string
|
||||
|
||||
Returns:
|
||||
str: AWS region code
|
||||
|
||||
Raises:
|
||||
ValueError: If ARN format is invalid or region cannot be extracted
|
||||
"""
|
||||
try:
|
||||
parts = arn.split(':')
|
||||
if len(parts) < 4:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
region = parts[3]
|
||||
if not region:
|
||||
raise ValueError(
|
||||
f"Region not found in ARN: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
return region
|
||||
|
||||
except IndexError:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
|
||||
def test_agent(client, agent_arn, test_name, prompt):
|
||||
"""Test the agent with a given prompt
|
||||
|
||||
Args:
|
||||
client: boto3 bedrock-agentcore client
|
||||
agent_arn: ARN of the agent runtime
|
||||
test_name: Name of the test for display
|
||||
prompt: The prompt to send to the agent
|
||||
|
||||
Returns:
|
||||
bool: True if test passed, False otherwise
|
||||
"""
|
||||
print(f"\n{'=' * 80}")
|
||||
print(f"TEST: {test_name}")
|
||||
print(f"{'=' * 80}\n")
|
||||
print(f"Prompt: '{prompt}'")
|
||||
print("-" * 80)
|
||||
|
||||
try:
|
||||
response = client.invoke_agent_runtime(
|
||||
agentRuntimeArn=agent_arn,
|
||||
qualifier="DEFAULT",
|
||||
payload=json.dumps({"prompt": prompt}),
|
||||
)
|
||||
|
||||
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
|
||||
print(f"Content Type: {response.get('contentType', 'N/A')}")
|
||||
|
||||
# Read the streaming response body
|
||||
response_text = ""
|
||||
if 'response' in response:
|
||||
response_body = response['response'].read()
|
||||
response_text = response_body.decode('utf-8')
|
||||
|
||||
if response_text:
|
||||
try:
|
||||
result = json.loads(response_text)
|
||||
response_content = result.get('response', response_text)
|
||||
print(f"\n✅ Response:\n{response_content}")
|
||||
except json.JSONDecodeError:
|
||||
print(f"\n✅ Response:\n{response_text}")
|
||||
else:
|
||||
print("\n⚠️ No response content received")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test execution function"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Error: Missing required agent ARN argument")
|
||||
print("\nUsage:")
|
||||
print(f" {sys.argv[0]} <agent_arn>")
|
||||
print("\nExample:")
|
||||
print(f" {sys.argv[0]} arn:aws:bedrock-agentcore:<region>:123456789012:runtime/agent-id")
|
||||
print("\nOr from Terraform:")
|
||||
print(f" {sys.argv[0]} $(terraform output -raw agent_runtime_arn)")
|
||||
sys.exit(1)
|
||||
|
||||
agent_arn = sys.argv[1]
|
||||
|
||||
# Extract region from ARN
|
||||
try:
|
||||
region = extract_region_from_arn(agent_arn)
|
||||
except ValueError as e:
|
||||
print(f"\n❌ ERROR: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
print("=" * 80)
|
||||
print("BASIC AGENT TEST SUITE")
|
||||
print("=" * 80)
|
||||
print(f"\nAgent ARN: {agent_arn}")
|
||||
print(f"Region: {region}\n")
|
||||
|
||||
# Initialize boto3 client with extracted region
|
||||
client = boto3.client("bedrock-agentcore", region_name=region)
|
||||
|
||||
# Test cases for basic agent (no tools, just Q&A)
|
||||
tests = [
|
||||
{
|
||||
"name": "Simple Greeting",
|
||||
"prompt": "Hello! Can you introduce yourself?",
|
||||
},
|
||||
{
|
||||
"name": "Reasoning Task",
|
||||
"prompt": "Explain what cloud computing is in simple terms and list three key benefits.",
|
||||
},
|
||||
]
|
||||
|
||||
# Run all tests
|
||||
results = []
|
||||
for test in tests:
|
||||
passed = test_agent(client, agent_arn, test["name"], test["prompt"])
|
||||
results.append({"name": test["name"], "passed": passed})
|
||||
|
||||
# Print summary
|
||||
print(f"\n{'=' * 80}")
|
||||
print("TEST SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
for result in results:
|
||||
status = "✅ PASSED" if result["passed"] else "❌ FAILED"
|
||||
print(f"{status} - {result['name']}")
|
||||
|
||||
# Overall result
|
||||
passed_count = sum(1 for r in results if r["passed"])
|
||||
total_count = len(results)
|
||||
|
||||
print(f"\n{'=' * 80}")
|
||||
if passed_count == total_count:
|
||||
print("✅ ALL TESTS PASSED")
|
||||
else:
|
||||
print(f"⚠️ {passed_count}/{total_count} TESTS PASSED")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if passed_count == total_count else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
variable "agent_name" {
|
||||
description = "Name for the agent runtime"
|
||||
type = string
|
||||
default = "BasicAgent"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
|
||||
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "network_mode" {
|
||||
description = "Network mode for AgentCore resources"
|
||||
type = string
|
||||
default = "PUBLIC"
|
||||
|
||||
validation {
|
||||
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
|
||||
error_message = "Network mode must be either PUBLIC or PRIVATE."
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Docker image tag"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "aws_region" {
|
||||
description = "AWS region for deployment (REQUIRED)"
|
||||
type = string
|
||||
# No default - must be explicitly provided
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
|
||||
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "stack_name" {
|
||||
description = "Stack name for resource naming"
|
||||
type = string
|
||||
default = "agentcore-basic"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Description of the agent runtime"
|
||||
type = string
|
||||
default = "Basic agent runtime without memory, code interpreter, or browser"
|
||||
}
|
||||
|
||||
variable "environment_variables" {
|
||||
description = "Environment variables for the agent runtime"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "ecr_repository_name" {
|
||||
description = "Name of the ECR repository"
|
||||
type = string
|
||||
default = "basic-agent"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.21"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = "~> 0.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "AgentCore"
|
||||
Pattern = "basic-runtime"
|
||||
Environment = var.environment
|
||||
StackName = var.stack_name
|
||||
ManagedBy = "Terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Terraform files
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
!terraform.tfvars.example
|
||||
tfplan
|
||||
|
||||
# Generated archives
|
||||
*.zip
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,646 @@
|
||||
# End-to-End Weather Agent on Amazon Bedrock AgentCore (Terraform)
|
||||
|
||||
This Terraform module deploys a comprehensive weather agent using Amazon Bedrock AgentCore Runtime with integrated AgentCore tools (Browser, Code Interpreter, and Memory).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [What's Included](#whats-included)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Deployment Process](#deployment-process)
|
||||
- [Authentication Model](#authentication-model)
|
||||
- [Testing](#testing)
|
||||
- [Agent Capabilities](#agent-capabilities)
|
||||
- [Customization](#customization)
|
||||
- [File Structure](#file-structure)
|
||||
- [Monitoring and Observability](#monitoring-and-observability)
|
||||
- [Security](#security)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Advanced Topics](#advanced-topics)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Resources](#resources)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
This pattern demonstrates deploying a full-featured weather agent with Amazon Bedrock AgentCore tools, enabling sophisticated weather intelligence capabilities through web browsing, code execution, and persistent memory.
|
||||
|
||||
**Key Features:**
|
||||
- Integrated AgentCore tools (Browser, Code Interpreter, Memory)
|
||||
- Automated Docker image building via CodeBuild
|
||||
- S3-based artifact storage for analysis results
|
||||
- IAM-based security with tool-specific permissions
|
||||
- Weather data visualization and analysis capabilities
|
||||
|
||||
This makes it ideal for:
|
||||
- Building intelligent weather information systems
|
||||
- Creating data analysis pipelines with code execution
|
||||
- Implementing web scraping for real-time weather data
|
||||
- Learning AgentCore tool integration patterns
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
### System Components
|
||||
|
||||
**Weather Agent Runtime**
|
||||
- Processes natural language weather queries
|
||||
- Coordinates multiple tools for comprehensive responses
|
||||
- Generates visualizations and detailed analyses
|
||||
- Maintains conversation context across sessions
|
||||
|
||||
**AgentCore Tools**
|
||||
|
||||
1. **Browser Tool**
|
||||
- Accesses weather websites and advisories
|
||||
- Scrapes real-time weather data
|
||||
- Retrieves alerts and warnings
|
||||
- Checks weather-related news
|
||||
|
||||
2. **Code Interpreter Tool**
|
||||
- Executes Python for data analysis
|
||||
- Creates weather visualizations (charts, graphs)
|
||||
- Performs statistical calculations
|
||||
- Processes and transforms weather data
|
||||
|
||||
3. **Memory**
|
||||
- Stores conversation history
|
||||
- Remembers user preferences
|
||||
- Maintains context across sessions
|
||||
- Event expiry: 30 days
|
||||
|
||||
### Tool Integration
|
||||
|
||||
The agent seamlessly coordinates all tools:
|
||||
- **Query Processing**: Understands weather-related requests
|
||||
- **Tool Selection**: Chooses appropriate tools automatically
|
||||
- **Data Analysis**: Uses Code Interpreter for complex calculations
|
||||
- **Web Access**: Employs Browser for real-time information
|
||||
- **Context Retention**: Leverages Memory for personalized responses
|
||||
|
||||
## What's Included
|
||||
|
||||
This Terraform configuration creates:
|
||||
|
||||
- **2 S3 Buckets**:
|
||||
- Source code storage with versioning
|
||||
- Results bucket for analysis artifacts
|
||||
- **1 ECR Repository**: Container registry for ARM64 Docker image
|
||||
- **1 CodeBuild Project**: Automated image building and pushing
|
||||
- **2 IAM Roles**:
|
||||
- Agent execution role (with tool permissions)
|
||||
- CodeBuild service role
|
||||
- **1 Agent Runtime**: Weather agent with tool integration
|
||||
- **3 AgentCore Tools**:
|
||||
- Browser for web access
|
||||
- Code Interpreter for analysis
|
||||
- Memory for context retention
|
||||
- **Build Automation**: Automatic rebuild on code changes (MD5-based detection)
|
||||
- **Supporting Resources**: S3 lifecycle policies, ECR lifecycle policies, IAM policies
|
||||
|
||||
**Total:** ~20 AWS resources deployed and managed by Terraform
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Tools
|
||||
|
||||
1. **Terraform** (>= 1.6)
|
||||
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
|
||||
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
|
||||
|
||||
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
|
||||
|
||||
2. **AWS CLI** (configured with credentials)
|
||||
```bash
|
||||
aws configure
|
||||
```
|
||||
|
||||
3. **Python 3.11+** (for testing scripts and memory initialization)
|
||||
```bash
|
||||
python --version # Verify Python 3.11 or later
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
**Note**: `boto3` is required for:
|
||||
- Running test scripts (`test_weather_agent.py`)
|
||||
- Automatic memory initialization during deployment
|
||||
|
||||
4. **Docker** (for local testing, optional)
|
||||
|
||||
### AWS Account Requirements
|
||||
|
||||
- AWS Account with appropriate permissions
|
||||
- Access to Amazon Bedrock AgentCore service
|
||||
- Permissions to create:
|
||||
- S3 buckets
|
||||
- ECR repositories
|
||||
- CodeBuild projects
|
||||
- IAM roles and policies
|
||||
- AgentCore Runtime resources
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Configure Variables
|
||||
|
||||
Copy the example variables file and customize:
|
||||
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
```
|
||||
|
||||
Edit `terraform.tfvars` with your preferred values:
|
||||
- `agent_name`: Name for the weather agent (default: "WeatherAgent")
|
||||
- `memory_name`: Name for memory resource (default: "WeatherAgentMemory")
|
||||
- `stack_name`: Stack identifier (default: "agentcore-weather")
|
||||
- `aws_region`: AWS region for deployment (default: "us-west-2")
|
||||
- `network_mode`: PUBLIC or PRIVATE networking
|
||||
|
||||
### 2. Initialize Terraform
|
||||
|
||||
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
|
||||
|
||||
**Quick start with local state:**
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
**Method 1: Using Deploy Script (Recommended)**
|
||||
|
||||
```bash
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The script validates configuration, shows the plan, and deploys all resources.
|
||||
|
||||
**Method 2: Direct Terraform Commands**
|
||||
|
||||
```bash
|
||||
terraform plan
|
||||
terraform apply
|
||||
```
|
||||
|
||||
**Note**: Deployment includes creating infrastructure, building the Docker image, and provisioning all AgentCore tools. Total deployment time: **~8-12 minutes**
|
||||
|
||||
### 4. Verify Deployment
|
||||
|
||||
```bash
|
||||
# View all outputs
|
||||
terraform output
|
||||
|
||||
# Get Agent and Tool IDs
|
||||
terraform output agent_runtime_arn
|
||||
terraform output browser_id
|
||||
terraform output code_interpreter_id
|
||||
terraform output memory_id
|
||||
```
|
||||
|
||||
## Deployment Process
|
||||
|
||||
### Build and Deployment Sequence
|
||||
|
||||
The deployment follows this sequence:
|
||||
|
||||
```
|
||||
1. S3 Buckets Creation (source & results)
|
||||
2. ECR Repository Creation
|
||||
3. IAM Roles Creation (with tool permissions)
|
||||
4. AgentCore Tools Creation (Browser, Code Interpreter, Memory)
|
||||
5. CodeBuild Project Creation
|
||||
6. Docker Image Build
|
||||
7. Weather Agent Runtime Creation (with tool IDs as environment variables)
|
||||
```
|
||||
|
||||
**Tool Integration:**
|
||||
- Agent receives tool IDs as environment variables
|
||||
- IAM role grants permissions for tool operations
|
||||
- Tools are created before agent runtime
|
||||
- Results bucket for code interpreter outputs
|
||||
|
||||
### Build Triggers
|
||||
|
||||
The infrastructure automatically triggers Docker image builds:
|
||||
- When source code changes (MD5 hash detection)
|
||||
- When infrastructure changes require rebuild
|
||||
- Build typically takes 5-8 minutes
|
||||
|
||||
### Memory Initialization
|
||||
|
||||
Memory is automatically initialized with activity preferences (good/ok/poor weather activities) via `scripts/init-memory.py` during deployment. The agent uses these preferences for weather-based activity recommendations.
|
||||
|
||||
### Observability
|
||||
|
||||
Full observability is automatically configured with CloudWatch Logs (14-day retention) and X-Ray traces. Logs are delivered via vended logs delivery to `/aws/vendedlogs/bedrock-agentcore/${runtime_id}`. Access via CloudWatch Console or `aws logs tail` command.
|
||||
|
||||
## Authentication Model
|
||||
|
||||
This pattern uses **IAM-based authentication with workload identity tokens**:
|
||||
|
||||
- **Service Principal**: Agent assumes IAM role via `bedrock-agentcore.amazonaws.com`
|
||||
- **Workload Identity**: Agent obtains access tokens for secure operations
|
||||
- **Tool Access**: IAM permissions for Browser, Code Interpreter, and Memory
|
||||
- **S3 Access**: Agent can write analysis results to S3 bucket
|
||||
|
||||
**Note**: This is a backend infrastructure pattern with no user authentication layer. For user-facing applications, you would add Cognito or API Gateway authorizers separately.
|
||||
|
||||
## Testing
|
||||
|
||||
The included `test_weather_agent.py` script is **infrastructure-agnostic** and works with any deployment method (Terraform, CDK, CloudFormation, or manual).
|
||||
|
||||
### Prerequisites for Testing
|
||||
|
||||
Before testing, ensure you have the required packages installed:
|
||||
|
||||
**Option A: Using uv (Recommended)**
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Option B: System-wide installation**
|
||||
```bash
|
||||
pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Note**: `boto3` is required for the test script to invoke the agent runtime via AWS API.
|
||||
|
||||
### Basic Testing
|
||||
|
||||
```bash
|
||||
# Get ARN from Terraform
|
||||
AGENT_ARN=$(terraform output -raw agent_runtime_arn)
|
||||
|
||||
# Test the agent
|
||||
python test_weather_agent.py $AGENT_ARN
|
||||
```
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
The script runs two comprehensive tests:
|
||||
1. **Simple Weather Query**: Basic weather information request
|
||||
2. **Complex Query with Tools**: Demonstrates browser, code interpreter, and memory usage together
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
TEST 1: Simple Weather Query ✅
|
||||
TEST 2: Complex Query with Tools ✅
|
||||
|
||||
✅ ALL TESTS PASSED
|
||||
```
|
||||
|
||||
## Agent Capabilities
|
||||
|
||||
### Weather Agent
|
||||
|
||||
**Core Capabilities:**
|
||||
- Natural language weather queries
|
||||
- Real-time weather data access
|
||||
- Historical weather analysis
|
||||
- Weather forecasting insights
|
||||
- Multi-location comparisons
|
||||
- Travel weather planning
|
||||
|
||||
**Integrated Tools:**
|
||||
|
||||
1. **Browser Tool**
|
||||
- Access weather.gov, weather.com, and other weather sites
|
||||
- Retrieve current conditions and forecasts
|
||||
- Check weather alerts and warnings
|
||||
- Gather radar and satellite imagery links
|
||||
|
||||
2. **Code Interpreter Tool**
|
||||
- Analyze temperature trends
|
||||
- Create weather visualizations (line charts, bar graphs)
|
||||
- Calculate statistics (averages, extremes)
|
||||
- Process historical weather data
|
||||
- Generate custom weather reports
|
||||
|
||||
3. **Memory**
|
||||
- Remember user location preferences
|
||||
- Track frequently requested locations
|
||||
- Maintain conversation context
|
||||
- Store user preferences for units (F/C)
|
||||
- Pre-initialized with activity preferences for weather-based recommendations
|
||||
|
||||
### Example Interactions
|
||||
|
||||
**Simple Query:**
|
||||
```
|
||||
User: "What's the weather like in Seattle?"
|
||||
Agent: [Uses Browser] Provides current conditions, forecast, temperature
|
||||
```
|
||||
|
||||
**Analysis Query:**
|
||||
```
|
||||
User: "Compare temperatures between New York and Miami over the past week"
|
||||
Agent: [Uses Browser + Code Interpreter] Fetches data, creates comparison chart
|
||||
```
|
||||
|
||||
**Planning Query:**
|
||||
```
|
||||
User: "I'm planning a road trip from Boston to Miami next week. What should I expect?"
|
||||
Agent: [Uses Browser + Memory] Provides route-based weather forecast, remembers trip details
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify Agent Code
|
||||
|
||||
1. **Edit Agent Files**
|
||||
```bash
|
||||
vim agent-code/weather_agent.py
|
||||
vim agent-code/requirements.txt
|
||||
vim agent-code/Dockerfile
|
||||
```
|
||||
|
||||
2. **Redeploy**
|
||||
```bash
|
||||
terraform apply # Automatically detects changes and rebuilds
|
||||
```
|
||||
|
||||
### Add Additional AgentCore Tools
|
||||
|
||||
To add more tools:
|
||||
1. Create new tool resource file (e.g., `gateway.tf`)
|
||||
2. Add tool ID to agent environment variables in `main.tf`
|
||||
3. Update IAM permissions in `iam.tf` if needed
|
||||
4. Update `outputs.tf` with new tool outputs
|
||||
|
||||
### Modify Network Configuration
|
||||
|
||||
Change from PUBLIC to PRIVATE networking:
|
||||
|
||||
```hcl
|
||||
# terraform.tfvars
|
||||
network_mode = "PRIVATE"
|
||||
```
|
||||
|
||||
Requires VPC configuration (not included in this module).
|
||||
|
||||
### Customize Memory Settings
|
||||
|
||||
Adjust memory retention period:
|
||||
|
||||
```hcl
|
||||
# memory.tf
|
||||
event_expiry_duration = 60 # Change from 30 to 60 days
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
end-to-end-weather-agent/
|
||||
├── agent-code/ # Weather agent source code
|
||||
│ ├── weather_agent.py # Agent implementation
|
||||
│ ├── requirements.txt # Python dependencies
|
||||
│ └── Dockerfile # Container definition
|
||||
├── scripts/ # Build automation and initialization
|
||||
│ ├── build-image.sh # Docker build script
|
||||
│ └── init-memory.py # Memory initialization script
|
||||
├── main.tf # Agent runtime configuration
|
||||
├── browser.tf # Browser tool
|
||||
├── code_interpreter.tf # Code interpreter tool
|
||||
├── memory.tf # Memory resource
|
||||
├── memory-init.tf # Memory initialization automation
|
||||
├── observability.tf # CloudWatch Logs and X-Ray traces
|
||||
├── iam.tf # IAM roles and policies
|
||||
├── s3.tf # S3 buckets
|
||||
├── ecr.tf # ECR repository
|
||||
├── codebuild.tf # CodeBuild project
|
||||
├── outputs.tf # Output values
|
||||
├── variables.tf # Input variables
|
||||
├── versions.tf # Provider versions
|
||||
├── buildspec.yml # CodeBuild specification
|
||||
├── test_weather_agent.py # Infrastructure-agnostic test script
|
||||
├── deploy.sh # Deployment automation
|
||||
├── destroy.sh # Cleanup automation
|
||||
├── terraform.tfvars.example # Example configuration
|
||||
├── backend.tf.example # Remote state example
|
||||
├── .gitignore # Git exclusions
|
||||
├── architecture.png # Architecture diagram
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### CloudWatch Logs (Automatic)
|
||||
|
||||
Terraform automatically creates CloudWatch Log Group with vended logs delivery:
|
||||
|
||||
```bash
|
||||
# Get log group name
|
||||
LOG_GROUP=$(terraform output -raw log_group_name)
|
||||
|
||||
# Tail agent logs
|
||||
aws logs tail $LOG_GROUP --follow
|
||||
|
||||
# CodeBuild logs
|
||||
aws logs tail /aws/codebuild/agentcore-weather-agent-build --follow
|
||||
```
|
||||
|
||||
### X-Ray Traces (Automatic)
|
||||
|
||||
Distributed tracing automatically delivered to X-Ray. View traces in [X-Ray Console](https://console.aws.amazon.com/xray/home).
|
||||
|
||||
### Metrics
|
||||
|
||||
Access metrics in CloudWatch:
|
||||
- Agent invocation count
|
||||
- Tool usage frequency (Browser, Code Interpreter, Memory)
|
||||
- Agent execution duration
|
||||
- Error rates
|
||||
- Code interpreter execution time
|
||||
|
||||
### AWS Console
|
||||
|
||||
Monitor in AWS Console:
|
||||
- **CloudWatch Logs**: Vended logs at `/aws/vendedlogs/bedrock-agentcore/${runtime_id}`
|
||||
- **X-Ray**: Distributed request traces
|
||||
- **Bedrock AgentCore**: [Console Link](https://console.aws.amazon.com/bedrock/home#/agentcore)
|
||||
- **ECR Repository**: Docker images
|
||||
- **CodeBuild**: Build status
|
||||
- **S3 Results Bucket**: Generated artifacts
|
||||
|
||||
## Security
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
**Agent Execution Role:**
|
||||
- Standard AgentCore permissions
|
||||
- ECR image pull access
|
||||
- S3 read/write for results bucket
|
||||
- CloudWatch Logs write access
|
||||
- Tool-specific permissions (Browser, Code Interpreter, Memory access)
|
||||
|
||||
**CodeBuild Role:**
|
||||
- S3 access to source bucket
|
||||
- ECR push access
|
||||
- CloudWatch Logs write access
|
||||
|
||||
### Network Security
|
||||
|
||||
- Agent runs in specified network mode (PUBLIC/PRIVATE)
|
||||
- ECR repository has account-level access controls
|
||||
- S3 buckets block public access
|
||||
- IAM policies follow least-privilege principle
|
||||
- Tool resources use network isolation
|
||||
|
||||
### Secrets Management
|
||||
|
||||
For sensitive data:
|
||||
- Use AWS Secrets Manager
|
||||
- Pass secret ARNs as environment variables
|
||||
- Retrieve secrets at runtime in agent code
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: Agent cannot access tools
|
||||
- **Solution**: Verify tool IDs are set as environment variables
|
||||
- **Check**: IAM permissions include tool access
|
||||
|
||||
**Issue**: Code Interpreter fails
|
||||
- **Solution**: Check CloudWatch logs for Python errors
|
||||
- **Check**: Verify results bucket permissions
|
||||
|
||||
**Issue**: Browser tool times out
|
||||
- **Solution**: Check network connectivity
|
||||
- **Check**: Verify target websites are accessible
|
||||
|
||||
**Issue**: Build fails
|
||||
- **Solution**: Check CodeBuild logs in CloudWatch
|
||||
- **Check**: Verify source code is in correct directory
|
||||
|
||||
**Issue**: Runtime not created
|
||||
- **Solution**: Verify ECR image exists and is tagged correctly
|
||||
- **Check**: Review Terraform state for errors
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check Terraform state
|
||||
terraform show
|
||||
|
||||
# Validate configuration
|
||||
terraform validate
|
||||
|
||||
# View specific resource
|
||||
terraform state show aws_bedrockagentcore_agent_runtime.agent
|
||||
|
||||
# Get tool IDs
|
||||
terraform output browser_id
|
||||
terraform output code_interpreter_id
|
||||
terraform output memory_id
|
||||
|
||||
# Check results bucket
|
||||
aws s3 ls s3://$(terraform output -raw results_bucket_name)/
|
||||
|
||||
# Get detailed build logs
|
||||
PROJECT_NAME=$(terraform output -raw agent_codebuild_project_name)
|
||||
aws codebuild batch-get-builds --ids $(aws codebuild list-builds-for-project --project-name $PROJECT_NAME --query 'ids[0]' --output text)
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Automated Cleanup
|
||||
|
||||
```bash
|
||||
chmod +x destroy.sh
|
||||
./destroy.sh
|
||||
```
|
||||
|
||||
The script shows the destruction plan, requires confirmation, and destroys all resources.
|
||||
|
||||
### Manual Cleanup
|
||||
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
**Important**: Verify in AWS Console that all resources are deleted:
|
||||
- Bedrock AgentCore runtimes
|
||||
- ECR repositories
|
||||
- S3 buckets
|
||||
- CodeBuild projects
|
||||
- IAM roles
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Adding Custom Tools
|
||||
|
||||
1. Define tool schema in agent code
|
||||
2. Implement tool handler function
|
||||
3. Register tool with agent
|
||||
4. Rebuild and deploy
|
||||
|
||||
### Implementing Memory
|
||||
|
||||
Add session management in agent code:
|
||||
```python
|
||||
session_data = {}
|
||||
|
||||
def handle_request(input_text, session_id):
|
||||
if session_id not in session_data:
|
||||
session_data[session_id] = {}
|
||||
# Use session_data for context
|
||||
```
|
||||
|
||||
### Multi-Region Deployment
|
||||
|
||||
For multi-region:
|
||||
1. Configure backend for state locking
|
||||
2. Deploy to each region separately
|
||||
3. Use Route53 for failover
|
||||
4. Consider cross-region replication for S3/ECR
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Test the deployment**
|
||||
```bash
|
||||
python test_weather_agent.py $(terraform output -raw agent_runtime_arn)
|
||||
```
|
||||
|
||||
2. **Customize the agent** for your specific use case
|
||||
- Modify weather data sources
|
||||
- Add custom weather analysis logic
|
||||
- Integrate additional external APIs
|
||||
- Enhance visualization capabilities
|
||||
|
||||
3. **Explore related patterns**
|
||||
- [Multi-Agent Runtime](../multi-agent-runtime/) - Agent-to-Agent communication
|
||||
- [MCP Server Pattern](../mcp-server-agentcore-runtime/) - MCP protocol with JWT auth
|
||||
- [AgentCore Samples](https://github.com/aws-samples/amazon-bedrock-agentcore-samples) - More examples
|
||||
|
||||
4. **Add production features**
|
||||
- Monitoring and alerting
|
||||
- Custom authentication layer
|
||||
- VPC deployment for private networking
|
||||
- CI/CD pipeline integration
|
||||
- Rate limiting and throttling
|
||||
|
||||
## Resources
|
||||
|
||||
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
|
||||
- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [Agent-to-Agent Communication](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-a2a.html)
|
||||
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT-0 license. See the [LICENSE](../../../LICENSE) file for details.
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt && \
|
||||
pip install --no-cache-dir aws-opentelemetry-distro==0.10.1
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 bedrock_agentcore
|
||||
USER bedrock_agentcore
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8000
|
||||
|
||||
COPY . .
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/ping || exit 1
|
||||
|
||||
CMD ["opentelemetry-instrument", "python", "-m", "weather_agent"]
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
strands-agents
|
||||
strands-agents-tools
|
||||
uv
|
||||
boto3
|
||||
bedrock-agentcore
|
||||
bedrock-agentcore-starter-toolkit
|
||||
browser-use==0.3.2
|
||||
langchain-aws>=0.1.0
|
||||
rich
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
from strands import Agent, tool
|
||||
from strands_tools import use_aws
|
||||
from typing import Dict, Any
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
|
||||
from bedrock_agentcore.tools.browser_client import BrowserClient
|
||||
from browser_use import Agent as BrowserAgent
|
||||
from browser_use.browser.session import BrowserSession
|
||||
from browser_use.browser import BrowserProfile
|
||||
from langchain_aws import ChatBedrockConverse
|
||||
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter
|
||||
from bedrock_agentcore.memory import MemoryClient
|
||||
from rich.console import Console
|
||||
import re
|
||||
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
console = Console()
|
||||
|
||||
# Configuration - All required, no defaults
|
||||
BROWSER_ID = os.getenv('BROWSER_ID')
|
||||
CODE_INTERPRETER_ID = os.getenv('CODE_INTERPRETER_ID')
|
||||
MEMORY_ID = os.getenv('MEMORY_ID')
|
||||
RESULTS_BUCKET = os.getenv('RESULTS_BUCKET')
|
||||
AWS_REGION = os.getenv('AWS_REGION')
|
||||
|
||||
# Validate required environment variables
|
||||
required_vars = {
|
||||
'BROWSER_ID': BROWSER_ID,
|
||||
'CODE_INTERPRETER_ID': CODE_INTERPRETER_ID,
|
||||
'MEMORY_ID': MEMORY_ID,
|
||||
'RESULTS_BUCKET': RESULTS_BUCKET,
|
||||
'AWS_REGION': AWS_REGION
|
||||
}
|
||||
missing = [k for k, v in required_vars.items() if not v]
|
||||
if missing:
|
||||
raise EnvironmentError(f"Required environment variables not set: {', '.join(missing)}")
|
||||
|
||||
# Async helper functions
|
||||
async def run_browser_task(browser_session, bedrock_chat, task: str) -> str:
|
||||
"""Run a browser automation task using browser_use"""
|
||||
try:
|
||||
console.print(f"[blue]🤖 Executing browser task:[/blue] {task[:100]}...")
|
||||
|
||||
agent = BrowserAgent(
|
||||
task=task,
|
||||
llm=bedrock_chat,
|
||||
browser=browser_session
|
||||
)
|
||||
|
||||
result = await agent.run()
|
||||
console.print("[green]✅ Browser task completed successfully![/green]")
|
||||
|
||||
if 'done' in result.last_action() and 'text' in result.last_action()['done']:
|
||||
return result.last_action()['done']['text']
|
||||
else:
|
||||
raise ValueError("NO Data")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ Browser task error: {e}[/red]")
|
||||
raise
|
||||
|
||||
async def initialize_browser_session():
|
||||
"""Initialize Browser-use session with AgentCore WebSocket connection"""
|
||||
try:
|
||||
client = BrowserClient(AWS_REGION)
|
||||
client.start(identifier=BROWSER_ID)
|
||||
|
||||
ws_url, headers = client.generate_ws_headers()
|
||||
console.print(f"[cyan]🔗 Browser WebSocket URL: {ws_url[:50]}...[/cyan]")
|
||||
|
||||
browser_profile = BrowserProfile(
|
||||
headers=headers,
|
||||
timeout=150000,
|
||||
)
|
||||
|
||||
browser_session = BrowserSession(
|
||||
cdp_url=ws_url,
|
||||
browser_profile=browser_profile,
|
||||
keep_alive=True
|
||||
)
|
||||
|
||||
console.print("[cyan]🔄 Initializing browser session...[/cyan]")
|
||||
await browser_session.start()
|
||||
|
||||
bedrock_chat = ChatBedrockConverse(
|
||||
model_id="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
region_name=AWS_REGION
|
||||
)
|
||||
|
||||
console.print("[green]✅ Browser session initialized and ready[/green]")
|
||||
return browser_session, bedrock_chat, client
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ Failed to initialize browser session: {e}[/red]")
|
||||
raise
|
||||
|
||||
# Tools for Strands Agent
|
||||
@tool
|
||||
async def get_weather_data(city: str) -> Dict[str, Any]:
|
||||
"""Get weather data for a city using browser automation"""
|
||||
browser_session = None
|
||||
|
||||
try:
|
||||
console.print(f"[cyan]🌐 Getting weather data for {city}[/cyan]")
|
||||
|
||||
browser_session, bedrock_chat, browser_client = await initialize_browser_session()
|
||||
|
||||
task = f"""Instruction: Extract 8-Day Weather Forecast for {city} from weather.gov
|
||||
Steps:
|
||||
- Go to https://weather.gov.
|
||||
- Enter "{city}" into the search box and Click on `GO` to execute the search.
|
||||
- On the local forecast page, click the "Printable Forecast" link.
|
||||
- Wait for the printable forecast page to load completely.
|
||||
- For each day in the forecast, extract these fields:
|
||||
- date (format YYYY-MM-DD)
|
||||
- high (highest temperature)
|
||||
- low (lowest temperature)
|
||||
- conditions (short weather summary, e.g., "Clear")
|
||||
- wind (wind speed as an integer; use mph or km/h as consistent)
|
||||
- precip (precipitation chance or amount, zero if none)
|
||||
- Format the extracted data as a JSON array of daily forecast objects, e.g.:
|
||||
```json
|
||||
[
|
||||
{{
|
||||
"date": "2025-09-17",
|
||||
"high": 78,
|
||||
"low": 62,
|
||||
"conditions": "Clear",
|
||||
"wind": 10,
|
||||
"precip": 80
|
||||
}},
|
||||
{{
|
||||
"date": "2025-09-18",
|
||||
"high": 82,
|
||||
"low": 65,
|
||||
"conditions": "Partly Cloudy",
|
||||
"wind": 10,
|
||||
"precip": 80
|
||||
|
||||
}}
|
||||
// ... Repeat for each day ...
|
||||
]```
|
||||
|
||||
- Return only this JSON array as the final output.
|
||||
|
||||
Additional Notes:
|
||||
Use null or 0 if any numeric value is missing.
|
||||
Avoid scraping ads, navigation, or unrelated page elements.
|
||||
If "Printable Forecast" is missing, fallback to the main forecast page.
|
||||
Include error handling (e.g., return an empty array if forecast data isn't found).
|
||||
Confirm the city name matches the requested location before returning results.
|
||||
"""
|
||||
|
||||
result = await run_browser_task(browser_session, bedrock_chat, task)
|
||||
|
||||
if browser_client :
|
||||
browser_client.stop()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"content": [{"text": result}]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ Error getting weather data: {e}[/red]")
|
||||
return {
|
||||
"status": "error",
|
||||
"content": [{"text": f"Error getting weather data: {str(e)}"}]
|
||||
}
|
||||
|
||||
finally:
|
||||
if browser_session:
|
||||
console.print("[yellow]🔌 Closing browser session...[/yellow]")
|
||||
with suppress(Exception):
|
||||
await browser_session.close()
|
||||
console.print("[green]✅ Browser session closed[/green]")
|
||||
|
||||
@tool
|
||||
def generate_analysis_code(weather_data: str) -> Dict[str, Any]:
|
||||
"""Generate Python code for weather classification"""
|
||||
try:
|
||||
query = f"""Create Python code to classify weather days as GOOD/OK/POOR:
|
||||
|
||||
Rules:
|
||||
- GOOD: 65-80°F, clear conditions, no rain
|
||||
- OK: 55-85°F, partly cloudy, slight rain chance
|
||||
- POOR: <55°F or >85°F, cloudy/rainy
|
||||
|
||||
Weather data:
|
||||
{weather_data}
|
||||
|
||||
Store weather data stored in python variable for using it in python code
|
||||
|
||||
Return code that outputs list of tuples: [('2025-09-16', 'GOOD'), ('2025-09-17', 'OK'), ...]"""
|
||||
|
||||
agent = Agent()
|
||||
result = agent(query)
|
||||
|
||||
pattern = r'```(?:json|python)\n(.*?)\n```'
|
||||
match = re.search(pattern, result.message['content'][0]['text'], re.DOTALL)
|
||||
python_code = match.group(1).strip() if match else result.message['content'][0]['text']
|
||||
|
||||
return {"status": "success", "content": [{"text": python_code}]}
|
||||
except Exception as e:
|
||||
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
|
||||
|
||||
@tool
|
||||
def execute_code(python_code: str) -> Dict[str, Any]:
|
||||
"""Execute Python code using AgentCore Code Interpreter"""
|
||||
try:
|
||||
code_client = CodeInterpreter(AWS_REGION)
|
||||
code_client.start(identifier=CODE_INTERPRETER_ID)
|
||||
|
||||
response = code_client.invoke("executeCode", {
|
||||
"code": python_code,
|
||||
"language": "python",
|
||||
"clearContext": True
|
||||
})
|
||||
|
||||
for event in response["stream"]:
|
||||
code_execute_result = json.dumps(event["result"])
|
||||
|
||||
analysis_results = json.loads(code_execute_result)
|
||||
console.print("Analysis results:", analysis_results)
|
||||
|
||||
return {"status": "success", "content": [{"text": str(analysis_results)}]}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
|
||||
|
||||
@tool
|
||||
def get_activity_preferences() -> Dict[str, Any]:
|
||||
"""Get activity preferences from memory"""
|
||||
try:
|
||||
client = MemoryClient(region_name=AWS_REGION)
|
||||
response = client.list_events(
|
||||
memory_id=MEMORY_ID,
|
||||
actor_id="user123",
|
||||
session_id="session456",
|
||||
max_results=50,
|
||||
include_payload=True
|
||||
)
|
||||
|
||||
preferences = response[0]["payload"][0]['blob'] if response else "No preferences found"
|
||||
return {"status": "success", "content": [{"text": preferences}]}
|
||||
except Exception as e:
|
||||
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
|
||||
|
||||
def create_weather_agent() -> Agent:
|
||||
"""Create the weather agent with all tools"""
|
||||
system_prompt = f"""You are a Weather-Based Activity Planning Assistant.
|
||||
|
||||
When a user asks about activities for a location, follow below stepes Sequentially:
|
||||
1. Extract city from user query
|
||||
2. Call get_weather_data(city) to get weather information
|
||||
3. Call generate_analysis_code(weather_data) to create classification code
|
||||
4. Call execute_code(python_code) to get Day Type ( GOOD, OK , POOR ) for forecasting dates.
|
||||
5. Call get_activity_preferences() to get user preferences
|
||||
6. Generate Activity Recommendations based on weather and preferences that you have recieved from previous steps
|
||||
7. Generate the comprehensive Markdown file (results.md) and store it in S3 Bucket : {RESULTS_BUCKET} through use_aws tool.
|
||||
|
||||
IMPORTANT: Provide complete recommendations and end your response. Do NOT ask follow-up questions or wait for additional input."""
|
||||
|
||||
return Agent(
|
||||
tools=[get_weather_data, generate_analysis_code, execute_code, get_activity_preferences, use_aws],
|
||||
system_prompt=system_prompt,
|
||||
name="WeatherActivityPlanner"
|
||||
)
|
||||
|
||||
@app.async_task
|
||||
async def async_main(query=None):
|
||||
"""Async main function"""
|
||||
console.print("🌤️ Weather-Based Activity Planner - Async Version")
|
||||
console.print("=" * 30)
|
||||
|
||||
agent = create_weather_agent()
|
||||
|
||||
query = query or "What should I do this weekend in Richmond VA?"
|
||||
console.print(f"\n[bold blue]🔍 Query:[/bold blue] {query}")
|
||||
console.print("-" * 50)
|
||||
|
||||
try:
|
||||
os.environ["BYPASS_TOOL_CONSENT"] = "True"
|
||||
result = agent(query)
|
||||
|
||||
return {
|
||||
"status": "completed",
|
||||
"result": result.message['content'][0]['text']
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ Error: {e}[/red]")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
@app.entrypoint
|
||||
async def invoke(payload=None):
|
||||
try:
|
||||
# change
|
||||
query = payload.get("prompt")
|
||||
|
||||
asyncio.create_task(async_main(query))
|
||||
|
||||
msg = (
|
||||
"Processing started ... "
|
||||
f"You can monitor status in CloudWatch logs at /aws/bedrock-agentcore/runtimes/<agent-runtime-id> ....."
|
||||
f"You can see the result at {RESULTS_BUCKET} ...."
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "Started",
|
||||
"message": msg
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# Terraform Backend Configuration - Remote State Storage
|
||||
# ============================================================================
|
||||
# This is an example backend configuration for storing Terraform state remotely
|
||||
# Copy this file to backend.tf and customize with your backend settings
|
||||
# Example: cp backend.tf.example backend.tf
|
||||
|
||||
# IMPORTANT: Uncomment and configure ONE of the backend options below
|
||||
|
||||
# Option 1: S3 Backend (Recommended for AWS)
|
||||
# terraform {
|
||||
# backend "s3" {
|
||||
# bucket = "your-terraform-state-bucket"
|
||||
# key = "agentcore/weather-agent/terraform.tfstate"
|
||||
# region = "us-west-2"
|
||||
# encrypt = true
|
||||
# dynamodb_table = "terraform-state-lock"
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 2: Terraform Cloud
|
||||
# terraform {
|
||||
# cloud {
|
||||
# organization = "your-organization"
|
||||
# workspaces {
|
||||
# name = "agentcore-weather-agent"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 3: Local Backend (Default - No configuration needed)
|
||||
# State will be stored locally in terraform.tfstate
|
||||
# Not recommended for team environments or production use
|
||||
@@ -0,0 +1,21 @@
|
||||
# ============================================================================
|
||||
# Browser Tool - For Web Browsing Capabilities
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_browser" "browser" {
|
||||
name = "${replace(var.stack_name, "-", "_")}_browser"
|
||||
description = "Browser tool for ${var.stack_name} weather agent to access weather websites and advisories"
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
tags = merge(
|
||||
var.common_tags,
|
||||
{
|
||||
Name = "${var.stack_name}-browser-tool"
|
||||
Module = "AgentCore-Tools"
|
||||
Tool = "Browser"
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
version: 0.2
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Installing dependencies...
|
||||
- yum install -y unzip
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Source code already extracted by CodeBuild...
|
||||
- cd $CODEBUILD_SRC_DIR
|
||||
- ls -la
|
||||
- echo Logging in to Amazon ECR...
|
||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image for Weather Agent ARM64...
|
||||
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
||||
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Build completed on `date`
|
||||
- echo Pushing the Weather Agent Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
- echo Weather Agent ARM64 Docker image pushed successfully
|
||||
@@ -0,0 +1,21 @@
|
||||
# ============================================================================
|
||||
# Code Interpreter Tool - For Python Code Execution and Data Analysis
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_code_interpreter" "code_interpreter" {
|
||||
name = "${replace(var.stack_name, "-", "_")}_code_interpreter"
|
||||
description = "Code interpreter tool for ${var.stack_name} weather agent to analyze weather data and create visualizations"
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
tags = merge(
|
||||
var.common_tags,
|
||||
{
|
||||
Name = "${var.stack_name}-code-interpreter-tool"
|
||||
Module = "AgentCore-Tools"
|
||||
Tool = "CodeInterpreter"
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# ============================================================================
|
||||
# CodeBuild Project - Build and Push Weather Agent Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_codebuild_project" "agent_image" {
|
||||
name = "${var.stack_name}-agent-build"
|
||||
description = "Build Weather Agent Docker image for ${var.stack_name}"
|
||||
service_role = aws_iam_role.codebuild.arn
|
||||
build_timeout = 60
|
||||
|
||||
artifacts {
|
||||
type = "NO_ARTIFACTS"
|
||||
}
|
||||
|
||||
environment {
|
||||
compute_type = "BUILD_GENERAL1_LARGE"
|
||||
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
|
||||
type = "ARM_CONTAINER"
|
||||
privileged_mode = true
|
||||
image_pull_credentials_type = "CODEBUILD"
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_DEFAULT_REGION"
|
||||
value = data.aws_region.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_ACCOUNT_ID"
|
||||
value = data.aws_caller_identity.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_REPO_NAME"
|
||||
value = aws_ecr_repository.weather_ecr.name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_TAG"
|
||||
value = var.image_tag
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "STACK_NAME"
|
||||
value = var.stack_name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AGENT_NAME"
|
||||
value = "weather-agent"
|
||||
}
|
||||
}
|
||||
|
||||
source {
|
||||
type = "S3"
|
||||
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
|
||||
buildspec = file("${path.module}/buildspec.yml")
|
||||
}
|
||||
|
||||
logs_config {
|
||||
cloudwatch_logs {
|
||||
group_name = "/aws/codebuild/${var.stack_name}-agent-build"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-build"
|
||||
Module = "CodeBuild"
|
||||
Agent = "WeatherAgent"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_iam_role_policy.codebuild
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Script for End-to-End Weather Agent (Terraform)
|
||||
# ============================================================================
|
||||
# This script automates the deployment process for the Weather Agent Terraform configuration
|
||||
# Usage: ./deploy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting End-to-End Weather Agent Deployment..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed. Please install Terraform >= 1.6"
|
||||
print_info "Visit: https://www.terraform.io/downloads"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Terraform version
|
||||
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
|
||||
print_success "Terraform version: $TERRAFORM_VERSION"
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
|
||||
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "AWS CLI is installed"
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
print_info "Run: aws configure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_success "AWS Account: $AWS_ACCOUNT"
|
||||
print_success "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking configuration files..."
|
||||
|
||||
# Check if terraform.tfvars exists
|
||||
if [ ! -f "terraform.tfvars" ]; then
|
||||
print_warning "terraform.tfvars not found"
|
||||
print_info "Creating terraform.tfvars from example..."
|
||||
|
||||
if [ -f "terraform.tfvars.example" ]; then
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
print_success "Created terraform.tfvars"
|
||||
print_warning "Please review and update terraform.tfvars with your settings"
|
||||
print_info "Then run this script again"
|
||||
exit 0
|
||||
else
|
||||
print_error "terraform.tfvars.example not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Configuration file found: terraform.tfvars"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Initialization
|
||||
# ============================================================================
|
||||
|
||||
print_info "Initializing Terraform..."
|
||||
if terraform init; then
|
||||
print_success "Terraform initialized successfully"
|
||||
else
|
||||
print_error "Terraform initialization failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Validation
|
||||
# ============================================================================
|
||||
|
||||
print_info "Validating Terraform configuration..."
|
||||
if terraform validate; then
|
||||
print_success "Terraform configuration is valid"
|
||||
else
|
||||
print_error "Terraform validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Format Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking Terraform formatting..."
|
||||
if terraform fmt -check -recursive > /dev/null 2>&1; then
|
||||
print_success "Terraform files are properly formatted"
|
||||
else
|
||||
print_warning "Some files need formatting. Running terraform fmt..."
|
||||
terraform fmt -recursive
|
||||
print_success "Files formatted"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating Terraform execution plan..."
|
||||
print_warning "This may take a few moments..."
|
||||
echo ""
|
||||
|
||||
if terraform plan -out=tfplan; then
|
||||
print_success "Terraform plan created successfully"
|
||||
else
|
||||
print_error "Terraform plan failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "DEPLOYMENT CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_info "This will deploy the following resources:"
|
||||
print_info " - 2x S3 Buckets (source code + results storage)"
|
||||
print_info " - 1x ECR Repository (weather agent)"
|
||||
print_info " - 1x CodeBuild Project (weather agent)"
|
||||
print_info " - Browser Tool (web scraping capability)"
|
||||
print_info " - Code Interpreter Tool (Python code execution)"
|
||||
print_info " - Memory Resource (conversation persistence)"
|
||||
print_info " - Memory Initializer Lambda (activity preferences)"
|
||||
print_info " - IAM Roles and Policies (agent + tools + Lambda)"
|
||||
print_info " - Weather Agent Runtime (full-featured)"
|
||||
echo ""
|
||||
print_info "The deployment includes:"
|
||||
print_info " - Building ARM64 Docker image"
|
||||
print_info " - Initializing memory with weather activity preferences"
|
||||
print_info " - Configuring Browser and Code Interpreter tools"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to proceed with deployment? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Deployment cancelled by user"
|
||||
rm -f tfplan
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Apply
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting deployment..."
|
||||
echo ""
|
||||
|
||||
if terraform apply tfplan; then
|
||||
print_success "Deployment completed successfully!"
|
||||
else
|
||||
print_error "Deployment failed"
|
||||
rm -f tfplan
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up plan file
|
||||
rm -f tfplan
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "DEPLOYMENT COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Retrieving deployment outputs..."
|
||||
echo ""
|
||||
|
||||
# Get outputs
|
||||
AGENT_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
|
||||
AGENT_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
|
||||
BROWSER_ID=$(terraform output -raw browser_id 2>/dev/null || echo "N/A")
|
||||
CODE_INTERP_ID=$(terraform output -raw code_interpreter_id 2>/dev/null || echo "N/A")
|
||||
MEMORY_ID=$(terraform output -raw memory_id 2>/dev/null || echo "N/A")
|
||||
RESULTS_BUCKET=$(terraform output -raw results_bucket_name 2>/dev/null || echo "N/A")
|
||||
|
||||
print_success "Weather Agent Runtime ID: $AGENT_ID"
|
||||
print_success "Weather Agent Runtime ARN: $AGENT_ARN"
|
||||
echo ""
|
||||
print_success "Browser Tool ID: $BROWSER_ID"
|
||||
print_success "Code Interpreter ID: $CODE_INTERP_ID"
|
||||
print_success "Memory ID: $MEMORY_ID"
|
||||
print_success "Results Bucket: $RESULTS_BUCKET"
|
||||
|
||||
echo ""
|
||||
print_info "Next Steps:"
|
||||
print_info "1. Test the weather agent:"
|
||||
print_info " python test_weather_agent.py $AGENT_ARN"
|
||||
echo ""
|
||||
print_info "2. View all outputs (includes test commands):"
|
||||
print_info " terraform output"
|
||||
echo ""
|
||||
print_info "3. Monitor in AWS Console:"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_success "Deployment completed successfully!"
|
||||
@@ -0,0 +1,306 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Destroy Script for End-to-End Weather Agent (Terraform)
|
||||
# ============================================================================
|
||||
# This script safely destroys all resources created by this Terraform configuration
|
||||
# Usage: ./destroy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting Resource Cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_info "AWS Account: $AWS_ACCOUNT"
|
||||
print_info "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Check for Terraform State
|
||||
# ============================================================================
|
||||
|
||||
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
|
||||
print_warning "No Terraform state found"
|
||||
print_info "Either no resources have been deployed, or state is stored remotely"
|
||||
|
||||
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Initializing Terraform to fetch remote state..."
|
||||
terraform init
|
||||
else
|
||||
print_info "Cleanup cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Show Destruction Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating destruction plan..."
|
||||
echo ""
|
||||
|
||||
if ! terraform plan -destroy; then
|
||||
print_error "Failed to create destruction plan"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Destruction Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_warning "This will permanently delete the following resources:"
|
||||
print_warning " - Weather Agent Runtime"
|
||||
print_warning " - Browser Tool"
|
||||
print_warning " - Code Interpreter Tool"
|
||||
print_warning " - Memory Resource (with all conversation history)"
|
||||
print_warning " - Memory Initializer Lambda"
|
||||
print_warning " - 2x S3 Buckets (source code + results storage)"
|
||||
print_warning " - 1x ECR Repository (including all images)"
|
||||
print_warning " - 1x CodeBuild Project"
|
||||
print_warning " - IAM Roles and Policies (agent + tools + Lambda)"
|
||||
print_warning " - CloudWatch Log Groups"
|
||||
echo ""
|
||||
print_warning "THIS ACTION CANNOT BE UNDONE!"
|
||||
echo ""
|
||||
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
|
||||
echo ""
|
||||
|
||||
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Destruction cancelled by user"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Double confirmation for safety
|
||||
print_warning "Second confirmation required..."
|
||||
read -p "Type 'DESTROY' to confirm: " -r
|
||||
echo ""
|
||||
|
||||
if [ "$REPLY" != "DESTROY" ]; then
|
||||
print_info "Destruction cancelled - confirmation text did not match"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Execute Destruction
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting resource destruction..."
|
||||
echo ""
|
||||
|
||||
if terraform destroy -auto-approve; then
|
||||
print_success "All resources destroyed successfully"
|
||||
else
|
||||
print_error "Destruction failed"
|
||||
print_warning "Some resources may still exist. Please check AWS Console"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Cleanup Local Files
|
||||
# ============================================================================
|
||||
|
||||
print_info "Cleaning up local Terraform files..."
|
||||
|
||||
# Ask about state file cleanup
|
||||
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -f terraform.tfstate
|
||||
rm -f terraform.tfstate.backup
|
||||
rm -f tfplan
|
||||
print_success "Local state files removed"
|
||||
fi
|
||||
|
||||
# Ask about .terraform directory
|
||||
read -p "Do you want to remove .terraform directory? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -rf .terraform
|
||||
rm -f .terraform.lock.hcl
|
||||
print_success ".terraform directory removed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verification
|
||||
# ============================================================================
|
||||
|
||||
print_info "Verifying resource cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check for ECR repositories
|
||||
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
|
||||
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$ECR_REPOS" -eq 0 ]; then
|
||||
print_success "ECR repositories cleaned up"
|
||||
else
|
||||
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for AgentCore runtimes
|
||||
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$RUNTIME_COUNT" -eq 0 ]; then
|
||||
print_success "Weather Agent runtime cleaned up"
|
||||
else
|
||||
print_warning "Found $RUNTIME_COUNT AgentCore runtime(s) matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for Memory resources
|
||||
MEMORY_COUNT=$(aws bedrock-agentcore list-memories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$MEMORY_COUNT" -eq 0 ]; then
|
||||
print_success "Memory resources cleaned up"
|
||||
else
|
||||
print_warning "Found $MEMORY_COUNT Memory resource(s) matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for Tools (Browser and Code Interpreter)
|
||||
TOOL_COUNT=$(aws bedrock-agentcore list-browser-customs --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
TOOL_COUNT=$((TOOL_COUNT + $(aws bedrock-agentcore list-code-interpreter-customs --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')))
|
||||
|
||||
if [ "$TOOL_COUNT" -eq 0 ]; then
|
||||
print_success "Tools (Browser + Code Interpreter) cleaned up"
|
||||
else
|
||||
print_warning "Found $TOOL_COUNT Tool(s) matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for Lambda functions
|
||||
LAMBDA_COUNT=$(aws lambda list-functions --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$LAMBDA_COUNT" -eq 0 ]; then
|
||||
print_success "Lambda functions cleaned up"
|
||||
else
|
||||
print_warning "Found $LAMBDA_COUNT Lambda function(s) matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for S3 buckets (source + results)
|
||||
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$S3_BUCKETS" -eq 0 ]; then
|
||||
print_success "S3 buckets cleaned up (source + results)"
|
||||
else
|
||||
print_warning "Found $S3_BUCKETS S3 bucket(s) matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Completion Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "CLEANUP COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Cleanup Summary:"
|
||||
print_success " ✓ Terraform resources destroyed"
|
||||
print_success " ✓ Local state files cleaned (if selected)"
|
||||
echo ""
|
||||
|
||||
print_info "What to verify in AWS Console:"
|
||||
print_info "1. Bedrock AgentCore - No runtimes remaining"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_info "2. Memory - No memory resources remaining"
|
||||
print_info " Check via: aws bedrock-agentcore list-memories --region $AWS_REGION"
|
||||
echo ""
|
||||
print_info "3. Tools - No browser/code interpreter tools remaining"
|
||||
print_info " Check via: aws bedrock-agentcore list-browser-customs --region $AWS_REGION"
|
||||
echo ""
|
||||
print_info "4. Lambda - No memory initializer functions remaining"
|
||||
print_info " https://console.aws.amazon.com/lambda/home?region=$AWS_REGION#/functions"
|
||||
echo ""
|
||||
print_info "5. S3 - No buckets remaining (source + results)"
|
||||
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "6. ECR - No repositories remaining"
|
||||
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "7. CodeBuild - No projects remaining"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "8. CloudWatch Logs - Check for orphaned log groups"
|
||||
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
|
||||
echo ""
|
||||
|
||||
print_success "Cleanup completed successfully!"
|
||||
print_info "You can safely re-deploy by running: ./deploy.sh"
|
||||
@@ -0,0 +1,65 @@
|
||||
# ============================================================================
|
||||
# ECR Repository - Container Registry for Weather Agent Image
|
||||
# ============================================================================
|
||||
|
||||
# Weather Agent ECR Repository
|
||||
resource "aws_ecr_repository" "weather_ecr" {
|
||||
name = "${var.stack_name}-${var.ecr_repository_name}"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
force_delete = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-ecr-repository"
|
||||
Module = "ECR"
|
||||
Agent = "WeatherAgent"
|
||||
}
|
||||
}
|
||||
|
||||
# ECR Repository Policy - Weather Agent
|
||||
resource "aws_ecr_repository_policy" "weather_ecr" {
|
||||
repository = aws_ecr_repository.weather_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowPullFromAccount"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
|
||||
}
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Lifecycle Policy - Weather Agent - Keep last 5 images
|
||||
resource "aws_ecr_lifecycle_policy" "weather_ecr" {
|
||||
repository = aws_ecr_repository.weather_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep last 5 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
# Data sources
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# ============================================================================
|
||||
# Weather Agent Execution Role - For AgentCore Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "agent_execution" {
|
||||
name = "${var.stack_name}-agent-execution-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AssumeRolePolicy"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "bedrock-agentcore.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.id
|
||||
}
|
||||
ArnLike = {
|
||||
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-execution-role"
|
||||
Module = "IAM"
|
||||
Agent = "WeatherAgent"
|
||||
}
|
||||
}
|
||||
|
||||
# Attach AWS managed policy for AgentCore
|
||||
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
|
||||
role = aws_iam_role.agent_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
|
||||
}
|
||||
|
||||
# Inline policy for agent execution
|
||||
resource "aws_iam_role_policy" "agent_execution" {
|
||||
name = "AgentCoreExecutionPolicy"
|
||||
role = aws_iam_role.agent_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRImageAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
Resource = aws_ecr_repository.weather_ecr.arn
|
||||
},
|
||||
{
|
||||
Sid = "ECRTokenAccess"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:GetAuthorizationToken"]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
||||
},
|
||||
# X-Ray Tracing
|
||||
{
|
||||
Sid = "XRayTracing"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Metrics
|
||||
{
|
||||
Sid = "CloudWatchMetrics"
|
||||
Effect = "Allow"
|
||||
Action = ["cloudwatch:PutMetricData"]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cloudwatch:namespace" = "bedrock-agentcore"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Bedrock Model Invocation
|
||||
{
|
||||
Sid = "BedrockModelInvocation"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# Workload Access Tokens
|
||||
{
|
||||
Sid = "GetAgentAccessToken"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
},
|
||||
# S3 Access for Results Bucket
|
||||
{
|
||||
Sid = "S3ResultsAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:ListBucket"
|
||||
]
|
||||
Resource = [
|
||||
aws_s3_bucket.results.arn,
|
||||
"${aws_s3_bucket.results.arn}/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CodeBuild Service Role - For Docker Image Building
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "codebuild" {
|
||||
name = "${var.stack_name}-codebuild-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "codebuild.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-codebuild-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Inline policy for CodeBuild
|
||||
resource "aws_iam_role_policy" "codebuild" {
|
||||
name = "CodeBuildPolicy"
|
||||
role = aws_iam_role.codebuild.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
|
||||
},
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetAuthorizationToken",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload"
|
||||
]
|
||||
Resource = [
|
||||
aws_ecr_repository.weather_ecr.arn,
|
||||
"*"
|
||||
]
|
||||
},
|
||||
# S3 Source Access
|
||||
{
|
||||
Sid = "S3SourceAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion"
|
||||
]
|
||||
Resource = "${aws_s3_bucket.agent_source.arn}/*"
|
||||
},
|
||||
{
|
||||
Sid = "S3BucketAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:ListBucket",
|
||||
"s3:GetBucketLocation"
|
||||
]
|
||||
Resource = aws_s3_bucket.agent_source.arn
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
# ============================================================================
|
||||
# Wait for IAM propagation before triggering build
|
||||
# ============================================================================
|
||||
|
||||
resource "time_sleep" "wait_for_iam" {
|
||||
depends_on = [
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_iam_role_policy.agent_execution
|
||||
]
|
||||
|
||||
create_duration = "30s"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Trigger CodeBuild - Build Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "null_resource" "trigger_build" {
|
||||
triggers = {
|
||||
build_project = aws_codebuild_project.agent_image.id
|
||||
image_tag = var.image_tag
|
||||
ecr_repository = aws_ecr_repository.weather_ecr.id
|
||||
source_code_md5 = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.weather_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.weather_ecr.repository_url}\""
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_codebuild_project.agent_image,
|
||||
aws_ecr_repository.weather_ecr,
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_s3_object.agent_source,
|
||||
time_sleep.wait_for_iam
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Weather Agent Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "weather_agent" {
|
||||
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.agent_name}"
|
||||
description = "Weather agent runtime for ${var.stack_name}"
|
||||
role_arn = aws_iam_role.agent_execution.arn
|
||||
|
||||
agent_runtime_artifact {
|
||||
container_configuration {
|
||||
container_uri = "${aws_ecr_repository.weather_ecr.repository_url}:${var.image_tag}"
|
||||
}
|
||||
}
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
environment_variables = {
|
||||
AWS_REGION = data.aws_region.current.id
|
||||
AWS_DEFAULT_REGION = data.aws_region.current.id
|
||||
RESULTS_BUCKET = aws_s3_bucket.results.id
|
||||
BROWSER_ID = aws_bedrockagentcore_browser.browser.browser_id
|
||||
CODE_INTERPRETER_ID = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_id
|
||||
MEMORY_ID = aws_bedrockagentcore_memory.memory.id
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-runtime"
|
||||
Environment = "production"
|
||||
Module = "BedrockAgentCore"
|
||||
Agent = "WeatherAgent"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
null_resource.trigger_build,
|
||||
aws_iam_role_policy.agent_execution,
|
||||
aws_iam_role_policy_attachment.agent_execution_managed,
|
||||
aws_bedrockagentcore_browser.browser,
|
||||
aws_bedrockagentcore_code_interpreter.code_interpreter,
|
||||
aws_bedrockagentcore_memory.memory
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# ============================================================================
|
||||
# Memory Initialization - Populate Memory with Activity Preferences
|
||||
# ============================================================================
|
||||
|
||||
# Initialize memory with activity preferences after memory is created
|
||||
resource "null_resource" "initialize_memory" {
|
||||
# Trigger re-initialization if memory ID changes
|
||||
triggers = {
|
||||
memory_id = aws_bedrockagentcore_memory.memory.id
|
||||
region = data.aws_region.current.id
|
||||
}
|
||||
|
||||
# Execute Python script to initialize memory
|
||||
provisioner "local-exec" {
|
||||
command = "python3 ${path.module}/scripts/init-memory.py"
|
||||
working_dir = path.module
|
||||
|
||||
environment = {
|
||||
MEMORY_ID = aws_bedrockagentcore_memory.memory.id
|
||||
AWS_REGION = data.aws_region.current.id
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure memory exists before initialization
|
||||
depends_on = [
|
||||
aws_bedrockagentcore_memory.memory
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "memory_initialization_status" {
|
||||
description = "Status of memory initialization"
|
||||
value = "Memory initialized with activity preferences"
|
||||
depends_on = [null_resource.initialize_memory]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# ============================================================================
|
||||
# Memory - For Persistent Conversation Context
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_memory" "memory" {
|
||||
name = "${replace(var.stack_name, "-", "_")}_${var.memory_name}"
|
||||
description = "Memory for ${var.stack_name} weather agent to maintain conversation context"
|
||||
event_expiry_duration = 30 # Days
|
||||
|
||||
tags = merge(
|
||||
var.common_tags,
|
||||
{
|
||||
Name = "${var.stack_name}-memory"
|
||||
Module = "AgentCore-Tools"
|
||||
Tool = "Memory"
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# ============================================================================
|
||||
# Observability Module - CloudWatch Logs and X-Ray Traces Delivery
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# Application Logs Setup
|
||||
# ============================================================================
|
||||
|
||||
# CloudWatch Log Group for vended log delivery
|
||||
resource "aws_cloudwatch_log_group" "agent_runtime_logs" {
|
||||
name = "/aws/vendedlogs/bedrock-agentcore/${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}"
|
||||
retention_in_days = 14
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-logs"
|
||||
Purpose = "Agent runtime application logs"
|
||||
Module = "Observability"
|
||||
}
|
||||
|
||||
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
|
||||
}
|
||||
|
||||
# Delivery Source for Application Logs
|
||||
resource "aws_cloudwatch_log_delivery_source" "logs" {
|
||||
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-logs-source"
|
||||
log_type = "APPLICATION_LOGS"
|
||||
resource_arn = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
|
||||
|
||||
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
|
||||
}
|
||||
|
||||
# Delivery Destination for Logs (CloudWatch Logs)
|
||||
resource "aws_cloudwatch_log_delivery_destination" "logs" {
|
||||
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-logs-destination"
|
||||
|
||||
delivery_destination_configuration {
|
||||
destination_resource_arn = aws_cloudwatch_log_group.agent_runtime_logs.arn
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-logs-destination"
|
||||
Purpose = "CloudWatch Logs delivery destination"
|
||||
Module = "Observability"
|
||||
}
|
||||
|
||||
depends_on = [aws_cloudwatch_log_group.agent_runtime_logs]
|
||||
}
|
||||
|
||||
# Delivery Connection for Logs
|
||||
resource "aws_cloudwatch_log_delivery" "logs" {
|
||||
delivery_source_name = aws_cloudwatch_log_delivery_source.logs.name
|
||||
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.logs.arn
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-logs-delivery"
|
||||
Purpose = "Connect logs source to CloudWatch destination"
|
||||
Module = "Observability"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_cloudwatch_log_delivery_source.logs,
|
||||
aws_cloudwatch_log_delivery_destination.logs
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# X-Ray Traces Setup
|
||||
# ============================================================================
|
||||
|
||||
# Delivery Source for Traces
|
||||
resource "aws_cloudwatch_log_delivery_source" "traces" {
|
||||
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-traces-source"
|
||||
log_type = "TRACES"
|
||||
resource_arn = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
|
||||
|
||||
depends_on = [aws_bedrockagentcore_agent_runtime.weather_agent]
|
||||
}
|
||||
|
||||
# Delivery Destination for Traces (X-Ray)
|
||||
resource "aws_cloudwatch_log_delivery_destination" "traces" {
|
||||
name = "${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id}-traces-destination"
|
||||
delivery_destination_type = "XRAY"
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-traces-destination"
|
||||
Purpose = "X-Ray traces delivery destination"
|
||||
Module = "Observability"
|
||||
}
|
||||
}
|
||||
|
||||
# Delivery Connection for Traces
|
||||
resource "aws_cloudwatch_log_delivery" "traces" {
|
||||
delivery_source_name = aws_cloudwatch_log_delivery_source.traces.name
|
||||
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.traces.arn
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-traces-delivery"
|
||||
Purpose = "Connect traces source to X-Ray destination"
|
||||
Module = "Observability"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_cloudwatch_log_delivery_source.traces,
|
||||
aws_cloudwatch_log_delivery_destination.traces
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "log_group_name" {
|
||||
description = "CloudWatch Log Group name for agent runtime vended logs"
|
||||
value = aws_cloudwatch_log_group.agent_runtime_logs.name
|
||||
}
|
||||
|
||||
output "log_group_arn" {
|
||||
description = "ARN of the CloudWatch Log Group"
|
||||
value = aws_cloudwatch_log_group.agent_runtime_logs.arn
|
||||
}
|
||||
|
||||
output "logs_delivery_id" {
|
||||
description = "ID of the logs delivery connection"
|
||||
value = aws_cloudwatch_log_delivery.logs.id
|
||||
}
|
||||
|
||||
output "traces_delivery_id" {
|
||||
description = "ID of the traces delivery connection"
|
||||
value = aws_cloudwatch_log_delivery.traces.id
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
# ============================================================================
|
||||
# Weather Agent Runtime Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "agent_runtime_id" {
|
||||
description = "ID of the weather agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_id
|
||||
}
|
||||
|
||||
output "agent_runtime_arn" {
|
||||
description = "ARN of the weather agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "agent_runtime_version" {
|
||||
description = "Version of the weather agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_version
|
||||
}
|
||||
|
||||
output "agent_ecr_repository_url" {
|
||||
description = "URL of the ECR repository for weather agent"
|
||||
value = aws_ecr_repository.weather_ecr.repository_url
|
||||
}
|
||||
|
||||
output "agent_execution_role_arn" {
|
||||
description = "ARN of the weather agent execution role"
|
||||
value = aws_iam_role.agent_execution.arn
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Build & Storage Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "codebuild_project_name" {
|
||||
description = "Name of the CodeBuild project for weather agent"
|
||||
value = aws_codebuild_project.agent_image.name
|
||||
}
|
||||
|
||||
output "source_bucket_name" {
|
||||
description = "S3 bucket containing weather agent source code"
|
||||
value = aws_s3_bucket.agent_source.id
|
||||
}
|
||||
|
||||
output "results_bucket_name" {
|
||||
description = "Name of the S3 bucket for agent results"
|
||||
value = aws_s3_bucket.results.id
|
||||
}
|
||||
|
||||
output "source_code_md5" {
|
||||
description = "MD5 hash of agent source code (triggers rebuild when changed)"
|
||||
value = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Testing Information
|
||||
# ============================================================================
|
||||
|
||||
output "test_agent_command" {
|
||||
description = "AWS CLI command to test weather agent"
|
||||
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-arn ${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn} --qualifier DEFAULT --payload '{\"prompt\": \"What's the weather like today and suggest activities?\"}' --region ${data.aws_region.current.id} response.json"
|
||||
}
|
||||
|
||||
output "test_script_command" {
|
||||
description = "Command to run the comprehensive test script"
|
||||
value = "python test_weather_agent.py ${aws_bedrockagentcore_agent_runtime.weather_agent.agent_runtime_arn}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Tool Resource Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "browser_id" {
|
||||
description = "ID of the browser tool"
|
||||
value = aws_bedrockagentcore_browser.browser.browser_id
|
||||
}
|
||||
|
||||
output "browser_arn" {
|
||||
description = "ARN of the browser tool"
|
||||
value = aws_bedrockagentcore_browser.browser.browser_arn
|
||||
}
|
||||
|
||||
output "code_interpreter_id" {
|
||||
description = "ID of the code interpreter tool"
|
||||
value = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_id
|
||||
}
|
||||
|
||||
output "code_interpreter_arn" {
|
||||
description = "ARN of the code interpreter tool"
|
||||
value = aws_bedrockagentcore_code_interpreter.code_interpreter.code_interpreter_arn
|
||||
}
|
||||
|
||||
output "memory_id" {
|
||||
description = "ID of the memory resource"
|
||||
value = aws_bedrockagentcore_memory.memory.id
|
||||
}
|
||||
|
||||
output "memory_arn" {
|
||||
description = "ARN of the memory resource"
|
||||
value = aws_bedrockagentcore_memory.memory.arn
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# ============================================================================
|
||||
# S3 Buckets for Weather Agent
|
||||
# ============================================================================
|
||||
|
||||
# Agent Source Code Bucket
|
||||
resource "aws_s3_bucket" "agent_source" {
|
||||
bucket_prefix = "${var.stack_name}-agent-source-"
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-source"
|
||||
Purpose = "Store Weather Agent source code for CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# Results Bucket (for agent-generated artifacts)
|
||||
resource "aws_s3_bucket" "results" {
|
||||
bucket_prefix = "${var.stack_name}-results-"
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-results"
|
||||
Purpose = "Store Weather Agent generated artifacts"
|
||||
}
|
||||
}
|
||||
|
||||
# Block public access - Agent Source
|
||||
resource "aws_s3_bucket_public_access_block" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Block public access - Results Bucket
|
||||
resource "aws_s3_bucket_public_access_block" "results" {
|
||||
bucket = aws_s3_bucket.results.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Enable versioning - Agent Source
|
||||
resource "aws_s3_bucket_versioning" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Enable versioning - Results Bucket
|
||||
resource "aws_s3_bucket_versioning" "results" {
|
||||
bucket = aws_s3_bucket.results.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Archive and Upload Agent Source Code
|
||||
# ============================================================================
|
||||
|
||||
# Archive agent-code/ directory
|
||||
data "archive_file" "agent_source" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/agent-code"
|
||||
output_path = "${path.module}/.terraform/agent-code.zip"
|
||||
}
|
||||
|
||||
# Upload Agent source to S3
|
||||
resource "aws_s3_object" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
key = "agent-code-${data.archive_file.agent_source.output_md5}.zip"
|
||||
source = data.archive_file.agent_source.output_path
|
||||
etag = data.archive_file.agent_source.output_md5
|
||||
|
||||
tags = {
|
||||
Name = "agent-source-code"
|
||||
Agent = "WeatherAgent"
|
||||
MD5 = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Build and Verify Docker Image for AgentCore Runtime
|
||||
# ============================================================================
|
||||
# This script is called by Terraform during deployment to:
|
||||
# 1. Trigger CodeBuild to build the Docker image
|
||||
# 2. Wait for the build to complete
|
||||
# 3. Verify the image was successfully pushed to ECR
|
||||
#
|
||||
# Parameters:
|
||||
# $1 - CodeBuild project name
|
||||
# $2 - AWS region
|
||||
# $3 - ECR repository name
|
||||
# $4 - Image tag
|
||||
# $5 - ECR repository URL
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parameters
|
||||
PROJECT_NAME="$1"
|
||||
REGION="$2"
|
||||
REPO_NAME="$3"
|
||||
IMAGE_TAG="$4"
|
||||
REPO_URL="$5"
|
||||
|
||||
# ============================================================================
|
||||
# Print functions
|
||||
# ============================================================================
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[✓]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[⚠]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[✗]${NC} $1"
|
||||
}
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Start Build Process
|
||||
# ============================================================================
|
||||
|
||||
print_header "Building Docker Image for AgentCore Runtime"
|
||||
|
||||
print_info "CodeBuild Project: $PROJECT_NAME"
|
||||
print_info "Region: $REGION"
|
||||
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
|
||||
echo ""
|
||||
|
||||
# Start CodeBuild
|
||||
print_info "Starting CodeBuild project..."
|
||||
|
||||
BUILD_ID=$(aws codebuild start-build \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--region "$REGION" \
|
||||
--query 'build.id' \
|
||||
--output text 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
print_error "Failed to start CodeBuild"
|
||||
echo "$BUILD_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build started: $BUILD_ID"
|
||||
print_info "Waiting for build to complete (typically 5-10 minutes)..."
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Monitor Build Progress
|
||||
# ============================================================================
|
||||
|
||||
ATTEMPT=0
|
||||
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
|
||||
|
||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
|
||||
STATUS=$(aws codebuild batch-get-builds \
|
||||
--ids "$BUILD_ID" \
|
||||
--region "$REGION" \
|
||||
--query 'builds[0].buildStatus' \
|
||||
--output text 2>/dev/null)
|
||||
|
||||
if [ "$STATUS" != "IN_PROGRESS" ]; then
|
||||
print_info "Build process completed with status: $STATUS"
|
||||
break
|
||||
fi
|
||||
|
||||
# Progress indicator
|
||||
if [ $((ATTEMPT % 6)) -eq 0 ]; then
|
||||
MINUTES=$((ATTEMPT / 6))
|
||||
print_info "Build in progress... (${MINUTES} minutes elapsed)"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
||||
print_error "Build timeout after 10 minutes"
|
||||
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verify Image in ECR
|
||||
# ============================================================================
|
||||
|
||||
print_header "Verifying Docker Image in ECR"
|
||||
|
||||
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
|
||||
print_info "Waiting for ECR propagation..."
|
||||
echo ""
|
||||
|
||||
sleep 5 # Brief wait for ECR to register the push
|
||||
|
||||
VERIFY_ATTEMPT=0
|
||||
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
|
||||
|
||||
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
|
||||
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
|
||||
|
||||
if aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" >/dev/null 2>&1; then
|
||||
|
||||
print_success "Docker image successfully verified in ECR!"
|
||||
echo ""
|
||||
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
|
||||
|
||||
# Get image details
|
||||
IMAGE_SIZE=$(aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" \
|
||||
--query 'imageDetails[0].imageSizeInBytes' \
|
||||
--output text 2>/dev/null || echo "Unknown")
|
||||
|
||||
if [ "$IMAGE_SIZE" != "Unknown" ]; then
|
||||
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
|
||||
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Build and verification completed successfully!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
|
||||
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Error: Image Not Found
|
||||
# ============================================================================
|
||||
|
||||
print_error "Docker image not found in ECR after build completion"
|
||||
echo ""
|
||||
print_warning "This indicates the build or push step failed."
|
||||
print_info "Troubleshooting steps:"
|
||||
print_info " 1. Check CodeBuild logs:"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
print_info ""
|
||||
print_info " 2. Verify ECR repository:"
|
||||
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
|
||||
print_info ""
|
||||
print_info " 3. Check IAM permissions for CodeBuild role"
|
||||
|
||||
exit 1
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Memory Initialization Script for Weather Agent
|
||||
|
||||
This script initializes the AgentCore Memory with activity preferences
|
||||
that the weather agent uses for recommendations.
|
||||
|
||||
Usage:
|
||||
python init-memory.py
|
||||
|
||||
Environment Variables:
|
||||
MEMORY_ID (required): ID of the AgentCore Memory to initialize
|
||||
AWS_REGION (required): AWS region where the memory exists
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def main():
|
||||
"""Initialize memory with activity preferences."""
|
||||
|
||||
# Get required environment variables
|
||||
memory_id = os.environ.get('MEMORY_ID')
|
||||
region = os.environ.get('AWS_REGION')
|
||||
|
||||
if not memory_id:
|
||||
print("❌ ERROR: MEMORY_ID environment variable is required")
|
||||
sys.exit(1)
|
||||
|
||||
if not region:
|
||||
print("❌ ERROR: AWS_REGION environment variable is required")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"🎯 Initializing memory: {memory_id}")
|
||||
print(f"📍 Region: {region}")
|
||||
|
||||
# Activity preferences data structure
|
||||
activity_preferences = {
|
||||
"good_weather": [
|
||||
"hiking",
|
||||
"beach volleyball",
|
||||
"outdoor picnic",
|
||||
"farmers market",
|
||||
"gardening",
|
||||
"photography",
|
||||
"bird watching"
|
||||
],
|
||||
"ok_weather": [
|
||||
"walking tours",
|
||||
"outdoor dining",
|
||||
"park visits",
|
||||
"museums"
|
||||
],
|
||||
"poor_weather": [
|
||||
"indoor museums",
|
||||
"shopping",
|
||||
"restaurants",
|
||||
"movies"
|
||||
]
|
||||
}
|
||||
|
||||
# Convert to JSON string for storage
|
||||
activity_preferences_json = json.dumps(activity_preferences)
|
||||
|
||||
try:
|
||||
# Initialize bedrock-agentcore client
|
||||
client = boto3.client('bedrock-agentcore', region_name=region)
|
||||
|
||||
# Create timestamp
|
||||
timestamp = datetime.utcnow().isoformat() + 'Z'
|
||||
|
||||
print("📝 Creating memory event with activity preferences...")
|
||||
|
||||
# Create memory event
|
||||
response = client.create_event(
|
||||
memoryId=memory_id,
|
||||
actorId="user123",
|
||||
sessionId="session456",
|
||||
eventTimestamp=timestamp,
|
||||
payload=[
|
||||
{
|
||||
'blob': activity_preferences_json
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
print("✅ Memory initialized successfully!")
|
||||
print(f"📊 Event ID: {response.get('eventId', 'N/A')}")
|
||||
print(f"📦 Preferences stored: {len(activity_preferences)} categories")
|
||||
print(f" - Good weather: {len(activity_preferences['good_weather'])} activities")
|
||||
print(f" - OK weather: {len(activity_preferences['ok_weather'])} activities")
|
||||
print(f" - Poor weather: {len(activity_preferences['poor_weather'])} activities")
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ ERROR: Failed to initialize memory: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,36 @@
|
||||
# ============================================================================
|
||||
# End-to-End Weather Agent - Example Configuration
|
||||
# ============================================================================
|
||||
# Copy this file to terraform.tfvars and customize
|
||||
# Example: cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Agent Configuration
|
||||
agent_name = "WeatherAgent"
|
||||
memory_name = "WeatherAgentMemory"
|
||||
stack_name = "agentcore-weather"
|
||||
|
||||
# Network Configuration
|
||||
network_mode = "PUBLIC" # PUBLIC or PRIVATE
|
||||
|
||||
# Container Configuration
|
||||
ecr_repository_name = "weather-agent"
|
||||
image_tag = "latest"
|
||||
|
||||
# AWS Configuration
|
||||
aws_region = "us-west-2"
|
||||
environment = "dev"
|
||||
|
||||
# Optional: Environment Variables (if needed for custom configurations)
|
||||
# environment_variables = {
|
||||
# LOG_LEVEL = "INFO"
|
||||
# }
|
||||
|
||||
# Notes:
|
||||
# - Weather Agent: Full-featured agent with Browser, Code Interpreter, and Memory
|
||||
# - Browser Tool: Web scraping and data collection capability
|
||||
# - Code Interpreter: Python code execution for data analysis
|
||||
# - Memory: Persistent conversation memory with weather activity preferences
|
||||
# - S3 Results Bucket: Storage for agent-generated artifacts (charts, data files)
|
||||
# - Agent code in agent-code/ directory
|
||||
# - Memory initialized with activity preferences (good/ok/poor weather)
|
||||
# - Test with: python test_weather_agent.py
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Weather Agent Test Script
|
||||
|
||||
This script tests the Weather Agent with various prompts including
|
||||
weather queries, code interpreter usage, and browser tool interactions.
|
||||
|
||||
Usage:
|
||||
python test_weather_agent.py <agent_arn>
|
||||
|
||||
agent_arn: ARN of the weather agent runtime (required)
|
||||
|
||||
Examples:
|
||||
# Test weather agent
|
||||
python test_weather_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/weather-agent-id
|
||||
|
||||
# From Terraform outputs
|
||||
python test_weather_agent.py $(terraform output -raw agent_runtime_arn)
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def extract_region_from_arn(arn):
|
||||
"""Extract AWS region from agent runtime ARN.
|
||||
|
||||
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
|
||||
|
||||
Args:
|
||||
arn: Agent runtime ARN string
|
||||
|
||||
Returns:
|
||||
str: AWS region code
|
||||
|
||||
Raises:
|
||||
ValueError: If ARN format is invalid or region cannot be extracted
|
||||
"""
|
||||
try:
|
||||
parts = arn.split(':')
|
||||
if len(parts) < 4:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
region = parts[3]
|
||||
if not region:
|
||||
raise ValueError(
|
||||
f"Region not found in ARN: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
return region
|
||||
|
||||
except IndexError:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
|
||||
def test_agent(client, agent_arn, test_name, prompt):
|
||||
"""Test the agent with a given prompt
|
||||
|
||||
Args:
|
||||
client: boto3 bedrock-agentcore client
|
||||
agent_arn: ARN of the agent runtime
|
||||
test_name: Name of the test for display
|
||||
prompt: Test prompt to send
|
||||
"""
|
||||
print(f"\nTest: {test_name}")
|
||||
print(f"Prompt: '{prompt}'")
|
||||
print("-" * 80)
|
||||
|
||||
try:
|
||||
response = client.invoke_agent_runtime(
|
||||
agentRuntimeArn=agent_arn,
|
||||
qualifier="DEFAULT",
|
||||
payload=json.dumps({"prompt": prompt})
|
||||
)
|
||||
|
||||
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
|
||||
print(f"Content Type: {response.get('contentType', 'N/A')}")
|
||||
|
||||
# Read the streaming response body
|
||||
response_text = ""
|
||||
if 'response' in response:
|
||||
response_body = response['response'].read()
|
||||
response_text = response_body.decode('utf-8')
|
||||
|
||||
if response_text:
|
||||
try:
|
||||
result = json.loads(response_text)
|
||||
response_content = result.get('response', response_text)
|
||||
# Truncate long responses for readability
|
||||
if len(response_content) > 500:
|
||||
print(f"\n✅ Response:\n{response_content[:500]}...")
|
||||
print("\n[Response truncated for display]")
|
||||
else:
|
||||
print(f"\n✅ Response:\n{response_content}")
|
||||
except json.JSONDecodeError:
|
||||
if len(response_text) > 500:
|
||||
print(f"\n✅ Response:\n{response_text[:500]}...")
|
||||
else:
|
||||
print(f"\n✅ Response:\n{response_text}")
|
||||
else:
|
||||
print("\n⚠️ No response content received")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def test_weather_agent(agent_arn):
|
||||
"""Test the weather agent with various scenarios
|
||||
|
||||
Args:
|
||||
agent_arn: Weather agent runtime ARN
|
||||
"""
|
||||
|
||||
# Extract region from ARN
|
||||
try:
|
||||
region = extract_region_from_arn(agent_arn)
|
||||
except ValueError as e:
|
||||
print(f"\n❌ ERROR: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("WEATHER AGENT TEST SUITE")
|
||||
print("="*80)
|
||||
print(f"\nAgent ARN: {agent_arn}")
|
||||
print(f"Region: {region}")
|
||||
|
||||
# Create bedrock-agentcore client with extracted region
|
||||
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
|
||||
|
||||
test_results = []
|
||||
|
||||
# Test 1: Simple weather query
|
||||
print("\n" + "="*80)
|
||||
print("TEST 1: Simple Weather Query")
|
||||
print("="*80)
|
||||
result = test_agent(
|
||||
agentcore_client,
|
||||
agent_arn,
|
||||
"Basic Weather",
|
||||
"What's the weather like in San Francisco today?"
|
||||
)
|
||||
test_results.append(("Simple Weather Query", result))
|
||||
|
||||
# Test 2: Complex query with tools (browser + code interpreter + memory)
|
||||
print("\n" + "="*80)
|
||||
print("TEST 2: Complex Query with Tools")
|
||||
print("="*80)
|
||||
result = test_agent(
|
||||
agentcore_client,
|
||||
agent_arn,
|
||||
"Weather Analysis with Tools",
|
||||
"Look up current weather conditions for Seattle, create a visualization of the temperature trend, and suggest outdoor activities based on the forecast."
|
||||
)
|
||||
test_results.append(("Complex Query with Tools", result))
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*80)
|
||||
print("TEST SUMMARY")
|
||||
print("="*80)
|
||||
|
||||
all_passed = True
|
||||
for test_name, passed in test_results:
|
||||
status = "✅ PASSED" if passed else "❌ FAILED"
|
||||
print(f"{status} - {test_name}")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
print("\n" + "="*80)
|
||||
if all_passed:
|
||||
print("✅ ALL TESTS PASSED")
|
||||
else:
|
||||
print("⚠️ SOME TESTS FAILED")
|
||||
print("="*80 + "\n")
|
||||
|
||||
return all_passed
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
print("\n❌ ERROR: Agent runtime ARN is required")
|
||||
print("\nTo get your agent ARN:")
|
||||
print(" - Terraform: terraform output agent_runtime_arn")
|
||||
print(" - CloudFormation: aws cloudformation describe-stacks --stack-name <stack> --query 'Stacks[0].Outputs'")
|
||||
print(" - CDK: cdk deploy --outputs-file outputs.json")
|
||||
print(" - Console: Check Bedrock Agent Core console")
|
||||
sys.exit(1)
|
||||
|
||||
agent_arn = sys.argv[1]
|
||||
|
||||
# Validate ARN format
|
||||
if not agent_arn.startswith("arn:aws:bedrock-agentcore:"):
|
||||
print(f"\n❌ ERROR: Invalid ARN format: {agent_arn}")
|
||||
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests
|
||||
success = test_weather_agent(agent_arn)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,79 @@
|
||||
variable "agent_name" {
|
||||
description = "Name for the weather agent runtime"
|
||||
type = string
|
||||
default = "WeatherAgent"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
|
||||
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "memory_name" {
|
||||
description = "Name for the agent memory resource"
|
||||
type = string
|
||||
default = "WeatherAgentMemory"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.memory_name))
|
||||
error_message = "Memory name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "network_mode" {
|
||||
description = "Network mode for AgentCore resources"
|
||||
type = string
|
||||
default = "PUBLIC"
|
||||
|
||||
validation {
|
||||
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
|
||||
error_message = "Network mode must be either PUBLIC or PRIVATE."
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Docker image tag"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "aws_region" {
|
||||
description = "AWS region for deployment (REQUIRED)"
|
||||
type = string
|
||||
# No default - must be explicitly provided
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
|
||||
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, staging, prod)"
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "common_tags" {
|
||||
description = "Common tags to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "stack_name" {
|
||||
description = "Stack name for resource naming"
|
||||
type = string
|
||||
default = "agentcore-weather"
|
||||
}
|
||||
|
||||
variable "environment_variables" {
|
||||
description = "Environment variables for the agent runtime"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "ecr_repository_name" {
|
||||
description = "Name of the ECR repository"
|
||||
type = string
|
||||
default = "weather-agent"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.21"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = "~> 0.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "AgentCore"
|
||||
Pattern = "end-to-end-weather-agent"
|
||||
Environment = var.environment
|
||||
StackName = var.stack_name
|
||||
ManagedBy = "Terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Terraform files
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
!terraform.tfvars.example
|
||||
tfplan
|
||||
|
||||
# Generated archives
|
||||
*.zip
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,580 @@
|
||||
# MCP Server on AgentCore Runtime - Terraform
|
||||
|
||||
This pattern demonstrates deploying an MCP (Model Context Protocol) server on Amazon Bedrock AgentCore Runtime using Terraform. It creates an MCP server with JWT authentication and three custom tools.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Testing the MCP Server](#testing-the-mcp-server)
|
||||
- [Sample Tool Invocations](#sample-tool-invocations)
|
||||
- [Customization](#customization)
|
||||
- [File Structure](#file-structure)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Pricing](#pricing)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Resources](#resources)
|
||||
- [🤝 Contributing](#-contributing)
|
||||
- [📄 License](#-license)
|
||||
|
||||
## Overview
|
||||
|
||||
This Terraform configuration creates an MCP server deployment that includes:
|
||||
|
||||
- **MCP Server**: Hosts three custom tools (add_numbers, multiply_numbers, greet_user)
|
||||
- **JWT Authentication**: Cognito User Pool for secure access
|
||||
- **AgentCore Runtime**: Serverless hosting with MCP protocol support
|
||||
- **ECR Repository**: Stores the Docker container image
|
||||
- **CodeBuild Project**: Automatically builds the ARM64 Docker image
|
||||
|
||||
The stack uses the Amazon Bedrock AgentCore Python SDK to wrap agent functions as an MCP server compatible with Amazon Bedrock AgentCore. When hosting tools, the SDK implements the [Stateless Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) transport protocol with the `MCP-Session-Id` header for session isolation.
|
||||
|
||||
This makes it ideal for:
|
||||
- Learning MCP protocol with AgentCore Runtime
|
||||
- Building secure MCP servers with JWT authentication
|
||||
- Understanding MCP tool development patterns
|
||||
- Creating custom tools for AI agents
|
||||
|
||||
### Tutorial Details
|
||||
|
||||
| Information | Details |
|
||||
|:--------------------|:----------------------------------------------------------|
|
||||
| Tutorial type | Hosting Tools |
|
||||
| Tool type | MCP server |
|
||||
| Tutorial components | Terraform, AgentCore Runtime, MCP server, Cognito |
|
||||
| Tutorial vertical | Cross-vertical |
|
||||
| Example complexity | Intermediate |
|
||||
| SDK used | Amazon BedrockAgentCore Python SDK and MCP Client |
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
The architecture consists of:
|
||||
|
||||
- **User/MCP Client**: Sends requests to the MCP server with JWT authentication
|
||||
- **Amazon Cognito**: Provides JWT-based authentication
|
||||
- User Pool with pre-created test user (testuser/MyPassword123!)
|
||||
- User Pool Client for application access
|
||||
- **AWS CodeBuild**: Builds the ARM64 Docker container image with the MCP server
|
||||
- **Amazon ECR Repository**: Stores the container image
|
||||
- **AgentCore Runtime**: Hosts the MCP Server
|
||||
- **MCP Server**: Exposes three tools via HTTP transport on port 8000
|
||||
- `add_numbers`: Adds two numbers
|
||||
- `multiply_numbers`: Multiplies two numbers
|
||||
- `greet_user`: Greets a user by name
|
||||
- Validates JWT tokens from Cognito
|
||||
- Processes MCP tool invocations
|
||||
- **IAM Roles**:
|
||||
- IAM role for CodeBuild (builds and pushes images)
|
||||
- IAM role for AgentCore Runtime (runtime permissions)
|
||||
|
||||
## What's Included
|
||||
|
||||
This Terraform configuration creates:
|
||||
|
||||
- **S3 Bucket**: Stores MCP server source code for version-controlled builds
|
||||
- **ECR Repository**: Container registry for the MCP server Docker image
|
||||
- **CodeBuild Project**: Automated Docker image building and pushing
|
||||
- **Cognito User Pool**: JWT authentication with pre-configured test user
|
||||
- **Cognito User Pool Client**: Application client for authentication
|
||||
- **IAM Roles**: Execution roles for AgentCore, CodeBuild, and Cognito operations
|
||||
- **AgentCore Runtime**: Serverless MCP server runtime with JWT validation
|
||||
|
||||
### MCP Server Code Management
|
||||
|
||||
The `mcp-server-code/` directory contains your MCP server's source files:
|
||||
- `mcp_server.py` - MCP server implementation with three tools
|
||||
- `Dockerfile` - Container configuration
|
||||
- `requirements.txt` - Python dependencies (mcp>=1.10.0, boto3, bedrock-agentcore)
|
||||
|
||||
**Automatic Change Detection**:
|
||||
- Terraform archives the `mcp-server-code/` directory
|
||||
- Uploads to S3 with MD5-based versioning
|
||||
- CodeBuild pulls from S3 and builds the Docker image
|
||||
- Any changes to files trigger automatic rebuild (new files, modifications, deletions)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Tools
|
||||
|
||||
1. **Terraform** (>= 1.6)
|
||||
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
|
||||
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
|
||||
|
||||
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
|
||||
|
||||
2. **AWS CLI** (configured with credentials)
|
||||
```bash
|
||||
aws configure
|
||||
```
|
||||
|
||||
3. **Python 3.11+** (for testing scripts)
|
||||
```bash
|
||||
python --version # Verify Python 3.11 or later
|
||||
pip install boto3 mcp
|
||||
```
|
||||
|
||||
4. **Docker** (for local testing, optional)
|
||||
|
||||
### AWS Account Requirements
|
||||
|
||||
- AWS Account with appropriate permissions
|
||||
- Access to Amazon Bedrock AgentCore service
|
||||
- Permissions to create:
|
||||
- ECR repositories
|
||||
- CodeBuild projects
|
||||
- Cognito User Pools
|
||||
- IAM roles and policies
|
||||
- AgentCore Runtime resources
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Configure Variables
|
||||
|
||||
Copy the example variables file and customize:
|
||||
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
```
|
||||
|
||||
Edit `terraform.tfvars` with your preferred values.
|
||||
|
||||
### 2. Initialize Terraform
|
||||
|
||||
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
|
||||
|
||||
**Quick start with local state:**
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
|
||||
|
||||
### 3. Review the Plan
|
||||
|
||||
```bash
|
||||
terraform plan
|
||||
```
|
||||
|
||||
### 4. Deploy
|
||||
|
||||
**Method 1: Using Deploy Script (Recommended)**
|
||||
|
||||
Make the script executable (first-time only):
|
||||
```bash
|
||||
chmod +x deploy.sh
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The deploy script:
|
||||
- Validates Terraform configuration
|
||||
- Shows deployment plan
|
||||
- Prompts for confirmation
|
||||
- Applies changes
|
||||
|
||||
**Method 2: Direct Terraform Commands**
|
||||
|
||||
```bash
|
||||
terraform apply
|
||||
```
|
||||
|
||||
When prompted, type `yes` to confirm the deployment.
|
||||
|
||||
**Note**: The deployment process includes:
|
||||
1. Creating ECR repository
|
||||
2. Building Docker image via CodeBuild
|
||||
3. Creating Cognito User Pool and test user
|
||||
4. Creating AgentCore Runtime with MCP protocol
|
||||
|
||||
Total deployment time: **~5-10 minutes**
|
||||
|
||||
### 5. Get Outputs
|
||||
|
||||
After deployment completes:
|
||||
|
||||
```bash
|
||||
terraform output
|
||||
```
|
||||
|
||||
Example output:
|
||||
```
|
||||
agent_runtime_id = "AGENT1234567890"
|
||||
agent_runtime_arn = "arn:aws:bedrock-agentcore:us-west-2:123456789012:agent-runtime/AGENT1234567890"
|
||||
cognito_user_pool_id = "us-west-2_AbCdEfGhI"
|
||||
cognito_user_pool_client_id = "1234567890abcdefghijklmno"
|
||||
cognito_discovery_url = "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_AbCdEfGhI/.well-known/openid-configuration"
|
||||
test_username = "testuser"
|
||||
get_token_command = "python get_token.py 1234567890abcdefghijklmno testuser MyPassword123! us-west-2"
|
||||
```
|
||||
|
||||
## Authentication Model
|
||||
|
||||
This pattern uses **Cognito JWT-based authentication**:
|
||||
|
||||
- **JWT Tokens**: Cognito User Pool issues JWT tokens for authentication
|
||||
- **Custom JWT Authorizer**: Runtime validates JWT tokens against Cognito discovery URL
|
||||
- **Test User**: Pre-configured user (testuser/MyPassword123!) for testing
|
||||
- **Token Expiry**: JWT tokens expire after 1 hour
|
||||
- **Discovery URL**: OpenID Connect discovery endpoint for token validation
|
||||
|
||||
**Authentication Flow:**
|
||||
1. User authenticates with Cognito User Pool
|
||||
2. Cognito issues JWT access token
|
||||
3. Client includes JWT token in MCP request headers
|
||||
4. Runtime validates token using Cognito's OIDC discovery endpoint
|
||||
5. Authorized requests processed by MCP server
|
||||
|
||||
**Note**: This is a backend authentication pattern for MCP tool access. For user-facing applications, integrate with your identity provider or use Cognito hosted UI for end-user authentication.
|
||||
|
||||
## Testing the MCP Server
|
||||
|
||||
### Prerequisites for Testing
|
||||
|
||||
Before testing, ensure you have the required packages installed:
|
||||
|
||||
**Option A: Using uv (Recommended)**
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv pip install boto3 mcp # Both required for MCP server testing
|
||||
```
|
||||
|
||||
**Option B: System-wide installation**
|
||||
```bash
|
||||
pip install boto3 mcp # Both required for MCP server testing
|
||||
```
|
||||
|
||||
**Note**: Both `boto3` (for AWS API calls) and `mcp` (for MCP protocol) are required for testing the MCP server.
|
||||
|
||||
### Step 1: Get Authentication Token
|
||||
|
||||
First, get a JWT token from Cognito:
|
||||
|
||||
```bash
|
||||
# Use the command from terraform outputs
|
||||
terraform output -raw get_token_command | bash
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
# Get the Client ID
|
||||
CLIENT_ID=$(terraform output -raw cognito_user_pool_client_id)
|
||||
REGION=$(terraform output -raw aws_region)
|
||||
|
||||
# Get authentication token
|
||||
python get_token.py $CLIENT_ID testuser MyPassword123! $REGION
|
||||
```
|
||||
|
||||
This will output a JWT token. Copy the token for the next step.
|
||||
|
||||
### Step 2: Test the MCP Server
|
||||
|
||||
```bash
|
||||
# Get the Runtime ARN
|
||||
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn)
|
||||
REGION=$(terraform output -raw aws_region)
|
||||
|
||||
# Test the MCP server (replace YOUR_JWT_TOKEN with the token from step 1)
|
||||
python test_mcp_server.py $RUNTIME_ARN YOUR_JWT_TOKEN $REGION
|
||||
```
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
🔄 Initializing MCP session...
|
||||
✓ MCP session initialized
|
||||
|
||||
🔄 Listing available tools...
|
||||
|
||||
📋 Available MCP Tools:
|
||||
==================================================
|
||||
🔧 add_numbers: Add two numbers together
|
||||
🔧 multiply_numbers: Multiply two numbers together
|
||||
🔧 greet_user: Greet a user by name
|
||||
|
||||
🧪 Testing MCP Tools:
|
||||
==================================================
|
||||
|
||||
➕ Testing add_numbers(5, 3)...
|
||||
Result: 8
|
||||
|
||||
✖️ Testing multiply_numbers(4, 7)...
|
||||
Result: 28
|
||||
|
||||
👋 Testing greet_user('Alice')...
|
||||
Result: Hello, Alice! Nice to meet you.
|
||||
|
||||
✅ MCP tool testing completed!
|
||||
```
|
||||
|
||||
## Sample Tool Invocations
|
||||
|
||||
Try these MCP tool calls:
|
||||
|
||||
1. **Add Numbers**:
|
||||
```python
|
||||
# Tool: add_numbers
|
||||
# Parameters: {"a": 10, "b": 25}
|
||||
# Expected Result: 35
|
||||
```
|
||||
|
||||
2. **Multiply Numbers**:
|
||||
```python
|
||||
# Tool: multiply_numbers
|
||||
# Parameters: {"a": 6, "b": 7}
|
||||
# Expected Result: 42
|
||||
```
|
||||
|
||||
3. **Greet User**:
|
||||
```python
|
||||
# Tool: greet_user
|
||||
# Parameters: {"name": "John"}
|
||||
# Expected Result: "Hello, John! Nice to meet you."
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify MCP Server Code
|
||||
|
||||
Edit files in `mcp-server-code/` and deploy:
|
||||
|
||||
**Adding New Tools**:
|
||||
|
||||
Edit `mcp-server-code/mcp_server.py`:
|
||||
```python
|
||||
@mcp.tool()
|
||||
def subtract_numbers(a: int, b: int) -> int:
|
||||
"""Subtract two numbers"""
|
||||
return a - b
|
||||
```
|
||||
|
||||
**Update Dependencies**:
|
||||
|
||||
Edit `mcp-server-code/requirements.txt`:
|
||||
```
|
||||
mcp>=1.10.0
|
||||
boto3
|
||||
bedrock-agentcore
|
||||
your-new-package>=1.0.0
|
||||
```
|
||||
|
||||
Changes are automatically detected and trigger rebuild. Run `terraform apply` to deploy.
|
||||
|
||||
### Modify Authentication
|
||||
|
||||
To change Cognito password policy, edit `cognito.tf`:
|
||||
```hcl
|
||||
password_policy {
|
||||
minimum_length = 12
|
||||
require_uppercase = true
|
||||
require_lowercase = true
|
||||
require_numbers = true
|
||||
require_symbols = true
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add to `terraform.tfvars`:
|
||||
```hcl
|
||||
environment_variables = {
|
||||
LOG_LEVEL = "DEBUG"
|
||||
CUSTOM_VAR = "value"
|
||||
}
|
||||
```
|
||||
|
||||
### Network Mode
|
||||
|
||||
Set `network_mode = "PRIVATE"` for VPC deployment (requires additional VPC configuration).
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
mcp-server-agentcore-runtime/
|
||||
├── main.tf # AgentCore runtime with MCP protocol
|
||||
├── variables.tf # Input variables
|
||||
├── outputs.tf # Output values (includes Cognito)
|
||||
├── versions.tf # Provider configuration
|
||||
├── iam.tf # IAM roles and policies
|
||||
├── s3.tf # S3 bucket for MCP server source
|
||||
├── ecr.tf # ECR repository
|
||||
├── codebuild.tf # Docker build automation
|
||||
├── cognito.tf # Cognito User Pool & Client
|
||||
├── buildspec.yml # CodeBuild build specification
|
||||
├── terraform.tfvars.example # Example configuration
|
||||
├── backend.tf.example # Remote state example
|
||||
├── mcp-server-code/ # MCP server source code
|
||||
│ ├── mcp_server.py # MCP server with 3 tools
|
||||
│ ├── Dockerfile # Container configuration
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── scripts/ # Build automation scripts
|
||||
│ └── build-image.sh # CodeBuild trigger & verification
|
||||
├── get_token.py # Cognito JWT token retrieval
|
||||
├── test_mcp_server.py # MCP server testing script
|
||||
├── deploy.sh # Deployment helper script
|
||||
├── destroy.sh # Cleanup helper script
|
||||
├── architecture.png # Architecture diagram
|
||||
├── .gitignore # Git ignore patterns
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CodeBuild Fails
|
||||
|
||||
If the Docker build fails:
|
||||
|
||||
1. Check CodeBuild logs:
|
||||
```bash
|
||||
PROJECT_NAME=$(terraform output -raw codebuild_project_name)
|
||||
aws codebuild batch-get-builds \
|
||||
--ids $PROJECT_NAME
|
||||
```
|
||||
|
||||
2. Common issues:
|
||||
- Network connectivity issues
|
||||
- ECR authentication problems
|
||||
- Python dependency conflicts in requirements.txt
|
||||
|
||||
### Runtime Creation Fails
|
||||
|
||||
If the runtime creation fails:
|
||||
|
||||
1. Verify the Docker image exists:
|
||||
```bash
|
||||
REPO_NAME=$(terraform output -raw ecr_repository_url | cut -d'/' -f2)
|
||||
aws ecr describe-images --repository-name $REPO_NAME
|
||||
```
|
||||
|
||||
2. Check IAM role permissions
|
||||
3. Verify Bedrock AgentCore service quotas
|
||||
4. Ensure MCP protocol is properly configured
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
If JWT authentication fails:
|
||||
|
||||
1. Verify Cognito user exists:
|
||||
```bash
|
||||
USER_POOL_ID=$(terraform output -raw cognito_user_pool_id)
|
||||
aws cognito-idp admin-get-user \
|
||||
--user-pool-id $USER_POOL_ID \
|
||||
--username testuser
|
||||
```
|
||||
|
||||
2. Check token expiration (tokens expire after 1 hour)
|
||||
3. Verify the discovery URL is accessible
|
||||
4. Ensure allowed_clients matches the client ID
|
||||
|
||||
### MCP Server Connection Fails
|
||||
|
||||
If MCP tool invocations fail:
|
||||
|
||||
1. Check runtime status in AWS Console
|
||||
2. Review CloudWatch Logs for the runtime
|
||||
3. Verify JWT token is valid and not expired
|
||||
4. Check that MCP protocol is configured correctly
|
||||
5. Ensure the runtime is in ACTIVE state
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Destroy All Resources
|
||||
|
||||
Make the script executable (first-time only):
|
||||
```bash
|
||||
chmod +x destroy.sh
|
||||
```
|
||||
|
||||
Then cleanup:
|
||||
```bash
|
||||
./destroy.sh
|
||||
```
|
||||
|
||||
Or use Terraform directly:
|
||||
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
**Note**: This will delete:
|
||||
- AgentCore Runtime
|
||||
- ECR Repository (and all images)
|
||||
- Cognito User Pool (and all users)
|
||||
- S3 Bucket (and all source code archives)
|
||||
- All IAM roles and policies
|
||||
|
||||
### Verify Cleanup
|
||||
|
||||
Confirm all resources are deleted:
|
||||
|
||||
```bash
|
||||
# Check AgentCore runtimes
|
||||
aws bedrock-agentcore list-agent-runtimes
|
||||
|
||||
# Check ECR repositories
|
||||
aws ecr describe-repositories | grep mcp-server
|
||||
|
||||
# Check Cognito User Pools
|
||||
aws cognito-idp list-user-pools --max-results 10
|
||||
```
|
||||
|
||||
## Pricing
|
||||
|
||||
For current pricing information, please refer to:
|
||||
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
|
||||
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
|
||||
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
|
||||
- [Amazon Cognito Pricing](https://aws.amazon.com/cognito/pricing/)
|
||||
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
|
||||
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
||||
- [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/)
|
||||
|
||||
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Explore More Patterns
|
||||
|
||||
- [Basic Runtime](../basic-runtime/) - Simpler deployment without MCP protocol
|
||||
- [Multi-Agent Runtime](../multi-agent-runtime/) - Deploy multiple coordinating agents
|
||||
- [End-to-End Weather Agent](../end-to-end-weather-agent/) - Full-featured agent with tools
|
||||
|
||||
### Extend This Pattern
|
||||
|
||||
- Add more MCP tools to `mcp-server-code/mcp_server.py`
|
||||
- Integrate with external APIs
|
||||
- Add persistent storage (DynamoDB, S3)
|
||||
- Implement custom authentication logic
|
||||
- Add monitoring and alerting
|
||||
- Deploy to VPC for private networking
|
||||
|
||||
### Learn More About MCP
|
||||
|
||||
- [Model Context Protocol Specification](https://modelcontextprotocol.io/specification/)
|
||||
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
|
||||
- [Building MCP Servers](https://modelcontextprotocol.io/docs/building-mcp-servers)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Terraform AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)
|
||||
- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
|
||||
- [Amazon Cognito Documentation](https://docs.aws.amazon.com/cognito/)
|
||||
- [AgentCore Samples Repository](https://github.com/aws-samples/amazon-bedrock-agentcore-samples)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](../../../LICENSE) file for details.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# Terraform Backend Configuration - Remote State Storage
|
||||
# ============================================================================
|
||||
# This is an example backend configuration for storing Terraform state remotely
|
||||
# Copy this file to backend.tf and customize with your backend settings
|
||||
# Example: cp backend.tf.example backend.tf
|
||||
|
||||
# IMPORTANT: Uncomment and configure ONE of the backend options below
|
||||
|
||||
# Option 1: S3 Backend (Recommended for AWS)
|
||||
# terraform {
|
||||
# backend "s3" {
|
||||
# bucket = "your-terraform-state-bucket"
|
||||
# key = "agentcore/mcp-server-agentcore-runtime/terraform.tfstate"
|
||||
# region = "us-west-2"
|
||||
# encrypt = true
|
||||
# dynamodb_table = "terraform-state-lock"
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 2: Terraform Cloud
|
||||
# terraform {
|
||||
# cloud {
|
||||
# organization = "your-organization"
|
||||
# workspaces {
|
||||
# name = "agentcore-basic-runtime"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 3: Local Backend (Default - No configuration needed)
|
||||
# State will be stored locally in terraform.tfstate
|
||||
# Not recommended for team environments or production use
|
||||
@@ -0,0 +1,29 @@
|
||||
version: 0.2
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Installing dependencies...
|
||||
- yum install -y unzip
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Source code already extracted by CodeBuild...
|
||||
- cd $CODEBUILD_SRC_DIR
|
||||
- ls -la
|
||||
- echo Logging in to Amazon ECR...
|
||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image for MCP server ARM64 from mcp-server-code/...
|
||||
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
||||
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Build completed on `date`
|
||||
- echo Pushing the Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
- echo ARM64 Docker image pushed successfully
|
||||
@@ -0,0 +1,90 @@
|
||||
# ============================================================================
|
||||
# CodeBuild Project - Build and Push MCP Server Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_codebuild_project" "agent_image" {
|
||||
name = "${var.stack_name}-mcp-server-build"
|
||||
description = "Build MCP server Docker image for ${var.stack_name}"
|
||||
service_role = aws_iam_role.codebuild.arn
|
||||
build_timeout = 60
|
||||
|
||||
artifacts {
|
||||
type = "NO_ARTIFACTS"
|
||||
}
|
||||
|
||||
environment {
|
||||
compute_type = "BUILD_GENERAL1_LARGE"
|
||||
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
|
||||
type = "ARM_CONTAINER"
|
||||
privileged_mode = true
|
||||
image_pull_credentials_type = "CODEBUILD"
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_DEFAULT_REGION"
|
||||
value = data.aws_region.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_ACCOUNT_ID"
|
||||
value = data.aws_caller_identity.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_REPO_NAME"
|
||||
value = aws_ecr_repository.server_ecr.name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_TAG"
|
||||
value = var.image_tag
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "STACK_NAME"
|
||||
value = var.stack_name
|
||||
}
|
||||
}
|
||||
|
||||
source {
|
||||
type = "S3"
|
||||
location = "${aws_s3_bucket.agent_source.id}/${aws_s3_object.agent_source.key}"
|
||||
buildspec = file("${path.module}/buildspec.yml")
|
||||
}
|
||||
|
||||
logs_config {
|
||||
cloudwatch_logs {
|
||||
group_name = "/aws/codebuild/${var.stack_name}-mcp-server-build"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-mcp-server-build"
|
||||
Module = "CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Trigger CodeBuild - Build Image Before Creating Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "null_resource" "trigger_build" {
|
||||
triggers = {
|
||||
build_project = aws_codebuild_project.agent_image.id
|
||||
image_tag = var.image_tag
|
||||
# Trigger rebuild if ECR repository changes
|
||||
ecr_repository = aws_ecr_repository.server_ecr.id
|
||||
# Trigger rebuild when source code changes (MD5 hash)
|
||||
source_code_md5 = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.agent_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.server_ecr.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.server_ecr.repository_url}\""
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_codebuild_project.agent_image,
|
||||
aws_ecr_repository.server_ecr,
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_s3_object.agent_source
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
# ============================================================================
|
||||
# Cognito User Pool for JWT Authentication
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_cognito_user_pool" "mcp_user_pool" {
|
||||
name = "${var.stack_name}-user-pool"
|
||||
|
||||
password_policy {
|
||||
minimum_length = 8
|
||||
require_uppercase = false
|
||||
require_lowercase = false
|
||||
require_numbers = false
|
||||
require_symbols = false
|
||||
}
|
||||
|
||||
schema {
|
||||
name = "email"
|
||||
attribute_data_type = "String"
|
||||
required = false
|
||||
mutable = true
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-user-pool"
|
||||
StackName = var.stack_name
|
||||
Module = "Cognito"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Cognito User Pool Client
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_cognito_user_pool_client" "mcp_client" {
|
||||
name = "${var.stack_name}-client"
|
||||
user_pool_id = aws_cognito_user_pool.mcp_user_pool.id
|
||||
|
||||
explicit_auth_flows = [
|
||||
"ALLOW_USER_PASSWORD_AUTH",
|
||||
"ALLOW_REFRESH_TOKEN_AUTH"
|
||||
]
|
||||
|
||||
generate_secret = false
|
||||
prevent_user_existence_errors = "ENABLED"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Test User
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_cognito_user" "test_user" {
|
||||
user_pool_id = aws_cognito_user_pool.mcp_user_pool.id
|
||||
username = "testuser"
|
||||
|
||||
message_action = "SUPPRESS"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Set Permanent Password for Test User
|
||||
# ============================================================================
|
||||
|
||||
resource "null_resource" "set_cognito_password" {
|
||||
triggers = {
|
||||
user_id = aws_cognito_user.test_user.id
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = <<-EOT
|
||||
aws cognito-idp admin-set-user-password \
|
||||
--user-pool-id ${aws_cognito_user_pool.mcp_user_pool.id} \
|
||||
--username testuser \
|
||||
--password 'MyPassword123!' \
|
||||
--permanent \
|
||||
--region ${data.aws_region.current.id}
|
||||
EOT
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_cognito_user.test_user
|
||||
]
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Script for MCP Server on AgentCore Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script automates the deployment process for the Terraform configuration
|
||||
# Usage: ./deploy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting MCP Server on AgentCore Runtime Deployment..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed. Please install Terraform >= 1.6"
|
||||
print_info "Visit: https://www.terraform.io/downloads"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Terraform version
|
||||
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
|
||||
print_success "Terraform version: $TERRAFORM_VERSION"
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
|
||||
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "AWS CLI is installed"
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
print_info "Run: aws configure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_success "AWS Account: $AWS_ACCOUNT"
|
||||
print_success "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking configuration files..."
|
||||
|
||||
# Check if terraform.tfvars exists
|
||||
if [ ! -f "terraform.tfvars" ]; then
|
||||
print_warning "terraform.tfvars not found"
|
||||
print_info "Creating terraform.tfvars from example..."
|
||||
|
||||
if [ -f "terraform.tfvars.example" ]; then
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
print_success "Created terraform.tfvars"
|
||||
print_warning "Please review and update terraform.tfvars with your settings"
|
||||
print_info "Then run this script again"
|
||||
exit 0
|
||||
else
|
||||
print_error "terraform.tfvars.example not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Configuration file found: terraform.tfvars"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Initialization
|
||||
# ============================================================================
|
||||
|
||||
print_info "Initializing Terraform..."
|
||||
if terraform init; then
|
||||
print_success "Terraform initialized successfully"
|
||||
else
|
||||
print_error "Terraform initialization failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Validation
|
||||
# ============================================================================
|
||||
|
||||
print_info "Validating Terraform configuration..."
|
||||
if terraform validate; then
|
||||
print_success "Terraform configuration is valid"
|
||||
else
|
||||
print_error "Terraform validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Format Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking Terraform formatting..."
|
||||
if terraform fmt -check -recursive > /dev/null 2>&1; then
|
||||
print_success "Terraform files are properly formatted"
|
||||
else
|
||||
print_warning "Some files need formatting. Running terraform fmt..."
|
||||
terraform fmt -recursive
|
||||
print_success "Files formatted"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating Terraform execution plan..."
|
||||
print_warning "This may take a few moments..."
|
||||
echo ""
|
||||
|
||||
if terraform plan -out=tfplan; then
|
||||
print_success "Terraform plan created successfully"
|
||||
else
|
||||
print_error "Terraform plan failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "DEPLOYMENT CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_info "This will deploy the following resources:"
|
||||
print_info " - S3 Bucket (source code storage)"
|
||||
print_info " - ECR Repository"
|
||||
print_info " - CodeBuild Project"
|
||||
print_info " - Cognito User Pool + Client (JWT authentication)"
|
||||
print_info " - IAM Roles and Policies"
|
||||
print_info " - AgentCore Runtime (with MCP protocol)"
|
||||
echo ""
|
||||
print_info "The deployment includes:"
|
||||
print_info " - Building ARM64 Docker image with MCP server"
|
||||
print_info " - Creating AWS resources"
|
||||
print_info " - Setting up JWT authentication with test user"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to proceed with deployment? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Deployment cancelled by user"
|
||||
rm -f tfplan
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Apply
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting deployment..."
|
||||
echo ""
|
||||
|
||||
if terraform apply tfplan; then
|
||||
print_success "Deployment completed successfully!"
|
||||
else
|
||||
print_error "Deployment failed"
|
||||
rm -f tfplan
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up plan file
|
||||
rm -f tfplan
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "DEPLOYMENT COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Retrieving deployment outputs..."
|
||||
echo ""
|
||||
|
||||
# Get outputs
|
||||
RUNTIME_ID=$(terraform output -raw agent_runtime_id 2>/dev/null || echo "N/A")
|
||||
RUNTIME_ARN=$(terraform output -raw agent_runtime_arn 2>/dev/null || echo "N/A")
|
||||
ECR_URL=$(terraform output -raw ecr_repository_url 2>/dev/null || echo "N/A")
|
||||
USER_POOL_ID=$(terraform output -raw cognito_user_pool_id 2>/dev/null || echo "N/A")
|
||||
CLIENT_ID=$(terraform output -raw cognito_user_pool_client_id 2>/dev/null || echo "N/A")
|
||||
|
||||
print_success "Agent Runtime ID: $RUNTIME_ID"
|
||||
print_success "Agent Runtime ARN: $RUNTIME_ARN"
|
||||
print_success "ECR Repository URL: $ECR_URL"
|
||||
print_success "Cognito User Pool ID: $USER_POOL_ID"
|
||||
print_success "Cognito Client ID: $CLIENT_ID"
|
||||
|
||||
echo ""
|
||||
print_info "Next Steps:"
|
||||
print_info "1. Get JWT token for authentication:"
|
||||
print_info " terraform output -raw get_token_command | bash"
|
||||
echo ""
|
||||
print_info "2. Test the MCP server:"
|
||||
print_info " # Export the JWT token from step 1, then:"
|
||||
print_info " python test_mcp_server.py $RUNTIME_ARN \$JWT_TOKEN $AWS_REGION"
|
||||
echo ""
|
||||
print_info "3. View all outputs:"
|
||||
print_info " terraform output"
|
||||
echo ""
|
||||
print_info "4. Monitor in AWS Console:"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_success "Deployment completed successfully!"
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Destroy Script for MCP Server on AgentCore Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script safely destroys all resources created by this Terraform configuration
|
||||
# Usage: ./destroy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting Resource Cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_info "AWS Account: $AWS_ACCOUNT"
|
||||
print_info "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Check for Terraform State
|
||||
# ============================================================================
|
||||
|
||||
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
|
||||
print_warning "No Terraform state found"
|
||||
print_info "Either no resources have been deployed, or state is stored remotely"
|
||||
|
||||
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Initializing Terraform to fetch remote state..."
|
||||
terraform init
|
||||
else
|
||||
print_info "Cleanup cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Show Destruction Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating destruction plan..."
|
||||
echo ""
|
||||
|
||||
if ! terraform plan -destroy; then
|
||||
print_error "Failed to create destruction plan"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Destruction Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_warning "This will permanently delete the following resources:"
|
||||
print_warning " - AgentCore Runtime (with MCP server)"
|
||||
print_warning " - Cognito User Pool (including test user)"
|
||||
print_warning " - S3 Bucket (source code storage)"
|
||||
print_warning " - ECR Repository (including all images)"
|
||||
print_warning " - CodeBuild Project"
|
||||
print_warning " - IAM Roles and Policies"
|
||||
print_warning " - CloudWatch Log Groups"
|
||||
echo ""
|
||||
print_warning "THIS ACTION CANNOT BE UNDONE!"
|
||||
echo ""
|
||||
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
|
||||
echo ""
|
||||
|
||||
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Destruction cancelled by user"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Double confirmation for safety
|
||||
print_warning "Second confirmation required..."
|
||||
read -p "Type 'DESTROY' to confirm: " -r
|
||||
echo ""
|
||||
|
||||
if [ "$REPLY" != "DESTROY" ]; then
|
||||
print_info "Destruction cancelled - confirmation text did not match"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Execute Destruction
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting resource destruction..."
|
||||
echo ""
|
||||
|
||||
if terraform destroy -auto-approve; then
|
||||
print_success "All resources destroyed successfully"
|
||||
else
|
||||
print_error "Destruction failed"
|
||||
print_warning "Some resources may still exist. Please check AWS Console"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Cleanup Local Files
|
||||
# ============================================================================
|
||||
|
||||
print_info "Cleaning up local Terraform files..."
|
||||
|
||||
# Ask about state file cleanup
|
||||
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -f terraform.tfstate
|
||||
rm -f terraform.tfstate.backup
|
||||
rm -f tfplan
|
||||
print_success "Local state files removed"
|
||||
fi
|
||||
|
||||
# Ask about .terraform directory
|
||||
read -p "Do you want to remove .terraform directory? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -rf .terraform
|
||||
rm -f .terraform.lock.hcl
|
||||
print_success ".terraform directory removed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verification
|
||||
# ============================================================================
|
||||
|
||||
print_info "Verifying resource cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check for ECR repositories
|
||||
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-mcp-server")
|
||||
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$ECR_REPOS" -eq 0 ]; then
|
||||
print_success "ECR repositories cleaned up"
|
||||
else
|
||||
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for AgentCore runtimes
|
||||
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$RUNTIME_COUNT" -eq 0 ]; then
|
||||
print_success "AgentCore runtimes cleaned up"
|
||||
else
|
||||
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for S3 buckets
|
||||
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$S3_BUCKETS" -eq 0 ]; then
|
||||
print_success "S3 buckets cleaned up"
|
||||
else
|
||||
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for Cognito User Pools
|
||||
COGNITO_POOLS=$(aws cognito-idp list-user-pools --max-results 60 --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$COGNITO_POOLS" -eq 0 ]; then
|
||||
print_success "Cognito User Pools cleaned up"
|
||||
else
|
||||
print_warning "Found $COGNITO_POOLS Cognito User Pools matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Completion Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "CLEANUP COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Cleanup Summary:"
|
||||
print_success " ✓ Terraform resources destroyed"
|
||||
print_success " ✓ Local state files cleaned (if selected)"
|
||||
echo ""
|
||||
|
||||
print_info "What to verify in AWS Console:"
|
||||
print_info "1. Bedrock AgentCore - No runtimes remaining"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_info "2. S3 - No buckets remaining"
|
||||
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "3. ECR - No repositories remaining"
|
||||
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "4. Cognito - No user pools remaining"
|
||||
print_info " https://console.aws.amazon.com/cognito/v2/idp/user-pools?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "5. CodeBuild - No projects remaining"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "6. CloudWatch Logs - Check for orphaned log groups"
|
||||
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
|
||||
echo ""
|
||||
|
||||
print_success "Cleanup completed successfully!"
|
||||
print_info "You can safely re-deploy by running: ./deploy.sh"
|
||||
@@ -0,0 +1,63 @@
|
||||
# ============================================================================
|
||||
# ECR Repository - Container Registry for Agent Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_ecr_repository" "server_ecr" {
|
||||
name = "${var.stack_name}-${var.ecr_repository_name}"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
force_delete = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-ecr-repository"
|
||||
Module = "ECR"
|
||||
}
|
||||
}
|
||||
|
||||
# ECR Repository Policy
|
||||
resource "aws_ecr_repository_policy" "server_ecr" {
|
||||
repository = aws_ecr_repository.server_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowPullFromAccount"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
|
||||
}
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Lifecycle Policy - Keep last 5 images
|
||||
resource "aws_ecr_lifecycle_policy" "server_ecr" {
|
||||
repository = aws_ecr_repository.server_ecr.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep last 5 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Cognito Authentication Script
|
||||
Matches the approach from the original tutorial
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import sys
|
||||
|
||||
|
||||
def get_token(client_id, username, password, region=None):
|
||||
"""Get authentication token from Cognito."""
|
||||
# Use provided region or default from environment/config
|
||||
if region:
|
||||
cognito_client = boto3.client("cognito-idp", region_name=region)
|
||||
else:
|
||||
cognito_client = boto3.client("cognito-idp")
|
||||
|
||||
try:
|
||||
auth_response = cognito_client.initiate_auth(
|
||||
ClientId=client_id,
|
||||
AuthFlow="USER_PASSWORD_AUTH",
|
||||
AuthParameters={"USERNAME": username, "PASSWORD": password},
|
||||
)
|
||||
|
||||
return auth_response["AuthenticationResult"]["AccessToken"]
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
print("Troubleshooting:")
|
||||
print(" - Verify the Client ID is correct")
|
||||
print(" - Ensure you're using the correct region")
|
||||
print(" - Check that the user exists and password is correct")
|
||||
print(" - Verify USER_PASSWORD_AUTH is enabled for this client")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4 or len(sys.argv) > 5:
|
||||
print("Usage: python get_token.py <client_id> <username> <password> [region]")
|
||||
sys.exit(1)
|
||||
|
||||
client_id = sys.argv[1]
|
||||
username = sys.argv[2]
|
||||
password = sys.argv[3]
|
||||
region = sys.argv[4] if len(sys.argv) == 5 else None
|
||||
|
||||
if region:
|
||||
print(f"Authenticating with Cognito in region {region}...")
|
||||
else:
|
||||
print("Authenticating with Cognito...")
|
||||
|
||||
token = get_token(client_id, username, password, region)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Authentication Successful!")
|
||||
print("=" * 70)
|
||||
print("\nAccess Token:")
|
||||
print(token)
|
||||
print("\n" + "=" * 70)
|
||||
print("Export Command:")
|
||||
print("=" * 70)
|
||||
print(f'\nexport JWT_TOKEN="{token}"')
|
||||
print("\nThen use in curl:")
|
||||
print('curl -H "Authorization: Bearer $JWT_TOKEN" <your-api-url>')
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,217 @@
|
||||
# Data sources
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# ============================================================================
|
||||
# Agent Execution Role - For AgentCore Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "agent_execution" {
|
||||
name = "${var.stack_name}-agent-execution-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AssumeRolePolicy"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "bedrock-agentcore.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.id
|
||||
}
|
||||
ArnLike = {
|
||||
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-agent-execution-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Attach AWS managed policy for AgentCore
|
||||
resource "aws_iam_role_policy_attachment" "agent_execution_managed" {
|
||||
role = aws_iam_role.agent_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
|
||||
}
|
||||
|
||||
# Inline policy for agent execution
|
||||
resource "aws_iam_role_policy" "agent_execution" {
|
||||
name = "AgentCoreExecutionPolicy"
|
||||
role = aws_iam_role.agent_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRImageAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
Resource = aws_ecr_repository.server_ecr.arn
|
||||
},
|
||||
{
|
||||
Sid = "ECRTokenAccess"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:GetAuthorizationToken"]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
||||
},
|
||||
# X-Ray Tracing
|
||||
{
|
||||
Sid = "XRayTracing"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Metrics
|
||||
{
|
||||
Sid = "CloudWatchMetrics"
|
||||
Effect = "Allow"
|
||||
Action = ["cloudwatch:PutMetricData"]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cloudwatch:namespace" = "bedrock-agentcore"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Bedrock Model Invocation
|
||||
{
|
||||
Sid = "BedrockModelInvocation"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# Workload Access Tokens
|
||||
{
|
||||
Sid = "GetAgentAccessToken"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CodeBuild Service Role - For Docker Image Building
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "codebuild" {
|
||||
name = "${var.stack_name}-codebuild-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "codebuild.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-codebuild-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Inline policy for CodeBuild
|
||||
resource "aws_iam_role_policy" "codebuild" {
|
||||
name = "CodeBuildPolicy"
|
||||
role = aws_iam_role.codebuild.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
|
||||
},
|
||||
# ECR Access
|
||||
{
|
||||
Sid = "ECRAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetAuthorizationToken",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload"
|
||||
]
|
||||
Resource = [
|
||||
aws_ecr_repository.server_ecr.arn,
|
||||
"*"
|
||||
]
|
||||
},
|
||||
# S3 Source Access (for agent-code)
|
||||
{
|
||||
Sid = "S3SourceAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion"
|
||||
]
|
||||
Resource = "${aws_s3_bucket.agent_source.arn}/*"
|
||||
},
|
||||
{
|
||||
Sid = "S3BucketAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:ListBucket",
|
||||
"s3:GetBucketLocation"
|
||||
]
|
||||
Resource = aws_s3_bucket.agent_source.arn
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# ============================================================================
|
||||
# AgentCore Runtime - Main Agent Runtime Resource
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "mcp_server" {
|
||||
agent_runtime_name = replace("${var.stack_name}_${var.agent_name}", "-", "_")
|
||||
description = var.description
|
||||
role_arn = aws_iam_role.agent_execution.arn
|
||||
|
||||
agent_runtime_artifact {
|
||||
container_configuration {
|
||||
container_uri = "${aws_ecr_repository.server_ecr.repository_url}:${var.image_tag}"
|
||||
}
|
||||
}
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
# MCP Protocol Configuration
|
||||
protocol_configuration {
|
||||
server_protocol = "MCP"
|
||||
}
|
||||
|
||||
# JWT Authorization with Cognito
|
||||
authorizer_configuration {
|
||||
custom_jwt_authorizer {
|
||||
allowed_clients = [aws_cognito_user_pool_client.mcp_client.id]
|
||||
discovery_url = "https://cognito-idp.${data.aws_region.current.id}.amazonaws.com/${aws_cognito_user_pool.mcp_user_pool.id}/.well-known/openid-configuration"
|
||||
}
|
||||
}
|
||||
|
||||
environment_variables = merge(
|
||||
{
|
||||
AWS_REGION = var.aws_region
|
||||
AWS_DEFAULT_REGION = var.aws_region
|
||||
},
|
||||
var.environment_variables
|
||||
)
|
||||
|
||||
depends_on = [
|
||||
null_resource.trigger_build,
|
||||
null_resource.set_cognito_password,
|
||||
aws_iam_role_policy.agent_execution,
|
||||
aws_iam_role_policy_attachment.agent_execution_managed
|
||||
]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.11-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 bedrock_agentcore
|
||||
USER bedrock_agentcore
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-m", "mcp_server"]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(host="0.0.0.0", stateless_http=True)
|
||||
|
||||
@mcp.tool()
|
||||
def add_numbers(a: int, b: int) -> int:
|
||||
"""Add two numbers together"""
|
||||
return a + b
|
||||
|
||||
@mcp.tool()
|
||||
def multiply_numbers(a: int, b: int) -> int:
|
||||
"""Multiply two numbers together"""
|
||||
return a * b
|
||||
|
||||
@mcp.tool()
|
||||
def greet_user(name: str) -> str:
|
||||
"""Greet a user by name"""
|
||||
return f"Hello, {name}! Nice to meet you."
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
mcp>=1.10.0
|
||||
boto3
|
||||
bedrock-agentcore
|
||||
@@ -0,0 +1,99 @@
|
||||
output "agent_runtime_id" {
|
||||
description = "ID of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_id
|
||||
}
|
||||
|
||||
output "agent_runtime_arn" {
|
||||
description = "ARN of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "agent_runtime_version" {
|
||||
description = "Version of the created agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.mcp_server.agent_runtime_version
|
||||
}
|
||||
|
||||
output "ecr_repository_url" {
|
||||
description = "URL of the ECR repository"
|
||||
value = aws_ecr_repository.server_ecr.repository_url
|
||||
}
|
||||
|
||||
output "ecr_repository_arn" {
|
||||
description = "ARN of the ECR repository"
|
||||
value = aws_ecr_repository.server_ecr.arn
|
||||
}
|
||||
|
||||
output "agent_execution_role_arn" {
|
||||
description = "ARN of the agent execution role"
|
||||
value = aws_iam_role.agent_execution.arn
|
||||
}
|
||||
|
||||
output "codebuild_project_name" {
|
||||
description = "Name of the CodeBuild project"
|
||||
value = aws_codebuild_project.agent_image.name
|
||||
}
|
||||
|
||||
output "codebuild_project_arn" {
|
||||
description = "ARN of the CodeBuild project"
|
||||
value = aws_codebuild_project.agent_image.arn
|
||||
}
|
||||
|
||||
output "source_bucket_name" {
|
||||
description = "S3 bucket containing agent source code"
|
||||
value = aws_s3_bucket.agent_source.id
|
||||
}
|
||||
|
||||
output "source_bucket_arn" {
|
||||
description = "ARN of the S3 bucket containing agent source code"
|
||||
value = aws_s3_bucket.agent_source.arn
|
||||
}
|
||||
|
||||
output "source_object_key" {
|
||||
description = "S3 object key for the agent source code archive"
|
||||
value = aws_s3_object.agent_source.key
|
||||
}
|
||||
|
||||
output "source_code_md5" {
|
||||
description = "MD5 hash of the agent source code (triggers rebuild when changed)"
|
||||
value = data.archive_file.agent_source.output_md5
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Cognito Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "cognito_user_pool_id" {
|
||||
description = "ID of the Cognito User Pool"
|
||||
value = aws_cognito_user_pool.mcp_user_pool.id
|
||||
}
|
||||
|
||||
output "cognito_user_pool_arn" {
|
||||
description = "ARN of the Cognito User Pool"
|
||||
value = aws_cognito_user_pool.mcp_user_pool.arn
|
||||
}
|
||||
|
||||
output "cognito_user_pool_client_id" {
|
||||
description = "ID of the Cognito User Pool Client"
|
||||
value = aws_cognito_user_pool_client.mcp_client.id
|
||||
}
|
||||
|
||||
output "cognito_discovery_url" {
|
||||
description = "Cognito OIDC Discovery URL"
|
||||
value = "https://cognito-idp.${data.aws_region.current.id}.amazonaws.com/${aws_cognito_user_pool.mcp_user_pool.id}/.well-known/openid-configuration"
|
||||
}
|
||||
|
||||
output "test_username" {
|
||||
description = "Test username for authentication"
|
||||
value = "testuser"
|
||||
}
|
||||
|
||||
output "test_password" {
|
||||
description = "Test password for authentication"
|
||||
value = "MyPassword123!"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "get_token_command" {
|
||||
description = "Command to get authentication token"
|
||||
value = "python get_token.py ${aws_cognito_user_pool_client.mcp_client.id} testuser MyPassword123! ${data.aws_region.current.id}"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# ============================================================================
|
||||
# S3 Bucket for MCP Server Source Code (CDK Asset Equivalent)
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_s3_bucket" "agent_source" {
|
||||
bucket_prefix = "${var.stack_name}-source-"
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-mcp-server-source"
|
||||
Purpose = "Store MCP server source code for CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# Block public access
|
||||
resource "aws_s3_bucket_public_access_block" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Enable versioning for source code tracking
|
||||
resource "aws_s3_bucket_versioning" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Archive and Upload MCP Server Source Code
|
||||
# ============================================================================
|
||||
|
||||
# Archive mcp-server-code/ directory
|
||||
# This automatically detects ALL files including new ones
|
||||
data "archive_file" "agent_source" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/mcp-server-code"
|
||||
output_path = "${path.module}/.terraform/mcp-server-code.zip"
|
||||
}
|
||||
|
||||
# Upload to S3 (re-uploads when MD5 changes)
|
||||
resource "aws_s3_object" "agent_source" {
|
||||
bucket = aws_s3_bucket.agent_source.id
|
||||
key = "mcp-server-code-${data.archive_file.agent_source.output_md5}.zip"
|
||||
source = data.archive_file.agent_source.output_path
|
||||
etag = data.archive_file.agent_source.output_md5
|
||||
|
||||
tags = {
|
||||
Name = "mcp-server-source-code"
|
||||
MD5 = data.archive_file.agent_source.output_md5
|
||||
Timestamp = timestamp()
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Build and Verify Docker Image for AgentCore Runtime
|
||||
# ============================================================================
|
||||
# This script is called by Terraform during deployment to:
|
||||
# 1. Trigger CodeBuild to build the Docker image
|
||||
# 2. Wait for the build to complete
|
||||
# 3. Verify the image was successfully pushed to ECR
|
||||
#
|
||||
# Parameters:
|
||||
# $1 - CodeBuild project name
|
||||
# $2 - AWS region
|
||||
# $3 - ECR repository name
|
||||
# $4 - Image tag
|
||||
# $5 - ECR repository URL
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parameters
|
||||
PROJECT_NAME="$1"
|
||||
REGION="$2"
|
||||
REPO_NAME="$3"
|
||||
IMAGE_TAG="$4"
|
||||
REPO_URL="$5"
|
||||
|
||||
# ============================================================================
|
||||
# Print functions
|
||||
# ============================================================================
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[✓]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[⚠]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[✗]${NC} $1"
|
||||
}
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Start Build Process
|
||||
# ============================================================================
|
||||
|
||||
print_header "Building Docker Image for AgentCore Runtime"
|
||||
|
||||
print_info "CodeBuild Project: $PROJECT_NAME"
|
||||
print_info "Region: $REGION"
|
||||
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
|
||||
echo ""
|
||||
|
||||
# Start CodeBuild
|
||||
print_info "Starting CodeBuild project..."
|
||||
|
||||
BUILD_ID=$(aws codebuild start-build \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--region "$REGION" \
|
||||
--query 'build.id' \
|
||||
--output text 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
print_error "Failed to start CodeBuild"
|
||||
echo "$BUILD_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build started: $BUILD_ID"
|
||||
print_info "Waiting for build to complete (typically 5-10 minutes)..."
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Monitor Build Progress
|
||||
# ============================================================================
|
||||
|
||||
ATTEMPT=0
|
||||
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
|
||||
|
||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
|
||||
STATUS=$(aws codebuild batch-get-builds \
|
||||
--ids "$BUILD_ID" \
|
||||
--region "$REGION" \
|
||||
--query 'builds[0].buildStatus' \
|
||||
--output text 2>/dev/null)
|
||||
|
||||
if [ "$STATUS" != "IN_PROGRESS" ]; then
|
||||
print_info "Build process completed with status: $STATUS"
|
||||
break
|
||||
fi
|
||||
|
||||
# Progress indicator
|
||||
if [ $((ATTEMPT % 6)) -eq 0 ]; then
|
||||
MINUTES=$((ATTEMPT / 6))
|
||||
print_info "Build in progress... (${MINUTES} minutes elapsed)"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
||||
print_error "Build timeout after 10 minutes"
|
||||
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verify Image in ECR
|
||||
# ============================================================================
|
||||
|
||||
print_header "Verifying Docker Image in ECR"
|
||||
|
||||
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
|
||||
print_info "Waiting for ECR propagation..."
|
||||
echo ""
|
||||
|
||||
sleep 5 # Brief wait for ECR to register the push
|
||||
|
||||
VERIFY_ATTEMPT=0
|
||||
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
|
||||
|
||||
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
|
||||
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
|
||||
|
||||
if aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" >/dev/null 2>&1; then
|
||||
|
||||
print_success "Docker image successfully verified in ECR!"
|
||||
echo ""
|
||||
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
|
||||
|
||||
# Get image details
|
||||
IMAGE_SIZE=$(aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" \
|
||||
--query 'imageDetails[0].imageSizeInBytes' \
|
||||
--output text 2>/dev/null || echo "Unknown")
|
||||
|
||||
if [ "$IMAGE_SIZE" != "Unknown" ]; then
|
||||
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
|
||||
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Build and verification completed successfully!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
|
||||
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Error: Image Not Found
|
||||
# ============================================================================
|
||||
|
||||
print_error "Docker image not found in ECR after build completion"
|
||||
echo ""
|
||||
print_warning "This indicates the build or push step failed."
|
||||
print_info "Troubleshooting steps:"
|
||||
print_info " 1. Check CodeBuild logs:"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
print_info ""
|
||||
print_info " 2. Verify ECR repository:"
|
||||
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
|
||||
print_info ""
|
||||
print_info " 3. Check IAM permissions for CodeBuild role"
|
||||
|
||||
exit 1
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# ============================================================================
|
||||
# MCP Server on AgentCore Runtime - Example Configuration
|
||||
# ============================================================================
|
||||
# Copy this file to terraform.tfvars and customize
|
||||
# Example: cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Agent Configuration
|
||||
agent_name = "MCPServerAgent"
|
||||
stack_name = "agentcore-mcp-server"
|
||||
description = "MCP server runtime with JWT authentication"
|
||||
|
||||
# Network Configuration
|
||||
network_mode = "PUBLIC" # PUBLIC or PRIVATE
|
||||
|
||||
# Container Configuration
|
||||
ecr_repository_name = "mcp-server"
|
||||
image_tag = "latest"
|
||||
|
||||
# AWS Configuration
|
||||
aws_region = "us-west-2"
|
||||
environment = "dev"
|
||||
|
||||
# Optional: Environment Variables for MCP Server
|
||||
# environment_variables = {
|
||||
# LOG_LEVEL = "INFO"
|
||||
# }
|
||||
|
||||
# Notes:
|
||||
# - JWT auth via Cognito (test user: testuser/MyPassword123!)
|
||||
# - MCP server code in mcp-server-code/ directory
|
||||
# - Three tools included: add_numbers, multiply_numbers, greet_user
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for deployed MCP server
|
||||
Uses the MCP Python client library to properly communicate with the server
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
|
||||
|
||||
def extract_region_from_arn(arn):
|
||||
"""Extract AWS region from agent runtime ARN.
|
||||
|
||||
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
|
||||
|
||||
Args:
|
||||
arn: Agent runtime ARN string
|
||||
|
||||
Returns:
|
||||
str: AWS region code
|
||||
|
||||
Raises:
|
||||
ValueError: If ARN format is invalid or region cannot be extracted
|
||||
"""
|
||||
try:
|
||||
parts = arn.split(':')
|
||||
if len(parts) < 4:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
region = parts[3]
|
||||
if not region:
|
||||
raise ValueError(
|
||||
f"Region not found in ARN: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
return region
|
||||
|
||||
except IndexError:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
|
||||
async def test_mcp_server(agent_arn, bearer_token, region):
|
||||
"""Test the deployed MCP server."""
|
||||
|
||||
# Encode the ARN for URL
|
||||
encoded_arn = agent_arn.replace(":", "%3A").replace("/", "%2F")
|
||||
mcp_url = f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"
|
||||
|
||||
headers = {
|
||||
"authorization": f"Bearer {bearer_token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
print(f"Connecting to: {mcp_url}")
|
||||
print()
|
||||
|
||||
try:
|
||||
async with streamablehttp_client(
|
||||
mcp_url, headers, timeout=timedelta(seconds=120), terminate_on_close=False
|
||||
) as (read_stream, write_stream, _):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
print("🔄 Initializing MCP session...")
|
||||
await session.initialize()
|
||||
print("✓ MCP session initialized\n")
|
||||
|
||||
print("🔄 Listing available tools...")
|
||||
tool_result = await session.list_tools()
|
||||
|
||||
print("\n📋 Available MCP Tools:")
|
||||
print("=" * 50)
|
||||
for tool in tool_result.tools:
|
||||
print(f"🔧 {tool.name}: {tool.description}")
|
||||
|
||||
print("\n🧪 Testing MCP Tools:")
|
||||
print("=" * 50)
|
||||
|
||||
# Test add_numbers
|
||||
print("\n➕ Testing add_numbers(5, 3)...")
|
||||
add_result = await session.call_tool(
|
||||
name="add_numbers", arguments={"a": 5, "b": 3}
|
||||
)
|
||||
print(f" Result: {add_result.content[0].text}")
|
||||
|
||||
# Test multiply_numbers
|
||||
print("\n✖️ Testing multiply_numbers(4, 7)...")
|
||||
multiply_result = await session.call_tool(
|
||||
name="multiply_numbers", arguments={"a": 4, "b": 7}
|
||||
)
|
||||
print(f" Result: {multiply_result.content[0].text}")
|
||||
|
||||
# Test greet_user
|
||||
print("\n👋 Testing greet_user('Alice')...")
|
||||
greet_result = await session.call_tool(
|
||||
name="greet_user", arguments={"name": "Alice"}
|
||||
)
|
||||
print(f" Result: {greet_result.content[0].text}")
|
||||
|
||||
print("\n✅ MCP tool testing completed!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python test_mcp_server.py <agent_arn> <bearer_token> [region]")
|
||||
print("\nRegion is optional - will be extracted from ARN if not provided")
|
||||
print("\nExample:")
|
||||
print(
|
||||
" python test_mcp_server.py arn:aws:bedrock-agentcore:<region>:... eyJraWQiOiJ..."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
agent_arn = sys.argv[1]
|
||||
bearer_token = sys.argv[2]
|
||||
|
||||
# Extract region from ARN or use provided region
|
||||
if len(sys.argv) > 3:
|
||||
region = sys.argv[3]
|
||||
print(f"Using provided region: {region}")
|
||||
else:
|
||||
try:
|
||||
region = extract_region_from_arn(agent_arn)
|
||||
print(f"Extracted region from ARN: {region}")
|
||||
except ValueError as e:
|
||||
print(f"\n❌ ERROR: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
asyncio.run(test_mcp_server(agent_arn, bearer_token, region))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
variable "agent_name" {
|
||||
description = "Name for the agent runtime"
|
||||
type = string
|
||||
default = "MCPServerAgent"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.agent_name))
|
||||
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "network_mode" {
|
||||
description = "Network mode for AgentCore resources"
|
||||
type = string
|
||||
default = "PUBLIC"
|
||||
|
||||
validation {
|
||||
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
|
||||
error_message = "Network mode must be either PUBLIC or PRIVATE."
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Docker image tag"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "aws_region" {
|
||||
description = "AWS region for deployment (REQUIRED)"
|
||||
type = string
|
||||
# No default - must be explicitly provided
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
|
||||
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "stack_name" {
|
||||
description = "Stack name for resource naming"
|
||||
type = string
|
||||
default = "agentcore-mcp-server"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Description of the agent runtime"
|
||||
type = string
|
||||
default = "MCP server runtime with JWT authentication"
|
||||
}
|
||||
|
||||
variable "environment_variables" {
|
||||
description = "Environment variables for the agent runtime"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "ecr_repository_name" {
|
||||
description = "Name of the ECR repository"
|
||||
type = string
|
||||
default = "mcp-server"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.21"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = "~> 0.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "AgentCore"
|
||||
Pattern = "mcp-server-agentcore-runtime"
|
||||
Environment = var.environment
|
||||
StackName = var.stack_name
|
||||
ManagedBy = "Terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Terraform files
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
!terraform.tfvars.example
|
||||
tfplan
|
||||
|
||||
# Generated archives
|
||||
*.zip
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,561 @@
|
||||
# Multi-Agent Runtime on Amazon Bedrock AgentCore (Terraform)
|
||||
|
||||
This Terraform module deploys a multi-agent system using Amazon Bedrock AgentCore Runtime with Agent-to-Agent (A2A) communication capabilities.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Architecture](#architecture)
|
||||
- [What's Included](#whats-included)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Deployment Process](#deployment-process)
|
||||
- [Authentication Model](#authentication-model)
|
||||
- [Testing](#testing)
|
||||
- [Agent Capabilities](#agent-capabilities)
|
||||
- [Customization](#customization)
|
||||
- [File Structure](#file-structure)
|
||||
- [Monitoring and Observability](#monitoring-and-observability)
|
||||
- [Security](#security)
|
||||
- [Pricing](#pricing)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Advanced Topics](#advanced-topics)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Resources](#resources)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
This pattern demonstrates deploying a multi-agent system with two coordinating agents that communicate via the Agent-to-Agent (A2A) protocol. Agent1 (Orchestrator) can delegate specialized tasks to Agent2 (Specialist), enabling modular and scalable agent architectures.
|
||||
|
||||
**Key Features:**
|
||||
- Two-agent architecture with A2A communication
|
||||
- Automated Docker image building via CodeBuild
|
||||
- S3-based source code management with change detection
|
||||
- IAM-based security with least-privilege access
|
||||
- Sequential deployment ensuring proper dependencies
|
||||
|
||||
This makes it ideal for:
|
||||
- Building complex multi-agent workflows
|
||||
- Implementing agent specialization patterns
|
||||
- Creating scalable agent orchestration systems
|
||||
- Learning A2A communication protocols
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
### System Components
|
||||
|
||||
**Agent1 (Orchestrator Agent)**
|
||||
- Receives initial user requests
|
||||
- Orchestrates workflow between multiple agents
|
||||
- Contains a specialized tool (`call_specialist_agent`) to invoke Agent2
|
||||
- Has IAM permissions to invoke Agent2's runtime
|
||||
- Environment variable `AGENT2_ARN` enables A2A communication
|
||||
|
||||
**Agent2 (Specialist Agent)**
|
||||
- Independent specialist agent with domain-specific capabilities
|
||||
- Provides data analysis and processing functions
|
||||
- Can be invoked by Agent1 via A2A protocol
|
||||
- No dependencies on other agents
|
||||
|
||||
### Agent-to-Agent (A2A) Communication
|
||||
|
||||
The A2A communication pattern enables:
|
||||
- **Orchestration**: Agent1 coordinates complex workflows
|
||||
- **Specialization**: Agent2 focuses on specific capabilities
|
||||
- **Scalability**: Easy to add more specialized agents
|
||||
- **Security**: IAM-based authorization between agents
|
||||
|
||||
## What's Included
|
||||
|
||||
This Terraform configuration creates:
|
||||
|
||||
- **2 S3 Buckets**: Source code storage for both agents with versioning
|
||||
- **2 ECR Repositories**: Container registries for ARM64 Docker images
|
||||
- **2 CodeBuild Projects**: Automated image building and pushing
|
||||
- **3 IAM Roles**:
|
||||
- Agent1 execution role (with A2A permissions)
|
||||
- Agent2 execution role (standard permissions)
|
||||
- CodeBuild service role
|
||||
- **2 Agent Runtimes**:
|
||||
- Agent1 (Orchestrator) with AGENT2_ARN environment variable
|
||||
- Agent2 (Specialist) independent runtime
|
||||
- **Build Automation**: Automatic rebuild on code changes (MD5-based detection)
|
||||
- **Supporting Resources**: S3 lifecycle policies, ECR lifecycle policies, IAM policies
|
||||
|
||||
**Total:** ~30 AWS resources deployed and managed by Terraform
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Tools
|
||||
|
||||
1. **Terraform** (>= 1.6)
|
||||
- **Recommended**: [tfenv](https://github.com/tfutils/tfenv) for version management
|
||||
- **Or download directly**: [terraform.io/downloads](https://www.terraform.io/downloads)
|
||||
|
||||
**Note**: `brew install terraform` provides v1.5.7 (deprecated). Use tfenv or direct download for >= 1.6.
|
||||
|
||||
2. **AWS CLI** (configured with credentials)
|
||||
```bash
|
||||
aws configure
|
||||
```
|
||||
|
||||
3. **Python 3.11+** (for testing scripts)
|
||||
```bash
|
||||
python --version # Verify Python 3.11 or later
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
4. **Docker** (for local testing, optional)
|
||||
|
||||
### AWS Account Requirements
|
||||
|
||||
- AWS Account with appropriate permissions
|
||||
- Access to Amazon Bedrock AgentCore service
|
||||
- Permissions to create:
|
||||
- S3 buckets
|
||||
- ECR repositories
|
||||
- CodeBuild projects
|
||||
- IAM roles and policies
|
||||
- AgentCore Runtime resources
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Configure Variables
|
||||
|
||||
Copy the example variables file and customize:
|
||||
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
```
|
||||
|
||||
Edit `terraform.tfvars` with your preferred values:
|
||||
- `orchestrator_name`: Name for the orchestrator agent (default: "OrchestratorAgent")
|
||||
- `specialist_name`: Name for the specialist agent (default: "SpecialistAgent")
|
||||
- `stack_name`: Stack identifier (default: "agentcore-multi-agent")
|
||||
- `aws_region`: AWS region for deployment (default: "us-west-2")
|
||||
- `network_mode`: PUBLIC or PRIVATE networking
|
||||
|
||||
### 2. Initialize Terraform
|
||||
|
||||
See [State Management Options](../README.md#state-management-options) in the main README for detailed guidance on local vs. remote state.
|
||||
|
||||
**Quick start with local state:**
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
**For team collaboration, use remote state** - see the [main README](../README.md#state-management-options) for setup instructions.
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
**Method 1: Using Deploy Script (Recommended)**
|
||||
|
||||
```bash
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The script validates configuration, shows the plan, and deploys all resources.
|
||||
|
||||
**Method 2: Direct Terraform Commands**
|
||||
|
||||
```bash
|
||||
terraform plan
|
||||
terraform apply
|
||||
```
|
||||
|
||||
**Note**: Deployment includes creating infrastructure, building Docker images sequentially (Agent2 first, then Agent1), and establishing A2A communication. Total deployment time: **~5-10 minutes**
|
||||
|
||||
### 4. Verify Deployment
|
||||
|
||||
```bash
|
||||
# View all outputs
|
||||
terraform output
|
||||
|
||||
# Get Agent ARNs
|
||||
terraform output orchestrator_runtime_arn
|
||||
terraform output specialist_runtime_arn
|
||||
```
|
||||
|
||||
## Deployment Process
|
||||
|
||||
### Sequential Build Process
|
||||
|
||||
The deployment follows a strict sequence to ensure proper dependencies:
|
||||
|
||||
```
|
||||
1. S3 Buckets Creation (orchestrator & specialist)
|
||||
2. ECR Repositories Creation (orchestrator & specialist)
|
||||
3. IAM Roles Creation (with A2A permissions)
|
||||
4. CodeBuild Projects Creation (orchestrator & specialist)
|
||||
5. Agent2 Docker Build → Agent2 Runtime Creation
|
||||
6. Agent1 Docker Build → Agent1 Runtime Creation (depends on Agent2)
|
||||
```
|
||||
|
||||
**Critical Dependencies:**
|
||||
- Agent1 runtime depends on Agent2 runtime being created first
|
||||
- Agent1 build depends on Agent2 build completing successfully
|
||||
- Agent1 receives `AGENT2_ARN` as an environment variable
|
||||
|
||||
### Build Triggers
|
||||
|
||||
The infrastructure automatically triggers Docker image builds:
|
||||
- When source code changes (MD5 hash detection)
|
||||
- When infrastructure changes require rebuild
|
||||
- Sequential: Agent2 builds first, then Agent1
|
||||
|
||||
## Authentication Model
|
||||
|
||||
This pattern uses **IAM-based authentication with workload identity tokens**:
|
||||
|
||||
- **Service Principal**: Agents assume IAM roles via `bedrock-agentcore.amazonaws.com`
|
||||
- **Workload Identity**: Agents obtain access tokens for secure operations
|
||||
- **A2A Authorization**: Agent1 has `InvokeAgentRuntime` permission for Agent2
|
||||
- **API Access**: Direct AWS API invocation using IAM credentials
|
||||
|
||||
**Note**: This is a backend infrastructure pattern with no user authentication layer. For user-facing applications, you would add Cognito or API Gateway authorizers separately.
|
||||
|
||||
## Testing
|
||||
|
||||
The included `test_multi_agent.py` script is **infrastructure-agnostic** and works with any deployment method (Terraform, CDK, CloudFormation, or manual).
|
||||
|
||||
### Prerequisites for Testing
|
||||
|
||||
Before testing, ensure you have the required packages installed:
|
||||
|
||||
**Option A: Using uv (Recommended)**
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Option B: System-wide installation**
|
||||
```bash
|
||||
pip install boto3 # Required for agent invocation
|
||||
```
|
||||
|
||||
**Note**: `boto3` is required for the test script to invoke both agent runtimes via AWS API.
|
||||
|
||||
### Basic Testing
|
||||
|
||||
```bash
|
||||
# Get ARNs from Terraform
|
||||
ORCHESTRATOR_ARN=$(terraform output -raw orchestrator_runtime_arn)
|
||||
SPECIALIST_ARN=$(terraform output -raw specialist_runtime_arn)
|
||||
|
||||
# Test both agents
|
||||
python test_multi_agent.py $ORCHESTRATOR_ARN $SPECIALIST_ARN
|
||||
```
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
The script runs two tests:
|
||||
1. **Simple Query**: Basic orchestrator invocation
|
||||
2. **A2A Communication**: Orchestrator delegates to specialist via A2A protocol
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
TEST 1: Simple Query (Orchestrator) ✅
|
||||
TEST 2: Complex Query with A2A Communication ✅
|
||||
|
||||
✅ ALL TESTS PASSED
|
||||
```
|
||||
|
||||
## Agent Capabilities
|
||||
|
||||
### Agent1 (Orchestrator)
|
||||
|
||||
**Tools:**
|
||||
- `call_specialist_agent`: Invokes Agent2 for specialized processing
|
||||
- Parameters: `query` (string)
|
||||
- Returns: Processed results from Agent2
|
||||
|
||||
**Use Cases:**
|
||||
- Complex workflow orchestration
|
||||
- Multi-step data processing
|
||||
- Delegation to specialized agents
|
||||
|
||||
### Agent2 (Specialist)
|
||||
|
||||
**Capabilities:**
|
||||
- Domain-specific data analysis
|
||||
- Detailed information processing
|
||||
- Expert-level responses
|
||||
|
||||
**Use Cases:**
|
||||
- Data analysis and transformation
|
||||
- Domain-specific processing
|
||||
- Specialized computations
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify Agent Code
|
||||
|
||||
1. **Edit Agent Files**
|
||||
```bash
|
||||
# Orchestrator Agent
|
||||
vim agent-orchestrator-code/agent.py
|
||||
vim agent-orchestrator-code/requirements.txt
|
||||
|
||||
# Specialist Agent
|
||||
vim agent-specialist-code/agent.py
|
||||
vim agent-specialist-code/requirements.txt
|
||||
```
|
||||
|
||||
2. **Redeploy**
|
||||
```bash
|
||||
terraform apply # Automatically detects changes and rebuilds
|
||||
```
|
||||
|
||||
### Add More Agents
|
||||
|
||||
To add a new agent (e.g., Coordinator):
|
||||
1. Create `coordinator-code/` directory with implementation
|
||||
2. Add `coordinator.tf` for the runtime resource
|
||||
3. Update `s3.tf`, `ecr.tf`, `iam.tf`, `codebuild.tf`
|
||||
4. Create `buildspec-coordinator.yml`
|
||||
5. Update `main.tf` for build sequence
|
||||
6. Update `outputs.tf` and `variables.tf`
|
||||
|
||||
### Modify Network Configuration
|
||||
|
||||
Change from PUBLIC to PRIVATE networking:
|
||||
|
||||
```hcl
|
||||
# terraform.tfvars
|
||||
network_mode = "PRIVATE"
|
||||
```
|
||||
|
||||
Requires VPC configuration (not included in this module).
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
multi-agent-runtime/
|
||||
├── agent-orchestrator-code/ # Orchestrator agent source code
|
||||
│ ├── agent.py # Main agent implementation
|
||||
│ ├── Dockerfile # Container definition
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── agent-specialist-code/ # Specialist agent source code
|
||||
│ ├── agent.py # Main agent implementation
|
||||
│ ├── Dockerfile # Container definition
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── orchestrator.tf # Orchestrator runtime configuration
|
||||
├── specialist.tf # Specialist runtime configuration
|
||||
├── main.tf # Main Terraform configuration
|
||||
├── variables.tf # Input variables
|
||||
├── outputs.tf # Output definitions
|
||||
├── iam.tf # IAM roles and policies
|
||||
├── s3.tf # S3 buckets for source code
|
||||
├── ecr.tf # ECR repositories
|
||||
├── codebuild.tf # CodeBuild projects
|
||||
├── versions.tf # Terraform and provider versions
|
||||
├── buildspec-orchestrator.yml # Orchestrator build specification
|
||||
├── buildspec-specialist.yml # Specialist build specification
|
||||
├── terraform.tfvars.example # Example variable values
|
||||
├── backend.tf.example # Example backend configuration
|
||||
├── deploy.sh # Deployment automation script
|
||||
├── destroy.sh # Cleanup automation script
|
||||
├── test_multi_agent.py # Infrastructure-agnostic test script
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### CloudWatch Logs
|
||||
|
||||
```bash
|
||||
# Orchestrator logs
|
||||
aws logs tail /aws/bedrock-agentcore/agentcore-multi-agent-orchestrator-runtime --follow
|
||||
|
||||
# Specialist logs
|
||||
aws logs tail /aws/bedrock-agentcore/agentcore-multi-agent-specialist-runtime --follow
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
Access metrics in CloudWatch:
|
||||
- Agent invocation count
|
||||
- Agent execution duration
|
||||
- Error rates
|
||||
- A2A call metrics
|
||||
|
||||
### AWS Console
|
||||
|
||||
Monitor in AWS Console:
|
||||
- **Bedrock AgentCore**: [Console Link](https://console.aws.amazon.com/bedrock/home#/agentcore)
|
||||
- **ECR Repositories**: View Docker images
|
||||
- **CodeBuild**: Monitor build status
|
||||
- **CloudWatch**: View logs and metrics
|
||||
|
||||
## Security
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
**Agent1 Execution Role:**
|
||||
- Standard AgentCore permissions
|
||||
- **Critical**: `bedrock-agentcore:InvokeAgentRuntime` for Agent2
|
||||
|
||||
**Agent2 Execution Role:**
|
||||
- Standard AgentCore permissions only
|
||||
- No cross-agent invocation permissions needed
|
||||
|
||||
**CodeBuild Role:**
|
||||
- S3 access to both agent source buckets
|
||||
- ECR push access to both repositories
|
||||
- CloudWatch Logs write access
|
||||
|
||||
### Network Security
|
||||
|
||||
- Agents run in specified network mode (PUBLIC/PRIVATE)
|
||||
- ECR repositories have account-level access controls
|
||||
- S3 buckets block public access
|
||||
- IAM policies follow least-privilege principle
|
||||
|
||||
### Secrets Management
|
||||
|
||||
For sensitive data:
|
||||
- Use AWS Secrets Manager
|
||||
- Pass secret ARNs as environment variables
|
||||
- Retrieve secrets at runtime in agent code
|
||||
|
||||
## Pricing
|
||||
|
||||
For current pricing information, please refer to:
|
||||
- [Amazon Bedrock Pricing](https://aws.amazon.com/bedrock/pricing/)
|
||||
- [Amazon ECR Pricing](https://aws.amazon.com/ecr/pricing/)
|
||||
- [AWS CodeBuild Pricing](https://aws.amazon.com/codebuild/pricing/)
|
||||
- [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/)
|
||||
- [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
||||
|
||||
**Note**: Actual costs depend on your usage patterns, AWS region, and specific services consumed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: Agent1 fails to invoke Agent2
|
||||
- **Solution**: Verify AGENT2_ARN environment variable is set
|
||||
- **Check**: IAM permissions include InvokeAgentRuntime
|
||||
|
||||
**Issue**: Build fails
|
||||
- **Solution**: Check CodeBuild logs in CloudWatch
|
||||
- **Check**: Verify source code is in correct directories
|
||||
|
||||
**Issue**: Runtime not created
|
||||
- **Solution**: Verify ECR image exists and is tagged correctly
|
||||
- **Check**: Review Terraform state for errors
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check Terraform state
|
||||
terraform show
|
||||
|
||||
# Validate configuration
|
||||
terraform validate
|
||||
|
||||
# View specific resource
|
||||
terraform state show aws_bedrockagentcore_agent_runtime.orchestrator
|
||||
|
||||
# Get detailed build logs
|
||||
PROJECT_NAME=$(terraform output -raw orchestrator_codebuild_project)
|
||||
aws codebuild batch-get-builds --ids $(aws codebuild list-builds-for-project --project-name $PROJECT_NAME --query 'ids[0]' --output text)
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Automated Cleanup
|
||||
|
||||
```bash
|
||||
chmod +x destroy.sh
|
||||
./destroy.sh
|
||||
```
|
||||
|
||||
The script shows the destruction plan, requires confirmation, and destroys all resources.
|
||||
|
||||
### Manual Cleanup
|
||||
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
**Important**: Verify in AWS Console that all resources are deleted:
|
||||
- Bedrock AgentCore runtimes
|
||||
- ECR repositories
|
||||
- S3 buckets
|
||||
- CodeBuild projects
|
||||
- IAM roles
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Adding Custom Tools
|
||||
|
||||
1. Define tool schema in agent code
|
||||
2. Implement tool handler function
|
||||
3. Register tool with agent
|
||||
4. Rebuild and deploy
|
||||
|
||||
### Implementing Memory
|
||||
|
||||
Add session management in agent code:
|
||||
```python
|
||||
session_data = {}
|
||||
|
||||
def handle_request(input_text, session_id):
|
||||
if session_id not in session_data:
|
||||
session_data[session_id] = {}
|
||||
# Use session_data for context
|
||||
```
|
||||
|
||||
### Multi-Region Deployment
|
||||
|
||||
For multi-region:
|
||||
1. Configure backend for state locking
|
||||
2. Deploy to each region separately
|
||||
3. Use Route53 for failover
|
||||
4. Consider cross-region replication for S3/ECR
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Test the deployment**
|
||||
```bash
|
||||
python test_multi_agent.py $(terraform output -raw orchestrator_runtime_arn) $(terraform output -raw specialist_runtime_arn)
|
||||
```
|
||||
|
||||
2. **Customize agents** for your specific use case
|
||||
- Add domain-specific tools to agents
|
||||
- Implement custom business logic
|
||||
- Integrate with external APIs
|
||||
|
||||
3. **Explore related patterns**
|
||||
- [MCP Server Pattern](../mcp-server-agentcore-runtime/) - MCP protocol with JWT auth
|
||||
- [AgentCore Samples](https://github.com/aws-samples/amazon-bedrock-agentcore-samples) - More examples
|
||||
|
||||
4. **Add production features**
|
||||
- Monitoring and alerting
|
||||
- Custom authentication layer (if needed)
|
||||
- VPC deployment for private networking
|
||||
- CI/CD pipeline integration
|
||||
|
||||
## Resources
|
||||
|
||||
- [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html)
|
||||
- [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
|
||||
- [Agent-to-Agent Communication](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-a2a.html)
|
||||
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT-0 license. See the [LICENSE](../../../LICENSE) file for details.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.11-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install aws-opentelemetry-distro>=0.10.1
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 bedrock_agentcore
|
||||
USER bedrock_agentcore
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8000
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
from strands import Agent, tool
|
||||
from typing import Dict, Any
|
||||
import boto3
|
||||
import json
|
||||
import os
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
||||
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
# Environment variable for Specialist Agent ARN (required - set by Terraform)
|
||||
SPECIALIST_ARN = os.getenv('SPECIALIST_ARN')
|
||||
if not SPECIALIST_ARN:
|
||||
raise EnvironmentError("SPECIALIST_ARN environment variable is required")
|
||||
|
||||
def invoke_specialist(query: str) -> str:
|
||||
"""Helper function to invoke specialist agent using boto3"""
|
||||
try:
|
||||
# Get region from environment (set by AgentCore runtime)
|
||||
region = os.getenv('AWS_REGION')
|
||||
if not region:
|
||||
raise EnvironmentError("AWS_REGION environment variable is required")
|
||||
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
|
||||
|
||||
# Invoke specialist agent runtime (using AWS sample format)
|
||||
response = agentcore_client.invoke_agent_runtime(
|
||||
agentRuntimeArn=SPECIALIST_ARN,
|
||||
qualifier="DEFAULT",
|
||||
payload=json.dumps({"prompt": query})
|
||||
)
|
||||
|
||||
# Handle streaming response (text/event-stream)
|
||||
if "text/event-stream" in response.get("contentType", ""):
|
||||
result = ""
|
||||
for line in response["response"].iter_lines(chunk_size=10):
|
||||
if line:
|
||||
line = line.decode("utf-8")
|
||||
# Remove 'data: ' prefix if present
|
||||
if line.startswith("data: "):
|
||||
line = line[6:]
|
||||
result += line
|
||||
return result
|
||||
|
||||
# Handle JSON response
|
||||
elif response.get("contentType") == "application/json":
|
||||
content = []
|
||||
for chunk in response.get("response", []):
|
||||
content.append(chunk.decode('utf-8'))
|
||||
response_data = json.loads(''.join(content))
|
||||
return json.dumps(response_data)
|
||||
|
||||
# Handle other response types
|
||||
else:
|
||||
response_body = response['response'].read()
|
||||
return response_body.decode('utf-8')
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
return f"Error invoking specialist agent: {str(e)}\nDetails: {error_details}"
|
||||
|
||||
@tool
|
||||
def call_specialist_agent(query: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Call the specialist agent for detailed analysis or complex tasks.
|
||||
Use this tool when you need expert analysis or detailed information.
|
||||
|
||||
Args:
|
||||
query: The question or task to send to the specialist agent
|
||||
|
||||
Returns:
|
||||
The specialist agent's response
|
||||
"""
|
||||
result = invoke_specialist(query)
|
||||
return {
|
||||
"status": "success",
|
||||
"content": [{"text": result}]
|
||||
}
|
||||
|
||||
def create_orchestrator_agent() -> Agent:
|
||||
"""Create the orchestrator agent with the tool to call specialist agent"""
|
||||
system_prompt = """You are an orchestrator agent.
|
||||
You can handle simple queries directly, but for complex analytical tasks,
|
||||
you should delegate to the specialist agent using the call_specialist_agent tool.
|
||||
|
||||
Use the specialist agent when:
|
||||
- The query requires detailed analysis
|
||||
- The query is about complex topics
|
||||
- The user explicitly asks for expert analysis
|
||||
|
||||
Handle simple queries (greetings, basic questions) yourself."""
|
||||
|
||||
return Agent(
|
||||
tools=[call_specialist_agent],
|
||||
system_prompt=system_prompt,
|
||||
name="OrchestratorAgent"
|
||||
)
|
||||
|
||||
@app.entrypoint
|
||||
async def invoke(payload=None):
|
||||
"""Main entrypoint for orchestrator agent"""
|
||||
try:
|
||||
# Get the query from payload
|
||||
query = payload.get("prompt", "Hello, how are you?") if payload else "Hello, how are you?"
|
||||
|
||||
# Create and use the orchestrator agent
|
||||
agent = create_orchestrator_agent()
|
||||
response = agent(query)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"agent": "orchestrator",
|
||||
"response": response.message['content'][0]['text']
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"agent": "orchestrator",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
strands-agents
|
||||
boto3>=1.40.0
|
||||
botocore>=1.40.0
|
||||
bedrock-agentcore
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.11-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install aws-opentelemetry-distro>=0.10.1
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 bedrock_agentcore
|
||||
USER bedrock_agentcore
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8000
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
from strands import Agent
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
||||
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
def create_specialist_agent() -> Agent:
|
||||
"""Create a specialist agent that handles specific analytical tasks"""
|
||||
system_prompt = """You are a specialist analytical agent.
|
||||
You are an expert at analyzing data and providing detailed insights.
|
||||
When asked questions, provide thorough, well-reasoned responses with specific details.
|
||||
Focus on accuracy and completeness in your answers."""
|
||||
|
||||
return Agent(
|
||||
system_prompt=system_prompt,
|
||||
name="SpecialistAgent"
|
||||
)
|
||||
|
||||
@app.entrypoint
|
||||
async def invoke(payload=None):
|
||||
"""Main entrypoint for specialist agent"""
|
||||
try:
|
||||
# Get the query from payload
|
||||
query = payload.get("prompt", "Hello") if payload else "Hello"
|
||||
|
||||
# Create and use the specialist agent
|
||||
agent = create_specialist_agent()
|
||||
response = agent(query)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"agent": "specialist",
|
||||
"response": response.message['content'][0]['text']
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"agent": "specialist",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
strands-agents
|
||||
boto3>=1.40.0
|
||||
botocore>=1.40.0
|
||||
bedrock-agentcore
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# Terraform Backend Configuration - Remote State Storage
|
||||
# ============================================================================
|
||||
# This is an example backend configuration for storing Terraform state remotely
|
||||
# Copy this file to backend.tf and customize with your backend settings
|
||||
# Example: cp backend.tf.example backend.tf
|
||||
|
||||
# IMPORTANT: Uncomment and configure ONE of the backend options below
|
||||
|
||||
# Option 1: S3 Backend (Recommended for AWS)
|
||||
# terraform {
|
||||
# backend "s3" {
|
||||
# bucket = "your-terraform-state-bucket"
|
||||
# key = "agentcore/multi-agent-runtime/terraform.tfstate"
|
||||
# region = "us-west-2"
|
||||
# encrypt = true
|
||||
# dynamodb_table = "terraform-state-lock"
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 2: Terraform Cloud
|
||||
# terraform {
|
||||
# cloud {
|
||||
# organization = "your-organization"
|
||||
# workspaces {
|
||||
# name = "agentcore-basic-runtime"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# Option 3: Local Backend (Default - No configuration needed)
|
||||
# State will be stored locally in terraform.tfstate
|
||||
# Not recommended for team environments or production use
|
||||
@@ -0,0 +1,29 @@
|
||||
version: 0.2
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Installing dependencies...
|
||||
- yum install -y unzip
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Source code already extracted by CodeBuild...
|
||||
- cd $CODEBUILD_SRC_DIR
|
||||
- ls -la
|
||||
- echo Logging in to Amazon ECR...
|
||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image for Orchestrator Agent ARM64...
|
||||
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
||||
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Build completed on `date`
|
||||
- echo Pushing the Orchestrator Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
- echo Orchestrator Agent ARM64 Docker image pushed successfully
|
||||
@@ -0,0 +1,29 @@
|
||||
version: 0.2
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Installing dependencies...
|
||||
- yum install -y unzip
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Source code already extracted by CodeBuild...
|
||||
- cd $CODEBUILD_SRC_DIR
|
||||
- ls -la
|
||||
- echo Logging in to Amazon ECR...
|
||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image for Specialist Agent ARM64...
|
||||
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
||||
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Build completed on `date`
|
||||
- echo Pushing the Specialist Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
||||
- echo Specialist Agent ARM64 Docker image pushed successfully
|
||||
@@ -0,0 +1,154 @@
|
||||
# ============================================================================
|
||||
# CodeBuild Project - Build and Push Orchestrator Agent Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_codebuild_project" "orchestrator_image" {
|
||||
name = "${var.stack_name}-orchestrator-build"
|
||||
description = "Build Orchestrator agent Docker image for ${var.stack_name}"
|
||||
service_role = aws_iam_role.codebuild.arn
|
||||
build_timeout = 60
|
||||
|
||||
artifacts {
|
||||
type = "NO_ARTIFACTS"
|
||||
}
|
||||
|
||||
environment {
|
||||
compute_type = "BUILD_GENERAL1_LARGE"
|
||||
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
|
||||
type = "ARM_CONTAINER"
|
||||
privileged_mode = true
|
||||
image_pull_credentials_type = "CODEBUILD"
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_DEFAULT_REGION"
|
||||
value = data.aws_region.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_ACCOUNT_ID"
|
||||
value = data.aws_caller_identity.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_REPO_NAME"
|
||||
value = aws_ecr_repository.orchestrator.name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_TAG"
|
||||
value = var.image_tag
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "STACK_NAME"
|
||||
value = var.stack_name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AGENT_NAME"
|
||||
value = "orchestrator"
|
||||
}
|
||||
}
|
||||
|
||||
source {
|
||||
type = "S3"
|
||||
location = "${aws_s3_bucket.orchestrator_source.id}/${aws_s3_object.orchestrator_source.key}"
|
||||
buildspec = file("${path.module}/buildspec-orchestrator.yml")
|
||||
}
|
||||
|
||||
logs_config {
|
||||
cloudwatch_logs {
|
||||
group_name = "/aws/codebuild/${var.stack_name}-orchestrator-build"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-orchestrator-build"
|
||||
Module = "CodeBuild"
|
||||
Agent = "Orchestrator"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_iam_role_policy.codebuild
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CodeBuild Project - Build and Push Specialist Agent Docker Image
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_codebuild_project" "specialist_image" {
|
||||
name = "${var.stack_name}-specialist-build"
|
||||
description = "Build Specialist agent Docker image for ${var.stack_name}"
|
||||
service_role = aws_iam_role.codebuild.arn
|
||||
build_timeout = 60
|
||||
|
||||
artifacts {
|
||||
type = "NO_ARTIFACTS"
|
||||
}
|
||||
|
||||
environment {
|
||||
compute_type = "BUILD_GENERAL1_LARGE"
|
||||
image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
|
||||
type = "ARM_CONTAINER"
|
||||
privileged_mode = true
|
||||
image_pull_credentials_type = "CODEBUILD"
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_DEFAULT_REGION"
|
||||
value = data.aws_region.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AWS_ACCOUNT_ID"
|
||||
value = data.aws_caller_identity.current.id
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_REPO_NAME"
|
||||
value = aws_ecr_repository.specialist.name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "IMAGE_TAG"
|
||||
value = var.image_tag
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "STACK_NAME"
|
||||
value = var.stack_name
|
||||
}
|
||||
|
||||
environment_variable {
|
||||
name = "AGENT_NAME"
|
||||
value = "specialist"
|
||||
}
|
||||
}
|
||||
|
||||
source {
|
||||
type = "S3"
|
||||
location = "${aws_s3_bucket.specialist_source.id}/${aws_s3_object.specialist_source.key}"
|
||||
buildspec = file("${path.module}/buildspec-specialist.yml")
|
||||
}
|
||||
|
||||
logs_config {
|
||||
cloudwatch_logs {
|
||||
group_name = "/aws/codebuild/${var.stack_name}-specialist-build"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-specialist-build"
|
||||
Module = "CodeBuild"
|
||||
Agent = "Specialist"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_iam_role_policy.codebuild
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Note: Build triggers are defined in main.tf for proper sequencing
|
||||
# Specialist builds first, then Orchestrator (which depends on Specialist ARN)
|
||||
# ============================================================================
|
||||
@@ -0,0 +1,252 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Script for Multi-Agent Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script automates the deployment process for the Multi-Agent Terraform configuration
|
||||
# Usage: ./deploy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting Multi-Agent Runtime Deployment..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed. Please install Terraform >= 1.6"
|
||||
print_info "Visit: https://www.terraform.io/downloads"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Terraform version
|
||||
TERRAFORM_VERSION=$(terraform version -json | grep -o '"terraform_version":"[^"]*' | cut -d'"' -f4)
|
||||
print_success "Terraform version: $TERRAFORM_VERSION"
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed. Please install and configure AWS CLI"
|
||||
print_info "Visit: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "AWS CLI is installed"
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
print_info "Run: aws configure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_success "AWS Account: $AWS_ACCOUNT"
|
||||
print_success "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking configuration files..."
|
||||
|
||||
# Check if terraform.tfvars exists
|
||||
if [ ! -f "terraform.tfvars" ]; then
|
||||
print_warning "terraform.tfvars not found"
|
||||
print_info "Creating terraform.tfvars from example..."
|
||||
|
||||
if [ -f "terraform.tfvars.example" ]; then
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
print_success "Created terraform.tfvars"
|
||||
print_warning "Please review and update terraform.tfvars with your settings"
|
||||
print_info "Then run this script again"
|
||||
exit 0
|
||||
else
|
||||
print_error "terraform.tfvars.example not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Configuration file found: terraform.tfvars"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Initialization
|
||||
# ============================================================================
|
||||
|
||||
print_info "Initializing Terraform..."
|
||||
if terraform init; then
|
||||
print_success "Terraform initialized successfully"
|
||||
else
|
||||
print_error "Terraform initialization failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Validation
|
||||
# ============================================================================
|
||||
|
||||
print_info "Validating Terraform configuration..."
|
||||
if terraform validate; then
|
||||
print_success "Terraform configuration is valid"
|
||||
else
|
||||
print_error "Terraform validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Format Check
|
||||
# ============================================================================
|
||||
|
||||
print_info "Checking Terraform formatting..."
|
||||
if terraform fmt -check -recursive > /dev/null 2>&1; then
|
||||
print_success "Terraform files are properly formatted"
|
||||
else
|
||||
print_warning "Some files need formatting. Running terraform fmt..."
|
||||
terraform fmt -recursive
|
||||
print_success "Files formatted"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating Terraform execution plan..."
|
||||
print_warning "This may take a few moments..."
|
||||
echo ""
|
||||
|
||||
if terraform plan -out=tfplan; then
|
||||
print_success "Terraform plan created successfully"
|
||||
else
|
||||
print_error "Terraform plan failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "DEPLOYMENT CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_info "This will deploy the following resources:"
|
||||
print_info " - 2x S3 Buckets (Orchestrator & Specialist source code storage)"
|
||||
print_info " - 2x ECR Repositories (Orchestrator & Specialist)"
|
||||
print_info " - 2x CodeBuild Projects (Orchestrator & Specialist)"
|
||||
print_info " - IAM Roles and Policies (with A2A permissions)"
|
||||
print_info " - Specialist Runtime (Independent)"
|
||||
print_info " - Orchestrator Runtime (Depends on Specialist)"
|
||||
echo ""
|
||||
print_info "The deployment includes:"
|
||||
print_info " - Building ARM64 Docker images for both agents"
|
||||
print_info " - Creating AWS resources with proper dependencies"
|
||||
print_info " - Setting up Agent-to-Agent (A2A) communication"
|
||||
echo ""
|
||||
|
||||
read -p "Do you want to proceed with deployment? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Deployment cancelled by user"
|
||||
rm -f tfplan
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Terraform Apply
|
||||
# ============================================================================
|
||||
|
||||
print_info "Starting deployment..."
|
||||
echo ""
|
||||
|
||||
if terraform apply tfplan; then
|
||||
print_success "Deployment completed successfully!"
|
||||
else
|
||||
print_error "Deployment failed"
|
||||
rm -f tfplan
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up plan file
|
||||
rm -f tfplan
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "DEPLOYMENT COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Retrieving deployment outputs..."
|
||||
echo ""
|
||||
|
||||
# Get outputs
|
||||
ORCHESTRATOR_ID=$(terraform output -raw orchestrator_runtime_id 2>/dev/null || echo "N/A")
|
||||
ORCHESTRATOR_ARN=$(terraform output -raw orchestrator_runtime_arn 2>/dev/null || echo "N/A")
|
||||
SPECIALIST_ID=$(terraform output -raw specialist_runtime_id 2>/dev/null || echo "N/A")
|
||||
SPECIALIST_ARN=$(terraform output -raw specialist_runtime_arn 2>/dev/null || echo "N/A")
|
||||
|
||||
print_success "Orchestrator Runtime ID: $ORCHESTRATOR_ID"
|
||||
print_success "Orchestrator Runtime ARN: $ORCHESTRATOR_ARN"
|
||||
echo ""
|
||||
print_success "Specialist Runtime ID: $SPECIALIST_ID"
|
||||
print_success "Specialist Runtime ARN: $SPECIALIST_ARN"
|
||||
|
||||
echo ""
|
||||
print_info "Next Steps:"
|
||||
print_info "1. Test the multi-agent system:"
|
||||
print_info " python test_multi_agent.py"
|
||||
echo ""
|
||||
print_info "2. View all outputs (includes test commands):"
|
||||
print_info " terraform output"
|
||||
echo ""
|
||||
print_info "3. Monitor in AWS Console:"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_success "Deployment completed successfully!"
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Destroy Script for Multi-Agent Runtime (Terraform)
|
||||
# ============================================================================
|
||||
# This script safely destroys all resources created by this Terraform configuration
|
||||
# Usage: ./destroy.sh
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Pre-flight Checks
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting Resource Cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check Terraform installation
|
||||
if ! command_exists terraform; then
|
||||
print_error "Terraform is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS CLI installation
|
||||
if ! command_exists aws; then
|
||||
print_error "AWS CLI is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check AWS credentials
|
||||
if ! aws sts get-caller-identity > /dev/null 2>&1; then
|
||||
print_error "AWS credentials are not configured or invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
||||
AWS_REGION=$(aws configure get region)
|
||||
print_info "AWS Account: $AWS_ACCOUNT"
|
||||
print_info "AWS Region: $AWS_REGION"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Check for Terraform State
|
||||
# ============================================================================
|
||||
|
||||
if [ ! -f "terraform.tfstate" ] && [ ! -f ".terraform/terraform.tfstate" ]; then
|
||||
print_warning "No Terraform state found"
|
||||
print_info "Either no resources have been deployed, or state is stored remotely"
|
||||
|
||||
read -p "Do you want to attempt to import state from backend? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Initializing Terraform to fetch remote state..."
|
||||
terraform init
|
||||
else
|
||||
print_info "Cleanup cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Show Destruction Plan
|
||||
# ============================================================================
|
||||
|
||||
print_info "Creating destruction plan..."
|
||||
echo ""
|
||||
|
||||
if ! terraform plan -destroy; then
|
||||
print_error "Failed to create destruction plan"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Destruction Confirmation
|
||||
# ============================================================================
|
||||
|
||||
print_warning "========================================"
|
||||
print_warning "RESOURCE DESTRUCTION CONFIRMATION"
|
||||
print_warning "========================================"
|
||||
print_warning "This will permanently delete the following resources:"
|
||||
print_warning " - Orchestrator Runtime"
|
||||
print_warning " - Specialist Runtime"
|
||||
print_warning " - 2x S3 Buckets (source code storage)"
|
||||
print_warning " - 2x ECR Repositories (including all images)"
|
||||
print_warning " - 2x CodeBuild Projects"
|
||||
print_warning " - IAM Roles and Policies (including A2A permissions)"
|
||||
print_warning " - CloudWatch Log Groups"
|
||||
echo ""
|
||||
print_warning "THIS ACTION CANNOT BE UNDONE!"
|
||||
echo ""
|
||||
print_info "Resources in other AWS services (e.g., S3 buckets) may still incur costs"
|
||||
echo ""
|
||||
|
||||
read -p "Are you absolutely sure you want to destroy all resources? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
print_info "Destruction cancelled by user"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Double confirmation for safety
|
||||
print_warning "Second confirmation required..."
|
||||
read -p "Type 'DESTROY' to confirm: " -r
|
||||
echo ""
|
||||
|
||||
if [ "$REPLY" != "DESTROY" ]; then
|
||||
print_info "Destruction cancelled - confirmation text did not match"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Execute Destruction
|
||||
# ============================================================================
|
||||
|
||||
print_warning "Starting resource destruction..."
|
||||
echo ""
|
||||
|
||||
if terraform destroy -auto-approve; then
|
||||
print_success "All resources destroyed successfully"
|
||||
else
|
||||
print_error "Destruction failed"
|
||||
print_warning "Some resources may still exist. Please check AWS Console"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Cleanup Local Files
|
||||
# ============================================================================
|
||||
|
||||
print_info "Cleaning up local Terraform files..."
|
||||
|
||||
# Ask about state file cleanup
|
||||
read -p "Do you want to remove local Terraform state files? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -f terraform.tfstate
|
||||
rm -f terraform.tfstate.backup
|
||||
rm -f tfplan
|
||||
print_success "Local state files removed"
|
||||
fi
|
||||
|
||||
# Ask about .terraform directory
|
||||
read -p "Do you want to remove .terraform directory? (yes/no): " -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
rm -rf .terraform
|
||||
rm -f .terraform.lock.hcl
|
||||
print_success ".terraform directory removed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verification
|
||||
# ============================================================================
|
||||
|
||||
print_info "Verifying resource cleanup..."
|
||||
echo ""
|
||||
|
||||
# Check for ECR repositories
|
||||
STACK_NAME=$(grep 'stack_name' terraform.tfvars 2>/dev/null | cut -d'"' -f2 || echo "agentcore-basic")
|
||||
ECR_REPOS=$(aws ecr describe-repositories --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$ECR_REPOS" -eq 0 ]; then
|
||||
print_success "ECR repositories cleaned up"
|
||||
else
|
||||
print_warning "Found $ECR_REPOS ECR repositories matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for AgentCore runtimes (both agents)
|
||||
RUNTIME_COUNT=$(aws bedrock-agentcore list-agent-runtimes --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$RUNTIME_COUNT" -eq 0 ]; then
|
||||
print_success "AgentCore runtimes cleaned up (Orchestrator and Specialist)"
|
||||
else
|
||||
print_warning "Found $RUNTIME_COUNT AgentCore runtimes matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
# Check for S3 buckets (both agent source buckets)
|
||||
S3_BUCKETS=$(aws s3api list-buckets --region $AWS_REGION 2>/dev/null | grep "$STACK_NAME" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$S3_BUCKETS" -eq 0 ]; then
|
||||
print_success "S3 buckets cleaned up (Orchestrator and Specialist source buckets)"
|
||||
else
|
||||
print_warning "Found $S3_BUCKETS S3 buckets matching '$STACK_NAME'"
|
||||
print_info "These may need manual cleanup"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Completion Summary
|
||||
# ============================================================================
|
||||
|
||||
print_success "========================================"
|
||||
print_success "CLEANUP COMPLETED"
|
||||
print_success "========================================"
|
||||
echo ""
|
||||
|
||||
print_info "Cleanup Summary:"
|
||||
print_success " ✓ Terraform resources destroyed"
|
||||
print_success " ✓ Local state files cleaned (if selected)"
|
||||
echo ""
|
||||
|
||||
print_info "What to verify in AWS Console:"
|
||||
print_info "1. Bedrock AgentCore - No runtimes remaining"
|
||||
print_info " https://console.aws.amazon.com/bedrock/home?region=$AWS_REGION#/agentcore"
|
||||
echo ""
|
||||
print_info "2. S3 - No buckets remaining"
|
||||
print_info " https://console.aws.amazon.com/s3/buckets?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "3. ECR - No repositories remaining (Orchestrator & Specialist)"
|
||||
print_info " https://console.aws.amazon.com/ecr/repositories?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "4. CodeBuild - No projects remaining (Orchestrator & Specialist)"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects?region=$AWS_REGION"
|
||||
echo ""
|
||||
print_info "6. CloudWatch Logs - Check for orphaned log groups"
|
||||
print_info " https://console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups"
|
||||
echo ""
|
||||
|
||||
print_success "Cleanup completed successfully!"
|
||||
print_info "You can safely re-deploy by running: ./deploy.sh"
|
||||
@@ -0,0 +1,127 @@
|
||||
# ============================================================================
|
||||
# ECR Repositories - Container Registries for Agent Images
|
||||
# ============================================================================
|
||||
|
||||
# Orchestrator Agent ECR Repository
|
||||
resource "aws_ecr_repository" "orchestrator" {
|
||||
name = "${var.stack_name}-${var.ecr_repository_name}-orchestrator"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
force_delete = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-orchestrator-ecr-repository"
|
||||
Module = "ECR"
|
||||
Agent = "Orchestrator"
|
||||
}
|
||||
}
|
||||
|
||||
# Specialist Agent ECR Repository
|
||||
resource "aws_ecr_repository" "specialist" {
|
||||
name = "${var.stack_name}-${var.ecr_repository_name}-specialist"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
force_delete = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-specialist-ecr-repository"
|
||||
Module = "ECR"
|
||||
Agent = "Specialist"
|
||||
}
|
||||
}
|
||||
|
||||
# ECR Repository Policy - Orchestrator
|
||||
resource "aws_ecr_repository_policy" "orchestrator" {
|
||||
repository = aws_ecr_repository.orchestrator.name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowPullFromAccount"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
|
||||
}
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Repository Policy - Specialist
|
||||
resource "aws_ecr_repository_policy" "specialist" {
|
||||
repository = aws_ecr_repository.specialist.name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowPullFromAccount"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
|
||||
}
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Lifecycle Policy - Orchestrator - Keep last 5 images
|
||||
resource "aws_ecr_lifecycle_policy" "orchestrator" {
|
||||
repository = aws_ecr_repository.orchestrator.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep last 5 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ECR Lifecycle Policy - Specialist - Keep last 5 images
|
||||
resource "aws_ecr_lifecycle_policy" "specialist" {
|
||||
repository = aws_ecr_repository.specialist.name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep last 5 images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
# Data sources
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# ============================================================================
|
||||
# Orchestrator Agent Execution Role - For AgentCore Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "orchestrator_execution" {
|
||||
name = "${var.stack_name}-orchestrator-execution-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AssumeRolePolicy"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "bedrock-agentcore.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.id
|
||||
}
|
||||
ArnLike = {
|
||||
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-orchestrator-execution-role"
|
||||
Module = "IAM"
|
||||
Agent = "Orchestrator"
|
||||
}
|
||||
}
|
||||
|
||||
# Attach AWS managed policy for AgentCore - Orchestrator
|
||||
resource "aws_iam_role_policy_attachment" "orchestrator_execution_managed" {
|
||||
role = aws_iam_role.orchestrator_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
|
||||
}
|
||||
|
||||
# Inline policy for orchestrator execution
|
||||
resource "aws_iam_role_policy" "orchestrator_execution" {
|
||||
name = "OrchestratorCoreExecutionPolicy"
|
||||
role = aws_iam_role.orchestrator_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# ECR Access for Orchestrator
|
||||
{
|
||||
Sid = "ECRImageAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
Resource = aws_ecr_repository.orchestrator.arn
|
||||
},
|
||||
{
|
||||
Sid = "ECRTokenAccess"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:GetAuthorizationToken"]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
||||
},
|
||||
# X-Ray Tracing
|
||||
{
|
||||
Sid = "XRayTracing"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Metrics
|
||||
{
|
||||
Sid = "CloudWatchMetrics"
|
||||
Effect = "Allow"
|
||||
Action = ["cloudwatch:PutMetricData"]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cloudwatch:namespace" = "bedrock-agentcore"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Bedrock Model Invocation
|
||||
{
|
||||
Sid = "BedrockModelInvocation"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# Workload Access Tokens
|
||||
{
|
||||
Sid = "GetAgentAccessToken"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Orchestrator A2A Policy - Allows Orchestrator to Invoke Specialist
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role_policy" "orchestrator_invoke_specialist" {
|
||||
name = "OrchestratorInvokeSpecialistPolicy"
|
||||
role = aws_iam_role.orchestrator_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "InvokeSpecialistRuntime"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:InvokeAgentRuntime"
|
||||
]
|
||||
Resource = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:runtime/*"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Specialist Agent Execution Role - For AgentCore Runtime
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "specialist_execution" {
|
||||
name = "${var.stack_name}-specialist-execution-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AssumeRolePolicy"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "bedrock-agentcore.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.id
|
||||
}
|
||||
ArnLike = {
|
||||
"aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:*"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-specialist-execution-role"
|
||||
Module = "IAM"
|
||||
Agent = "Specialist"
|
||||
}
|
||||
}
|
||||
|
||||
# Attach AWS managed policy for AgentCore - Specialist
|
||||
resource "aws_iam_role_policy_attachment" "specialist_execution_managed" {
|
||||
role = aws_iam_role.specialist_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess"
|
||||
}
|
||||
|
||||
# Inline policy for specialist execution
|
||||
resource "aws_iam_role_policy" "specialist_execution" {
|
||||
name = "SpecialistCoreExecutionPolicy"
|
||||
role = aws_iam_role.specialist_execution.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# ECR Access for Specialist
|
||||
{
|
||||
Sid = "ECRImageAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
Resource = aws_ecr_repository.specialist.arn
|
||||
},
|
||||
{
|
||||
Sid = "ECRTokenAccess"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:GetAuthorizationToken"]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
||||
},
|
||||
# X-Ray Tracing
|
||||
{
|
||||
Sid = "XRayTracing"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# CloudWatch Metrics
|
||||
{
|
||||
Sid = "CloudWatchMetrics"
|
||||
Effect = "Allow"
|
||||
Action = ["cloudwatch:PutMetricData"]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cloudwatch:namespace" = "bedrock-agentcore"
|
||||
}
|
||||
}
|
||||
},
|
||||
# Bedrock Model Invocation
|
||||
{
|
||||
Sid = "BedrockModelInvocation"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
Resource = "*"
|
||||
},
|
||||
# Workload Access Tokens
|
||||
{
|
||||
Sid = "GetAgentAccessToken"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CodeBuild Service Role - For Docker Image Building (Both Agents)
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_iam_role" "codebuild" {
|
||||
name = "${var.stack_name}-codebuild-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "codebuild.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRole"
|
||||
}]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-codebuild-role"
|
||||
Module = "IAM"
|
||||
}
|
||||
}
|
||||
|
||||
# Inline policy for CodeBuild
|
||||
resource "aws_iam_role_policy" "codebuild" {
|
||||
name = "CodeBuildPolicy"
|
||||
role = aws_iam_role.codebuild.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
# CloudWatch Logs
|
||||
{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.id}:log-group:/aws/codebuild/*"
|
||||
},
|
||||
# ECR Access for both repositories
|
||||
{
|
||||
Sid = "ECRAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetAuthorizationToken",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload"
|
||||
]
|
||||
Resource = [
|
||||
aws_ecr_repository.orchestrator.arn,
|
||||
aws_ecr_repository.specialist.arn,
|
||||
"*"
|
||||
]
|
||||
},
|
||||
# S3 Source Access for both agent code buckets
|
||||
{
|
||||
Sid = "S3SourceAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion"
|
||||
]
|
||||
Resource = [
|
||||
"${aws_s3_bucket.orchestrator_source.arn}/*",
|
||||
"${aws_s3_bucket.specialist_source.arn}/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
Sid = "S3BucketAccess"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:ListBucket",
|
||||
"s3:GetBucketLocation"
|
||||
]
|
||||
Resource = [
|
||||
aws_s3_bucket.orchestrator_source.arn,
|
||||
aws_s3_bucket.specialist_source.arn
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
# ============================================================================
|
||||
# Wait for IAM propagation before triggering builds
|
||||
# ============================================================================
|
||||
|
||||
resource "time_sleep" "wait_for_iam" {
|
||||
depends_on = [
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_iam_role_policy.orchestrator_execution,
|
||||
aws_iam_role_policy.specialist_execution
|
||||
]
|
||||
|
||||
create_duration = "30s"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Trigger CodeBuild - Sequential Build Process
|
||||
# Specialist builds first (independent), then Orchestrator (depends on Specialist)
|
||||
# ============================================================================
|
||||
|
||||
# Trigger Specialist Build (Independent - Builds First)
|
||||
resource "null_resource" "trigger_build_specialist" {
|
||||
triggers = {
|
||||
build_project = aws_codebuild_project.specialist_image.id
|
||||
image_tag = var.image_tag
|
||||
ecr_repository = aws_ecr_repository.specialist.id
|
||||
source_code_md5 = data.archive_file.specialist_source.output_md5
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.specialist_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.specialist.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.specialist.repository_url}\""
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_codebuild_project.specialist_image,
|
||||
aws_ecr_repository.specialist,
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_s3_object.specialist_source,
|
||||
time_sleep.wait_for_iam
|
||||
]
|
||||
}
|
||||
|
||||
# Trigger Orchestrator Build (Depends on Specialist Build Completion)
|
||||
resource "null_resource" "trigger_build_orchestrator" {
|
||||
triggers = {
|
||||
build_project = aws_codebuild_project.orchestrator_image.id
|
||||
image_tag = var.image_tag
|
||||
ecr_repository = aws_ecr_repository.orchestrator.id
|
||||
source_code_md5 = data.archive_file.orchestrator_source.output_md5
|
||||
# Also rebuild if Specialist build changes
|
||||
specialist_build = null_resource.trigger_build_specialist.id
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "${path.module}/scripts/build-image.sh \"${aws_codebuild_project.orchestrator_image.name}\" \"${data.aws_region.current.id}\" \"${aws_ecr_repository.orchestrator.name}\" \"${var.image_tag}\" \"${aws_ecr_repository.orchestrator.repository_url}\""
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_codebuild_project.orchestrator_image,
|
||||
aws_ecr_repository.orchestrator,
|
||||
aws_iam_role_policy.codebuild,
|
||||
aws_s3_object.orchestrator_source,
|
||||
null_resource.trigger_build_specialist, # CRITICAL: Wait for Specialist build
|
||||
time_sleep.wait_for_iam
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# ============================================================================
|
||||
# Orchestrator Agent Runtime - Depends on Specialist Agent
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "orchestrator" {
|
||||
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.orchestrator_name}"
|
||||
description = "Orchestrator agent runtime for ${var.stack_name}"
|
||||
role_arn = aws_iam_role.orchestrator_execution.arn
|
||||
|
||||
agent_runtime_artifact {
|
||||
container_configuration {
|
||||
container_uri = "${aws_ecr_repository.orchestrator.repository_url}:${var.image_tag}"
|
||||
}
|
||||
}
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
# CRITICAL: Specialist Agent ARN for A2A communication
|
||||
environment_variables = {
|
||||
AWS_REGION = data.aws_region.current.id
|
||||
AWS_DEFAULT_REGION = data.aws_region.current.id
|
||||
SPECIALIST_ARN = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_arn
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-orchestrator-runtime"
|
||||
Environment = "production"
|
||||
Module = "BedrockAgentCore"
|
||||
Agent = "Orchestrator"
|
||||
}
|
||||
|
||||
# CRITICAL: Must wait for Specialist Agent to be created first
|
||||
depends_on = [
|
||||
aws_bedrockagentcore_agent_runtime.specialist,
|
||||
null_resource.trigger_build_orchestrator,
|
||||
aws_iam_role_policy.orchestrator_execution,
|
||||
aws_iam_role_policy.orchestrator_invoke_specialist,
|
||||
aws_iam_role_policy_attachment.orchestrator_execution_managed
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
# ============================================================================
|
||||
# Orchestrator Agent Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "orchestrator_runtime_id" {
|
||||
description = "ID of orchestrator agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_id
|
||||
}
|
||||
|
||||
output "orchestrator_runtime_arn" {
|
||||
description = "ARN of orchestrator agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "orchestrator_runtime_version" {
|
||||
description = "Version of orchestrator agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_version
|
||||
}
|
||||
|
||||
output "orchestrator_ecr_repository_url" {
|
||||
description = "URL of the ECR repository for orchestrator agent"
|
||||
value = aws_ecr_repository.orchestrator.repository_url
|
||||
}
|
||||
|
||||
output "orchestrator_execution_role_arn" {
|
||||
description = "ARN of the orchestrator agent execution role"
|
||||
value = aws_iam_role.orchestrator_execution.arn
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Specialist Agent Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "specialist_runtime_id" {
|
||||
description = "ID of specialist agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_id
|
||||
}
|
||||
|
||||
output "specialist_runtime_arn" {
|
||||
description = "ARN of specialist agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "specialist_runtime_version" {
|
||||
description = "Version of specialist agent runtime"
|
||||
value = aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_version
|
||||
}
|
||||
|
||||
output "specialist_ecr_repository_url" {
|
||||
description = "URL of the ECR repository for specialist agent"
|
||||
value = aws_ecr_repository.specialist.repository_url
|
||||
}
|
||||
|
||||
output "specialist_execution_role_arn" {
|
||||
description = "ARN of the specialist agent execution role"
|
||||
value = aws_iam_role.specialist_execution.arn
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Build & Storage Outputs
|
||||
# ============================================================================
|
||||
|
||||
output "orchestrator_codebuild_project_name" {
|
||||
description = "Name of the CodeBuild project for orchestrator agent"
|
||||
value = aws_codebuild_project.orchestrator_image.name
|
||||
}
|
||||
|
||||
output "specialist_codebuild_project_name" {
|
||||
description = "Name of the CodeBuild project for specialist agent"
|
||||
value = aws_codebuild_project.specialist_image.name
|
||||
}
|
||||
|
||||
output "orchestrator_source_bucket_name" {
|
||||
description = "S3 bucket containing orchestrator agent source code"
|
||||
value = aws_s3_bucket.orchestrator_source.id
|
||||
}
|
||||
|
||||
output "specialist_source_bucket_name" {
|
||||
description = "S3 bucket containing specialist agent source code"
|
||||
value = aws_s3_bucket.specialist_source.id
|
||||
}
|
||||
|
||||
output "orchestrator_source_code_md5" {
|
||||
description = "MD5 hash of orchestrator source code (triggers rebuild when changed)"
|
||||
value = data.archive_file.orchestrator_source.output_md5
|
||||
}
|
||||
|
||||
output "specialist_source_code_md5" {
|
||||
description = "MD5 hash of specialist source code (triggers rebuild when changed)"
|
||||
value = data.archive_file.specialist_source.output_md5
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Testing Information
|
||||
# ============================================================================
|
||||
|
||||
output "test_orchestrator_command" {
|
||||
description = "AWS CLI command to test orchestrator agent"
|
||||
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-id ${aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_id} --qualifier DEFAULT --payload '{\"prompt\": \"Hello, how are you?\"}' --region ${data.aws_region.current.id} response.json"
|
||||
}
|
||||
|
||||
output "test_specialist_command" {
|
||||
description = "AWS CLI command to test specialist agent"
|
||||
value = "aws bedrock-agentcore invoke-agent-runtime --agent-runtime-id ${aws_bedrockagentcore_agent_runtime.specialist.agent_runtime_id} --qualifier DEFAULT --payload '{\"prompt\": \"Explain cloud computing\"}' --region ${data.aws_region.current.id} response.json"
|
||||
}
|
||||
|
||||
output "test_script_command" {
|
||||
description = "Command to test multi-agent communication"
|
||||
value = "python test_multi_agent.py ${aws_bedrockagentcore_agent_runtime.orchestrator.agent_runtime_arn}"
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
# ============================================================================
|
||||
# S3 Buckets for Agent Source Code (CDK Asset Equivalent)
|
||||
# ============================================================================
|
||||
|
||||
# Orchestrator Agent Source Bucket
|
||||
resource "aws_s3_bucket" "orchestrator_source" {
|
||||
bucket_prefix = "acma-orch-src-" # Shortened to fit 37 char limit
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-orchestrator-source"
|
||||
Purpose = "Store Orchestrator agent source code for CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# Specialist Agent Source Bucket
|
||||
resource "aws_s3_bucket" "specialist_source" {
|
||||
bucket_prefix = "acma-spec-src-" # Shortened to fit 37 char limit
|
||||
force_destroy = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-specialist-source"
|
||||
Purpose = "Store Specialist agent source code for CodeBuild"
|
||||
}
|
||||
}
|
||||
|
||||
# Block public access - Orchestrator
|
||||
resource "aws_s3_bucket_public_access_block" "orchestrator_source" {
|
||||
bucket = aws_s3_bucket.orchestrator_source.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Block public access - Specialist
|
||||
resource "aws_s3_bucket_public_access_block" "specialist_source" {
|
||||
bucket = aws_s3_bucket.specialist_source.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
# Enable versioning - Orchestrator
|
||||
resource "aws_s3_bucket_versioning" "orchestrator_source" {
|
||||
bucket = aws_s3_bucket.orchestrator_source.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Enable versioning - Specialist
|
||||
resource "aws_s3_bucket_versioning" "specialist_source" {
|
||||
bucket = aws_s3_bucket.specialist_source.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Archive and Upload Agent Source Code
|
||||
# ============================================================================
|
||||
|
||||
# Archive agent-orchestrator-code/ directory
|
||||
data "archive_file" "orchestrator_source" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/agent-orchestrator-code"
|
||||
output_path = "${path.module}/.terraform/agent-orchestrator-code.zip"
|
||||
}
|
||||
|
||||
# Archive agent-specialist-code/ directory
|
||||
data "archive_file" "specialist_source" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/agent-specialist-code"
|
||||
output_path = "${path.module}/.terraform/agent-specialist-code.zip"
|
||||
}
|
||||
|
||||
# Upload Orchestrator source to S3
|
||||
resource "aws_s3_object" "orchestrator_source" {
|
||||
bucket = aws_s3_bucket.orchestrator_source.id
|
||||
key = "agent-orchestrator-code-${data.archive_file.orchestrator_source.output_md5}.zip"
|
||||
source = data.archive_file.orchestrator_source.output_path
|
||||
etag = data.archive_file.orchestrator_source.output_md5
|
||||
|
||||
tags = {
|
||||
Name = "agent-orchestrator-source-code"
|
||||
Agent = "Orchestrator"
|
||||
MD5 = data.archive_file.orchestrator_source.output_md5
|
||||
}
|
||||
}
|
||||
|
||||
# Upload Specialist source to S3
|
||||
resource "aws_s3_object" "specialist_source" {
|
||||
bucket = aws_s3_bucket.specialist_source.id
|
||||
key = "agent-specialist-code-${data.archive_file.specialist_source.output_md5}.zip"
|
||||
source = data.archive_file.specialist_source.output_path
|
||||
etag = data.archive_file.specialist_source.output_md5
|
||||
|
||||
tags = {
|
||||
Name = "agent-specialist-source-code"
|
||||
Agent = "Specialist"
|
||||
MD5 = data.archive_file.specialist_source.output_md5
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Build and Verify Docker Image for AgentCore Runtime
|
||||
# ============================================================================
|
||||
# This script is called by Terraform during deployment to:
|
||||
# 1. Trigger CodeBuild to build the Docker image
|
||||
# 2. Wait for the build to complete
|
||||
# 3. Verify the image was successfully pushed to ECR
|
||||
#
|
||||
# Parameters:
|
||||
# $1 - CodeBuild project name
|
||||
# $2 - AWS region
|
||||
# $3 - ECR repository name
|
||||
# $4 - Image tag
|
||||
# $5 - ECR repository URL
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parameters
|
||||
PROJECT_NAME="$1"
|
||||
REGION="$2"
|
||||
REPO_NAME="$3"
|
||||
IMAGE_TAG="$4"
|
||||
REPO_URL="$5"
|
||||
|
||||
# ============================================================================
|
||||
# Print functions
|
||||
# ============================================================================
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[✓]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[⚠]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[✗]${NC} $1"
|
||||
}
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}============================================${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Start Build Process
|
||||
# ============================================================================
|
||||
|
||||
print_header "Building Docker Image for AgentCore Runtime"
|
||||
|
||||
print_info "CodeBuild Project: $PROJECT_NAME"
|
||||
print_info "Region: $REGION"
|
||||
print_info "Target Image: $REPO_URL:$IMAGE_TAG"
|
||||
echo ""
|
||||
|
||||
# Start CodeBuild
|
||||
print_info "Starting CodeBuild project..."
|
||||
|
||||
BUILD_ID=$(aws codebuild start-build \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--region "$REGION" \
|
||||
--query 'build.id' \
|
||||
--output text 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
print_error "Failed to start CodeBuild"
|
||||
echo "$BUILD_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build started: $BUILD_ID"
|
||||
print_info "Waiting for build to complete (typically 5-10 minutes)..."
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Monitor Build Progress
|
||||
# ============================================================================
|
||||
|
||||
ATTEMPT=0
|
||||
MAX_ATTEMPTS=60 # 10 minutes (60 * 10s)
|
||||
|
||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
|
||||
STATUS=$(aws codebuild batch-get-builds \
|
||||
--ids "$BUILD_ID" \
|
||||
--region "$REGION" \
|
||||
--query 'builds[0].buildStatus' \
|
||||
--output text 2>/dev/null)
|
||||
|
||||
if [ "$STATUS" != "IN_PROGRESS" ]; then
|
||||
print_info "Build process completed with status: $STATUS"
|
||||
break
|
||||
fi
|
||||
|
||||
# Progress indicator
|
||||
if [ $((ATTEMPT % 6)) -eq 0 ]; then
|
||||
MINUTES=$((ATTEMPT / 6))
|
||||
print_info "Build in progress... (${MINUTES} minutes elapsed)"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
||||
print_error "Build timeout after 10 minutes"
|
||||
print_warning "Check build status at: https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# Verify Image in ECR
|
||||
# ============================================================================
|
||||
|
||||
print_header "Verifying Docker Image in ECR"
|
||||
|
||||
print_info "Checking for image: $REPO_NAME:$IMAGE_TAG"
|
||||
print_info "Waiting for ECR propagation..."
|
||||
echo ""
|
||||
|
||||
sleep 5 # Brief wait for ECR to register the push
|
||||
|
||||
VERIFY_ATTEMPT=0
|
||||
MAX_VERIFY_ATTEMPTS=12 # 1 minute (12 * 5s)
|
||||
|
||||
while [ $VERIFY_ATTEMPT -lt $MAX_VERIFY_ATTEMPTS ]; do
|
||||
VERIFY_ATTEMPT=$((VERIFY_ATTEMPT + 1))
|
||||
|
||||
if aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" >/dev/null 2>&1; then
|
||||
|
||||
print_success "Docker image successfully verified in ECR!"
|
||||
echo ""
|
||||
print_info "Image URI: $REPO_URL:$IMAGE_TAG"
|
||||
|
||||
# Get image details
|
||||
IMAGE_SIZE=$(aws ecr describe-images \
|
||||
--repository-name "$REPO_NAME" \
|
||||
--image-ids imageTag="$IMAGE_TAG" \
|
||||
--region "$REGION" \
|
||||
--query 'imageDetails[0].imageSizeInBytes' \
|
||||
--output text 2>/dev/null || echo "Unknown")
|
||||
|
||||
if [ "$IMAGE_SIZE" != "Unknown" ]; then
|
||||
IMAGE_SIZE_MB=$((IMAGE_SIZE / 1024 / 1024))
|
||||
print_info "Image Size: ${IMAGE_SIZE_MB} MB"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Build and verification completed successfully!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $((VERIFY_ATTEMPT % 3)) -eq 0 ]; then
|
||||
print_info "Still waiting for image to appear in ECR... (attempt $VERIFY_ATTEMPT/$MAX_VERIFY_ATTEMPTS)"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Error: Image Not Found
|
||||
# ============================================================================
|
||||
|
||||
print_error "Docker image not found in ECR after build completion"
|
||||
echo ""
|
||||
print_warning "This indicates the build or push step failed."
|
||||
print_info "Troubleshooting steps:"
|
||||
print_info " 1. Check CodeBuild logs:"
|
||||
print_info " https://console.aws.amazon.com/codesuite/codebuild/projects/$PROJECT_NAME/history?region=$REGION"
|
||||
print_info ""
|
||||
print_info " 2. Verify ECR repository:"
|
||||
print_info " aws ecr describe-images --repository-name $REPO_NAME --region $REGION"
|
||||
print_info ""
|
||||
print_info " 3. Check IAM permissions for CodeBuild role"
|
||||
|
||||
exit 1
|
||||
@@ -0,0 +1,37 @@
|
||||
# ============================================================================
|
||||
# Specialist Agent Runtime - Independent Agent
|
||||
# ============================================================================
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "specialist" {
|
||||
agent_runtime_name = "${replace(var.stack_name, "-", "_")}_${var.specialist_name}"
|
||||
description = "Specialist agent runtime for ${var.stack_name}"
|
||||
role_arn = aws_iam_role.specialist_execution.arn
|
||||
|
||||
agent_runtime_artifact {
|
||||
container_configuration {
|
||||
container_uri = "${aws_ecr_repository.specialist.repository_url}:${var.image_tag}"
|
||||
}
|
||||
}
|
||||
|
||||
network_configuration {
|
||||
network_mode = var.network_mode
|
||||
}
|
||||
|
||||
environment_variables = {
|
||||
AWS_REGION = data.aws_region.current.id
|
||||
AWS_DEFAULT_REGION = data.aws_region.current.id
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name}-specialist-runtime"
|
||||
Environment = "production"
|
||||
Module = "BedrockAgentCore"
|
||||
Agent = "Specialist"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
null_resource.trigger_build_specialist,
|
||||
aws_iam_role_policy.specialist_execution,
|
||||
aws_iam_role_policy_attachment.specialist_execution_managed
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# ============================================================================
|
||||
# Multi-Agent Runtime - Example Configuration
|
||||
# ============================================================================
|
||||
# Copy this file to terraform.tfvars and customize
|
||||
# Example: cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Agent Configuration
|
||||
orchestrator_name = "OrchestratorAgent"
|
||||
specialist_name = "SpecialistAgent"
|
||||
stack_name = "agentcore-multi-agent"
|
||||
|
||||
# Network Configuration
|
||||
network_mode = "PUBLIC" # PUBLIC or PRIVATE
|
||||
|
||||
# Container Configuration
|
||||
ecr_repository_name = "multi-agent"
|
||||
image_tag = "latest"
|
||||
|
||||
# AWS Configuration
|
||||
aws_region = "us-west-2"
|
||||
environment = "dev"
|
||||
|
||||
# Optional: Environment Variables (if needed for custom configurations)
|
||||
# environment_variables = {
|
||||
# LOG_LEVEL = "INFO"
|
||||
# }
|
||||
|
||||
# Notes:
|
||||
# - Orchestrator Agent: Receives user requests and delegates to Specialist
|
||||
# - Specialist Agent: Provides specialized data processing capabilities
|
||||
# - Orchestrator code in agent-orchestrator-code/ directory (includes A2A invocation logic)
|
||||
# - Specialist code in agent-specialist-code/ directory (independent specialist)
|
||||
# - A2A communication: Orchestrator can invoke Specialist via call_specialist_agent tool
|
||||
# - Sequential deployment: Specialist builds first, then Orchestrator
|
||||
# - Test with: python test_multi_agent.py
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Multi-Agent System Test Script
|
||||
|
||||
This script tests a multi-agent system with Agent-to-Agent (A2A) communication.
|
||||
It can work with agents deployed via any method (Terraform, CloudFormation, CDK, manual).
|
||||
|
||||
Usage:
|
||||
python test_multi_agent.py <orchestrator_arn> [specialist_arn]
|
||||
|
||||
orchestrator_arn: ARN of the orchestrator agent (required)
|
||||
specialist_arn: ARN of the specialist agent (optional, for independent testing)
|
||||
|
||||
Examples:
|
||||
# Test orchestrator with A2A communication
|
||||
python test_multi_agent.py arn:aws:bedrock-agentcore:<region>:123456789012:runtime/orchestrator-id
|
||||
|
||||
# Test both agents independently
|
||||
python test_multi_agent.py \\
|
||||
arn:aws:bedrock-agentcore:<region>:123456789012:runtime/orchestrator-id \\
|
||||
arn:aws:bedrock-agentcore:<region>:123456789012:runtime/specialist-id
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def extract_region_from_arn(arn):
|
||||
"""Extract AWS region from agent runtime ARN.
|
||||
|
||||
ARN format: arn:aws:bedrock-agentcore:REGION:account:runtime/id
|
||||
|
||||
Args:
|
||||
arn: Agent runtime ARN string
|
||||
|
||||
Returns:
|
||||
str: AWS region code
|
||||
|
||||
Raises:
|
||||
ValueError: If ARN format is invalid or region cannot be extracted
|
||||
"""
|
||||
try:
|
||||
parts = arn.split(':')
|
||||
if len(parts) < 4:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
region = parts[3]
|
||||
if not region:
|
||||
raise ValueError(
|
||||
f"Region not found in ARN: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
return region
|
||||
|
||||
except IndexError:
|
||||
raise ValueError(
|
||||
f"Invalid ARN format: {arn}\n"
|
||||
f"Expected format: arn:aws:bedrock-agentcore:REGION:account:runtime/id"
|
||||
)
|
||||
|
||||
|
||||
def test_agent(client, agent_arn, agent_name, prompt):
|
||||
"""Test a single agent with a given prompt
|
||||
|
||||
Args:
|
||||
client: boto3 bedrock-agentcore client
|
||||
agent_arn: ARN of the agent runtime
|
||||
agent_name: Name for display purposes
|
||||
prompt: Test prompt to send
|
||||
"""
|
||||
print(f"\nPrompt: '{prompt}'")
|
||||
print("-" * 80)
|
||||
|
||||
try:
|
||||
response = client.invoke_agent_runtime(
|
||||
agentRuntimeArn=agent_arn,
|
||||
qualifier="DEFAULT",
|
||||
payload=json.dumps({"prompt": prompt})
|
||||
)
|
||||
|
||||
print(f"Status: {response['ResponseMetadata']['HTTPStatusCode']}")
|
||||
print(f"Content Type: {response.get('contentType', 'N/A')}")
|
||||
|
||||
# Read the streaming response body
|
||||
response_text = ""
|
||||
if 'response' in response:
|
||||
response_body = response['response'].read()
|
||||
response_text = response_body.decode('utf-8')
|
||||
|
||||
if response_text:
|
||||
try:
|
||||
result = json.loads(response_text)
|
||||
response_content = result.get('response', response_text)
|
||||
# Truncate long responses for readability
|
||||
if len(response_content) > 500:
|
||||
print(f"\n✅ Response:\n{response_content[:500]}...")
|
||||
print("\n[Response truncated for display]")
|
||||
else:
|
||||
print(f"\n✅ Response:\n{response_content}")
|
||||
except json.JSONDecodeError:
|
||||
if len(response_text) > 500:
|
||||
print(f"\n✅ Response:\n{response_text[:500]}...")
|
||||
else:
|
||||
print(f"\n✅ Response:\n{response_text}")
|
||||
else:
|
||||
print("\n⚠️ No response content received")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error testing {agent_name}: {e}")
|
||||
return False
|
||||
|
||||
def test_multi_agent(orchestrator_arn, specialist_arn=None):
|
||||
"""Test the multi-agent system
|
||||
|
||||
Args:
|
||||
orchestrator_arn: Orchestrator agent runtime ARN (required)
|
||||
specialist_arn: Specialist agent runtime ARN (optional)
|
||||
"""
|
||||
|
||||
# Extract region from orchestrator ARN
|
||||
try:
|
||||
region = extract_region_from_arn(orchestrator_arn)
|
||||
except ValueError as e:
|
||||
print(f"\n❌ ERROR: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("MULTI-AGENT SYSTEM TEST")
|
||||
print("="*80)
|
||||
print(f"\nOrchestrator Agent ARN: {orchestrator_arn}")
|
||||
if specialist_arn:
|
||||
print(f"Specialist Agent ARN: {specialist_arn}")
|
||||
else:
|
||||
print("Specialist Agent: Not provided (will test Orchestrator only)")
|
||||
print(f"Region: {region}")
|
||||
|
||||
# Create bedrock-agentcore client with extracted region
|
||||
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
|
||||
|
||||
test_results = []
|
||||
|
||||
# Test 1: Simple query to Orchestrator
|
||||
print("\n" + "="*80)
|
||||
print("TEST 1: Simple Query (Orchestrator)")
|
||||
print("="*80)
|
||||
result = test_agent(
|
||||
agentcore_client,
|
||||
orchestrator_arn,
|
||||
"Orchestrator",
|
||||
"Hello! Can you introduce yourself and your capabilities?"
|
||||
)
|
||||
test_results.append(("Simple Query", result))
|
||||
|
||||
# Test 2: Complex query triggering A2A communication
|
||||
print("\n" + "="*80)
|
||||
print("TEST 2: Complex Query with A2A Communication")
|
||||
print("="*80)
|
||||
result = test_agent(
|
||||
agentcore_client,
|
||||
orchestrator_arn,
|
||||
"Orchestrator",
|
||||
"I need expert analysis. Please coordinate with the specialist agent to provide a comprehensive explanation of cloud computing architectures and best practices."
|
||||
)
|
||||
test_results.append(("A2A Communication Test", result))
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*80)
|
||||
print("TEST SUMMARY")
|
||||
print("="*80)
|
||||
|
||||
all_passed = True
|
||||
for test_name, passed in test_results:
|
||||
status = "✅ PASSED" if passed else "❌ FAILED"
|
||||
print(f"{status} - {test_name}")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
print("\n" + "="*80)
|
||||
if all_passed:
|
||||
print("✅ ALL TESTS PASSED")
|
||||
else:
|
||||
print("⚠️ SOME TESTS FAILED")
|
||||
print("="*80 + "\n")
|
||||
|
||||
return all_passed
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
print("\n❌ ERROR: Agent runtime ARN is required")
|
||||
print("\nTo get your agent ARN:")
|
||||
print(" - Terraform: terraform output orchestrator_runtime_arn")
|
||||
print(" - CloudFormation: aws cloudformation describe-stacks --stack-name <stack> --query 'Stacks[0].Outputs'")
|
||||
print(" - CDK: cdk deploy --outputs-file outputs.json")
|
||||
print(" - Console: Check Bedrock Agent Core console")
|
||||
sys.exit(1)
|
||||
|
||||
orchestrator_arn = sys.argv[1]
|
||||
specialist_arn = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
# Validate ARN format
|
||||
if not orchestrator_arn.startswith("arn:aws:bedrock-agentcore:"):
|
||||
print(f"\n❌ ERROR: Invalid ARN format for orchestrator: {orchestrator_arn}")
|
||||
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
|
||||
sys.exit(1)
|
||||
|
||||
if specialist_arn and not specialist_arn.startswith("arn:aws:bedrock-agentcore:"):
|
||||
print(f"\n❌ ERROR: Invalid ARN format for specialist: {specialist_arn}")
|
||||
print("Expected format: arn:aws:bedrock-agentcore:region:account:runtime/runtime-id")
|
||||
sys.exit(1)
|
||||
|
||||
# Run tests
|
||||
success = test_multi_agent(orchestrator_arn, specialist_arn)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
variable "orchestrator_name" {
|
||||
description = "Name for the orchestrator agent runtime"
|
||||
type = string
|
||||
default = "OrchestratorAgent"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.orchestrator_name))
|
||||
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "specialist_name" {
|
||||
description = "Name for the specialist agent runtime"
|
||||
type = string
|
||||
default = "SpecialistAgent"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-zA-Z][a-zA-Z0-9_]{0,47}$", var.specialist_name))
|
||||
error_message = "Agent name must start with a letter, max 48 characters, alphanumeric and underscores only."
|
||||
}
|
||||
}
|
||||
|
||||
variable "network_mode" {
|
||||
description = "Network mode for AgentCore resources"
|
||||
type = string
|
||||
default = "PUBLIC"
|
||||
|
||||
validation {
|
||||
condition = contains(["PUBLIC", "PRIVATE"], var.network_mode)
|
||||
error_message = "Network mode must be either PUBLIC or PRIVATE."
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Docker image tag"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "aws_region" {
|
||||
description = "AWS region for deployment (REQUIRED)"
|
||||
type = string
|
||||
# No default - must be explicitly provided
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.aws_region))
|
||||
error_message = "Must be a valid AWS region (e.g., us-east-1, eu-west-1)"
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "stack_name" {
|
||||
description = "Stack name for resource naming"
|
||||
type = string
|
||||
default = "agentcore-multi-agent"
|
||||
}
|
||||
|
||||
variable "environment_variables" {
|
||||
description = "Environment variables for the agent runtime"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "ecr_repository_name" {
|
||||
description = "Base name of the ECR repositories"
|
||||
type = string
|
||||
default = "multi-agent"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 6.21"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = "~> 0.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "AgentCore"
|
||||
Pattern = "multi-agent-runtime"
|
||||
Environment = var.environment
|
||||
StackName = var.stack_name
|
||||
ManagedBy = "Terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user