Benjamin Lannon

Select a theme. Click on the overlay or the button again to exit

Checking for unsupported Node.js AWS Lambda Functions

Posted on:

The Node.js team maintains a repo called nodejs/release with a JSON file in it, schedule.json. With this, I was able to setup a small Node script I run daily on AWS to track if any of my lambdas are using a runtime that is no longer being maintained.

Lambda Code

The core functionality consists of collecting metadata on all of the lambda functions in my AWS account and then looping over each function that is in node.

When the ListFunctions command from AWS returns, the runtime format looks like nodejs18.x. I parse that out to just grab the major version, 18, append a "v" on the front, and then use that as the key in the schedule.json file to get an end date for the runtime.

Then I do a diff between the end date of that version and today and if the value is negative, then the end date has passed and I notify myself of such. I am using another lambda function I have in my account called SendMessageOverDiscordWebhook to send a discord message to a private discord to inform myself that the function needs to be updated.

const {
LambdaClient,
ListFunctionsCommand,
InvokeCommand,
} = require("@aws-sdk/client-lambda");

const dayjs = require("dayjs");

const lambdaClient = new LambdaClient({
region: "us-east-1",
});

exports.handler = async function run() {
const listFunctionsResp = await lambdaClient.send(
new ListFunctionsCommand({})
);

const today = dayjs();

const versions = await fetch(
"https://raw.githubusercontent.com/nodejs/Release/main/schedule.json"
).then((resp) => resp.json());

const oldFunctions = listFunctionsResp.Functions.filter((fn) => {
return fn.Runtime.startsWith("nodejs");
}).filter((fn) => {
const nodeVersion = `v${fn.Runtime.split("nodejs")[1].split(".x")[0]}`;

const versionEndDate = dayjs(versions[nodeVersion].end);

return versionEndDate.diff(today, "days") < 0;
});
for (let fn of oldFunctions) {
await lambdaClient.send(
new InvokeCommand({
FunctionName: "SendMessageOverDiscordWebhook",
Payload: JSON.stringify({
discordWebookURL: process.env.DISCORD_WEBHOOK_URL,
message: `NOTICE: The lambda function ${fn.FunctionName} is using a deprecated version of Nodejs: ${fn.Runtime}`,
}),
})
);
}

return {
stausCode: 200,
body: JSON.stringify({ message: "DONE" }),
};
};

Deployment Code (CDK)

Then in the CDK code I use to deploy this function, I have set up the function to run every day at midnight UTC. The majority of the code is straightforward other than adding an inline IAM policy which I am granting this function the lambda:listFunctions action which is needed to get all of the functions and related metadata.

As well, the SendMessageOverDiscordWebhook lambda is managed outside this CDK stack, but I can fetch it from my account using the Function.fromFunctionName method.

const { Construct } = require("constructs");
const { Duration, Tags } = require("aws-cdk-lib");
const { Rule, Schedule } = require("aws-cdk-lib/aws-events");
const { LambdaFunction } = require("aws-cdk-lib/aws-events-targets");
const { Policy, PolicyStatement, Effect } = require("aws-cdk-lib/aws-iam");
const {
Function,
Code,
Runtime,
Architecture,
} = require("aws-cdk-lib/aws-lambda");
const path = require("path");

class CheckNodeLambdas extends Construct {
constructor(scope, id, opts) {
super(scope, id);
this.fn = new Function(this, "CheckNodeLambdasFn", {
code: Code.fromAsset(
path.join(__dirname, "..", "lambdas", "check-node-lambdas")
),
description:
"A function that checks my nodejs lambda functions to see if any are using outdated runtimes",
handler: "index.handler",
runtime: Runtime.NODEJS_18_X,
timeout: Duration.seconds(10),
architecture: Architecture.ARM_64,
environment: {
DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL,
},
memorySize: 512,
});

this.fn.role?.attachInlinePolicy(
new Policy(this, "LambdaPolicy", {
statements: [
new PolicyStatement({
actions: ["lambda:listFunctions"],
effect: Effect.ALLOW,
resources: ["*"],
}),
],
})
);

const SendMessageOverDiscordWebhookFn = Function.fromFunctionName(
this,
"SendMessageOverDiscordWebhookFn",
"SendMessageOverDiscordWebhook"
);

SendMessageOverDiscordWebhookFn.grantInvoke(this.fn);

const checkNodeLambdasSchedule = new Rule(
this,
`checkNodeLambdasSchedule`,
{
schedule: Schedule.expression("cron(0 0 * * ? *)"),
}
);

checkNodeLambdasSchedule.addTarget(new LambdaFunction(this.fn));
}
}

module.exports = { CheckNodeLambdas };