import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import { EnvName, prefix } from "./config"; import { AppService } from "./constructs/app-service"; import { ConfigStore } from "./constructs/config-store"; import { InstanceRole } from "./constructs/instance-role"; import { AL2023_ARM64_SSM_CONTEXT_KEY, cachedArm64AmazonLinux2023, } from "./constructs/ami-cache"; export interface OpenSweStackProps extends cdk.StackProps { /** open-swe environment — drives the `open-swe--*` resource naming. */ readonly envName: EnvName; } /** * Per-env open-swe stack (`open-swe-dev` / `open-swe-prod`). Resource names are * prefixed `open-swe--*`. * * Composes: the per-env least-privilege instance role (T6), the Secrets/SSM * config store (T11), and the compute + ingress wiring (T12, AppService — EC2 * box, instance SG, target group, imported-listener rules, Route53 aliases, * 30-day log groups). The shared VPC and ALB are imported, never owned. Synth is * offline (AMI is the cdk.context.json-pinned placeholder until T12-deploy). */ export class OpenSweStack extends cdk.Stack { public readonly instanceRole: InstanceRole; public readonly configStore: ConfigStore; public readonly appService: AppService; constructor(scope: Construct, id: string, props: OpenSweStackProps) { super(scope, id, props); const envName = props.envName; const p = prefix(envName); cdk.Tags.of(this).add("project", "open-swe"); cdk.Tags.of(this).add("env", envName); cdk.Tags.of(this).add("ManagedBy", "cdk"); // Per-env least-privilege EC2 instance role (open-swe--instance-role). this.instanceRole = new InstanceRole(this, "Instance", envName); // Secrets Manager + SSM Parameter Store shells the boot hook reads // (deploy/seahaven/fetch-config.sh). Secret shells are value-less and // populated out-of-band; IaC-managed SSM params carry real derivable values. // The instance role already grants read on open-swe-/* + /open-swe-/*. this.configStore = new ConfigStore(this, "Config", { envName }); // AMI cache discipline (see lib/constructs/ami-cache.ts). T3 is synth-only: // resolve + surface the pinned AMI id ONLY when it is already cached in // cdk.context.json, so synth never makes a live SSM call. T12 consumes // `cachedArm64AmazonLinux2023()` for the actual ec2.Instance. if (this.node.tryGetContext(AL2023_ARM64_SSM_CONTEXT_KEY) !== undefined) { const amiId = cachedArm64AmazonLinux2023().getImage(this).imageId; new cdk.CfnOutput(this, "PinnedAmiId", { value: amiId, description: "Cached AL2023 ARM64 AMI id (pinned in cdk.context.json; consumed by the T12 EC2 instance).", }); } // T12: compute + ingress. Imports the shared seahaven-vpc + ALB and adds the // env's EC2 box, instance SG, target group, listener rules, DNS, log groups. this.appService = new AppService(this, "App", { envName, instanceRole: this.instanceRole.role, }); new cdk.CfnOutput(this, "InstanceRoleArn", { value: this.instanceRole.role.roleArn, description: `${p} EC2 instance role ARN.`, }); new cdk.CfnOutput(this, "InstanceId", { value: this.appService.instance.instanceId, description: `${p} EC2 instance id.`, }); new cdk.CfnOutput(this, "TargetGroupArn", { value: this.appService.targetGroup.targetGroupArn, description: `${p} ALB target group ARN (→ instance:80 nginx).`, }); } }