1import * as EC2 from "@/AWS/EC2";
2import * as RDS from "@/AWS/RDS";
3import * as SecretsManager from "@/AWS/SecretsManager";
4import * as Effect from "effect/Effect";
5
6/**
7 * Shared Aurora fixture infrastructure for the RDSData bindings Lambda and
8 * the Drizzle-over-IAM Lambda. Both fixtures yield this effect; resource
9 * declarations dedupe by logical ID so the stack provisions one cluster.
10 *
11 * Topology: an isolated VPC (no gateways — the Data API is HTTP and the
12 * IAM-auth Lambda only opens an in-VPC socket), two subnets in distinct AZs
13 * for the DB subnet group, an ingress-free security group for the Lambda,
14 * and a DB security group that admits Postgres traffic from the Lambda
15 * security group only.
16 */
17export const RDSDataInfra = Effect.gen(function* () {
18 const vpc = yield* EC2.Vpc("Vpc", {
19 cidrBlock: "10.61.0.0/16",
20 });
21 const subnetA = yield* EC2.Subnet("SubnetA", {
22 vpcId: vpc.vpcId,
23 cidrBlock: "10.61.0.0/24",
24 availabilityZone: "us-west-2a",
25 });
26 const subnetB = yield* EC2.Subnet("SubnetB", {
27 vpcId: vpc.vpcId,
28 cidrBlock: "10.61.1.0/24",
29 availabilityZone: "us-west-2b",
30 });
31
32 // Security group for the VPC-attached Drizzle Lambda. No ingress — it only
33 // dials out to the cluster.
34 const lambdaSecurityGroup = yield* EC2.SecurityGroup("LambdaSecurityGroup", {
35 vpcId: vpc.vpcId,
36 description: "VPC-attached Lambda for the Aurora IAM-auth socket path",
37 });
38
39 const securityGroup = yield* EC2.SecurityGroup("DbSecurityGroup", {
40 vpcId: vpc.vpcId,
41 description: "Aurora Data API test cluster (Postgres from the Lambda SG)",
42 ingress: [
43 {
44 ipProtocol: "tcp",
45 fromPort: 5432,
46 toPort: 5432,
47 referencedGroupId: lambdaSecurityGroup.groupId,
48 description: "Postgres from the Drizzle IAM-auth Lambda",
49 },
50 ],
51 });
52
53 const subnetGroup = yield* RDS.DBSubnetGroup("SubnetGroup", {
54 description: "RDSData bindings test cluster",
55 subnetIds: [subnetA.subnetId, subnetB.subnetId],
56 });
57
58 // The Data API authenticates with a Secrets Manager secret whose JSON
59 // payload carries `username` + `password`; the cluster reads the same
60 // secret for its master credentials (`masterUserSecretArn`).
61 const secret = yield* SecretsManager.Secret("Secret", {
62 description: "Credentials for the RDSData bindings test cluster",
63 generateSecretString: {
64 secretStringTemplate: JSON.stringify({ username: "app" }),
65 generateStringKey: "password",
66 PasswordLength: 32,
67 ExcludeCharacters: "\"'@/\\",
68 },
69 });
70
71 const cluster = yield* RDS.DBCluster("Cluster", {
72 engine: "aurora-postgresql",
73 engineMode: "provisioned",
74 databaseName: "app",
75 dbSubnetGroupName: subnetGroup.dbSubnetGroupName,
76 vpcSecurityGroupIds: [securityGroup.groupId],
77 enableHttpEndpoint: true,
78 // Exercised by the RDS.Connect IAM-auth path (rds-db:connect + presigned
79 // token as the password).
80 enableIAMDatabaseAuthentication: true,
81 serverlessV2ScalingConfiguration: {
82 MinCapacity: 0.5,
83 MaxCapacity: 1,
84 },
85 masterUserSecretArn: secret.secretArn,
86 });
87
88 // Cluster members inherit the subnet group and security groups from the
89 // cluster — RDS rejects `CreateDBInstance` with VpcSecurityGroupIds for a
90 // cluster member ("Set vpc security group for the DB Cluster").
91 yield* RDS.DBInstance("Writer", {
92 dbClusterIdentifier: cluster.dbClusterIdentifier,
93 dbInstanceClass: "db.serverless",
94 engine: "aurora-postgresql",
95 });
96
97 return {
98 vpc,
99 subnetA,
100 subnetB,
101 lambdaSecurityGroup,
102 securityGroup,
103 subnetGroup,
104 secret,
105 cluster,
106 };
107});