codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
580 lines (517 loc) • 16.7 kB
text/typescript
/**
* AWS Cloud Provider Implementation
* Production-ready AWS integration for enterprise deployment
*/
import * as AWS from '@aws-sdk/client-ec2';
import * as ECS from '@aws-sdk/client-ecs';
import * as EKS from '@aws-sdk/client-eks';
import * as CloudFormation from '@aws-sdk/client-cloudformation';
import * as AutoScaling from '@aws-sdk/client-auto-scaling';
import { logger } from '../../core/logger.js';
export interface AWSDeploymentConfig {
region: string;
accountId: string;
vpcId?: string;
subnetIds?: string[];
securityGroupIds?: string[];
eksClusterName?: string;
ecsClusterName?: string;
iamRoleArn?: string;
tags?: Record<string, string>;
}
export interface AWSInstance {
instanceId: string;
instanceType: string;
state: string;
publicIp?: string;
privateIp: string;
launchTime: Date;
tags: Record<string, string>;
}
export class AWSProvider {
private ec2Client: AWS.EC2Client;
private ecsClient: ECS.ECSClient;
private eksClient: EKS.EKSClient;
private cfClient: CloudFormation.CloudFormationClient;
private autoScalingClient: AutoScaling.AutoScalingClient;
private config: AWSDeploymentConfig;
constructor(config: AWSDeploymentConfig) {
this.config = config;
const awsConfig = {
region: config.region,
credentials: this.loadCredentials(),
};
this.ec2Client = new AWS.EC2Client(awsConfig);
this.ecsClient = new ECS.ECSClient(awsConfig);
this.eksClient = new EKS.EKSClient(awsConfig);
this.cfClient = new CloudFormation.CloudFormationClient(awsConfig);
this.autoScalingClient = new AutoScaling.AutoScalingClient(awsConfig);
}
/**
* Load AWS credentials from environment or IAM role
*/
private loadCredentials() {
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
return {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
sessionToken: process.env.AWS_SESSION_TOKEN,
};
}
// Will use IAM role if running on EC2/ECS/Lambda
return undefined;
}
/**
* Deploy application using CloudFormation
*/
async deployStack(
stackName: string,
templateBody: string,
parameters?: Record<string, string>
): Promise<string> {
try {
const params: CloudFormation.CreateStackInput = {
StackName: stackName,
TemplateBody: templateBody,
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
Parameters: Object.entries(parameters || {}).map(([key, value]) => ({
ParameterKey: key,
ParameterValue: value,
})),
Tags: Object.entries(this.config.tags || {}).map(([key, value]) => ({
Key: key,
Value: value,
})),
};
const command = new CloudFormation.CreateStackCommand(params);
const response = await this.cfClient.send(command);
logger.info(`CloudFormation stack creation initiated: ${response.StackId}`);
// Wait for stack creation to complete
await this.waitForStackComplete(stackName);
return response.StackId!;
} catch (error) {
logger.error('Failed to deploy CloudFormation stack:', error);
throw error;
}
}
/**
* Wait for CloudFormation stack to complete
*/
private async waitForStackComplete(stackName: string): Promise<void> {
const maxAttempts = 120; // 30 minutes with 15-second intervals
let attempts = 0;
while (attempts < maxAttempts) {
const command = new CloudFormation.DescribeStacksCommand({
StackName: stackName,
});
const response = await this.cfClient.send(command);
const stack = response.Stacks?.[0];
if (!stack) {
throw new Error(`Stack ${stackName} not found`);
}
const status = stack.StackStatus;
if (status?.includes('COMPLETE') && !status.includes('CLEANUP')) {
logger.info(`Stack ${stackName} completed with status: ${status}`);
return;
}
if (status?.includes('FAILED') || status?.includes('ROLLBACK')) {
throw new Error(`Stack ${stackName} failed with status: ${status}`);
}
await new Promise(resolve => setTimeout(resolve, 15000)); // Wait 15 seconds
attempts++;
}
throw new Error(`Stack ${stackName} creation timed out`);
}
/**
* Deploy to ECS Fargate
*/
async deployToECS(
serviceName: string,
taskDefinition: string,
desiredCount: number = 1
): Promise<string> {
try {
// Register task definition
const registerCommand = new ECS.RegisterTaskDefinitionCommand({
family: taskDefinition,
networkMode: 'awsvpc',
requiresCompatibilities: ['FARGATE'],
cpu: '256',
memory: '512',
containerDefinitions: [
{
name: serviceName,
image: `${this.config.accountId}.dkr.ecr.${this.config.region}.amazonaws.com/${serviceName}:latest`,
essential: true,
portMappings: [
{
containerPort: 3000,
protocol: 'tcp',
},
],
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-group': `/ecs/${serviceName}`,
'awslogs-region': this.config.region,
'awslogs-stream-prefix': 'ecs',
},
},
},
],
executionRoleArn: this.config.iamRoleArn,
taskRoleArn: this.config.iamRoleArn,
});
const taskDefResponse = await this.ecsClient.send(registerCommand);
// Create or update service
const serviceCommand = new ECS.CreateServiceCommand({
cluster: this.config.ecsClusterName,
serviceName: serviceName,
taskDefinition: taskDefResponse.taskDefinition?.taskDefinitionArn,
desiredCount: desiredCount,
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
subnets: this.config.subnetIds,
securityGroups: this.config.securityGroupIds,
assignPublicIp: 'ENABLED',
},
},
deploymentConfiguration: {
maximumPercent: 200,
minimumHealthyPercent: 100,
},
});
const serviceResponse = await this.ecsClient.send(serviceCommand);
logger.info(`ECS service deployed: ${serviceResponse.service?.serviceArn}`);
return serviceResponse.service?.serviceArn || '';
} catch (error: any) {
if (error.name === 'ResourceNotFoundException') {
logger.warn('ECS cluster not found, creating new cluster');
await this.createECSCluster();
return this.deployToECS(serviceName, taskDefinition, desiredCount);
}
throw error;
}
}
/**
* Create ECS cluster
*/
private async createECSCluster(): Promise<void> {
const command = new ECS.CreateClusterCommand({
clusterName: this.config.ecsClusterName,
capacityProviders: ['FARGATE', 'FARGATE_SPOT'],
defaultCapacityProviderStrategy: [
{
capacityProvider: 'FARGATE',
weight: 1,
base: 1,
},
{
capacityProvider: 'FARGATE_SPOT',
weight: 4,
},
],
settings: [
{
name: 'containerInsights',
value: 'enabled',
},
],
});
await this.ecsClient.send(command);
logger.info(`ECS cluster created: ${this.config.ecsClusterName}`);
}
/**
* Deploy to EKS Kubernetes cluster
*/
async deployToEKS(
deploymentName: string,
containerImage: string,
replicas: number = 3
): Promise<void> {
// This would use kubectl or Kubernetes API client
// For production, use @kubernetes/client-node
const k8sManifest = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: deploymentName,
namespace: 'default',
},
spec: {
replicas: replicas,
selector: {
matchLabels: {
app: deploymentName,
},
},
template: {
metadata: {
labels: {
app: deploymentName,
},
},
spec: {
containers: [
{
name: deploymentName,
image: containerImage,
ports: [
{
containerPort: 3000,
},
],
resources: {
requests: {
memory: '256Mi',
cpu: '250m',
},
limits: {
memory: '512Mi',
cpu: '500m',
},
},
},
],
},
},
},
};
logger.info(`Deploying to EKS: ${deploymentName}`);
// Would apply this manifest using Kubernetes client
}
/**
* Launch EC2 instances
*/
async launchInstances(
instanceType: string = 't3.medium',
count: number = 1,
userData?: string
): Promise<AWSInstance[]> {
try {
const command = new AWS.RunInstancesCommand({
ImageId: await this.getLatestAMI(),
InstanceType: instanceType as any,
MinCount: count,
MaxCount: count,
KeyName: process.env.AWS_KEY_PAIR_NAME,
SecurityGroupIds: this.config.securityGroupIds,
SubnetId: this.config.subnetIds?.[0],
UserData: userData ? Buffer.from(userData).toString('base64') : undefined,
TagSpecifications: [
{
ResourceType: 'instance',
Tags: Object.entries(this.config.tags || {}).map(([key, value]) => ({
Key: key,
Value: value,
})),
},
],
IamInstanceProfile: {
Arn: this.config.iamRoleArn,
},
});
const response = await this.ec2Client.send(command);
const instances: AWSInstance[] =
response.Instances?.map(instance => ({
instanceId: instance.InstanceId!,
instanceType: instance.InstanceType!,
state: instance.State?.Name || 'pending',
publicIp: instance.PublicIpAddress,
privateIp: instance.PrivateIpAddress!,
launchTime: instance.LaunchTime!,
tags: this.parseTags(instance.Tags),
})) || [];
logger.info(`Launched ${instances.length} EC2 instances`);
return instances;
} catch (error) {
logger.error('Failed to launch EC2 instances:', error);
throw error;
}
}
/**
* Get latest Amazon Linux 2 AMI
*/
private async getLatestAMI(): Promise<string> {
const command = new AWS.DescribeImagesCommand({
Owners: ['amazon'],
Filters: [
{
Name: 'name',
Values: ['amzn2-ami-hvm-*-x86_64-gp2'],
},
{
Name: 'state',
Values: ['available'],
},
],
});
const response = await this.ec2Client.send(command);
const images = response.Images || [];
// Sort by creation date and get the latest
images.sort(
(a, b) => new Date(b.CreationDate || 0).getTime() - new Date(a.CreationDate || 0).getTime()
);
return images[0]?.ImageId || 'ami-0c55b159cbfafe1f0'; // Fallback AMI
}
/**
* Create auto-scaling group
*/
async createAutoScalingGroup(
groupName: string,
minSize: number = 1,
maxSize: number = 10,
desiredCapacity: number = 2
): Promise<void> {
try {
// First create launch template
const launchTemplateCommand = new AWS.CreateLaunchTemplateCommand({
LaunchTemplateName: `${groupName}-template`,
LaunchTemplateData: {
ImageId: await this.getLatestAMI(),
InstanceType: 't3.medium',
SecurityGroupIds: this.config.securityGroupIds,
IamInstanceProfile: {
Arn: this.config.iamRoleArn,
},
UserData: Buffer.from(this.getUserDataScript()).toString('base64'),
},
});
const templateResponse = await this.ec2Client.send(launchTemplateCommand);
// Create auto-scaling group
const asgCommand = new AutoScaling.CreateAutoScalingGroupCommand({
AutoScalingGroupName: groupName,
LaunchTemplate: {
LaunchTemplateId: templateResponse.LaunchTemplate?.LaunchTemplateId,
Version: '$Latest',
},
MinSize: minSize,
MaxSize: maxSize,
DesiredCapacity: desiredCapacity,
VPCZoneIdentifier: this.config.subnetIds?.join(','),
HealthCheckType: 'ELB',
HealthCheckGracePeriod: 300,
Tags: Object.entries(this.config.tags || {}).map(([key, value]) => ({
Key: key,
Value: value,
PropagateAtLaunch: true,
})),
});
await this.autoScalingClient.send(asgCommand);
logger.info(`Auto-scaling group created: ${groupName}`);
} catch (error) {
logger.error('Failed to create auto-scaling group:', error);
throw error;
}
}
/**
* Get user data script for instance initialization
*/
private getUserDataScript(): string {
return `#!/bin/bash
# Update system
yum update -y
# Install Docker
amazon-linux-extras install docker -y
service docker start
usermod -a -G docker ec2-user
# Install Docker Compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# Install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
# Pull and run application
docker pull ${this.config.accountId}.dkr.ecr.${this.config.region}.amazonaws.com/codecrucible:latest
docker run -d -p 80:3000 --name codecrucible --restart=always \\
${this.config.accountId}.dkr.ecr.${this.config.region}.amazonaws.com/codecrucible:latest
`;
}
/**
* Terminate instances
*/
async terminateInstances(instanceIds: string[]): Promise<void> {
const command = new AWS.TerminateInstancesCommand({
InstanceIds: instanceIds,
});
await this.ec2Client.send(command);
logger.info(`Terminated instances: ${instanceIds.join(', ')}`);
}
/**
* Get instance status
*/
async getInstanceStatus(instanceIds: string[]): Promise<AWSInstance[]> {
const command = new AWS.DescribeInstancesCommand({
InstanceIds: instanceIds,
});
const response = await this.ec2Client.send(command);
const instances: AWSInstance[] = [];
for (const reservation of response.Reservations || []) {
for (const instance of reservation.Instances || []) {
instances.push({
instanceId: instance.InstanceId!,
instanceType: instance.InstanceType!,
state: instance.State?.Name || 'unknown',
publicIp: instance.PublicIpAddress,
privateIp: instance.PrivateIpAddress!,
launchTime: instance.LaunchTime!,
tags: this.parseTags(instance.Tags),
});
}
}
return instances;
}
/**
* Parse EC2 tags
*/
private parseTags(tags?: AWS.Tag[]): Record<string, string> {
const result: Record<string, string> = {};
for (const tag of tags || []) {
if (tag.Key && tag.Value) {
result[tag.Key] = tag.Value;
}
}
return result;
}
/**
* Create VPC and networking
*/
async createVPC(cidrBlock: string = '10.0.0.0/16'): Promise<string> {
const command = new AWS.CreateVpcCommand({
CidrBlock: cidrBlock,
TagSpecifications: [
{
ResourceType: 'vpc',
Tags: [
{
Key: 'Name',
Value: 'CodeCrucible-VPC',
},
...Object.entries(this.config.tags || {}).map(([key, value]) => ({
Key: key,
Value: value,
})),
],
},
],
});
const response = await this.ec2Client.send(command);
const vpcId = response.Vpc?.VpcId!;
logger.info(`Created VPC: ${vpcId}`);
return vpcId;
}
/**
* Scale service
*/
async scaleService(serviceName: string, desiredCount: number): Promise<void> {
const command = new ECS.UpdateServiceCommand({
cluster: this.config.ecsClusterName,
service: serviceName,
desiredCount: desiredCount,
});
await this.ecsClient.send(command);
logger.info(`Scaled service ${serviceName} to ${desiredCount} instances`);
}
}
export default AWSProvider;