UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

179 lines • 7.85 kB
import { NestedStack } from "aws-cdk-lib"; import { Duration } from "aws-cdk-lib"; import { Function, Code } from "aws-cdk-lib/aws-lambda"; import { Effect, PolicyStatement, Role, ServicePrincipal, } from "aws-cdk-lib/aws-iam"; import config from "./config.js"; import path from "node:path"; /** * Nested stack for Lambda functions. Can contain sync or async functions * Create multiple instances to handle both */ export class LambdaStack extends NestedStack { props; /** * Once stack created, this array will contain all processing functions */ processingFunctions; /** * Once stack created, this array will contain all sync function ARNs */ syncLambdaArns; /** * Once stack created, this array will contain all async run lambda functions */ asyncRunLambdas; constructor(scope, id, props) { super(scope, id, props); this.props = props; this.processingFunctions = []; this.asyncRunLambdas = []; this.syncLambdaArns = []; // Create lambdas for all functions this.createProcessingFunctions(); } getProcessingFunctions() { return this.processingFunctions; } getAsyncRunLambdas() { return this.asyncRunLambdas; } getSyncLambdaArns() { return this.syncLambdaArns; } /** * Create Lambda function constructs */ createProcessingFunctions = () => { this.processingFunctions.push(...this.createSyncFunctions()); this.processingFunctions.push(...this.createAsyncFunctions()); }; /** Create Lambda function constructs for sync functions that return result immediately */ createSyncFunctions = () => { const syncFunctionMetas = [ ...this.props.manifest.preprocessingFunctions, ...this.props.manifest.geoprocessingFunctions.filter((func) => func.executionMode === "sync"), ]; return syncFunctionMetas.map((functionMeta) => { const rootPointer = getHandlerPointer(functionMeta); const pkgName = getHandlerPkgName(functionMeta); const functionName = `gp-${this.props.projectName}-sync-${functionMeta.title}`; const codePath = path.join(this.props.projectPath, ".build", pkgName); // console.log("codePath", codePath); // console.log("rootPointer", rootPointer); const func = new Function(this, `${functionMeta.title}GpSyncHandler`, { runtime: config.NODE_RUNTIME, code: Code.fromAsset(codePath), handler: rootPointer, functionName, memorySize: functionMeta.memory, timeout: Duration.seconds(functionMeta.timeout || config.SYNC_LAMBDA_TIMEOUT), description: functionMeta.description, }); this.syncLambdaArns.push(func.functionArn); return { func, meta: functionMeta, }; }); }; /** Create Lambda function constructs for functions that return result async */ createAsyncFunctions = () => { const asyncFunctionMetas = this.props.manifest.geoprocessingFunctions.filter((func) => func.executionMode === "async" && func.purpose !== "preprocessing"); return asyncFunctionMetas.map((functionMeta, index) => { const rootPointer = getHandlerPointer(functionMeta); const pkgName = getHandlerPkgName(functionMeta); const startFunctionName = `gp-${this.props.projectName}-async-${functionMeta.title}-start`; const runFunctionName = `gp-${this.props.projectName}-async-${functionMeta.title}-run`; /** * startHandler Lambda is connected to the REST API allowing client to * start a GP function task, which invokes the runHandler Lambda */ const startFunc = new Function(this, `${functionMeta.title}GpAsyncHandlerStart`, { runtime: config.NODE_RUNTIME, code: Code.fromAsset(path.join(this.props.projectPath, ".build", pkgName)), handler: rootPointer, functionName: startFunctionName, memorySize: functionMeta.memory, timeout: Duration.seconds(config.ASYNC_LAMBDA_START_TIMEOUT), description: functionMeta.description, environment: { ASYNC_REQUEST_TYPE: "start", RUN_HANDLER_FUNCTION_NAME: runFunctionName, }, }); /** * runHandler Lambda is invoked by startHandler Lambda * Used for running GP function and reporting back results async via socket */ const runFunc = new Function(this, `${functionMeta.title}GpAsyncHandlerRun`, { runtime: config.NODE_RUNTIME, code: Code.fromAsset(path.join(this.props.projectPath, ".build", pkgName)), handler: rootPointer, functionName: runFunctionName, memorySize: functionMeta.memory, timeout: Duration.seconds(functionMeta.timeout || config.ASYNC_LAMBDA_RUN_TIMEOUT), description: functionMeta.description, environment: { ASYNC_REQUEST_TYPE: "run", }, }); // Allow start function to invoke run function const invokeAsyncRunLambdaPolicy = new PolicyStatement({ effect: Effect.ALLOW, resources: [runFunc.functionArn], actions: ["lambda:InvokeFunction"], }); const asyncLambdaRole = new Role(this, "GpAsyncLambdaRole" + index, { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); asyncLambdaRole.addToPolicy(invokeAsyncRunLambdaPolicy); startFunc.addToRolePolicy(invokeAsyncRunLambdaPolicy); this.asyncRunLambdas.push(runFunc); return { startFunc, runFunc, meta: functionMeta, }; }); }; /** * Given run lambda functions across all lambda stacks, creates policies allowing them to invoke sync lambdas within this lambda stack */ createLambdaSyncPolicies(runLambdas) { // Create invoke policy for each worker function in this lambda stack const invokeSyncLambdaPolicies = this.syncLambdaArns.map((arn) => { return new PolicyStatement({ effect: Effect.ALLOW, resources: [arn], actions: ["lambda:InvokeFunction"], }); }); // Allow all async run lambdas to invoke sync functions in this lambda stack using this policy for (const [index, runLambda] of runLambdas.entries()) { for (const [index2, curInvokeSyncLambdaPolicy,] of invokeSyncLambdaPolicies.entries()) { const syncLambdaRole = new Role(this, "GpSyncLambdaRole" + index + "_" + index2, { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), }); syncLambdaRole.addToPolicy(curInvokeSyncLambdaPolicy); runLambda.addToRolePolicy(curInvokeSyncLambdaPolicy); } } } } /** * Returns root lambda handler method pointer in module.function dot notation */ export function getHandlerPointer(funcMeta) { return `${funcMeta.handlerFilename .replace(/\.js$/, "") .replace(/\.ts$/, "")}Handler.handler`; } /** * Returns build package name to look for handler */ export function getHandlerPkgName(funcMeta) { return `${funcMeta.handlerFilename .replace(/\.js$/, "") .replace(/\.ts$/, "")}`; } //# sourceMappingURL=LambdaStack.js.map