Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update NodeJS lambdas to node 20 in CFN templates #82

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ terraform.tfvars
.terraform.lock.hcl

.tmuxp.yaml

# Ignore JS/TS dependencies installed with npm
node_modules
10 changes: 1 addition & 9 deletions cleanup-imported-ebs-snapshots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,7 @@ Because the source code for the lambda function is written in a separate TypeScr
npm install
```

Then run the build after you modify the code of the lambda function inside of `src/index.ts`.

```bash
npm run build
```

Then copy the output of thee JS file from the `dist/index.js` into the `cloudformation.json` file as the `InlineCode` property of the lambda function resource at the end of the template. Make sure to indent the code according to YAML syntax (the code must be two spaces deeper than the `InlineCode:` key).

Now you can deploy the resulting stack with the following bash script.
Then run the following script to build the TypeScript code, modify the lambda function inside of `src/index.ts` and deploy a test CFN stack out of it. Make sure you have `yq` installed.

```
./deploy.sh
Expand Down
34 changes: 15 additions & 19 deletions cleanup-imported-ebs-snapshots/cloudformation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ Parameters:
KeepMaxAgeInDays:
Type: Number
Default: '7'
Description: >
Description: |-
The maximum age of snapshots to keep in days.
If a snapshot is older than this value, it will be deleted unless there
it fits into {KeepMinAmount} of latest snapshots.

KeepMinAmount:
Type: Number
Default: '7'
Description: >
Description: |-
The minimum amount of snapshots to keep for each volume even if they are older than {KeepMaxAgeInDays}.
If there are less than this amount of snapshots for a volume, none of them will be deleted.
Resources:
Expand All @@ -23,28 +22,28 @@ Resources:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: [lambda.amazonaws.com] }
Action: [sts:AssumeRole]
Principal:
Service: [lambda.amazonaws.com]
Action: ['sts:AssumeRole']
Policies:
- PolicyName: EBSSnashotsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: [ec2:DescribeSnapshots]
Action: ['ec2:DescribeSnapshots']
Resource: "*"
- Effect: Allow
Action: [ec2:DeleteSnapshot]
Action: ['ec2:DeleteSnapshot']
Resource: "*"
Condition:
StringLike:
aws:ResourceTag/elastio:imported-to-rp: "*"
- Effect: Allow
# We don't give the lambda a permission to create log groups
# because we pre-create the log group ourselves
Action: [logs:CreateLogStream, logs:PutLogEvents]
Action: ['logs:CreateLogStream', 'logs:PutLogEvents']
Resource: arn:aws:logs:*:*:*

# The default log group that AWS Lambda creates has retention disabled.
# We don't want to store logs indefinitely, so we create a custom log group with
# retention enabled.
Expand All @@ -55,7 +54,6 @@ Resources:
# This lambda does destructive operations, so we want to keep the logs for a long time
# to be safe and to be able to debug any issues if they happen.
RetentionInDays: 180

Lambda:
Type: AWS::Serverless::Function
# We want to create a custom log group with retention enabled first,
Expand All @@ -64,24 +62,22 @@ Resources:
Properties:
FunctionName: elastio-cleanup-imported-ebs-snapshots
Description: "A Lambda function to delete EBS snapshots tagged with elastio:imported-to-rp"
Runtime: nodejs18.x
Runtime: nodejs20.x
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Events:
Schedule:
Type: ScheduleV2
Properties:
ScheduleExpression: rate(1 hour)

Environment:
Variables:
DELETE_BY_TAG_KEY: elastio:imported-to-rp
KEEP_MAX_AGE_IN_DAYS: !Ref KeepMaxAgeInDays
KEEP_MIN_AMOUNT: !Ref KeepMinAmount

# Change the code if the lambda in the `src/index.ts` file and then run `npm run build`.
# After that, copy the content of the `dist/index.js` file and paste it here.
InlineCode: |
InlineCode: |-
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CleanupContext = exports.handler = void 0;
Expand All @@ -93,9 +89,9 @@ Resources:
const BROKEN_VOLUME_ID = 'vol-ffffffff';
const ec2 = new client_ec2_1.EC2Client({});
/**
* A handler that is invoked periodically based on an AWS Scheduler schedule
* and cleans up EBS snapshots that were already imported to Elastio.
*/
* A handler that is invoked periodically based on an AWS Scheduler schedule
* and cleans up EBS snapshots that were already imported to Elastio.
*/
async function handler() {
const now = new Date();
console.log(`Starting the cleanup relative to the current timestamp ${now.toISOString()}`);
Expand Down Expand Up @@ -206,8 +202,8 @@ Resources:
return new CleanupTotal(volumes, snapshots);
}
/**
* Retain only snapshots that need to be deleted
*/
* Retain only snapshots that need to be deleted
*/
filterSnapshotsToDelete() {
const toDeleteMap = new Map();
for (const [volumeId, toDelete] of this.snapshots) {
Expand Down
12 changes: 10 additions & 2 deletions cleanup-imported-ebs-snapshots/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

set -euo pipefail

project_dir=$(readlink -f $(dirname $0))
cd "$(dirname "${BASH_SOURCE[0]}")"

aws cloudformation deploy --template-file $project_dir/cloudformation.yaml \
npm run build

code=$(cat dist/index.js) \
yq --inplace '.Resources.Lambda.Properties.InlineCode = strenv(code)' \
cloudformation.yaml

set -x

aws cloudformation deploy --template-file cloudformation.yaml \
--capabilities CAPABILITY_IAM \
--stack-name elastio-imported-ebs-snapshots-cleanup-stack
5 changes: 5 additions & 0 deletions cleanup-leftover-ebs-volumes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

# We don't lock the dependencies and use the version of AWS SDK installed in the
# managed AWS Lambda NodeJS runtime. However, we use a `package.json` to install
# some TypeScript types to assist with development.
package-lock.json
87 changes: 48 additions & 39 deletions cleanup-leftover-ebs-volumes/cleanup-elastio-ebs-vols.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,69 @@ Resources:
Properties:
FunctionName: EBSVolumeCleanupFunction
Description: 'A Lambda function to delete un-attached EBS volumes older than 24 hours with the elastio:resource tag'
Runtime: nodejs16.x
Runtime: nodejs20.x
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
InlineCode: |
const AWS = require('aws-sdk');
const ec2 = new AWS.EC2();
InlineCode: |-
// @ts-check

const { EC2, paginateDescribeVolumes } = require('@aws-sdk/client-ec2');
const ec2 = new EC2();

const isVolumeOlderThan24Hours = (createTime) => {
const currentTime = new Date();
const volumeCreateTime = new Date(createTime);
const timeDifference = currentTime - volumeCreateTime;
const hoursDifference = timeDifference / (1000 * 60 * 60);
return hoursDifference > 24;
const currentTime = new Date();
const volumeCreateTime = new Date(createTime);
const timeDifference = currentTime - volumeCreateTime;
const hoursDifference = timeDifference / (1000 * 60 * 60);
return hoursDifference > 24;
};

exports.handler = async (event) => {
try {
console.log("Getting list of unattached EBS volumes with elastio:resource tag");
const { Volumes } = await ec2.describeVolumes({
Filters: [
{
Name: 'status',
Values: ['available']
},
{
Name: 'tag:elastio:resource',
Values: ['*']
exports.handler = async (_event) => {
try {
console.log("Getting list of unattached EBS volumes with elastio:resource tag");

const volumesPaginator = paginateDescribeVolumes(
{
client: ec2,
pageSize: 500.
},
{
Filters: [
{
Name: 'status',
Values: ['available']
},
{
Name: 'tag:elastio:resource',
Values: ['*']
}
]
}
);

for await (const page of volumesPaginator) {
for (const volume of page.Volumes ?? []) {
if (isVolumeOlderThan24Hours(volume.CreateTime)) {
console.log(`Deleting unattached Elastio EBS volume ${volume.VolumeId} older than 24 hours`);
await ec2.deleteVolume({ VolumeId: volume.VolumeId });
console.log(`EBS volume ${volume.VolumeId} deleted`);
} else {
console.log(`EBS volume ${volume.VolumeId} is not old enough; skipping it`);
}
}
}
]
}).promise();

for (const volume of Volumes) {
if (isVolumeOlderThan24Hours(volume.CreateTime)) {
console.log("Deleting unattached Elastio EBS volume ${volume.VolumeId} older than 24 hours");
await ec2.deleteVolume({ VolumeId: volume.VolumeId }).promise();
console.log("EBS volume ${volume.VolumeId} deleted");
} else {
console.log("EBS volume ${volume.VolumeId} is not old enough; skipping it");
}
return { statusCode: 200, body: JSON.stringify({ message: 'EBS volumes cleanup successful' }) };
} catch (error) {
console.error(error);
throw error;
}

return { statusCode: 200, body: JSON.stringify({ message: 'EBS volumes cleanup successful' }) };
} catch (error) {
console.error(error);
throw error;
}
};
Events:
Schedule1:
Type: Schedule
Properties:
Schedule: 'rate(1 hour)'

LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
Expand Down Expand Up @@ -94,7 +104,6 @@ Resources:
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'

EBSVolumeCleanupLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
Expand Down
21 changes: 21 additions & 0 deletions cleanup-leftover-ebs-volumes/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
#
# This is a script that updates the labmda source code in the CFN template from the
# lambda.js file where it's easier to edit and maintain. Then it deploys the CFN
# stack with the updated lambda code for testing.
#
# You need yq and AWS CLI set up.

set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")"

code=$(cat lambda.js) \
yq --inplace '.Resources.EBSVolumeCleanupFunction.Properties.InlineCode = strenv(code)' \
cleanup-elastio-ebs-vols.yaml

set -x

aws cloudformation deploy --template-file cleanup-elastio-ebs-vols.yaml \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--stack-name elastio-cleanup-elastio-ebs-volumes
54 changes: 54 additions & 0 deletions cleanup-leftover-ebs-volumes/lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @ts-check

const { EC2, paginateDescribeVolumes } = require('@aws-sdk/client-ec2');
const ec2 = new EC2();

const isVolumeOlderThan24Hours = (createTime) => {
const currentTime = new Date();
const volumeCreateTime = new Date(createTime);
const timeDifference = currentTime - volumeCreateTime;
const hoursDifference = timeDifference / (1000 * 60 * 60);
return hoursDifference > 24;
};

exports.handler = async (_event) => {
try {
console.log("Getting list of unattached EBS volumes with elastio:resource tag");

const volumesPaginator = paginateDescribeVolumes(
{
client: ec2,
pageSize: 500.
},
{
Filters: [
{
Name: 'status',
Values: ['available']
},
{
Name: 'tag:elastio:resource',
Values: ['*']
}
]
}
);

for await (const page of volumesPaginator) {
for (const volume of page.Volumes ?? []) {
if (isVolumeOlderThan24Hours(volume.CreateTime)) {
console.log(`Deleting unattached Elastio EBS volume ${volume.VolumeId} older than 24 hours`);
await ec2.deleteVolume({ VolumeId: volume.VolumeId });
console.log(`EBS volume ${volume.VolumeId} deleted`);
} else {
console.log(`EBS volume ${volume.VolumeId} is not old enough; skipping it`);
}
}
}

return { statusCode: 200, body: JSON.stringify({ message: 'EBS volumes cleanup successful' }) };
} catch (error) {
console.error(error);
throw error;
}
};
7 changes: 7 additions & 0 deletions cleanup-leftover-ebs-volumes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "cleanup-leftover-ebs-volumes",
"version": "1.0.0",
"dependencies": {
"@aws-sdk/client-ec2": "3.561"
}
}