UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

192 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EpsilonApiStack = void 0; const aws_cdk_lib_1 = require("aws-cdk-lib"); const aws_batch_1 = require("aws-cdk-lib/aws-batch"); const aws_lambda_1 = require("aws-cdk-lib/aws-lambda"); const aws_iam_1 = require("aws-cdk-lib/aws-iam"); const aws_sns_1 = require("aws-cdk-lib/aws-sns"); const aws_sqs_1 = require("aws-cdk-lib/aws-sqs"); const aws_sns_subscriptions_1 = require("aws-cdk-lib/aws-sns-subscriptions"); const aws_events_1 = require("aws-cdk-lib/aws-events"); const aws_events_targets_1 = require("aws-cdk-lib/aws-events-targets"); const aws_ecr_assets_1 = require("aws-cdk-lib/aws-ecr-assets"); const string_ratchet_1 = require("@bitblit/ratchet/common/string-ratchet"); const epsilon_stack_util_1 = require("./epsilon-stack-util"); const epsilon_build_properties_1 = require("../../epsilon-build-properties"); class EpsilonApiStack extends aws_cdk_lib_1.Stack { constructor(scope, id, props) { super(scope, id, props); // Build the docker image first const dockerImageAsset = new aws_ecr_assets_1.DockerImageAsset(this, id + 'DockerImage', { directory: props.dockerFileFolder, file: props.dockerFileName, }); const dockerImageCode = aws_lambda_1.DockerImageCode.fromImageAsset(props.dockerFileFolder, { file: props.dockerFileName }); const notificationTopic = new aws_sns_1.Topic(this, id + 'WorkNotificationTopic'); const workQueue = new aws_sqs_1.Queue(this, id + 'WorkQueue', Object.assign({ fifo: true, retentionPeriod: aws_cdk_lib_1.Duration.hours(8), visibilityTimeout: aws_cdk_lib_1.Duration.minutes(5), contentBasedDeduplication: true }, props)); const interApiGenericEventTopic = new aws_sns_1.Topic(this, id + 'InterApiTopic'); const epsilonEnv = { EPSILON_AWS_REGION: string_ratchet_1.StringRatchet.safeString(aws_cdk_lib_1.Stack.of(this).region), EPSILON_AWS_AVAILABILITY_ZONES: string_ratchet_1.StringRatchet.safeString(JSON.stringify(aws_cdk_lib_1.Stack.of(this).availabilityZones)), EPSILON_BACKGROUND_SQS_QUEUE_URL: string_ratchet_1.StringRatchet.safeString(workQueue.queueUrl), EPSILON_BACKGROUND_SNS_TOPIC_ARN: string_ratchet_1.StringRatchet.safeString(notificationTopic.topicArn), EPSILON_INTER_API_EVENT_TOPIC_ARN: string_ratchet_1.StringRatchet.safeString(interApiGenericEventTopic.topicArn), EPSILON_LIB_BUILD_HASH: string_ratchet_1.StringRatchet.safeString(epsilon_build_properties_1.EpsilonBuildProperties.buildHash), EPSILON_LIB_BUILD_TIME: string_ratchet_1.StringRatchet.safeString(epsilon_build_properties_1.EpsilonBuildProperties.buildTime), EPSILON_LIB_BUILD_BRANCH_OR_TAG: string_ratchet_1.StringRatchet.safeString(epsilon_build_properties_1.EpsilonBuildProperties.buildBranchOrTag), EPSILON_LIB_BUILD_VERSION: string_ratchet_1.StringRatchet.safeString(epsilon_build_properties_1.EpsilonBuildProperties.buildVersion), }; const env = Object.assign({}, props.extraEnvironmentalVars || {}, epsilonEnv); // Then build the Batch compute stuff... const ecsRole = new aws_iam_1.Role(this, id + 'AwsEcsRole', { assumedBy: new aws_iam_1.ServicePrincipal('ec2.amazonaws.com'), inlinePolicies: { root: new aws_iam_1.PolicyDocument({ statements: epsilon_stack_util_1.EpsilonStackUtil.ECS_POLICY_STATEMENTS, }), }, }); const ecsInstanceProfile = new aws_iam_1.CfnInstanceProfile(this, id + 'EcsInstanceProfile', { path: '/', roles: [ecsRole.roleName], }); const jobRole = new aws_iam_1.Role(this, id + 'AwsBatchRole', { assumedBy: new aws_iam_1.ServicePrincipal('ecs-tasks.amazonaws.com'), managedPolicies: [aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')], inlinePolicies: { root: new aws_iam_1.PolicyDocument({ statements: epsilon_stack_util_1.EpsilonStackUtil.createDefaultPolicyStatementList(props, workQueue, notificationTopic, interApiGenericEventTopic), }), }, }); // Created AWSServiceBatchRole // https://docs.aws.amazon.com/batch/latest/userguide/service_IAM_role.html const compEnvProps = { replaceComputeEnvironment: false, computeResources: { minvCpus: 0, maxvCpus: 16, instanceTypes: ['optimal'], instanceRole: ecsInstanceProfile.attrArn, ec2KeyPair: props.batchInstancesEc2KeyPairName, type: 'EC2', subnets: props.vpcSubnetIds.map((s) => 'subnet-' + s), securityGroupIds: props.lambdaSecurityGroupIds.map((s) => 'sg-' + s), allocationStrategy: 'BEST_FIT', }, serviceRole: 'arn:aws:iam::' + props.env.account + ':role/AWSBatchServiceRole', type: 'MANAGED', state: 'ENABLED', }; const compEnv = new aws_batch_1.CfnComputeEnvironment(this, id + 'ComputeEnv', compEnvProps); const batchJobQueueProps = { state: 'ENABLED', priority: 1, computeEnvironmentOrder: [ { computeEnvironment: compEnv.attrComputeEnvironmentArn, order: 1, }, ], // the properties below are optional //jobQueueName: 'jobQueueName', //schedulingPolicyArn: 'schedulingPolicyArn', //state: 'state', tags: { tagsKey: id, }, }; const batchJobQueue = new aws_batch_1.CfnJobQueue(this, id + 'BatchJobQueue', batchJobQueueProps); const batchEnvVars = epsilon_stack_util_1.EpsilonStackUtil.toEnvironmentVariables([ env, props.extraEnvironmentalVars || {}, { EPSILON_RUNNING_IN_AWS_BATCH: true, }, ]); const jobProps = { type: 'container', platformCapabilities: ['EC2'], containerProperties: { mountPoints: [], volumes: [], memory: 4294, privileged: false, jobRoleArn: jobRole.roleArn, readonlyRootFilesystem: false, vcpus: 1, image: dockerImageAsset.imageUri, command: ['Ref::taskName', 'Ref::taskData', 'Ref::traceId', 'Ref::traceDepth'], environment: batchEnvVars, }, }; const jobDef = new aws_batch_1.CfnJobDefinition(this, id + 'JobDefinition', jobProps); const lambdaRole = new aws_iam_1.Role(this, 'customRole', { roleName: id + 'LambdaCustomRole', assumedBy: new aws_iam_1.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')], inlinePolicies: { root: new aws_iam_1.PolicyDocument({ statements: epsilon_stack_util_1.EpsilonStackUtil.createDefaultPolicyStatementList(props, workQueue, notificationTopic, interApiGenericEventTopic), }), }, }); // Add AWS batch vars to the environment env['EPSILON_AWS_BATCH_JOB_DEFINITION_ARN'] = jobDef.ref; env['EPSILON_AWS_BATCH_JOB_QUEUE_ARN'] = batchJobQueue.ref; this.webHandler = new aws_lambda_1.DockerImageFunction(this, id + 'Web', { //reservedConcurrentExecutions: 1, retryAttempts: 2, //allowAllOutbound: true, // Needs a VPC memorySize: props.webMemorySizeMb || 128, ephemeralStorageSize: aws_cdk_lib_1.Size.mebibytes(512), timeout: aws_cdk_lib_1.Duration.seconds(props.webTimeoutSeconds || 20), code: dockerImageCode, role: lambdaRole, environment: env, }); if ((props === null || props === void 0 ? void 0 : props.webLambdaPingMinutes) && props.webLambdaPingMinutes > 0) { // Wire up the cron handler const rule = new aws_events_1.Rule(this, id + 'WebKeepaliveRule', { schedule: aws_events_1.Schedule.rate(aws_cdk_lib_1.Duration.minutes(Math.ceil(props.webLambdaPingMinutes))), }); rule.addTarget(new aws_events_targets_1.LambdaFunction(this.webHandler)); } const fnUrl = this.webHandler.addFunctionUrl({ authType: aws_lambda_1.FunctionUrlAuthType.NONE, cors: { allowedOrigins: ['*'], allowedHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key'], allowedMethods: [aws_lambda_1.HttpMethod.ALL], allowCredentials: true, }, }); this.backgroundHandler = new aws_lambda_1.DockerImageFunction(this, id + 'Background', { //reservedConcurrentExecutions: 1, retryAttempts: 2, // allowAllOutbound: true, memorySize: props.backgroundMemorySizeMb || 3000, ephemeralStorageSize: aws_cdk_lib_1.Size.mebibytes(512), timeout: aws_cdk_lib_1.Duration.seconds(props.backgroundTimeoutSeconds || 900), code: dockerImageCode, role: lambdaRole, environment: env, }); notificationTopic.addSubscription(new aws_sns_subscriptions_1.LambdaSubscription(this.backgroundHandler)); interApiGenericEventTopic.addSubscription(new aws_sns_subscriptions_1.LambdaSubscription(this.backgroundHandler)); // Wire up the cron handler const rule = new aws_events_1.Rule(this, id + 'CronRule', { schedule: aws_events_1.Schedule.rate(aws_cdk_lib_1.Duration.minutes(1)), }); rule.addTarget(new aws_events_targets_1.LambdaFunction(this.backgroundHandler)); this.apiDomain = aws_cdk_lib_1.Lazy.uncachedString({ produce: (context) => { const resolved = context.resolve(fnUrl.url); return { 'Fn::Select': [2, { 'Fn::Split': ['/', resolved] }] }; }, }); } } exports.EpsilonApiStack = EpsilonApiStack; //# sourceMappingURL=epsilon-api-stack.js.map