AWSTemplateFormatVersion: '2010-09-09'
Description: 'DMS Migration with S3 and AgentCore DMS Management Agent'
Parameters:
# Source Database Parameters
SourceEngine:
Type: String
AllowedValues: [postgres, mysql, aurora-postgresql, aurora, mariadb]
Description: Source database engine type
SourceSecretArn:
Type: String
Description: ARN of AWS Secrets Manager secret containing source database credentials
SourceDatabaseName:
Type: String
Description: Source database name
SourceDatabasePort:
Type: Number
Default: 5432
SourceDatabaseCIDR:
Type: String
Description: Source database VPC CIDR block for DMS security group ingress. Required only if creating new DMS security group.
SourceDBSchema:
Type: String
Default: public
Description: Source database schema name. For PostgreSQL/Aurora PostgreSQL - enter schema name (e.g., public). For MySQL/MariaDB/Aurora MySQL - use same value as SourceDatabaseName
# DMS Replication Instance Parameters
ExistingReplicationInstanceArn:
Type: String
Default: ''
Description: (Optional) Existing DMS Replication Instance ARN
ReplicationInstanceClass:
Type: String
Default: dms.t3.medium
AllowedValues: [dms.t3.micro, dms.t3.small, dms.t3.medium, dms.t3.large, dms.c5.large, dms.c5.xlarge, dms.c5.2xlarge]
Description: DMS replication instance class - only used when creating new replication instance
ReplicationInstanceStorage:
Type: Number
Default: 50
MinValue: 5
MaxValue: 6144
Description: Allocated storage in GB - only used when creating new replication instance
ReplicationInstanceEngineVersion:
Type: String
Default: ''
Description: (Optional) DMS engine version (e.g., 3.6.1). Leave empty for latest version.
# DMS VPC Parameters
DMSVpcId:
Type: String
Default: ''
Description: (Optional) VPC ID for DMS - not required if using existing replication instance
DMSSubnetId1:
Type: String
Default: ''
Description: (Optional) First subnet ID for DMS - not required if using existing replication instance
DMSSubnetId2:
Type: String
Default: ''
Description: (Optional) Second subnet ID for DMS - not required if using existing replication instance
DMSSecurityGroupId:
Type: String
Default: ''
Description: (Optional) Security group ID for DMS - not required if using existing replication instance
DMSRouteTableIds:
Type: CommaDelimitedList
Default: ''
Description: (Optional) Private route table IDs for S3 VPC endpoint when using existing VPC. Only attach to route tables where DMS and Lambda resources are deployed (comma-delimited).
CreateS3VPCEndpoint:
Type: String
Default: 'true'
AllowedValues: ['true', 'false']
Description: Create S3 VPC endpoint. Set to false if S3 endpoint already exists in your VPC.
# Lambda VPC Parameters
LambdaVpcId:
Type: String
Description: VPC ID for Lambda functions
LambdaSubnetIds:
Type: CommaDelimitedList
Description: Subnet IDs for Lambda functions (comma-delimited)
LambdaSecurityGroupId:
Type: String
Description: Security group ID for Lambda functions
# Lambda Layer Parameters
PsycopgLayerArn:
Type: String
Default: ''
Description: Required for PostgreSQL source and DSQL target
PymysqlLayerArn:
Type: String
Default: ''
Description: Required for MySQL/MariaDB source
# Target Aurora DSQL Parameters
TargetSecretArn:
Type: String
Description: ARN of AWS Secrets Manager secret containing Aurora DSQL configuration (endpoint, database, schema, username)
TargetRegion:
Type: String
Default: us-east-1
Description: Target Aurora DSQL region
# S3 Target Parameters
S3BucketName:
Type: String
Default: ''
Description: S3 bucket name for DMS target (leave empty to auto-generate)
S3BucketPrefix:
Type: String
Default: 'dms-cdc/'
Description: S3 prefix for DMS CDC files
# AgentCore Configuration
AgentCoreName:
Type: String
Default: "dmsagent"
Description: "Name for the AgentCore agent"
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9_]{0,47}$"
ImageTag:
Type: String
Default: "latest"
Description: "Tag for the Docker image"
NetworkMode:
Type: String
Default: "PUBLIC"
Description: "Network mode for AgentCore resources"
AllowedValues:
- PUBLIC
- VPC
# AgentCore VPC Parameters (required for VPC mode)
AgentCoreVpcId:
Type: String
Default: ''
Description: (Optional) VPC ID for AgentCore - required if NetworkMode is VPC
AgentCoreSubnetIds:
Type: CommaDelimitedList
Default: ''
Description: (Optional) Subnet IDs for AgentCore (comma-delimited) - required if NetworkMode is VPC
AgentCoreSecurityGroupId:
Type: String
Default: ''
Description: (Optional) Security group for AgentCore - required if NetworkMode is VPC
FoundationModelId:
Type: String
Description: Foundation model ID or inference profile ARN
Default: us.anthropic.claude-haiku-4-5-20251001-v1:0
Conditions:
CreateReplicationInstance: !Equals [!Ref ExistingReplicationInstanceArn, '']
CreateVPC: !And
- !Equals [!Ref DMSVpcId, '']
- !Equals [!Ref ExistingReplicationInstanceArn, '']
CreateSecurityGroup: !And
- !Equals [!Ref DMSSecurityGroupId, '']
- !Equals [!Ref ExistingReplicationInstanceArn, '']
IsPostgreSQL: !Or [!Equals [!Ref SourceEngine, 'postgres'], !Equals [!Ref SourceEngine, 'aurora-postgresql']]
IsMySQL: !Or [!Equals [!Ref SourceEngine, 'mysql'], !Equals [!Ref SourceEngine, 'aurora'], !Equals [!Ref SourceEngine, 'mariadb']]
UsePsycopgLayer: !And [!Condition IsPostgreSQL, !Not [!Equals [!Ref PsycopgLayerArn, '']]]
UsePymysqlLayer: !And [!Condition IsMySQL, !Not [!Equals [!Ref PymysqlLayerArn, '']]]
UseEngineVersion: !Not [!Equals [!Ref ReplicationInstanceEngineVersion, '']]
IsFullArn: !Equals [!Select [0, !Split [":", !Ref FoundationModelId]], "arn"]
IsInferenceProfile: !Or
- !Equals [!Select [0, !Split [".", !Ref FoundationModelId]], "us"]
- !Equals [!Select [0, !Split [".", !Ref FoundationModelId]], "eu"]
UseCustomS3Bucket: !Not [!Equals [!Ref S3BucketName, '']]
HasRouteTableIds: !Not [!Equals [!Join ['', !Ref DMSRouteTableIds], '']]
ShouldCreateS3Endpoint: !And
- !Equals [!Ref CreateS3VPCEndpoint, 'true']
- !Or [!Condition CreateVPC, !Condition HasRouteTableIds]
UsePrivateNetwork: !Equals [!Ref NetworkMode, 'VPC']
Resources:
# SECURITY SECTION - KMS
EncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: KMS key for encrypting S3, logs, and ECR
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action: 'kms:*'
Resource: '*'
- Sid: Allow Service Usage
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action:
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:ReEncrypt*'
- 'kms:GenerateDataKey*'
- 'kms:CreateGrant'
- 'kms:DescribeKey'
Resource: '*'
Condition:
StringEquals:
'kms:ViaService':
- !Sub 'secretsmanager.${AWS::Region}.amazonaws.com'
- !Sub 'logs.${AWS::Region}.amazonaws.com'
- !Sub 'sqs.${AWS::Region}.amazonaws.com'
- !Sub 'ecr.${AWS::Region}.amazonaws.com'
- Sid: Allow CloudWatch Logs
Effect: Allow
Principal:
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Action:
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:ReEncrypt*'
- 'kms:GenerateDataKey*'
- 'kms:CreateGrant'
- 'kms:DescribeKey'
Resource: '*'
Condition:
ArnLike:
'kms:EncryptionContext:aws:logs:arn': !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
- Sid: Allow S3
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:GenerateDataKey'
- 'kms:DescribeKey'
Resource: '*'
EncryptionKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub 'alias/${AWS::StackName}-encryption-key'
TargetKeyId: !Ref EncryptionKey
# VPC AND NETWORKING SECTION
DMSVPC:
Type: AWS::EC2::VPC
Condition: CreateVPC
Properties:
CidrBlock: 10.100.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: DMS-Migration-VPC
PublicSubnet:
Type: AWS::EC2::Subnet
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
CidrBlock: 10.100.0.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: DMS-Public-Subnet
PrivateSubnet1:
Type: AWS::EC2::Subnet
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
CidrBlock: 10.100.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: DMS-Private-Subnet-1
PrivateSubnet2:
Type: AWS::EC2::Subnet
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
CidrBlock: 10.100.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: DMS-Private-Subnet-2
InternetGateway:
Type: AWS::EC2::InternetGateway
Condition: CreateVPC
Properties:
Tags:
- Key: Name
Value: DMS-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
InternetGatewayId: !Ref InternetGateway
EIP:
Type: AWS::EC2::EIP
Condition: CreateVPC
Properties:
Domain: vpc
NATGateway:
Type: AWS::EC2::NatGateway
Condition: CreateVPC
Properties:
AllocationId: !GetAtt EIP.AllocationId
SubnetId: !Ref PublicSubnet
Tags:
- Key: Name
Value: DMS-NAT-Gateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
Tags:
- Key: Name
Value: DMS-Public-RT
PublicRoute:
Type: AWS::EC2::Route
Condition: CreateVPC
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Condition: CreateVPC
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Condition: CreateVPC
Properties:
VpcId: !Ref DMSVPC
Tags:
- Key: Name
Value: DMS-Private-RT
PrivateRoute:
Type: AWS::EC2::Route
Condition: CreateVPC
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NATGateway
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Condition: CreateVPC
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Condition: CreateVPC
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateRouteTable
S3VPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Condition: ShouldCreateS3Endpoint
Properties:
VpcId: !If [CreateVPC, !Ref DMSVPC, !Ref DMSVpcId]
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
VpcEndpointType: Gateway
RouteTableIds: !If
- CreateVPC
- - !Ref PrivateRouteTable
- !Ref PublicRouteTable
- !Ref DMSRouteTableIds
DMSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Condition: CreateSecurityGroup
Properties:
GroupDescription: DMS Replication Instance Security Group
VpcId: !If [CreateVPC, !Ref DMSVPC, !Ref DMSVpcId]
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref SourceDatabasePort
ToPort: !Ref SourceDatabasePort
CidrIp: !Ref SourceDatabaseCIDR
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: DMS-SecurityGroup
# DMS SECTION
DMSSubnetGroup:
Type: AWS::DMS::ReplicationSubnetGroup
Condition: CreateReplicationInstance
Properties:
ReplicationSubnetGroupDescription: DMS Subnet Group
SubnetIds:
- !If [CreateVPC, !Ref PrivateSubnet1, !Ref DMSSubnetId1]
- !If [CreateVPC, !Ref PrivateSubnet2, !Ref DMSSubnetId2]
DMSVPCRole:
Type: AWS::IAM::Role
Condition: CreateReplicationInstance
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: dms.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole
DMSCloudWatchLogsRole:
Type: AWS::IAM::Role
Condition: CreateReplicationInstance
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: dms.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonDMSCloudWatchLogsRole
DMSReplicationInstance:
Type: AWS::DMS::ReplicationInstance
Condition: CreateReplicationInstance
DependsOn: [DMSVPCRole, DMSCloudWatchLogsRole]
Properties:
ReplicationInstanceIdentifier: !Sub 'dms-replication-${AWS::StackName}'
ReplicationInstanceClass: !Ref ReplicationInstanceClass
AllocatedStorage: !Ref ReplicationInstanceStorage
EngineVersion: !If [UseEngineVersion, !Ref ReplicationInstanceEngineVersion, !Ref 'AWS::NoValue']
VpcSecurityGroupIds:
- !If [CreateSecurityGroup, !Ref DMSSecurityGroup, !Ref DMSSecurityGroupId]
ReplicationSubnetGroupIdentifier: !Ref DMSSubnetGroup
PubliclyAccessible: false
MultiAZ: false
DMSSecretsManagerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- dms.amazonaws.com
- !Sub 'dms.${AWS::Region}.amazonaws.com'
Action: sts:AssumeRole
Policies:
- PolicyName: SecretsManagerAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Ref SourceSecretArn
- !Ref TargetSecretArn
- Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: !GetAtt EncryptionKey.Arn
Condition:
StringEquals:
'kms:ViaService': !Sub 'secretsmanager.${AWS::Region}.amazonaws.com'
StringLike:
'kms:EncryptionContext:SecretARN': !Ref SourceSecretArn
DMSSourceEndpoint:
Type: AWS::DMS::Endpoint
Properties:
EndpointIdentifier: !Sub 'source-${AWS::StackName}'
EndpointType: source
EngineName: !Ref SourceEngine
DatabaseName: !If [IsPostgreSQL, !Ref SourceDatabaseName, !Ref 'AWS::NoValue']
PostgreSqlSettings: !If
- IsPostgreSQL
- SecretsManagerAccessRoleArn: !GetAtt DMSSecretsManagerRole.Arn
SecretsManagerSecretId: !Ref SourceSecretArn
- !Ref 'AWS::NoValue'
MySqlSettings: !If
- IsMySQL
- SecretsManagerAccessRoleArn: !GetAtt DMSSecretsManagerRole.Arn
SecretsManagerSecretId: !Ref SourceSecretArn
- !Ref 'AWS::NoValue'
DMSS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !If [UseCustomS3Bucket, !Ref S3BucketName, !Sub '${AWS::StackName}-dms-target-${AWS::AccountId}']
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !GetAtt EncryptionKey.Arn
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
LifecycleConfiguration:
Rules:
- Id: DeleteOldCDCFiles
Status: Enabled
ExpirationInDays: 30
DMSS3Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: dms.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:DeleteObject
- s3:PutObjectTagging
Resource: !Sub '${DMSS3Bucket.Arn}/${S3BucketPrefix}*'
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !GetAtt DMSS3Bucket.Arn
- Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
- kms:DescribeKey
Resource: !GetAtt EncryptionKey.Arn
DMSTargetEndpoint:
Type: AWS::DMS::Endpoint
Properties:
EndpointIdentifier: !Sub 's3-target-${AWS::StackName}'
EndpointType: target
EngineName: s3
S3Settings:
BucketName: !Ref DMSS3Bucket
BucketFolder: !Ref S3BucketPrefix
ServiceAccessRoleArn: !GetAtt DMSS3Role.Arn
DataFormat: csv
CompressionType: gzip
EnableStatistics: true
IncludeOpForFullLoad: true
TimestampColumnName: dms_timestamp
AddColumnName: true
CdcPath: 'cdc'
EncryptionMode: SSE_KMS
ServerSideEncryptionKmsKeyId: !GetAtt EncryptionKey.Arn
DMSReplicationTask:
Type: AWS::DMS::ReplicationTask
Properties:
ReplicationTaskIdentifier: !Sub 'migration-task-${AWS::StackName}'
SourceEndpointArn: !Ref DMSSourceEndpoint
TargetEndpointArn: !Ref DMSTargetEndpoint
ReplicationInstanceArn: !If [CreateReplicationInstance, !Ref DMSReplicationInstance, !Ref ExistingReplicationInstanceArn]
MigrationType: full-load-and-cdc
TableMappings: !Sub |
{
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "1",
"object-locator": {
"schema-name": "${SourceDBSchema}",
"table-name": "%"
},
"rule-action": "include"
}
]
}
ReplicationTaskSettings: |
{
"TargetMetadata": {
"SupportLobs": true,
"FullLobMode": false,
"LobChunkSize": 64,
"LimitedSizeLobMode": true,
"LobMaxSize": 32
},
"FullLoadSettings": {
"TargetTablePrepMode": "DROP_AND_CREATE",
"CreatePkAfterFullLoad": false,
"StopTaskCachedChangesApplied": false,
"StopTaskCachedChangesNotApplied": false,
"MaxFullLoadSubTasks": 8,
"TransactionConsistencyTimeout": 600,
"CommitRate": 10000
},
"Logging": {
"EnableLogging": true,
"LogComponents": [
{
"Id": "TRANSFORMATION",
"Severity": "LOGGER_SEVERITY_DEFAULT"
},
{
"Id": "SOURCE_UNLOAD",
"Severity": "LOGGER_SEVERITY_DEFAULT"
},
{
"Id": "TARGET_LOAD",
"Severity": "LOGGER_SEVERITY_DEFAULT"
}
]
},
"ChangeProcessingDdlHandlingPolicy": {
"HandleSourceTableDropped": true,
"HandleSourceTableTruncated": true,
"HandleSourceTableAltered": true
},
"ChangeProcessingTuning": {
"BatchApplyPreserveTransaction": true,
"BatchApplyTimeoutMin": 1,
"BatchApplyTimeoutMax": 30,
"BatchApplyMemoryLimit": 500,
"BatchSplitSize": 0,
"MinTransactionSize": 1000,
"CommitTimeout": 1,
"MemoryLimitTotal": 1024,
"MemoryKeepTime": 60,
"StatementCacheSize": 50
}
}
# S3 Processor Lambda
S3ProcessorExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: LambdaS3AuroraDSQL
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
- s3:ListBucket
Resource:
- !GetAtt DMSS3Bucket.Arn
- !Sub '${DMSS3Bucket.Arn}/*'
- Effect: Allow
Action:
- dsql:DbConnect
- dsql:DbConnectAdmin
Resource: !Sub 'arn:aws:dsql:${TargetRegion}:${AWS::AccountId}:cluster/*'
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Ref SourceSecretArn
- !Ref TargetSecretArn
- Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt EncryptionKey.Arn
DMSS3ProcessorFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-dms-s3-processor'
Runtime: python3.14
Handler: index.lambda_handler
Role: !GetAtt S3ProcessorExecutionRole.Arn
Timeout: 300
ReservedConcurrentExecutions: 50
MemorySize: 512
VpcConfig:
SecurityGroupIds:
- !Ref LambdaSecurityGroupId
SubnetIds: !Ref LambdaSubnetIds
Environment:
Variables:
DSQL_SECRET_ARN: !Ref TargetSecretArn
SOURCE_DB_SCHEMA: !Ref SourceDBSchema
SOURCE_DB_SECRET_ARN: !Ref SourceSecretArn
ENABLE_UUID_CONVERSION: 'true'
SCHEMA_VERSION: '1'
Layers:
- !Ref PsycopgLayerArn
- !If [IsMySQL, !Ref PymysqlLayerArn, !Ref 'AWS::NoValue']
Code:
ZipFile: |
def lambda_handler(event, context):
print("Placeholder - Update with DMS S3 processor code")
print(f"S3 Event: {event}")
return {'statusCode': 200, 'body': 'Update function code'}
S3LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref DMSS3ProcessorFunction
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceArn: !GetAtt DMSS3Bucket.Arn
SourceAccount: !Ref AWS::AccountId
S3BucketNotificationConfig:
Type: Custom::S3BucketNotification
DependsOn: S3LambdaInvokePermission
Properties:
ServiceToken: !GetAtt S3NotificationConfigFunction.Arn
BucketName: !Ref DMSS3Bucket
LambdaFunctionArn: !GetAtt DMSS3ProcessorFunction.Arn
Prefix: !Ref S3BucketPrefix
S3NotificationConfigFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-s3-notification-config'
Runtime: python3.14
Handler: index.handler
Role: !GetAtt S3NotificationConfigRole.Arn
Timeout: 60
Code:
ZipFile: |
import boto3, json, logging
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
s3 = boto3.client('s3')
def handler(event, context):
try:
bucket = event['ResourceProperties']['BucketName']
lambda_arn = event['ResourceProperties']['LambdaFunctionArn']
prefix = event['ResourceProperties']['Prefix']
if event['RequestType'] == 'Delete':
s3.put_bucket_notification_configuration(
Bucket=bucket,
NotificationConfiguration={}
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
s3.put_bucket_notification_configuration(
Bucket=bucket,
NotificationConfiguration={
'LambdaFunctionConfigurations': [{
'LambdaFunctionArn': lambda_arn,
'Events': ['s3:ObjectCreated:*'],
'Filter': {
'Key': {
'FilterRules': [{'Name': 'prefix', 'Value': prefix}]
}
}
}]
}
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
logger.error(f'Error: {str(e)}')
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
S3NotificationConfigRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: S3NotificationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutBucketNotification
- s3:GetBucketNotification
Resource: !GetAtt DMSS3Bucket.Arn
# DMS Agent Lambda Function
DMSAgentLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${AWS::StackName}-dms-agent-lambda-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: DMSAgentPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-*"
- Effect: Allow
Action:
- dms:DescribeReplicationTasks
Resource: !Sub 'arn:aws:dms:${AWS::Region}:${AWS::AccountId}:*'
- Effect: Allow
Action:
- dms:StartReplicationTask
- dms:StopReplicationTask
Resource: !Ref DMSReplicationTask
- Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt EncryptionKey.Arn
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Ref SourceSecretArn
- !Ref TargetSecretArn
- Effect: Allow
Action:
- dsql:DbConnect
- dsql:DbConnectAdmin
Resource: !Sub 'arn:aws:dsql:${TargetRegion}:${AWS::AccountId}:cluster/*'
DMSAgentLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-dms-agent-lambda'
Runtime: python3.14
Handler: index.lambda_handler
Role: !GetAtt DMSAgentLambdaExecutionRole.Arn
ReservedConcurrentExecutions: 10
Timeout: 300
MemorySize: 512
Code:
ZipFile: |
# PLACEHOLDER: Replace with actual Lambda function code
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': 'Lambda function placeholder - replace with actual code'
}
Environment:
Variables:
DMS_TASK_ARN: !Ref DMSReplicationTask
SOURCE_SECRET_ARN: !Ref SourceSecretArn
DSQL_SECRET_ARN: !Ref TargetSecretArn
SOURCE_SCHEMA: !Ref SourceDBSchema
VpcConfig:
SecurityGroupIds:
- !Ref LambdaSecurityGroupId
SubnetIds:
- !Select [0, !Ref LambdaSubnetIds]
Layers:
- !Ref PsycopgLayerArn
- !If [IsMySQL, !Ref PymysqlLayerArn, !Ref 'AWS::NoValue']
# AgentCore ECR Repository
AgentCoreECRRepository:
Type: AWS::ECR::Repository
DeletionPolicy: Delete
UpdateReplacePolicy: Delete
Properties:
RepositoryName: !Sub "${AWS::StackName}-${AgentCoreName}"
EncryptionConfiguration:
EncryptionType: KMS
KmsKey: !GetAtt EncryptionKey.Arn
ImageTagMutability: IMMUTABLE
EmptyOnDelete: true
ImageScanningConfiguration:
ScanOnPush: true
# AgentCore IAM Roles
AgentCoreExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-agentcore-execution-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: bedrock-agentcore.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
ArnLike:
aws:SourceArn: !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:*"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess
Policies:
- PolicyName: AgentCoreExecutionPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: ECRImageAccess
Effect: Allow
Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:BatchCheckLayerAvailability
- ecr:GetAuthorizationToken
Resource:
- !GetAtt AgentCoreECRRepository.Arn
- "*"
- Sid: CloudWatchLogs
Effect: Allow
Action:
- logs:DescribeLogStreams
- logs:CreateLogGroup
- logs:DescribeLogGroups
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Sid: BedrockModelInvocation
Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
Resource: "*"
- Sid: LambdaInvocation
Effect: Allow
Action: lambda:InvokeFunction
Resource: !GetAtt DMSAgentLambda.Arn
AgentCoreCodeBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-agentcore-codebuild-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: CloudWatchLogs
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${AWS::StackName}-*"
- Sid: KMSAccess
Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt EncryptionKey.Arn
- Sid: ECRAuthToken
Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: "*"
- Sid: ECRRepositoryAccess
Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource: !GetAtt AgentCoreECRRepository.Arn
AgentCoreCustomResourceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-agentcore-custom-resource-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CustomResourcePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: CodeBuildAccess
Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
Resource: !GetAtt AgentCoreImageBuildProject.Arn
- Sid: KMSAccess
Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt EncryptionKey.Arn
# AgentCore Custom Resource Lambda
AgentCoreCodeBuildTriggerFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-agentcore-codebuild-trigger"
Handler: index.handler
Role: !GetAtt AgentCoreCustomResourceRole.Arn
ReservedConcurrentExecutions: 5
Runtime: python3.14
Timeout: 900
Code:
ZipFile: |
import boto3, json, logging, time
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
logger.info('Received event: %s', json.dumps(event))
try:
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
project_name = event['ResourceProperties']['ProjectName']
codebuild = boto3.client('codebuild')
response = codebuild.start_build(projectName=project_name)
build_id = response['build']['id']
logger.info(f"Started build: {build_id}")
max_wait_time = context.get_remaining_time_in_millis() / 1000 - 30
start_time = time.time()
while True:
if time.time() - start_time > max_wait_time:
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': 'Build timeout'})
return
build_response = codebuild.batch_get_builds(ids=[build_id])
build_status = build_response['builds'][0]['buildStatus']
if build_status == 'SUCCEEDED':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {'BuildId': build_id})
return
elif build_status in ['FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': f'Build failed: {build_status}'})
return
time.sleep(30)
except Exception as e:
logger.error('Error: %s', str(e))
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
# AgentCore CodeBuild Project
AgentCoreImageBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${AWS::StackName}-${AgentCoreName}-build"
EncryptionKey: !GetAtt EncryptionKey.Arn
LogsConfig:
CloudWatchLogs:
Status: ENABLED
GroupName: !Sub '/aws/codebuild/${AWS::StackName}-${AgentCoreName}-build'
ServiceRole: !GetAtt AgentCoreCodeBuildRole.Arn
Artifacts:
Type: NO_ARTIFACTS
Environment:
Type: ARM_CONTAINER
ComputeType: BUILD_GENERAL1_LARGE
Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
PrivilegedMode: true
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: AWS_ACCOUNT_ID
Value: !Ref AWS::AccountId
- Name: IMAGE_REPO_NAME
Value: !Ref AgentCoreECRRepository
- Name: IMAGE_TAG
Value: !Ref ImageTag
- Name: DMS_LAMBDA_ARN
Value: !GetAtt DMSAgentLambda.Arn
- Name: FOUNDATION_MODEL_ID
Value: !If
- IsFullArn
- !Ref FoundationModelId
- !If
- IsInferenceProfile
- !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/${FoundationModelId}'
- !Sub 'arn:aws:bedrock:${AWS::Region}::foundation-model/${FoundationModelId}'
Source:
Type: NO_SOURCE
BuildSpec: !Sub |
version: 0.2
phases:
pre_build:
commands:
- 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 Building DMS AgentCore Agent...
- |
cat > requirements.txt << 'EOF'
strands-agents
strands-agents-tools
boto3
bedrock-agentcore
bedrock-agentcore-starter-toolkit
pydantic
python-dotenv
EOF
- |
cat > dms_agent.py << 'AGENTEOF'
from strands.telemetry import StrandsTelemetry
from strands.types.content import Message
from strands.models import BedrockModel
from strands.agent.conversation_manager import SlidingWindowConversationManager
from strands import Agent, tool
import json, sys, os, re, io, uuid, asyncio, boto3
from typing import Union, Optional, Annotated, Dict, List, Any, Literal
from pydantic import BaseModel, Field
from dotenv import load_dotenv
from bedrock_agentcore.runtime.context import RequestContext
from bedrock_agentcore import BedrockAgentCoreApp
load_dotenv()
app = BedrockAgentCoreApp()
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
REGION = os.getenv('AWS_REGION', 'us-east-1')
MODEL_ID = os.getenv('FOUNDATION_MODEL_ID')
LAMBDA_ARN = os.getenv('DMS_LAMBDA_ARN')
llm_ORCHESTRATION = BedrockModel(model_id=MODEL_ID, region_name=REGION, temperature=0, max_tokens=16384, stop_sequences=[''], top_k=250)
ORCHESTRATION_TEMPLATE = """
$$You are a DMS (Database Migration Service) management assistant. You can start/stop DMS replication tasks and validate data between source and target Aurora DSQL databases. Use the available functions to help users manage their data migration and validation tasks.$$ You are a helpful assistant with tool/function calling capabilities. If you need an input parameter for a tool/function, ask the user to provide that parameter before making a call to that function/tool. You will have access to a separate tool/function that you MUST use to ask questions to the user. Never call a tool/function before gathering all parameters required for the tool/function call. It is your responsibility to pick the correct tools/functions that are going to help you answer the user questions. Continue using the provided tools/functions until the initial user request is perfectly addressed. If you do not have the necessary tools/functions to address the initial request, call it out and terminate conversation. When you receive a tool/function call response, use the output to format an answer to the original user question. Provide your final answer to the user's question within xml tags. I have also provided default values for the following arguments to use within the functions that are available to you:
$$attributes$$
Please use these default values for the specified arguments whenever you call the relevant functions. A value may have to be reformatted to correctly match the input format the function specification requires (e.g. changing a date to match the correct date format).
"""
class DmsActionsStartDmsPostApplicationJson(BaseModel):
content_type_annotation: Literal["application/json"]
start_type: Optional[str] = Field(None, description="Start type: 'start', 'restart' or 'reload' for full reload; 'resume' or 'continue' to continue from last position (default: restart)")
@tool(inputSchema=DmsActionsStartDmsPostApplicationJson.model_json_schema())
def dms_actions_start_dms_post(content_type_annotation: str, start_type: Optional[str] = None) -> str:
lambda_client = boto3.client('lambda', region_name=REGION)
model_dump = {"content_type_annotation": content_type_annotation}
if start_type:
model_dump["start_type"] = start_type
request_body = {"content": {"application/json": {"properties": []}}}
for param_name, param_value in model_dump.items():
if param_name != "content_type_annotation":
request_body["content"]["application/json"]["properties"].append({"name": param_name, "value": param_value})
try:
payload = {"messageVersion": "1.0", "agent": {"name": "dms_management_agent", "id": "AGENTCORE", "alias": "LATEST", "version": "DRAFT"}, "sessionId": "", "sessionAttributes": {}, "promptSessionAttributes": {}, "actionGroup": "dms_actions", "apiPath": "/start-dms", "inputText": last_input, "httpMethod": "POST", "parameters": [], "requestBody": request_body}
response = lambda_client.invoke(FunctionName=LAMBDA_ARN, InvocationType='RequestResponse', Payload=json.dumps(payload))
return str(json.loads(response['Payload'].read().decode('utf-8')))
except Exception as e:
return f"Error executing start_dms/post: {str(e)}"
@tool()
def dms_actions_stop_dms_post() -> str:
lambda_client = boto3.client('lambda', region_name=REGION)
try:
payload = {"messageVersion": "1.0", "agent": {"name": "dms_management_agent", "id": "AGENTCORE", "alias": "LATEST", "version": "DRAFT"}, "sessionId": "", "sessionAttributes": {}, "promptSessionAttributes": {}, "actionGroup": "dms_actions", "apiPath": "/stop-dms", "inputText": last_input, "httpMethod": "POST", "parameters": []}
response = lambda_client.invoke(FunctionName=LAMBDA_ARN, InvocationType='RequestResponse', Payload=json.dumps(payload))
return str(json.loads(response['Payload'].read().decode('utf-8')))
except Exception as e:
return f"Error executing stop_dms/post: {str(e)}"
@tool()
def dms_actions_check_status_post() -> str:
lambda_client = boto3.client('lambda', region_name=REGION)
try:
payload = {"messageVersion": "1.0", "agent": {"name": "dms_management_agent", "id": "AGENTCORE", "alias": "LATEST", "version": "DRAFT"}, "sessionId": "", "sessionAttributes": {}, "promptSessionAttributes": {}, "actionGroup": "dms_actions", "apiPath": "/check-status", "inputText": last_input, "httpMethod": "POST", "parameters": []}
response = lambda_client.invoke(FunctionName=LAMBDA_ARN, InvocationType='RequestResponse', Payload=json.dumps(payload))
return str(json.loads(response['Payload'].read().decode('utf-8')))
except Exception as e:
return f"Error executing check_status/post: {str(e)}"
class DmsActionsValidateDataPostApplicationJson(BaseModel):
content_type_annotation: Literal["application/json"]
table_name: Optional[str] = Field(None, description="Specific table name to validate (optional - validates all tables if not provided)")
source_database: Optional[str] = Field(None, description="Source database name (optional - uses database from secrets if not provided)")
@tool(inputSchema=DmsActionsValidateDataPostApplicationJson.model_json_schema())
def dms_actions_validate_data_post(content_type_annotation: str, table_name: Optional[str] = None, source_database: Optional[str] = None) -> str:
lambda_client = boto3.client('lambda', region_name=REGION)
model_dump = {"content_type_annotation": content_type_annotation}
if table_name:
model_dump["table_name"] = table_name
if source_database:
model_dump["source_database"] = source_database
request_body = {"content": {"application/json": {"properties": []}}}
for param_name, param_value in model_dump.items():
if param_name != "content_type_annotation":
request_body["content"]["application/json"]["properties"].append({"name": param_name, "value": param_value})
try:
payload = {"messageVersion": "1.0", "agent": {"name": "dms_management_agent", "id": "AGENTCORE", "alias": "LATEST", "version": "DRAFT"}, "sessionId": "", "sessionAttributes": {}, "promptSessionAttributes": {}, "actionGroup": "dms_actions", "apiPath": "/validate-data", "inputText": last_input, "httpMethod": "POST", "parameters": [], "requestBody": request_body}
response = lambda_client.invoke(FunctionName=LAMBDA_ARN, InvocationType='RequestResponse', Payload=json.dumps(payload))
return str(json.loads(response['Payload'].read().decode('utf-8')))
except Exception as e:
return f"Error executing validate_data/post: {str(e)}"
action_group_tools = [dms_actions_start_dms_post, dms_actions_stop_dms_post, dms_actions_check_status_post, dms_actions_validate_data_post]
checkpointer_STM = SlidingWindowConversationManager()
tools = []
tools_used = set()
strands_telemetry = StrandsTelemetry()
strands_telemetry.setup_meter(enable_console_exporter=True)
strands_telemetry.setup_console_exporter()
tools += action_group_tools
_agent = None
first_turn = True
last_input = ""
user_id = ""
def get_agent():
global _agent
if _agent is None:
_agent = Agent(model=llm_ORCHESTRATION, system_prompt=ORCHESTRATION_TEMPLATE, tools=tools, conversation_manager=checkpointer_STM)
return _agent
def invoke_agent(question: str):
global last_input
last_input = question
agent = get_agent()
original_stdout = sys.stdout
sys.stdout = io.StringIO()
response = agent(question)
sys.stdout = original_stdout
return response
@app.entrypoint
def endpoint(payload, context):
try:
session_id = context.session_id or payload.get("sessionId", uuid.uuid4().hex[:8])
tools_used.clear()
agent_query = payload.get("message", "")
if not agent_query:
return {'error': "No query provided, please provide a 'message' field in the payload."}
agent_result = invoke_agent(agent_query)
tools_used.update(list(agent_result.metrics.tool_metrics.keys()))
response_content = str(agent_result)
sources = []
urls = re.findall('(?:https?://|www\\.)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:/[^/\\s]*)*', response_content)
source_tags = re.findall(r"(.*?)", response_content)
sources.extend(urls)
sources.extend(source_tags)
sources = list(set(sources))
formatted_messages = [(agent_query, "USER"), (response_content if response_content else "No Response.", "ASSISTANT")]
return {'result': {'response': response_content, 'sources': sources, 'tools_used': list(tools_used), 'sessionId': session_id, 'messages': formatted_messages}}
except Exception as e:
return {'error': str(e)}
if __name__ == "__main__":
app.run()
AGENTEOF
- |
cat > Dockerfile << 'EOF'
FROM public.ecr.aws/docker/library/python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY dms_agent.py .
ENV AWS_REGION=$AWS_DEFAULT_REGION
ENV FOUNDATION_MODEL_ID=$FOUNDATION_MODEL_ID
ENV DMS_LAMBDA_ARN=$DMS_LAMBDA_ARN
EXPOSE 8080
CMD ["python", "dms_agent.py"]
EOF
- echo Building Docker image...
- 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 Pushing Docker image to ECR...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo Build completed successfully
AgentCoreBuildTrigger:
Type: Custom::CodeBuildTrigger
DependsOn: [AgentCoreImageBuildProject, AgentCoreCodeBuildTriggerFunction]
Properties:
ServiceToken: !GetAtt AgentCoreCodeBuildTriggerFunction.Arn
ProjectName: !Ref AgentCoreImageBuildProject
# AgentCore Runtime
DMSAgentRuntime:
Type: AWS::BedrockAgentCore::Runtime
DependsOn: AgentCoreBuildTrigger
Properties:
AgentRuntimeName: !Sub
- "${StackNameUnderscore}_${AgentCoreName}"
- StackNameUnderscore: !Join ["_", !Split ["-", !Ref "AWS::StackName"]]
AgentRuntimeArtifact:
ContainerConfiguration:
ContainerUri: !Sub "${AgentCoreECRRepository.RepositoryUri}:${ImageTag}"
RoleArn: !GetAtt AgentCoreExecutionRole.Arn
NetworkConfiguration:
NetworkMode: !Ref NetworkMode
VpcConfiguration: !If
- UsePrivateNetwork
- SecurityGroupIds:
- !Ref AgentCoreSecurityGroupId
SubnetIds: !Ref AgentCoreSubnetIds
- !Ref 'AWS::NoValue'
Description: !Sub "DMS Management Agent for ${AWS::StackName}"
EnvironmentVariables:
DMS_LAMBDA_ARN: !GetAtt DMSAgentLambda.Arn
AWS_REGION: !Ref AWS::Region
FOUNDATION_MODEL_ID: !If
- IsFullArn
- !Ref FoundationModelId
- !If
- IsInferenceProfile
- !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/${FoundationModelId}'
- !Sub 'arn:aws:bedrock:${AWS::Region}::foundation-model/${FoundationModelId}'
# Observability
CodeBuildLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/codebuild/${AWS::StackName}-${AgentCoreName}-build'
RetentionInDays: 14
KmsKeyId: !GetAtt EncryptionKey.Arn
DMSAgentCoreLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/vendedlogs/bedrock-agentcore/${DMSAgentRuntime.AgentRuntimeId}'
RetentionInDays: 14
KmsKeyId: !GetAtt EncryptionKey.Arn
DMSLogsDeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
Name: !Sub '${DMSAgentRuntime.AgentRuntimeId}-logs-source'
LogType: 'APPLICATION_LOGS'
ResourceArn: !GetAtt DMSAgentRuntime.AgentRuntimeArn
DependsOn: DMSAgentRuntime
DMSLogsDeliveryDestination:
Type: AWS::Logs::DeliveryDestination
Properties:
Name: !Sub '${DMSAgentRuntime.AgentRuntimeId}-logs-destination'
DeliveryDestinationType: 'CWL'
DestinationResourceArn: !GetAtt DMSAgentCoreLogGroup.Arn
DMSLogsDelivery:
Type: AWS::Logs::Delivery
Properties:
DeliverySourceName: !Ref DMSLogsDeliverySource
DeliveryDestinationArn: !GetAtt DMSLogsDeliveryDestination.Arn
DependsOn:
- DMSLogsDeliverySource
- DMSLogsDeliveryDestination
Outputs:
# DMS Stack Outputs
ReplicationInstanceArn:
Description: DMS Replication Instance ARN
Value: !If [CreateReplicationInstance, !Ref DMSReplicationInstance, !Ref ExistingReplicationInstanceArn]
Export:
Name: !Sub '${AWS::StackName}-ReplicationInstanceArn'
SourceEndpointArn:
Description: DMS Source Endpoint ARN
Value: !Ref DMSSourceEndpoint
Export:
Name: !Sub '${AWS::StackName}-SourceEndpointArn'
TargetEndpointArn:
Description: DMS Target Endpoint ARN (S3)
Value: !Ref DMSTargetEndpoint
Export:
Name: !Sub '${AWS::StackName}-TargetEndpointArn'
S3BucketName:
Description: S3 Bucket for DMS Target
Value: !Ref DMSS3Bucket
Export:
Name: !Sub '${AWS::StackName}-S3BucketName'
S3BucketArn:
Description: S3 Bucket ARN
Value: !GetAtt DMSS3Bucket.Arn
Export:
Name: !Sub '${AWS::StackName}-S3BucketArn'
S3ProcessorFunctionName:
Description: S3 Processor Lambda Function Name
Value: !Ref DMSS3ProcessorFunction
Export:
Name: !Sub '${AWS::StackName}-S3ProcessorFunctionName'
S3ProcessorFunctionArn:
Description: S3 Processor Lambda Function ARN
Value: !GetAtt DMSS3ProcessorFunction.Arn
Export:
Name: !Sub '${AWS::StackName}-S3ProcessorFunctionArn'
ReplicationTaskArn:
Description: DMS Replication Task ARN
Value: !Ref DMSReplicationTask
Export:
Name: !Sub '${AWS::StackName}-ReplicationTaskArn'
ReplicationTaskName:
Description: DMS Replication Task Name
Value: !Sub 'migration-task-${AWS::StackName}'
Export:
Name: !Sub '${AWS::StackName}-ReplicationTaskName'
# AgentCore Outputs
AgentRuntimeId:
Description: DMS AgentCore Runtime ID
Value: !GetAtt DMSAgentRuntime.AgentRuntimeId
Export:
Name: !Sub '${AWS::StackName}-AgentRuntimeId'
AgentRuntimeArn:
Description: DMS AgentCore Runtime ARN
Value: !GetAtt DMSAgentRuntime.AgentRuntimeArn
Export:
Name: !Sub '${AWS::StackName}-AgentRuntimeArn'
AgentCoreECRRepositoryUri:
Description: AgentCore ECR Repository URI
Value: !GetAtt AgentCoreECRRepository.RepositoryUri
Export:
Name: !Sub '${AWS::StackName}-AgentCoreECRRepositoryUri'
AgentCoreExecutionRoleArn:
Description: AgentCore Execution Role ARN
Value: !GetAtt AgentCoreExecutionRole.Arn
Export:
Name: !Sub '${AWS::StackName}-AgentCoreExecutionRoleArn'
DMSAgentLambdaFunctionArn:
Description: DMS Agent Lambda Function ARN
Value: !GetAtt DMSAgentLambda.Arn
Export:
Name: !Sub '${AWS::StackName}-DMSAgentLambdaArn'
DMSAgentLambdaFunctionName:
Description: DMS Agent Lambda Function Name
Value: !Ref DMSAgentLambda
Export:
Name: !Sub '${AWS::StackName}-DMSAgentLambdaName'
AgentCoreImageBuildProjectName:
Description: AgentCore CodeBuild Project Name
Value: !Ref AgentCoreImageBuildProject
Export:
Name: !Sub '${AWS::StackName}-AgentCoreCodeBuildProject'