UNPKG

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
/** * 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;