Skip to content

Instantly share code, notes, and snippets.

@PatrickJS
Last active November 10, 2023 19:23
Show Gist options
  • Select an option

  • Save PatrickJS/ff8dd91439451fc17b5850ab1b19923e to your computer and use it in GitHub Desktop.

Select an option

Save PatrickJS/ff8dd91439451fc17b5850ab1b19923e to your computer and use it in GitHub Desktop.
versioned lambdas in s3
import * as https from 'https';
import { NodeHttpHandler } from '@aws-sdk/node-http-handler';
import { promises as fs } from 'fs';
import path from 'path';
import { S3Client, GetObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
// Create an HTTPS agent with keep-alive enabled
const keepAliveAgent = new https.Agent({
keepAlive: true,
maxSockets: 50, // Maximum number of sockets to allow per host
maxFreeSockets: 10, // Maximum number of sockets to leave open in a free state
timeout: 60000, // Socket idle timeout
freeSocketTimeout: 30000 // Free socket keep-alive for 30 seconds
});
// Initialize the S3 client for the us-east-1 region
const s3 = new S3Client({
region: <--s3-region-->,
requestHandler: new NodeHttpHandler({
httpsAgent: keepAliveAgent
})
});
const BUCKET_NAME = <--Bucket-->;
// Helper function to download a file from S3
async function downloadFileFromS3(bucket, key, localPath) {
// Skip if key is a directory
if (!key.endsWith('/')) {
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
const data = await s3.send(command);
// Ensure the directory structure is present
await fs.mkdir(path.dirname(localPath), { recursive: true });
// Write the data to a local file
await fs.writeFile(localPath, data.Body);
}
}
// Helper function to list files under a specific S3 prefix
async function listFiles(bucket, prefix) {
const command = new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix });
const data = await s3.send(command);
return data.Contents.map(content => content.Key);
}
// Helper function to download all files for a specific version prefix
async function downloadVersionedFiles(bucket, versionPrefix) {
const files = await listFiles(bucket, versionPrefix);
for (const fileKey of files) {
if (!fileKey.endsWith('/')) { // Skip directories
const localPath = path.join('/tmp', fileKey);
await downloadFileFromS3(bucket, fileKey, localPath);
}
}
}
// Lambda handler function
export const handler = async (event) => {
const rawPath = event.rawPath;
const pathParts = rawPath.split('/').filter(Boolean); // Splits and removes empty strings
// Validate the path structure
if (pathParts.length < 4) {
return { statusCode: 400, body: 'Invalid path. Expected format: /org/repo/version/filename.js' };
}
// Extract path components
const [org, repo, version, ...rest] = pathParts;
const filename = rest.join('/');
const versionPrefix = `${org}/${repo}/${version}/`;
try {
// Download files from the specified versioned S3 path
await downloadVersionedFiles(BUCKET_NAME, versionPrefix);
const filePath = path.join('/tmp', versionPrefix, filename);
// Check if the file exists and is not a directory
try {
await fs.access(filePath);
} catch {
return { statusCode: 404, body: 'File not found.' };
}
// Dynamically import the module from the local file
// make sure to have package.json in the directory with { type: "module" }
const module = await import(filePath);
// Execute the module's default export if it exists
if (module.default) {
const result = await module.default(event);
return { statusCode: 200, body: JSON.stringify(result) };
} else {
return { statusCode: 500, body: 'No default export found in module.' };
}
} catch (error) {
console.error('Error:', error);
return { statusCode: 500, body: JSON.stringify({ message: 'Execution failed', error: error.message }) };
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment