Created
April 2, 2021 11:13
-
-
Save vibe/18339bb8cfafb4adefdaa67cfe3fa92b to your computer and use it in GitHub Desktop.
Terraform CDK Lambda Typescript/NodeJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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