Skip to content

Instantly share code, notes, and snippets.

@vibe
Created April 2, 2021 11:13
Show Gist options
  • Save vibe/18339bb8cfafb4adefdaa67cfe3fa92b to your computer and use it in GitHub Desktop.
Save vibe/18339bb8cfafb4adefdaa67cfe3fa92b to your computer and use it in GitHub Desktop.
Terraform CDK Lambda Typescript/NodeJS
import { Resource } from "cdktf";
import { Construct } from "constructs";
import { IamRole, LambdaFunction, LambdaLayerVersion, S3Bucket, S3BucketObject, SecurityGroup, Subnet, Vpc } from "@cdktf/provider-aws";
import { copySync, ensureDirSync, mkdtempSync, pathExistsSync, readJSONSync, removeSync, } from "fs-extra"
import { dirname, join, resolve } from "path";
import { buildSync } from 'esbuild'
import { spawnSync } from "child_process";
import { tmpdir } from "os";
import AdmZip from "adm-zip";
import { TemplateDirectory } from "@tf-cdk/template-directory";
interface NodeJSLambdaConfig {
entry: string,
handler: string,
timeout: number
vpc?: Vpc
vpcSubnets?: Subnet[],
securityGroup?: SecurityGroup,
inlinePolicies?: [],
overrides?: {
dependencyLayer: string
}
}
export class NodeJSLambda extends Resource {
arn: string;
functionName: string;
constructor(scope: Construct, name: string, config: NodeJSLambdaConfig) {
super(scope, name)
const { entry, timeout } = config;
const artifact_bucket = new S3Bucket(this, "cdk-artifact-bucket", {
bucketPrefix: `cdk-artifact-${name}`,
acl: 'private',
})
ensureDirSync(`cdktf.out/artifacts/${name}`)
const temp_directory = mkdtempSync(join(tmpdir(), `cdktf-${name}`))
const source_directory = dirname(entry)
const artifact_directory = resolve(`cdktf.out/artifacts/${name}`)
const package_json = join(source_directory, 'package.json')
const has_package_json = pathExistsSync(package_json)
copySync(source_directory, temp_directory)
if(has_package_json || config?.overrides?.dependencyLayer) {
ensureDirSync(join(temp_directory, 'nodejs'))
const layer_dir = config.overrides?.dependencyLayer ? dirname(config.overrides.dependencyLayer) : temp_directory
copySync(
join(layer_dir, 'package.json'),
join(temp_directory, 'nodejs', 'package.json')
)
spawnSync('npm', ['install', '--force'], {
cwd: join(temp_directory, 'nodejs')
})
const layer_artifact = new AdmZip()
layer_artifact.addLocalFolder(join(temp_directory, 'nodejs'), 'nodejs')
layer_artifact.writeZip(join(artifact_directory, 'layer.zip'))
}
buildSync({
entryPoints: [join(temp_directory, 'index.ts')],
bundle: true,
external: has_package_json ? Object.keys(readJSONSync(package_json)?.dependencies || {}) : [],
outfile: join(temp_directory, 'out.js'),
platform: 'node'
})
const function_artifact = new AdmZip()
function_artifact.addLocalFile(join(temp_directory, 'out.js'))
function_artifact.writeZip(join(artifact_directory, 'function.zip'))
removeSync(temp_directory)
const directory = new TemplateDirectory(this, "static-assets", {
baseDir:`cdktf.out/artifacts/${name}`
})
const artifacts = directory.files.map(({ key, contentType, sourcePath, digests }, i) =>
new S3BucketObject(this, `cdk-artifact-${name}-${i}`, {
bucket: artifact_bucket.bucket,
serverSideEncryption: "AES256",
etag: digests.sha256,
source: sourcePath,
contentType,
key,
}))
const layer_key = directory.files.find(f => f.key == 'layer.zip')?.key
const layer = layer_key ? new LambdaLayerVersion(this, `${name}-layer`, {
s3Bucket: artifact_bucket.bucket,
s3Key: directory.files.find(f => f.key == 'layer.zip')?.key,
layerName: `${name}-nodejs-layer`,
compatibleRuntimes: ["nodejs14.x"],
dependsOn: [...artifacts],
sourceCodeHash: directory.files.find(f => f.key == 'layer.zip')?.digests.sha256
}) : null
const role = this.LambdaRole({
// inlinePolicies: config?.inlinePolicies
});
const fn = new LambdaFunction(this, `${name}-nodejs-lambda`, {
functionName: `${name}`,
description: `CDK deployed lambda generated by ${name}`,
s3Bucket: artifact_bucket.bucket,
s3Key: directory.files.find(f => f.key == 'function.zip')?.key,
handler: "out.handler",
runtime: 'nodejs14.x',
role: role.arn,
layers: layer ? [layer.arn] : [],
vpcConfig: [{
securityGroupIds: config.securityGroup ? [config.securityGroup.id]: [],
subnetIds: config.vpcSubnets ? config.vpcSubnets.map(subnet => subnet.id) : []
}],
dependsOn: [...artifacts],
sourceCodeHash: directory.files.find(f => f.key == 'function.zip')?.digests.sha256,
timeout
})
this.arn = fn.arn
this.functionName = fn.functionName
}
LambdaRole = ({ inlinePolicies=[] }) => {
return new IamRole(this, "basic_lambda_role", {
description: "Basic Lambda Execution Role",
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
},
]
}),
inlinePolicy: [{
name: 'BasicSecretsManagerPolicy',
policy: JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
},
{
"Action": [
"secretsmanager:*",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStackResource",
"cloudformation:DescribeStacks",
"cloudformation:ExecuteChangeSet",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs",
"kms:DescribeKey",
"kms:ListAliases",
"kms:ListKeys",
"lambda:ListFunctions",
"rds:DescribeDBClusters",
"rds:DescribeDBInstances",
"redshift:DescribeClusters",
"tag:GetResources"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"lambda:AddPermission",
"lambda:CreateFunction",
"lambda:GetFunction",
"lambda:InvokeFunction",
"lambda:UpdateFunctionConfiguration"
],
"Effect": "Allow",
"Resource": "arn:aws:lambda:*:*:function:SecretsManager*"
},
{
"Action": [
"serverlessrepo:CreateCloudFormationChangeSet",
"serverlessrepo:GetApplication"
],
"Effect": "Allow",
"Resource": "arn:aws:serverlessrepo:*:*:applications/SecretsManager*"
},
{
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::awsserverlessrepo-changesets*",
"arn:aws:s3:::secrets-manager-rotation-apps-*/*"
]
}
]
})
},{
name: 'LambdaVPCAccessExeecutionRole',
policy: JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:AssignPrivateIpAddresses",
"ec2:UnassignPrivateIpAddresses"
],
"Resource": "*"
}
]
})
},
...inlinePolicies,
]
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment