Repository: cdklabs/cdk-monitoring-constructs (language: python)

Issue:
Lambda handlers should be upgraded to Node.js 18
### Version

v5.2.3

### Steps and/or minimal code example to reproduce

1. Use bitmap widgets or secrets monitoring

### Expected behavior

Have Lambda functions deployed that aren't using an EOL version of Node.js.

### Actual behavior

Functions are using Node.js 14.

### Other details

Relevant functions:
- https://github.com/cdklabs/cdk-monitoring-constructs/blob/81f0c6ba0211bca586c9b994ec7aa037b2cd6e3c/lib/dashboard/widget/BitmapWidget.ts#L37
- https://github.com/cdklabs/cdk-monitoring-constructs/blob/81f0c6ba0211bca586c9b994ec7aa037b2cd6e3c/lib/monitoring/aws-secretsmanager/SecretsManagerMetricsPublisher.ts#L37

The actual handlers need to be updated to use aws-sdk v3 and/or bundled code.


Discussion / hints:
> We are ending support for Node.js 14 in AWS Lambda. This follows Node.js 14 End-Of-Life (EOL) reached on April 30, 2023 [1].
>
> As described in the Lambda runtime support policy [2], end of support for language runtimes in Lambda happens in two stages. Starting November 27, 2023, Lambda will no longer apply security patches and other updates to the Node.js 14 runtime used by Lambda functions, and functions using Node.js 14 will no longer be eligible for technical support. In addition, you will no longer be able to create new Lambda functions using the Node.js 14 runtime. Starting January 25, 2024, you will no longer be able to update existing functions using the Node.js 14 runtime.
>
> We recommend that you upgrade your existing Node.js 14 functions to Node.js 18 before November 27, 2023.
>
> End of support does not impact function execution. Your functions will continue to run. However, they will be running on an unsupported runtime which is no longer maintained or patched by the AWS Lambda team.

Fix patch:
diff --git a/assets/SecretsManagerMetricsPublisher/index.js b/assets/SecretsManagerMetricsPublisher/index.js
index 51202fa2..8386a530 100644
--- a/assets/SecretsManagerMetricsPublisher/index.js
+++ b/assets/SecretsManagerMetricsPublisher/index.js
@@ -1,19 +1,16 @@
-const aws = require("aws-sdk");
+const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch');
+const { SecretsManagerClient, DescribeSecretCommand } = require('@aws-sdk/client-secrets-manager');
 
 const region = process.env.AWS_REGION;
 const millisPerDay = 1000 * 60 * 60 * 24;
 
 const clientOptions = {
-    region,
-    maxRetries: 5,
-    httpOptions: {
-        connectTimeout: 3 * 1000,
-        timeout: 3 * 1000,
-    },
+    signingRegion: region,
+    retryMode: 'standard',
 };
 
-const cloudwatch = new aws.CloudWatch(clientOptions);
-const sm = new aws.SecretsManager(clientOptions);
+const cloudwatchClient = new CloudWatchClient(clientOptions);
+const secretsManagerClient = new SecretsManagerClient(clientOptions);
 
 function daysSince(date, now = Date.now()) {
     const millis = now - date.getTime();
@@ -22,19 +19,18 @@ function daysSince(date, now = Date.now()) {
 }
 
 exports.handler = async (event, context) => {
-    console.debug("event:", JSON.stringify(event));
-    console.debug("context:", JSON.stringify(context));
+    console.info(`Retrieving secret for event ${JSON.stringify(event)}`);
+    console.debug(`context: ${JSON.stringify(context)}`);
 
-    console.info(`retrieving secret: ${event.secretId}`);
-    const secret = await sm
-        .describeSecret({
+    const secret = await secretsManagerClient.send(
+        new DescribeSecretCommand({
             SecretId: event.secretId,
         })
-        .promise();
+    );
 
-    console.debug("found secret: ", JSON.stringify(secret));
+    console.debug(`Found secret: ${JSON.stringify(secret)}`);
     if (!secret.Name || !secret.CreatedDate) {
-        throw new Error("invalid secret response");
+        throw new Error("Invalid secret response");
     }
 
     // use retrieved secret name in case secretId was an arn
@@ -43,40 +39,37 @@ exports.handler = async (event, context) => {
     const lastRotatedDate = secret.LastRotatedDate ?? secret.CreatedDate;
     const now = Date.now();
 
-    const metricData = [
-        {
-            MetricName: "DaysSinceLastChange",
-            Dimensions: [
-                {
-                    Name: "SecretName",
-                    Value: secretName,
-                },
-            ],
-            Unit: "Count",
-            Value: daysSince(lastChangedDate, now),
-        },
-        {
-            MetricName: "DaysSinceLastRotation",
-            Dimensions: [
-                {
-                    Name: "SecretName",
-                    Value: secretName,
-                },
-            ],
-            Unit: "Count",
-            Value: daysSince(lastRotatedDate, now),
-        },
-    ];
-
     const params = {
         Namespace: "SecretsManager",
-        MetricData: metricData,
+        MetricData: [
+            {
+                MetricName: "DaysSinceLastChange",
+                Dimensions: [
+                    {
+                        Name: "SecretName",
+                        Value: secretName,
+                    },
+                ],
+                Unit: "Count",
+                Value: daysSince(lastChangedDate, now),
+            },
+            {
+                MetricName: "DaysSinceLastRotation",
+                Dimensions: [
+                    {
+                        Name: "SecretName",
+                        Value: secretName,
+                    },
+                ],
+                Unit: "Count",
+                Value: daysSince(lastRotatedDate, now),
+            },
+        ],
     };
-
-    console.debug("putMetricData params: ", JSON.stringify(params));
-
-    console.info(`publishing metrics for secret: ${event.secretId}`);
-    await cloudwatch.putMetricData(params).promise();
+    console.debug(`putMetricData params: ${JSON.stringify(params)}`);
+    console.info(`Publishing metrics for secret "${event.secretId}"`);
+    const command = new PutMetricDataCommand(params);
+    await cloudwatchClient.send(command);
 
     return Promise.resolve();
 };
diff --git a/lib/monitoring/aws-secretsmanager/SecretsManagerMetricsPublisher.ts b/lib/monitoring/aws-secretsmanager/SecretsManagerMetricsPublisher.ts
index 065217c9..b7038f1a 100644
--- a/lib/monitoring/aws-secretsmanager/SecretsManagerMetricsPublisher.ts
+++ b/lib/monitoring/aws-secretsmanager/SecretsManagerMetricsPublisher.ts
@@ -34,7 +34,7 @@ export class SecretsManagerMetricsPublisher extends Construct {
         "Custom metrics publisher for SecretsManager Secrets (MonitoringCDKConstructs)",
       handler: "index.handler",
       memorySize: 128,
-      runtime: Runtime.NODEJS_14_X,
+      runtime: Runtime.NODEJS_18_X,
       timeout: Duration.seconds(60),
       logRetention: RetentionDays.ONE_DAY,
     });


Test patch:
diff --git a/test/facade/__snapshots__/MonitoringAspect.test.ts.snap b/test/facade/__snapshots__/MonitoringAspect.test.ts.snap
index 8acfb1d6..f1753a93 100644
--- a/test/facade/__snapshots__/MonitoringAspect.test.ts.snap
+++ b/test/facade/__snapshots__/MonitoringAspect.test.ts.snap
@@ -3449,7 +3449,7 @@ Object {
           "S3Bucket": Object {
             "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}",
           },
-          "S3Key": "370b9814d54928ec69d6005dd678b0f594a594948aa3322141bf10679ac71ee3.zip",
+          "S3Key": "d06eb8bb6deb3dba48c714538958f2ea17e2418a079ef471cb5b7b9de6a29d18.zip",
         },
         "Description": "Custom metrics publisher for SecretsManager Secrets (MonitoringCDKConstructs)",
         "Handler": "index.handler",
@@ -3460,7 +3460,7 @@ Object {
             "Arn",
           ],
         },
-        "Runtime": "nodejs14.x",
+        "Runtime": "nodejs18.x",
         "Timeout": 60,
       },
       "Type": "AWS::Lambda::Function",
diff --git a/test/monitoring/aws-secretsmanager/__snapshots__/SecretsManagerSecretMonitoring.test.ts.snap b/test/monitoring/aws-secretsmanager/__snapshots__/SecretsManagerSecretMonitoring.test.ts.snap
index 775f1dab..40411d4a 100644
--- a/test/monitoring/aws-secretsmanager/__snapshots__/SecretsManagerSecretMonitoring.test.ts.snap
+++ b/test/monitoring/aws-secretsmanager/__snapshots__/SecretsManagerSecretMonitoring.test.ts.snap
@@ -109,7 +109,7 @@ Object {
             "Arn",
           ],
         },
-        "Runtime": "nodejs14.x",
+        "Runtime": "nodejs18.x",
         "Timeout": 60,
       },
       "Type": "AWS::Lambda::Function",
@@ -435,7 +435,7 @@ Object {
             "Arn",
           ],
         },
-        "Runtime": "nodejs14.x",
+        "Runtime": "nodejs18.x",
         "Timeout": 60,
       },
       "Type": "AWS::Lambda::Function",