@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
192 lines • 10.5 kB
JavaScript
"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