in packages/blueprints/gen-ai-chatbot/static-assets/chatbot-genai-cdk/lib/api-publishment-stack.ts [41:214]
constructor(scope: Construct, id: string, props: ApiPublishmentStackProps) {
super(scope, id, props);
console.log(`usagePlan: ${JSON.stringify(props.usagePlan)}`); // DEBUG
const dbSecret = secretsmanager.Secret.fromSecretCompleteArn(
this,
"DbSecret",
props.dbConfigSecretArn
);
const deploymentStage = props.deploymentStage ?? "dev";
const vpc = ec2.Vpc.fromVpcAttributes(this, "Vpc", props.vpcConfig);
const chatQueue = new sqs.Queue(this, "ChatQueue", {
visibilityTimeout: cdk.Duration.minutes(30),
});
const handlerRole = new iam.Role(this, "HandlerRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
});
handlerRole.addToPolicy(
// Assume the table access role for row-level access control.
new iam.PolicyStatement({
actions: ["sts:AssumeRole"],
resources: [props.tableAccessRoleArn],
})
);
handlerRole.addToPolicy(
new iam.PolicyStatement({
actions: ["bedrock:*"],
resources: ["*"],
})
);
handlerRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaVPCAccessExecutionRole"
)
);
const largeMessageBucket = s3.Bucket.fromBucketName(
this,
"LargeMessageBucket",
props.largeMessageBucketName
);
largeMessageBucket.grantReadWrite(handlerRole);
// Handler for FastAPI
const apiHandler = new DockerImageFunction(this, "ApiHandler", {
code: DockerImageCode.fromImageAsset(
path.join(__dirname, "../../backend"),
{
platform: Platform.LINUX_AMD64,
file: "Dockerfile",
}
),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
memorySize: 1024,
timeout: cdk.Duration.minutes(15),
environment: {
PUBLISHED_API_ID: id.replace("ApiPublishmentStack", ""),
QUEUE_URL: chatQueue.queueUrl,
TABLE_NAME: props.conversationTableName,
CORS_ALLOW_ORIGINS: (props.corsOptions?.allowOrigins ?? ["*"]).join(
","
),
ACCOUNT: Stack.of(this).account,
REGION: Stack.of(this).region,
BEDROCK_REGION: props.bedrockRegion,
LARGE_MESSAGE_BUCKET: props.largeMessageBucketName,
TABLE_ACCESS_ROLE_ARN: props.tableAccessRoleArn,
DB_SECRETS_ARN: props.dbConfigSecretArn,
},
role: handlerRole,
});
// Handler for SQS consumer
const sqsConsumeHandler = new DockerImageFunction(
this,
"SqsConsumeHandler",
{
code: DockerImageCode.fromImageAsset(
path.join(__dirname, "../../backend"),
{
platform: Platform.LINUX_AMD64,
file: "websocket.Dockerfile",
cmd: ["app.sqs_consumer.handler"],
}
),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
memorySize: 1024,
timeout: cdk.Duration.minutes(15),
environment: {
PUBLISHED_API_ID: id.replace("ApiPublishmentStack", ""),
QUEUE_URL: chatQueue.queueUrl,
TABLE_NAME: props.conversationTableName,
CORS_ALLOW_ORIGINS: (props.corsOptions?.allowOrigins ?? ["*"]).join(
","
),
ACCOUNT: Stack.of(this).account,
REGION: Stack.of(this).region,
BEDROCK_REGION: props.bedrockRegion,
TABLE_ACCESS_ROLE_ARN: props.tableAccessRoleArn,
DB_SECRETS_ARN: props.dbConfigSecretArn,
},
role: handlerRole,
}
);
sqsConsumeHandler.addEventSource(
new lambdaEventSources.SqsEventSource(chatQueue)
);
chatQueue.grantSendMessages(apiHandler);
chatQueue.grantConsumeMessages(sqsConsumeHandler);
// Allow the handler to access the pgvector.
const dbSg = ec2.SecurityGroup.fromSecurityGroupId(
this,
"DbSecurityGroup",
props.dbSecurityGroupId
);
dbSg.connections.allowFrom(
sqsConsumeHandler,
ec2.Port.tcp(props.dbConfigPort)
);
const api = new apigateway.LambdaRestApi(this, "Api", {
restApiName: id,
handler: apiHandler,
proxy: true,
deployOptions: {
stageName: deploymentStage,
},
defaultMethodOptions: { apiKeyRequired: true },
defaultCorsPreflightOptions: props.corsOptions,
});
const apiKey = api.addApiKey("ApiKey", {
description: "Default api key (Auto generated by CDK)",
});
const usagePlan = api.addUsagePlan("UsagePlan", {
...props.usagePlan,
});
usagePlan.addApiKey(apiKey);
usagePlan.addApiStage({ stage: api.deploymentStage });
const association = new wafv2.CfnWebACLAssociation(
this,
"WebAclAssociation",
{
resourceArn: `arn:aws:apigateway:${this.region}::/restapis/${api.restApiId}/stages/${api.deploymentStage.stageName}`,
webAclArn: props.webAclArn,
}
);
association.addDependency(api.node.defaultChild as cdk.CfnResource);
this.chatQueue = chatQueue;
new CfnOutput(this, "ApiId", {
value: api.restApiId,
});
new CfnOutput(this, "ApiName", {
value: api.restApiName,
});
new CfnOutput(this, "ApiUsagePlanId", {
value: usagePlan.usagePlanId,
});
new CfnOutput(this, "AllowedOrigins", {
value: props.corsOptions?.allowOrigins?.join(",") ?? "*",
});
new CfnOutput(this, "DeploymentStage", {
value: deploymentStage,
});
}