import { Construct } from 'constructs'; import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecr from 'aws-cdk-lib/aws-ecr'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as path from 'path'; export interface SocketModeProps { table: dynamodb.ITable; } export class SocketModeConstruct extends Construct { constructor(scope: Construct, id: string, props: SocketModeProps) { super(scope, id); const account = cdk.Stack.of(this).account; const region = cdk.Stack.of(this).region; // ── VPC ──────────────────────────────────────────────────── const vpc = new ec2.Vpc(this, 'Vpc', { vpcName: 'exec-aide', ipAddresses: ec2.IpAddresses.cidr('10.30.0.0/16'), maxAzs: 1, natGateways: 0, subnetConfiguration: [ { cidrMask: 24, name: 'exec-aide-listener', subnetType: ec2.SubnetType.PUBLIC, }, ], }); const sg = new ec2.SecurityGroup(this, 'ListenerSG', { vpc, description: 'exec-aide listener - outbound only', allowAllOutbound: true, }); // ── ECR ──────────────────────────────────────────────────── new ecr.Repository(this, 'ListenerRepo', { repositoryName: 'exec-aide-listener', imageScanOnPush: true, lifecycleRules: [ { maxImageCount: 5, description: 'Keep last 5 images', }, ], removalPolicy: cdk.RemovalPolicy.RETAIN, }); // ── ECS Cluster + Task Definition ────────────────────────── const cluster = new ecs.Cluster(this, 'Cluster', { clusterName: 'exec-aide', vpc, }); const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', { family: 'exec-aide-listener', cpu: 256, memoryLimitMiB: 512, runtimePlatform: { cpuArchitecture: ecs.CpuArchitecture.ARM64, operatingSystemFamily: ecs.OperatingSystemFamily.LINUX, }, }); taskDef.addContainer('listener', { image: ecs.ContainerImage.fromAsset( path.join(__dirname, '../../listener'), ), essential: true, environment: { TABLE_NAME: props.table.tableName, SECRET_SLACK: 'exec-aide/slack-credentials', SSM_PREFIX: '/exec-aide', }, logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'listener', logGroup: new logs.LogGroup(this, 'ListenerLogGroup', { logGroupName: '/ecs/exec-aide-listener', retention: logs.RetentionDays.TWO_MONTHS, }), }), }); // ── Task role IAM ────────────────────────────────────────── props.table.grantReadWriteData(taskDef.taskRole); taskDef.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['secretsmanager:GetSecretValue'], resources: [ `arn:aws:secretsmanager:${region}:${account}:secret:exec-aide/slack-credentials-*`, ], })); taskDef.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['ssm:GetParametersByPath', 'ssm:GetParameter'], resources: [ `arn:aws:ssm:${region}:${account}:parameter/exec-aide`, `arn:aws:ssm:${region}:${account}:parameter/exec-aide/*`, ], })); // ── Fargate Service ──────────────────────────────────────── new ecs.FargateService(this, 'Service', { serviceName: 'exec-aide-listener', cluster, taskDefinition: taskDef, desiredCount: 1, assignPublicIp: true, securityGroups: [sg], vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); } }