1- import { Duration } from 'aws-cdk-lib' ;
1+ import { Duration , Stack , aws_ec2 } from 'aws-cdk-lib' ;
22import { PolicyStatement } from 'aws-cdk-lib/aws-iam' ;
33import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' ;
44import { Runtime } from 'aws-cdk-lib/aws-lambda' ;
@@ -19,6 +19,11 @@ export interface RedisQueueDepthMetricPublisherProps {
1919 * @default 'RedisQueueDepth'
2020 */
2121 readonly cwNamespace ?: string ;
22+ /**
23+ * The CloudWatch service to publish metrics to.
24+ * @default 'RedisQueueDepthMetricPublisher'
25+ */
26+ readonly cwService ?: string ;
2227 /**
2328 * Time intervals that Lambda will be triggered to publish metric in CloudWatch.
2429 * @default Duration.minutes(1)
@@ -54,7 +59,7 @@ export interface RedisQueueDepthMetricPublisherProps {
5459 * You can override these paths using {@link redisSecretPasswordPath}, and
5560 * {@link redisSecretUsernamePath} respectively.
5661 */
57- readonly redisSecretArn : string ;
62+ readonly redisSecretArn ? : string ;
5863 /**
5964 * In the best possible world, we would be using ABAC to allow decryption of SecretsManager payload.
6065 * If that is an option, leave this undefined.
@@ -68,13 +73,26 @@ export interface RedisQueueDepthMetricPublisherProps {
6873 *
6974 * @default 'password'
7075 */
71- readonly redisSecretPasswordPath : string ;
76+ readonly redisSecretPasswordPath ? : string ;
7277 /**
7378 * Override the JSON path for the username in the {@link redisSecretArn}
7479 *
7580 * @default 'username'
7681 */
77- readonly redisSecretUsernamePath : string ;
82+ readonly redisSecretUsernamePath ?: string ;
83+ /**
84+ * The regions to publish metrics to/observe metrics from.
85+ * @default ['us-west-2']
86+ */
87+ readonly regions ?: string [ ] ;
88+ /**
89+ * The VPC to run the Lambda in.
90+ */
91+ readonly vpc ?: any ;
92+ /**
93+ * The SecurityGroupId to grant the lambda to access redis clusters
94+ */
95+ readonly securityGroupId ?: string ;
7896}
7997
8098/**
@@ -86,6 +104,7 @@ export class RedisQueueDepthMetricPublisher extends Construct {
86104 readonly handler : NodejsFunction ;
87105 readonly rule : Rule ;
88106 readonly cwNamespace : string ;
107+ readonly cwService : string ;
89108
90109 /**
91110 * Creates a new instance of RedisQueueDepthMetricPublisher.
@@ -96,8 +115,15 @@ export class RedisQueueDepthMetricPublisher extends Construct {
96115
97116 constructor ( scope : Construct , id : Namer , props : RedisQueueDepthMetricPublisherProps ) {
98117 super ( scope , id . pascal ) ;
118+
119+ if ( ! props . redisSecretArn && ( ! props . vpc || ! props . securityGroupId ) ) {
120+ throw new Error ( 'Either a secretArn or a vpc/securityGroupId must be provided' ) ;
121+ }
122+
99123 this . publishFrequency = props . publishFrequency ?? Duration . minutes ( 1 ) ;
100124 this . cwNamespace = props . cwNamespace ?? 'RedisQueueDepth' ;
125+ this . cwService = props . cwService ?? 'RedisQueueDepthMetricPublisher' ;
126+ this . regions = props . regions ?? [ 'us-west-2' ] ;
101127 const myConstruct = this ;
102128
103129 // Note the name implies where we fetch the code from
@@ -108,16 +134,12 @@ export class RedisQueueDepthMetricPublisher extends Construct {
108134 } ,
109135 logRetention : props . cloudwatchLogsRetention ?? RetentionDays . THREE_MONTHS ,
110136 memorySize : 512 ,
111- runtime : Runtime . NODEJS_LATEST , // Should be at least node20, but let's be aggressive about this.
137+ runtime : Runtime . NODEJS_18_X , // Should be at least node20, but let's be aggressive about this.
112138 timeout : Duration . seconds ( 45 ) ,
139+ ...( props . vpc ? { vpc : props . vpc } : { } ) ,
113140 } ) ;
114141
115- [
116- new PolicyStatement ( {
117- sid : 'fetchRedisSecret' ,
118- actions : [ 'secretsmanager:GetSecretValue' ] ,
119- resources : [ props . redisSecretArn ] ,
120- } ) ,
142+ const policies = [
121143 new PolicyStatement ( {
122144 sid : 'putRedisQueueDepth' ,
123145 actions : [ 'cloudwatch:PutMetricData' ] ,
@@ -128,29 +150,53 @@ export class RedisQueueDepthMetricPublisher extends Construct {
128150 } ,
129151 } ,
130152 } ) ,
131- ] . forEach ( ( policy ) => this . handler . addToRolePolicy ( policy ) ) ;
153+ ] ;
132154
155+ if ( props . redisSecretArn ) {
156+ policies . push (
157+ new PolicyStatement ( {
158+ sid : 'fetchRedisSecret' ,
159+ actions : [ 'secretsmanager:GetSecretValue' ] ,
160+ resources : [ props . redisSecretArn ] ,
161+ } ) ,
162+ ) ;
163+ }
133164 if ( props . redisSecretKeyArn ) {
134- this . handler . addToRolePolicy (
165+ policies . push (
135166 new PolicyStatement ( {
136167 sid : 'decryptRedisSecret' ,
137168 actions : [ 'kms:Decrypt' ] ,
138169 resources : [ props . redisSecretKeyArn ] ,
139170 } ) ,
140171 ) ;
141172 }
173+ policies . forEach ( ( policy ) => this . handler . addToRolePolicy ( policy ) ) ;
142174
143175 this . handler
144176 . addEnvironment ( 'QUEUE_NAMES' , JSON . stringify ( props . queueNames ) )
145177 . addEnvironment ( 'REDIS_ADDR' , props . redisAddr )
146178 . addEnvironment ( 'REDIS_PORT' , props . redisPort ?? '6379' )
147- . addEnvironment ( 'REDIS_SECRET_ARN' , props . redisSecretArn )
179+ . addEnvironment ( 'REDIS_SECRET_ARN' , props . redisSecretArn ! || '' )
148180 . addEnvironment ( 'REDIS_SECRET_PASSWORD_PATH' , props . redisSecretPasswordPath ?? 'password' )
149- . addEnvironment ( 'REDIS_SECRET_USERNAME_PATH' , props . redisSecretUsernamePath ?? 'username' ) ;
181+ . addEnvironment ( 'REDIS_SECRET_USERNAME_PATH' , props . redisSecretUsernamePath ?? 'username' )
182+ . addEnvironment ( 'CW_SERVICE' , props . cwService ?? this . cwService )
183+ . addEnvironment ( 'CW_NAMESPACE' , props . cwNamespace ?? this . cwNamespace ) ;
184+
185+ if ( props . securityGroupId ) {
186+ const securityGroup = aws_ec2 . SecurityGroup . fromSecurityGroupId ( this , 'securityGroup' , props . securityGroupId ) ;
187+ this . handler . connections . allowTo ( securityGroup , aws_ec2 . Port . tcp ( 6379 ) ) ;
188+ }
150189
151190 this . rule = new Rule ( this , 'rule' , {
152191 schedule : Schedule . rate ( this . publishFrequency ) ,
153192 } ) ;
154193 this . rule . addTarget ( new LambdaFunction ( this . handler ) ) ;
155194 }
156195}
196+
197+ export class RedisQueueDepthMetricPublisherStack extends Stack {
198+ constructor ( scope : Construct , id : string , props : RedisQueueDepthMetricPublisherProps ) {
199+ super ( scope , id ) ;
200+ new RedisQueueDepthMetricPublisher ( this , new Namer ( [ id , 'lambda' ] ) , props ) ;
201+ }
202+ }
0 commit comments