@seasketch/geoprocessing
Version:
Geoprocessing and reporting framework for SeaSketch 2.0
179 lines • 7.85 kB
JavaScript
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