forked from shellscape/dot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnode-function.ts
More file actions
157 lines (136 loc) · 4.7 KB
/
Copy pathnode-function.ts
File metadata and controls
157 lines (136 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { Size, Duration } from 'aws-cdk-lib';
import { Alias, Function, LayerVersion, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
import {
BundlingOptions,
ICommandHooks,
NodejsFunction,
SourceMapMode
} from 'aws-cdk-lib/aws-lambda-nodejs';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import chalk from 'chalk';
import { DotStack } from '../constructs/Stack';
import { log } from '../log';
import { addFunctionAlarms, type AddFunctionOptions } from './function';
export { Runtime };
export interface AddNodeFunctionOptions extends Omit<AddFunctionOptions, 'handlerPath'> {
/**
* Commands to run before bundling and before the lambda archive is finalized
*/
entryFilePath?: string;
esbuild?: BundlingOptions;
handlerExportName?: string;
hooks?: Partial<ICommandHooks>;
runtime?: Runtime;
}
interface SetupFunctionArgs {
fnName: string;
handler: Function;
options: AddFunctionOptions;
}
export const addNodeFunction = (options: AddNodeFunctionOptions) => {
const {
concurrency,
deadLetterQueue,
entryFilePath = 'dist/lambda.ts',
environmentVariables = {},
esbuild = {},
handlerExportName = 'handler',
hooks,
memorySize = 2000,
name = '',
runtime = Runtime.NODEJS_22_X,
scope,
storageMb,
timeout = Duration.minutes(5)
} = options;
const { envName } = scope;
const baseName = DotStack.baseName(name, 'fn');
const baseHooks: ICommandHooks = {
afterBundling: () => [],
beforeBundling: () => [],
beforeInstall: () => []
};
const bundleOptions: BundlingOptions = {
...esbuild,
commandHooks: hooks ? { ...baseHooks, ...hooks } : void 0,
externalModules: [...(esbuild.externalModules || []), ...['pg-native']]
};
const fnName = scope.resourceName(baseName);
const defaultEnv: typeof environmentVariables = {
// Note: https://acloudguru.com/blog/engineering/building-more-cost-effective-lambda-functions-with-1-ms-billing
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
DEPLOY_ENV: envName,
IS_LAMBDA: 'true',
NODE_ENV: envName,
NODE_OPTIONS: `--enable-source-maps --max-old-space-size=${memorySize}`
};
log.info('Creating function:', chalk.dim(fnName));
log.info('From:', chalk.dim(entryFilePath));
log.info('Targeting:', chalk.dim(handlerExportName));
const handler = new NodejsFunction(scope, fnName, {
bundling: {
minify: true,
...bundleOptions,
sourceMap: true,
sourceMapMode: SourceMapMode.INLINE,
sourcesContent: false
},
deadLetterQueueEnabled: deadLetterQueue,
entry: entryFilePath,
environment: { ...defaultEnv, ...environmentVariables },
ephemeralStorageSize: storageMb ? Size.mebibytes(storageMb) : void 0,
functionName: fnName,
handler: handlerExportName,
logRetention: RetentionDays.ONE_WEEK,
memorySize,
reservedConcurrentExecutions: concurrency?.reserved,
runtime,
timeout,
tracing: Tracing.ACTIVE
});
return setupFunction({ fnName, handler, options });
};
const setupFunction = ({ fnName, handler, options }: SetupFunctionArgs) => {
const {
addEnvars,
alarmEmail,
concurrency,
environmentVariables = {},
layers,
layerArns,
scope
} = options;
if (concurrency?.provisioned) {
log.info(' - Provisioning Concurrency for:', chalk.dim(fnName));
if (concurrency?.provisioned?.max <= 0)
throw new RangeError(`concurrency.provisioned.max needs to be greater than 0`);
const aliasName = `${fnName}-alias`;
const alias = new Alias(scope, aliasName, { aliasName, version: handler.latestVersion });
const scaling = alias.addAutoScaling({
maxCapacity: Math.ceil(concurrency.provisioned.max),
minCapacity: Math.ceil(concurrency.provisioned.min || 1)
});
scaling.scaleOnUtilization({ utilizationTarget: concurrency.provisioned.percentage || 0.5 });
}
// TODO: Add schedule based provisioning
// https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html#autoscaling
if (layerArns?.length) {
const layerVersion = layerArns.map((arn, index) =>
LayerVersion.fromLayerVersionArn(scope, `${handler.functionName}-layer-${index}`, arn)
);
handler.addLayers(...layerVersion);
}
if (layers?.length) handler.addLayers(...layers);
scope.overrideId(handler, fnName);
addEnvars
// we don't want to override envars that we've already specified
?.filter((varName) => !environmentVariables[varName])
.forEach((varName) => {
const value = process.env[varName];
// eslint-disable-next-line no-unused-expressions
value && handler.addEnvironment(varName, value);
});
if (alarmEmail)
addFunctionAlarms(alarmEmail, handler, fnName.replace(`${scope.envName}-`, ''), scope);
return handler;
};