Skip to content

Commit 9c00810

Browse files
set up serverless Aurora CDK database (#73)
<!-- Please complete the following sections as necessary. --> ### Description <!-- Summary of the changes, related issue, relevant motivation, and context --> This PR resolves [Configure Aurora Serverless v2 PostgreSQL](newjersey/innovation-platform-pm#464) by implementing Infrastructure as Code (IaC) for an AWS Aurora Serverless v2 PostgreSQL database. ### Approach <!-- Any changed dependencies, e.g. requires an install/update/migration, etc. --> **Using AWS Cloud Development Kit (CDK) and TypeScript:** - Referenced the existing dev VPC (will update to prod VPC once PR is approved and production deployment schedule is set) - Created supporting resources: subnet group, security group, and Aurora cluster - Exported VPC and security group for use in API stack (lambdas) - Updated `FeedbackApiStack` to accept shared `vpc` and `securityGroup` from `FeedbackDbStack` - Wrote tests to validate mocked VPC, creation of resources, and configuration settings - Used `cdk synth` to generate and validate CloudFormation output #### AWS Resource Documentation - [Work with the AWS CDK in TypeScript](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html#typescript-newproject) - [How to create a CDK app](https://docs.aws.amazon.com/cdk/v2/guide/apps.html) - [Define an AWS Virtual Private Cloud](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Vpc.html) - [Create an RDS DB subnet group](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.SubnetGroup.html) - [Create an Amazon EC2 security group within a VPC](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.SecurityGroup.html) - [Create a clustered database](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.DatabaseCluster.html) - [AWS CDK Test Assertions](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions-readme.html) ### Steps to Test <!-- If this work affects a user's experience, provide steps to test these changes in-app. --> 1. Run `npm install` to install dependencies. 2. Run `npm run synth:dev` to synthesize the CDK app into a dev CloudFormation template. 3. 2. Run `npm run deploy-db:dev` to deploy the dev database AWS. 4. Verify synthesized template includes VPC, subnet group, security group, and Aurora cluster. #### **To check deployment (in AWS Console):** 1. Search for/Go to CloudFormation. 2. Choose your AWS region (e.g., us-east-1) at the top-right. 3. Look for a stack named `FeedbackDbStack`. 4. Confirm its status is ✅ CREATE_COMPLETE. ### Notes <!-- Additional information, key learnings, and future development considerations. --> - [x] Deployment requires AWS account authorization due to costs; awaiting leadership approval - [ ] Once PR is approved, we can: - [x] deploy to the dev account - [ ] merge PR into dev
2 parents dd330a3 + 7be504e commit 9c00810

12 files changed

Lines changed: 419 additions & 32 deletions

infra/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Feedback Widget Infrastructure (AWS CDK - TypeScript)
2+
3+
This project defines the infrastructure for the **Feedback Widget** using [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html) with TypeScript. It provisions an **Aurora PostgreSQL Serverless v2** database (DB) along with its supporting resources.
4+
5+
## Resources
6+
7+
| Resource | Logical ID | Purpose |
8+
|-----------------------|----------------------|---------|
9+
| **VPC** | `FeedbackVpc` | Isolated network environment for the DB |
10+
| **Subnet Group** | `FeedbackSubnetGroup` | Specifies DB placement in private subnets |
11+
| **Aurora Cluster** | `FeedbackDBCluster` | Serverless v2 Aurora PostgreSQL DB |
12+
| **Outputs** | `FeedbackDBClusterEndpoint` | Cluster endpoint for local development or reference |
13+
14+
## CDK Setup & Commands
15+
16+
To get started or continue working:
17+
18+
```bash
19+
npm install # Install dependencies
20+
npm run diff # Preview infrastructure changes
21+
npm run deploy # Deploy to AWS
22+
npm run deploy-db:dev # Deploy the dev database to AWS
23+
npm run synth:dev # Synthesizes the CDK app into a dev CloudFormation template

infra/bin/feedback-api.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

infra/bin/feedback.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import { FeedbackApiStack } from '../lib/feedback-api-stack';
3+
import { FeedbackDbStack } from '../lib/feedback-db-stack';
4+
5+
const app = new cdk.App();
6+
7+
const env = {
8+
account: '152320432929',
9+
region: 'us-east-1'
10+
};
11+
12+
new FeedbackDbStack(app, 'FeedbackDbStack', {
13+
env
14+
});
15+
16+
new FeedbackApiStack(app, 'FeedbackApiStack', {});

infra/cdk.context.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"vpc-provider:account=152320432929:filter.vpc-id=vpc-06ea0349e255c4c59:region=us-east-1:returnAsymmetricSubnets=true": {
3+
"vpcId": "vpc-06ea0349e255c4c59",
4+
"vpcCidrBlock": "10.43.78.0/24",
5+
"ownerAccountId": "152320432929",
6+
"availabilityZones": [],
7+
"subnetGroups": [
8+
{
9+
"name": "Private",
10+
"type": "Private",
11+
"subnets": [
12+
{
13+
"subnetId": "subnet-0184fa96237d9873f",
14+
"cidr": "10.43.78.0/26",
15+
"availabilityZone": "us-east-1a",
16+
"routeTableId": "rtb-07b4c8c744c04d403"
17+
},
18+
{
19+
"subnetId": "subnet-02e553a6971550663",
20+
"cidr": "10.43.78.64/26",
21+
"availabilityZone": "us-east-1b",
22+
"routeTableId": "rtb-07b4c8c744c04d403"
23+
}
24+
]
25+
}
26+
]
27+
}
28+
}

infra/cdk.json

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"app": "npx tsx bin/feedback-api.ts",
2+
"app": "npx tsx bin/feedback.ts",
33
"watch": {
44
"include": ["**"],
55
"exclude": [
@@ -17,7 +17,7 @@
1717
"context": {
1818
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
1919
"@aws-cdk/core:checkSecretUsage": true,
20-
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
20+
"@aws-cdk/core:target-partitions": ["aws"],
2121
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
2222
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
2323
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
@@ -74,6 +74,20 @@
7474
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
7575
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
7676
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
77-
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true
77+
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
78+
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
79+
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
80+
"@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": true,
81+
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
82+
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
83+
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
84+
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
85+
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
86+
"@aws-cdk/core:aspectPrioritiesMutating": true,
87+
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
88+
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
89+
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
90+
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
91+
"@aws-cdk/aws-lambda:useCdkManagedLogGroup": true
7892
}
7993
}

infra/jest.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { Config } from 'jest';
2+
3+
const config: Config = {
4+
testEnvironment: 'node',
5+
roots: ['<rootDir>/test'],
6+
testMatch: ['**/*.test.ts'],
7+
transform: {
8+
'^.+\\.tsx?$': 'ts-jest'
9+
}
10+
};
11+
12+
export default config;

infra/lib/feedback-api-stack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as iam from 'aws-cdk-lib/aws-iam';
66
import * as apigw from 'aws-cdk-lib/aws-apigateway';
77

88
export class FeedbackApiStack extends cdk.Stack {
9-
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
9+
constructor(scope: Construct, id: string, props: cdk.StackProps) {
1010
super(scope, id, props);
1111

1212
const lambdaExecutionRole = new iam.Role(

infra/lib/feedback-db-stack.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import { Construct } from 'constructs';
3+
import { aws_ec2 as ec2, aws_rds as rds } from 'aws-cdk-lib';
4+
5+
interface FeedbackDbStackProps extends cdk.StackProps {
6+
vpc?: ec2.IVpc; // allows for mocking in tests
7+
}
8+
9+
export class FeedbackDbStack extends cdk.Stack {
10+
vpc: ec2.IVpc;
11+
12+
constructor(scope: Construct, id: string, props: FeedbackDbStackProps) {
13+
super(scope, id, props);
14+
15+
this.vpc =
16+
props.vpc ??
17+
ec2.Vpc.fromLookup(this, 'DevVpc', {
18+
vpcId: 'vpc-06ea0349e255c4c59'
19+
});
20+
21+
const subnetGroup = new rds.SubnetGroup(this, 'FeedbackSubnetGroup', {
22+
description: 'Aurora DB private subnet group',
23+
vpc: this.vpc,
24+
subnetGroupName: 'feedback-subnet-group'
25+
});
26+
27+
const cluster = new rds.DatabaseCluster(this, 'FeedbackDBCluster', {
28+
engine: rds.DatabaseClusterEngine.auroraPostgres({
29+
version: rds.AuroraPostgresEngineVersion.VER_17_4
30+
}),
31+
backup: {
32+
retention: cdk.Duration.days(7)
33+
},
34+
credentials: rds.Credentials.fromGeneratedSecret('postgres'),
35+
defaultDatabaseName: 'feedbackWidgetDb',
36+
serverlessV2MaxCapacity: 2,
37+
serverlessV2MinCapacity: 0.5,
38+
storageEncrypted: true,
39+
subnetGroup: subnetGroup,
40+
vpc: this.vpc,
41+
vpcSubnets: {
42+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
43+
},
44+
writer: rds.ClusterInstance.serverlessV2('writer')
45+
});
46+
47+
const proxy = cluster.addProxy('FeedbackProxy', {
48+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
49+
secrets: [cluster.secret!],
50+
vpc: this.vpc
51+
});
52+
53+
new cdk.CfnOutput(this, 'FeedbackDBProxyEndpoint', {
54+
value: proxy.endpoint,
55+
description: 'Proxy endpoint to connect to'
56+
});
57+
}
58+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
3+
import * as Cdk from '../lib/feedback-db-stack';
4+
import { Template } from 'aws-cdk-lib/assertions';
5+
6+
describe('FeedbackDBStack', () => {
7+
let app: cdk.App;
8+
let stack: cdk.Stack;
9+
let feedbackDbStack: Cdk.FeedbackDbStack;
10+
let template: Template;
11+
12+
beforeEach(() => {
13+
app = new cdk.App();
14+
15+
const fakeEnv = {
16+
account: '123456789012',
17+
region: 'us-east-1'
18+
};
19+
20+
stack = new cdk.Stack(app, 'TestStack', {
21+
env: fakeEnv
22+
});
23+
24+
const mockedVpc = ec2.Vpc.fromVpcAttributes(stack, 'MockedVpc', {
25+
vpcId: 'vpc-123456789',
26+
availabilityZones: ['us-east-1a', 'us-east-1b'],
27+
privateSubnetIds: ['subnet-1234', 'subnet-5678']
28+
});
29+
30+
feedbackDbStack = new Cdk.FeedbackDbStack(app, 'FeedbackDbStack', {
31+
vpc: mockedVpc,
32+
env: fakeEnv
33+
});
34+
35+
template = Template.fromStack(feedbackDbStack);
36+
});
37+
38+
it('creates a subnet group', () => {
39+
template.resourceCountIs('AWS::RDS::DBSubnetGroup', 1);
40+
template.hasResourceProperties('AWS::RDS::DBSubnetGroup', {
41+
DBSubnetGroupDescription: 'Aurora DB private subnet group'
42+
});
43+
});
44+
45+
it('creates an Aurora DB cluster', () => {
46+
template.hasResourceProperties('AWS::RDS::DBCluster', {
47+
Engine: 'aurora-postgresql',
48+
DatabaseName: 'feedbackWidgetDb',
49+
StorageEncrypted: true,
50+
ServerlessV2ScalingConfiguration: {
51+
MinCapacity: 0.5,
52+
MaxCapacity: 2
53+
}
54+
});
55+
});
56+
});

0 commit comments

Comments
 (0)