1+ import * as cdk from 'aws-cdk-lib' ;
2+ import { Construct } from 'constructs' ;
3+ import * as ec2 from 'aws-cdk-lib/aws-ec2' ;
4+ import * as rds from 'aws-cdk-lib/aws-rds' ;
5+ import * as lambda from 'aws-cdk-lib/aws-lambda' ;
6+ import * as iam from 'aws-cdk-lib/aws-iam' ;
7+ import * as cr from 'aws-cdk-lib/custom-resources' ;
8+ import * as path from 'path' ;
9+
10+ export class PostgresLambdaStack extends cdk . Stack {
11+ constructor ( scope : Construct , id : string , props ?: cdk . StackProps ) {
12+ super ( scope , id , props ) ;
13+
14+ // Create a VPC for our application
15+ const vpc = new ec2 . Vpc ( this , 'PostgresLambdaVpc' , {
16+ maxAzs : 2 ,
17+ natGateways : 1 ,
18+ } ) ;
19+
20+ // Create a PostgreSQL Aurora Serverless v2 cluster
21+ const dbCluster = new rds . DatabaseCluster ( this , 'PostgresCluster' , {
22+ engine : rds . DatabaseClusterEngine . auroraPostgres ( {
23+ version : rds . AuroraPostgresEngineVersion . VER_17_4 ,
24+ } ) ,
25+ vpc : vpc ,
26+ writer : rds . ClusterInstance . serverlessV2 ( 'writer' ) ,
27+ serverlessV2MinCapacity : 0.5 ,
28+ serverlessV2MaxCapacity : 1 ,
29+ defaultDatabaseName : 'demodb' ,
30+ credentials : rds . Credentials . fromGeneratedSecret ( 'postgres' ) ,
31+ } ) ;
32+
33+ // Create a Lambda function that calls PostgreSQL
34+ const lambdaToPostgres = new lambda . Function ( this , 'LambdaToPostgres' , {
35+ runtime : lambda . Runtime . NODEJS_LATEST ,
36+ handler : 'index.handler' ,
37+ code : lambda . Code . fromAsset ( path . join ( __dirname , '../lambda/lambda-to-postgres' ) ) ,
38+ vpc,
39+ vpcSubnets : {
40+ subnetType : ec2 . SubnetType . PRIVATE_WITH_EGRESS ,
41+ } ,
42+ environment : {
43+ DB_SECRET_ARN : dbCluster . secret ?. secretArn || '' ,
44+ DB_NAME : 'demodb' ,
45+ } ,
46+ timeout : cdk . Duration . seconds ( 30 ) ,
47+ } ) ;
48+
49+ // Grant Lambda access to the DB
50+ dbCluster . connections . allowDefaultPortTo ( lambdaToPostgres ) ;
51+
52+ // Grant the Lambda function permission to read the database secret
53+ dbCluster . secret ?. grantRead ( lambdaToPostgres ) ;
54+
55+ // Create a Lambda function that is called by PostgreSQL
56+ const postgresFunction = new lambda . Function ( this , 'PostgresFunction' , {
57+ runtime : lambda . Runtime . NODEJS_LATEST ,
58+ handler : 'index.handler' ,
59+ code : lambda . Code . fromAsset ( path . join ( __dirname , '../lambda/postgres-to-lambda' ) ) ,
60+ environment : {
61+ FUNCTION_NAME : 'PostgresFunction' ,
62+ } ,
63+ timeout : cdk . Duration . seconds ( 30 ) ,
64+ } ) ;
65+
66+
67+ // Create a role for PostgreSQL to assume to invoke Lambda
68+ const postgresLambdaRole = new iam . Role ( this , 'PostgresLambdaRole' , {
69+ assumedBy : new iam . ServicePrincipal ( 'rds.amazonaws.com' ) ,
70+ } ) ;
71+
72+ postgresFunction . grantInvoke ( postgresLambdaRole ) ;
73+
74+
75+ const l1DbCluster = dbCluster . node . defaultChild as rds . CfnDBCluster
76+ const exisitingProperty = ( l1DbCluster . associatedRoles as [ ] ) || [ ] ;
77+ console . log ( exisitingProperty ) ;
78+
79+ const newRole : { FeatureName : string , RoleArn : string } = {
80+ FeatureName : "Lambda" , // Changed to PascalCase
81+ RoleArn : postgresLambdaRole . roleArn // Changed to PascalCase
82+ } ;
83+
84+ const updatedRoles : { [ key in 'featureName' | 'FeatureName' | 'roleArn' | 'RoleArn' ] ?: string ; } [ ] = [ ...exisitingProperty , newRole ] ;
85+
86+ l1DbCluster . addPropertyOverride ( 'AssociatedRoles' , updatedRoles ) ;
87+
88+ // Create Lambda function for PostgreSQL setup
89+ const setupFunction = new lambda . Function ( this , 'PostgresSetupFunction' , {
90+ runtime : lambda . Runtime . NODEJS_LATEST ,
91+ handler : 'index.handler' ,
92+ code : lambda . Code . fromAsset ( path . join ( __dirname , '../lambda/postgres-setup' ) ) ,
93+ vpc,
94+ vpcSubnets : {
95+ subnetType : ec2 . SubnetType . PRIVATE_WITH_EGRESS ,
96+ } ,
97+ environment : {
98+ DB_SECRET_ARN : dbCluster . secret ?. secretArn || '' ,
99+ DB_NAME : 'demodb' ,
100+ POSTGRES_FUNCTION_NAME : postgresFunction . functionName ,
101+ AWS_REGION : this . region ,
102+ } ,
103+ timeout : cdk . Duration . minutes ( 5 ) ,
104+ } ) ;
105+
106+ // Grant setup function access to the DB and secrets
107+ dbCluster . connections . allowDefaultPortTo ( setupFunction ) ;
108+ dbCluster . secret ?. grantRead ( setupFunction ) ;
109+
110+ // Create custom resource to trigger setup
111+ const setupProvider = new cr . Provider ( this , 'PostgresSetupProvider' , {
112+ onEventHandler : setupFunction ,
113+ } ) ;
114+
115+ new cdk . CustomResource ( this , 'PostgresSetupResource' , {
116+ serviceToken : setupProvider . serviceToken ,
117+ } ) ;
118+
119+ // Output the database endpoint and secret ARN
120+ new cdk . CfnOutput ( this , 'DBClusterEndpoint' , {
121+ value : dbCluster . clusterEndpoint . hostname ,
122+ description : 'The endpoint of the database cluster' ,
123+ } ) ;
124+
125+ new cdk . CfnOutput ( this , 'DBSecretArn' , {
126+ value : dbCluster . secret ?. secretArn || 'No secret created' ,
127+ description : 'The ARN of the database credentials secret' ,
128+ } ) ;
129+
130+ new cdk . CfnOutput ( this , 'LambdaToPostgresFunctionName' , {
131+ value : lambdaToPostgres . functionName ,
132+ description : 'The name of the Lambda function that calls PostgreSQL' ,
133+ } ) ;
134+
135+ new cdk . CfnOutput ( this , 'PostgresFunctionName' , {
136+ value : postgresFunction . functionName ,
137+ description : 'The name of the Lambda function that is called by PostgreSQL' ,
138+ } ) ;
139+
140+ new cdk . CfnOutput ( this , 'PostgresLambdaRoleArn' , {
141+ value : postgresLambdaRole . roleArn ,
142+ description : 'The ARN of the role that PostgreSQL can assume to invoke Lambda' ,
143+ } ) ;
144+ }
145+ }
0 commit comments