@digitraffic/common 2023.1.18-1 → 2023.1.23-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aws/infra/canaries/canary-alarm.js +11 -13
- package/dist/aws/infra/canaries/canary.js +2 -4
- package/dist/aws/infra/canaries/database-checker.js +4 -1
- package/dist/aws/infra/canaries/url-canary.js +1 -0
- package/dist/aws/infra/canaries/url-checker.d.ts +2 -2
- package/dist/aws/infra/canaries/url-checker.js +24 -5
- package/dist/aws/infra/sqs-integration.d.ts +1 -3
- package/dist/aws/infra/sqs-integration.js +28 -32
- package/dist/aws/infra/sqs-queue.d.ts +0 -2
- package/dist/aws/infra/sqs-queue.js +31 -24
- package/dist/aws/infra/stack/lambda-configs.d.ts +2 -31
- package/dist/aws/infra/stack/lambda-configs.js +5 -38
- package/dist/aws/infra/stack/monitoredfunction.js +3 -1
- package/dist/aws/infra/stacks/db-stack.js +1 -1
- package/dist/aws/infra/stacks/network-stack.d.ts +2 -1
- package/dist/aws/infra/stacks/network-stack.js +4 -2
- package/dist/aws/runtime/secrets/dbsecret.d.ts +0 -39
- package/dist/aws/runtime/secrets/dbsecret.js +1 -71
- package/dist/aws/runtime/secrets/proxy-holder.js +5 -4
- package/dist/aws/runtime/secrets/rds-holder.js +5 -4
- package/dist/aws/runtime/secrets/secret-holder.d.ts +0 -4
- package/dist/aws/runtime/secrets/secret-holder.js +6 -12
- package/dist/aws/runtime/secrets/secret.d.ts +0 -6
- package/dist/aws/runtime/secrets/secret.js +8 -17
- package/dist/database/database.d.ts +7 -0
- package/dist/database/database.js +19 -8
- package/dist/test/db-testutils.js +4 -5
- package/dist/utils/utils.d.ts +15 -0
- package/dist/utils/utils.js +3 -1
- package/package.json +1 -1
- package/src/aws/infra/canaries/canary-alarm.ts +26 -24
- package/src/aws/infra/canaries/canary.ts +2 -4
- package/src/aws/infra/canaries/database-checker.ts +4 -1
- package/src/aws/infra/canaries/url-canary.ts +2 -1
- package/src/aws/infra/canaries/url-checker.ts +28 -11
- package/src/aws/infra/sqs-integration.ts +51 -47
- package/src/aws/infra/sqs-queue.ts +85 -53
- package/src/aws/infra/stack/lambda-configs.ts +6 -69
- package/src/aws/infra/stack/monitoredfunction.ts +2 -1
- package/src/aws/infra/stacks/db-stack.ts +1 -1
- package/src/aws/infra/stacks/network-stack.ts +7 -3
- package/src/aws/runtime/secrets/dbsecret.ts +1 -117
- package/src/aws/runtime/secrets/proxy-holder.ts +2 -5
- package/src/aws/runtime/secrets/rds-holder.ts +2 -1
- package/src/aws/runtime/secrets/secret-holder.ts +8 -20
- package/src/aws/runtime/secrets/secret.ts +17 -22
- package/src/database/database.ts +14 -3
- package/src/test/db-testutils.ts +5 -2
- package/src/utils/utils.ts +2 -2
- package/dist/test/secret.d.ts +0 -3
- package/dist/test/secret.js +0 -25
- package/src/test/secret.ts +0 -23
@@ -1,7 +1,7 @@
|
|
1
1
|
import { IncomingMessage, RequestOptions } from "http";
|
2
2
|
import { Asserter } from "../../../test/asserter";
|
3
3
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
5
5
|
const synthetics = require("Synthetics");
|
6
6
|
import zlib = require("zlib");
|
7
7
|
import { MediaType } from "../../types/mediatypes";
|
@@ -19,12 +19,12 @@ const baseHeaders = {
|
|
19
19
|
Accept: "*/*",
|
20
20
|
} as Record<string, string>;
|
21
21
|
|
22
|
-
type CheckerFunction = (Res: IncomingMessage) => void
|
22
|
+
type CheckerFunction = (Res: IncomingMessage) => Promise<void>;
|
23
23
|
type JsonCheckerFunction<T> = (
|
24
24
|
json: T,
|
25
25
|
body: string,
|
26
26
|
message: IncomingMessage
|
27
|
-
) => void
|
27
|
+
) => Promise<void>;
|
28
28
|
|
29
29
|
export class UrlChecker {
|
30
30
|
private readonly requestOptions: RequestOptions;
|
@@ -43,8 +43,10 @@ export class UrlChecker {
|
|
43
43
|
headers,
|
44
44
|
};
|
45
45
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
46
47
|
synthetics.getConfiguration().disableRequestMetrics();
|
47
48
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
48
50
|
synthetics
|
49
51
|
.getConfiguration()
|
50
52
|
.withIncludeRequestBody(false)
|
@@ -79,6 +81,7 @@ export class UrlChecker {
|
|
79
81
|
},
|
80
82
|
};
|
81
83
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
82
85
|
return synthetics.executeHttpStep(
|
83
86
|
`Verify ${statusCode} for ${url.replace(/auth=.*/, "")}`,
|
84
87
|
requestOptions,
|
@@ -109,6 +112,7 @@ export class UrlChecker {
|
|
109
112
|
},
|
110
113
|
};
|
111
114
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
112
116
|
return synthetics.executeHttpStep(
|
113
117
|
`Verify 404 for ${url}`,
|
114
118
|
requestOptions,
|
@@ -124,6 +128,7 @@ export class UrlChecker {
|
|
124
128
|
},
|
125
129
|
};
|
126
130
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
127
132
|
return synthetics.executeHttpStep(
|
128
133
|
`Verify 400 for ${url}`,
|
129
134
|
requestOptions,
|
@@ -133,6 +138,7 @@ export class UrlChecker {
|
|
133
138
|
|
134
139
|
expect403WithoutApiKey(url: string, mediaType?: MediaType): Promise<void> {
|
135
140
|
if (
|
141
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
136
142
|
!this.requestOptions.headers ||
|
137
143
|
!this.requestOptions.headers[API_KEY_HEADER]
|
138
144
|
) {
|
@@ -147,6 +153,7 @@ export class UrlChecker {
|
|
147
153
|
},
|
148
154
|
};
|
149
155
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
150
157
|
return synthetics.executeHttpStep(
|
151
158
|
`Verify 403 for ${url}`,
|
152
159
|
requestOptions,
|
@@ -202,12 +209,14 @@ function validateStatusCodeAndContentType(
|
|
202
209
|
return (res: IncomingMessage) => {
|
203
210
|
return new Promise((resolve) => {
|
204
211
|
if (res.statusCode !== statusCode) {
|
205
|
-
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
213
|
+
throw new Error(`${res.statusCode!} ${res.statusMessage!}`);
|
206
214
|
}
|
207
215
|
|
208
216
|
if (res.headers["content-type"] !== contentType) {
|
209
217
|
throw new Error(
|
210
|
-
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
219
|
+
`Wrong content-type ${res.headers["content-type"]!}`
|
211
220
|
);
|
212
221
|
}
|
213
222
|
|
@@ -251,7 +260,7 @@ export class ResponseChecker {
|
|
251
260
|
fn: (json: T, body: string, res: IncomingMessage) => void
|
252
261
|
): CheckerFunction {
|
253
262
|
return this.responseChecker((body: string, res: IncomingMessage) => {
|
254
|
-
fn(JSON.parse(body), body, res);
|
263
|
+
fn(JSON.parse(body) as unknown as T, body, res);
|
255
264
|
});
|
256
265
|
}
|
257
266
|
|
@@ -264,7 +273,8 @@ export class ResponseChecker {
|
|
264
273
|
}
|
265
274
|
|
266
275
|
if (res.statusCode < 200 || res.statusCode > 299) {
|
267
|
-
|
276
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
277
|
+
throw new Error(`${res.statusCode} ${res.statusMessage!}`);
|
268
278
|
}
|
269
279
|
|
270
280
|
if (this.checkCors && !res.headers["access-control-allow-origin"]) {
|
@@ -273,7 +283,8 @@ export class ResponseChecker {
|
|
273
283
|
|
274
284
|
if (res.headers["content-type"] !== this.contentType) {
|
275
285
|
throw new Error(
|
276
|
-
|
286
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
287
|
+
`Wrong content-type ${res.headers["content-type"]!}`
|
277
288
|
);
|
278
289
|
}
|
279
290
|
|
@@ -291,7 +302,7 @@ export class ContentChecker {
|
|
291
302
|
return async (res: IncomingMessage): Promise<void> => {
|
292
303
|
const body = await getResponseBody(res);
|
293
304
|
|
294
|
-
fn(JSON.parse(body), body, res);
|
305
|
+
fn(JSON.parse(body) as unknown as T, body, res);
|
295
306
|
};
|
296
307
|
}
|
297
308
|
|
@@ -314,7 +325,8 @@ export class ContentTypeChecker {
|
|
314
325
|
}
|
315
326
|
|
316
327
|
if (res.statusCode < 200 || res.statusCode > 299) {
|
317
|
-
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
329
|
+
throw new Error(`${res.statusCode} ${res.statusMessage!}`);
|
318
330
|
}
|
319
331
|
|
320
332
|
if (!res.headers["access-control-allow-origin"]) {
|
@@ -323,7 +335,8 @@ export class ContentTypeChecker {
|
|
323
335
|
|
324
336
|
if (res.headers["content-type"] !== contentType) {
|
325
337
|
throw new Error(
|
326
|
-
|
338
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
339
|
+
`Wrong content-type ${res.headers["content-type"]!}`
|
327
340
|
);
|
328
341
|
}
|
329
342
|
};
|
@@ -353,6 +366,8 @@ export class HeaderChecker {
|
|
353
366
|
if (!res.headers[headerName]) {
|
354
367
|
throw new Error("Missing header: " + headerName);
|
355
368
|
}
|
369
|
+
|
370
|
+
return Promise.resolve();
|
356
371
|
};
|
357
372
|
}
|
358
373
|
|
@@ -361,6 +376,8 @@ export class HeaderChecker {
|
|
361
376
|
if (res.headers[headerName]) {
|
362
377
|
throw new Error("Header should not exist: " + headerName);
|
363
378
|
}
|
379
|
+
|
380
|
+
return Promise.resolve();
|
364
381
|
};
|
365
382
|
}
|
366
383
|
}
|
@@ -1,9 +1,14 @@
|
|
1
|
-
import {Aws} from "aws-cdk-lib";
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
import { Aws } from "aws-cdk-lib";
|
2
|
+
import {
|
3
|
+
AwsIntegration,
|
4
|
+
PassthroughBehavior,
|
5
|
+
RequestValidator,
|
6
|
+
Resource,
|
7
|
+
} from "aws-cdk-lib/aws-apigateway";
|
8
|
+
import { Queue } from "aws-cdk-lib/aws-sqs";
|
9
|
+
import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
|
10
|
+
import { IModel } from "aws-cdk-lib/aws-apigateway/lib/model";
|
11
|
+
import { Construct } from "constructs";
|
7
12
|
|
8
13
|
export function attachQueueToApiGatewayResource(
|
9
14
|
stack: Construct,
|
@@ -12,89 +17,88 @@ export function attachQueueToApiGatewayResource(
|
|
12
17
|
requestValidator: RequestValidator,
|
13
18
|
resourceName: string,
|
14
19
|
apiKeyRequired: boolean,
|
15
|
-
requestModels?:
|
20
|
+
requestModels?: Record<string, IModel>
|
16
21
|
) {
|
17
22
|
// role for API Gateway
|
18
23
|
const apiGwRole = new Role(stack, `${resourceName}APIGatewayToSQSRole`, {
|
19
|
-
assumedBy: new ServicePrincipal(
|
24
|
+
assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),
|
20
25
|
});
|
21
26
|
// grants API Gateway the right to send SQS messages
|
22
|
-
apiGwRole.addToPolicy(
|
23
|
-
|
24
|
-
queue.queueArn,
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
],
|
29
|
-
}));
|
27
|
+
apiGwRole.addToPolicy(
|
28
|
+
new PolicyStatement({
|
29
|
+
resources: [queue.queueArn],
|
30
|
+
actions: ["sqs:SendMessage"],
|
31
|
+
})
|
32
|
+
);
|
30
33
|
// grants API Gateway the right write CloudWatch Logs
|
31
|
-
apiGwRole.addToPolicy(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
34
|
+
apiGwRole.addToPolicy(
|
35
|
+
new PolicyStatement({
|
36
|
+
resources: ["*"],
|
37
|
+
actions: [
|
38
|
+
"logs:CreateLogGroup",
|
39
|
+
"logs:CreateLogStream",
|
40
|
+
"logs:DescribeLogGroups",
|
41
|
+
"logs:DescribeLogStreams",
|
42
|
+
"logs:PutLogEvents",
|
43
|
+
"logs:GetLogEvents",
|
44
|
+
"logs:FilterLogEvents",
|
45
|
+
],
|
46
|
+
})
|
47
|
+
);
|
45
48
|
// create an integration between API Gateway and an SQS queue
|
46
|
-
const fifoMessageGroupId = queue.fifo
|
49
|
+
const fifoMessageGroupId = queue.fifo
|
50
|
+
? "&MessageGroupId=AlwaysSameFifoGroup"
|
51
|
+
: "";
|
47
52
|
const sqsIntegration = new AwsIntegration({
|
48
|
-
service:
|
49
|
-
integrationHttpMethod:
|
53
|
+
service: "sqs",
|
54
|
+
integrationHttpMethod: "POST",
|
50
55
|
options: {
|
51
56
|
passthroughBehavior: PassthroughBehavior.NEVER,
|
52
57
|
credentialsRole: apiGwRole,
|
53
58
|
requestParameters: {
|
54
59
|
// SQS requires the Content-Type of the HTTP request to be application/x-www-form-urlencoded
|
55
|
-
|
60
|
+
"integration.request.header.Content-Type":
|
61
|
+
"'application/x-www-form-urlencoded'",
|
56
62
|
},
|
57
63
|
requestTemplates: {
|
58
64
|
// map the JSON request to a form parameter, FIFO needs also MessageGroupId
|
59
65
|
// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
|
60
|
-
|
66
|
+
"application/json": `Action=SendMessage${fifoMessageGroupId}&MessageBody=$util.urlEncode($input.body)`,
|
61
67
|
},
|
62
68
|
// these are required by SQS
|
63
69
|
integrationResponses: [
|
64
70
|
{
|
65
|
-
statusCode:
|
71
|
+
statusCode: "200",
|
66
72
|
responseTemplates: {
|
67
|
-
|
73
|
+
"text/html": "Success",
|
68
74
|
},
|
69
75
|
},
|
70
76
|
{
|
71
|
-
statusCode:
|
77
|
+
statusCode: "500",
|
72
78
|
responseTemplates: {
|
73
|
-
|
79
|
+
"text/html": "Error",
|
74
80
|
},
|
75
|
-
selectionPattern:
|
81
|
+
selectionPattern: "500",
|
76
82
|
},
|
77
|
-
|
78
83
|
],
|
79
|
-
|
80
84
|
},
|
81
85
|
path: `${Aws.ACCOUNT_ID}/${queue.queueName}`,
|
82
86
|
});
|
83
|
-
resource.addMethod(
|
87
|
+
resource.addMethod("POST", sqsIntegration, {
|
84
88
|
requestValidator,
|
85
89
|
apiKeyRequired,
|
86
90
|
requestModels: requestModels ?? {},
|
87
91
|
methodResponses: [
|
88
92
|
{
|
89
|
-
statusCode:
|
93
|
+
statusCode: "200",
|
90
94
|
responseParameters: {
|
91
|
-
|
95
|
+
"method.response.header.Content-Type": true,
|
92
96
|
},
|
93
97
|
},
|
94
98
|
{
|
95
|
-
statusCode:
|
99
|
+
statusCode: "500",
|
96
100
|
responseParameters: {
|
97
|
-
|
101
|
+
"method.response.header.Content-Type": true,
|
98
102
|
},
|
99
103
|
},
|
100
104
|
],
|
@@ -1,18 +1,20 @@
|
|
1
|
-
import {Queue, QueueEncryption, QueueProps} from "aws-cdk-lib/aws-sqs";
|
2
|
-
import {Duration} from "aws-cdk-lib";
|
3
|
-
import {BlockPublicAccess, Bucket} from "aws-cdk-lib/aws-s3";
|
4
|
-
import {PolicyStatement} from "aws-cdk-lib/aws-iam";
|
5
|
-
import {InlineCode, Runtime} from "aws-cdk-lib/aws-lambda";
|
6
|
-
import {RetentionDays} from "aws-cdk-lib/aws-logs";
|
7
|
-
import {SqsEventSource} from "aws-cdk-lib/aws-lambda-event-sources";
|
8
|
-
import {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
import {
|
13
|
-
import {
|
14
|
-
import {
|
15
|
-
import {
|
1
|
+
import { Queue, QueueEncryption, QueueProps } from "aws-cdk-lib/aws-sqs";
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
3
|
+
import { BlockPublicAccess, Bucket } from "aws-cdk-lib/aws-s3";
|
4
|
+
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
5
|
+
import { InlineCode, Runtime } from "aws-cdk-lib/aws-lambda";
|
6
|
+
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
7
|
+
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
8
|
+
import {
|
9
|
+
ComparisonOperator,
|
10
|
+
TreatMissingData,
|
11
|
+
} from "aws-cdk-lib/aws-cloudwatch";
|
12
|
+
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
13
|
+
import { ManagedUpload } from "aws-sdk/clients/s3";
|
14
|
+
import { S3 } from "aws-sdk";
|
15
|
+
import { SQSEvent, SQSHandler, SQSRecord } from "aws-lambda";
|
16
|
+
import { DigitrafficStack } from "./stack/stack";
|
17
|
+
import { MonitoredFunction } from "./stack/monitoredfunction";
|
16
18
|
|
17
19
|
/**
|
18
20
|
* Construct for creating SQS-queues.
|
@@ -21,20 +23,23 @@ import {MonitoredFunction} from "./stack/monitoredfunction";
|
|
21
23
|
* and an alarm for the queue. Anything that goes to the dlq will be written into the bucket and the alarm is activated.
|
22
24
|
*/
|
23
25
|
export class DigitrafficSqsQueue extends Queue {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
static create(
|
27
|
+
stack: DigitrafficStack,
|
28
|
+
name: string,
|
29
|
+
props: QueueProps
|
30
|
+
): DigitrafficSqsQueue {
|
29
31
|
const queueName = `${stack.configuration.shortName}-${name}-Queue`;
|
30
|
-
const queueProps = {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
const queueProps = {
|
33
|
+
...props,
|
34
|
+
...{
|
35
|
+
encryption: QueueEncryption.KMS_MANAGED,
|
36
|
+
queueName,
|
37
|
+
deadLetterQueue: props.deadLetterQueue ?? {
|
38
|
+
maxReceiveCount: 2,
|
39
|
+
queue: DigitrafficDLQueue.create(stack, name),
|
40
|
+
},
|
36
41
|
},
|
37
|
-
}
|
42
|
+
};
|
38
43
|
|
39
44
|
return new DigitrafficSqsQueue(stack, queueName, queueProps);
|
40
45
|
}
|
@@ -61,15 +66,15 @@ export class DigitrafficDLQueue {
|
|
61
66
|
functionName: dlqFunctionName,
|
62
67
|
code: getDlqCode(dlqBucket.bucketName),
|
63
68
|
timeout: Duration.seconds(10),
|
64
|
-
handler:
|
69
|
+
handler: "index.handler",
|
65
70
|
memorySize: 128,
|
66
71
|
reservedConcurrentExecutions: 1,
|
67
72
|
});
|
68
73
|
|
69
74
|
const statement = new PolicyStatement();
|
70
|
-
statement.addActions(
|
71
|
-
statement.addActions(
|
72
|
-
statement.addResources(dlqBucket.bucketArn +
|
75
|
+
statement.addActions("s3:PutObject");
|
76
|
+
statement.addActions("s3:PutObjectAcl");
|
77
|
+
statement.addResources(dlqBucket.bucketArn + "/*");
|
73
78
|
|
74
79
|
lambda.addToRolePolicy(statement);
|
75
80
|
lambda.addEventSource(new SqsEventSource(dlq));
|
@@ -84,18 +89,19 @@ function addDLQAlarm(stack: DigitrafficStack, dlqName: string, dlq: Queue) {
|
|
84
89
|
const alarmName = `${dlqName}-Alarm`;
|
85
90
|
dlq.metricNumberOfMessagesReceived({
|
86
91
|
period: Duration.minutes(5),
|
87
|
-
})
|
88
|
-
alarmName,
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
})
|
93
|
+
.createAlarm(stack, alarmName, {
|
94
|
+
alarmName,
|
95
|
+
threshold: 0,
|
96
|
+
evaluationPeriods: 1,
|
97
|
+
treatMissingData: TreatMissingData.NOT_BREACHING,
|
98
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
|
99
|
+
})
|
100
|
+
.addAlarmAction(new SnsAction(stack.warningTopic));
|
94
101
|
}
|
95
102
|
|
96
103
|
function getDlqCode(bName: string): InlineCode {
|
97
|
-
const functionBody = DLQ_LAMBDA_CODE
|
98
|
-
.replace("__bucketName__", bName)
|
104
|
+
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__", bName)
|
99
105
|
.replace("__upload__", uploadToS3.toString())
|
100
106
|
.replace("__doUpload__", doUpload.toString())
|
101
107
|
.replace("__handler__", createHandler().toString().substring(23)); // remove function handler() from signature
|
@@ -103,38 +109,64 @@ function getDlqCode(bName: string): InlineCode {
|
|
103
109
|
return new InlineCode(functionBody);
|
104
110
|
}
|
105
111
|
|
106
|
-
async function uploadToS3(
|
112
|
+
async function uploadToS3(
|
113
|
+
s3: S3,
|
114
|
+
bName: string,
|
115
|
+
body: string,
|
116
|
+
objectName: string
|
117
|
+
): Promise<void> {
|
107
118
|
try {
|
108
|
-
console.info(
|
119
|
+
console.info("writing %s to %s", objectName, bName);
|
109
120
|
await doUpload(s3, bName, body, objectName);
|
110
121
|
} catch (error) {
|
111
122
|
console.warn(error);
|
112
|
-
console.warn(
|
123
|
+
console.warn("method=uploadToS3 retrying upload to bucket %s", bName);
|
113
124
|
try {
|
114
125
|
await doUpload(s3, bName, body, objectName);
|
115
126
|
} catch (e2) {
|
116
|
-
console.error(
|
127
|
+
console.error(
|
128
|
+
"method=uploadToS3 failed retrying upload to bucket %s",
|
129
|
+
bName
|
130
|
+
);
|
117
131
|
}
|
118
132
|
}
|
119
133
|
}
|
120
134
|
|
121
|
-
function doUpload(
|
122
|
-
|
123
|
-
|
124
|
-
|
135
|
+
function doUpload(
|
136
|
+
s3: S3,
|
137
|
+
bName: string,
|
138
|
+
Body: string,
|
139
|
+
Key: string
|
140
|
+
): Promise<ManagedUpload.SendData> {
|
141
|
+
return s3
|
142
|
+
.upload({
|
143
|
+
Bucket: bName,
|
144
|
+
Body,
|
145
|
+
Key,
|
146
|
+
})
|
147
|
+
.promise();
|
125
148
|
}
|
126
149
|
|
127
150
|
// bucketName is unused, will be overridden in the actual lambda code below
|
128
|
-
const bucketName =
|
151
|
+
const bucketName = "";
|
129
152
|
|
130
153
|
function createHandler(): SQSHandler {
|
131
154
|
return async function handler(event: SQSEvent): Promise<void> {
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
133
|
-
const AWS = require(
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
|
156
|
+
const AWS = require("aws-sdk");
|
134
157
|
|
135
158
|
const millis = new Date().getTime();
|
136
|
-
await Promise.all(
|
137
|
-
|
159
|
+
await Promise.all(
|
160
|
+
event.Records.map((e: SQSRecord, idx: number) =>
|
161
|
+
uploadToS3(
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
163
|
+
new AWS.S3(),
|
164
|
+
bucketName,
|
165
|
+
e.body,
|
166
|
+
`dlq-${millis}-${idx}.json`
|
167
|
+
)
|
168
|
+
)
|
169
|
+
);
|
138
170
|
};
|
139
171
|
}
|
140
172
|
|
@@ -6,7 +6,7 @@ import {
|
|
6
6
|
Runtime,
|
7
7
|
} from "aws-cdk-lib/aws-lambda";
|
8
8
|
import { Duration } from "aws-cdk-lib";
|
9
|
-
import {
|
9
|
+
import { IVpc, SubnetSelection } from "aws-cdk-lib/aws-ec2";
|
10
10
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
11
11
|
import { Role } from "aws-cdk-lib/aws-iam";
|
12
12
|
import { DigitrafficStack } from "./stack";
|
@@ -19,26 +19,6 @@ export type DBLambdaEnvironment = LambdaEnvironment & {
|
|
19
19
|
DB_APPLICATION: string;
|
20
20
|
};
|
21
21
|
|
22
|
-
export interface LambdaConfiguration {
|
23
|
-
vpcId: string;
|
24
|
-
allowFromIpAddresses?: string[];
|
25
|
-
privateSubnetIds: string[];
|
26
|
-
availabilityZones: string[];
|
27
|
-
lambdaDbSgId: string;
|
28
|
-
dbProps?: DbProps;
|
29
|
-
defaultLambdaDurationSeconds?: number;
|
30
|
-
logsDestinationArn: string;
|
31
|
-
memorySize?: number;
|
32
|
-
runtime?: Runtime;
|
33
|
-
}
|
34
|
-
|
35
|
-
declare interface DbProps {
|
36
|
-
username: string;
|
37
|
-
password: string;
|
38
|
-
uri?: string;
|
39
|
-
ro_uri?: string;
|
40
|
-
}
|
41
|
-
|
42
22
|
export function databaseFunctionProps(
|
43
23
|
stack: DigitrafficStack,
|
44
24
|
environment: LambdaEnvironment,
|
@@ -61,9 +41,9 @@ export function databaseFunctionProps(
|
|
61
41
|
config
|
62
42
|
),
|
63
43
|
...{
|
64
|
-
vpc: stack.vpc
|
44
|
+
vpc: stack.vpc ?? undefined,
|
65
45
|
vpcSubnets,
|
66
|
-
securityGroup: stack.lambdaDbSg
|
46
|
+
securityGroup: stack.lambdaDbSg ?? undefined,
|
67
47
|
},
|
68
48
|
};
|
69
49
|
}
|
@@ -101,47 +81,6 @@ function getAssetCode(
|
|
101
81
|
return new AssetCode(lambdaPath);
|
102
82
|
}
|
103
83
|
|
104
|
-
/**
|
105
|
-
* Creates a base configuration for a Lambda that uses an RDS database
|
106
|
-
* @param vpc "Private" Lambdas are associated with a VPC
|
107
|
-
* @param lambdaDbSg Security Group shared by Lambda and RDS
|
108
|
-
* @param props Database connection properties for the Lambda
|
109
|
-
* @param config Lambda configuration
|
110
|
-
*/
|
111
|
-
export function dbLambdaConfiguration(
|
112
|
-
vpc: IVpc,
|
113
|
-
lambdaDbSg: ISecurityGroup,
|
114
|
-
props: LambdaConfiguration,
|
115
|
-
config: FunctionParameters
|
116
|
-
): FunctionProps {
|
117
|
-
return {
|
118
|
-
runtime: props.runtime ?? Runtime.NODEJS_16_X,
|
119
|
-
memorySize: props.memorySize ?? config.memorySize ?? 1024,
|
120
|
-
functionName: config.functionName,
|
121
|
-
code: config.code,
|
122
|
-
role: config.role,
|
123
|
-
handler: config.handler,
|
124
|
-
timeout: Duration.seconds(
|
125
|
-
config.timeout ?? props.defaultLambdaDurationSeconds ?? 60
|
126
|
-
),
|
127
|
-
environment: config.environment ?? {
|
128
|
-
DB_USER: props.dbProps?.username ?? "",
|
129
|
-
DB_PASS: props.dbProps?.password ?? "",
|
130
|
-
DB_URI:
|
131
|
-
(config.readOnly
|
132
|
-
? props.dbProps?.ro_uri
|
133
|
-
: props.dbProps?.uri) ?? "",
|
134
|
-
},
|
135
|
-
logRetention: RetentionDays.ONE_YEAR,
|
136
|
-
vpc: vpc,
|
137
|
-
vpcSubnets: {
|
138
|
-
subnets: vpc.privateSubnets,
|
139
|
-
},
|
140
|
-
securityGroups: [lambdaDbSg],
|
141
|
-
reservedConcurrentExecutions: config.reservedConcurrentExecutions ?? 3,
|
142
|
-
};
|
143
|
-
}
|
144
|
-
|
145
84
|
export function defaultLambdaConfiguration(
|
146
85
|
config: FunctionParameters
|
147
86
|
): FunctionProps {
|
@@ -155,7 +94,7 @@ export function defaultLambdaConfiguration(
|
|
155
94
|
reservedConcurrentExecutions: config.reservedConcurrentExecutions,
|
156
95
|
code: config.code,
|
157
96
|
role: config.role,
|
158
|
-
timeout: Duration.seconds(config.timeout
|
97
|
+
timeout: Duration.seconds(config.timeout ?? 10),
|
159
98
|
};
|
160
99
|
if (config.vpc) {
|
161
100
|
return {
|
@@ -163,7 +102,7 @@ export function defaultLambdaConfiguration(
|
|
163
102
|
...{
|
164
103
|
vpc: config.vpc,
|
165
104
|
vpcSubnets: {
|
166
|
-
subnets: config.vpc
|
105
|
+
subnets: config.vpc.privateSubnets,
|
167
106
|
},
|
168
107
|
},
|
169
108
|
};
|
@@ -178,9 +117,7 @@ export interface FunctionParameters {
|
|
178
117
|
code: Code;
|
179
118
|
handler: string;
|
180
119
|
readOnly?: boolean;
|
181
|
-
environment?:
|
182
|
-
[key: string]: string;
|
183
|
-
};
|
120
|
+
environment?: Record<string, string>;
|
184
121
|
reservedConcurrentExecutions?: number;
|
185
122
|
role?: Role;
|
186
123
|
vpc?: IVpc;
|
@@ -82,7 +82,8 @@ export class MonitoredFunction extends Function {
|
|
82
82
|
stack.configuration.production
|
83
83
|
) {
|
84
84
|
throw new Error(
|
85
|
-
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
86
|
+
`Function ${functionProps.functionName!} has DISABLE_ALARMS. Remove before installing to production or define your own properties!`
|
86
87
|
);
|
87
88
|
}
|
88
89
|
|
@@ -139,7 +139,7 @@ export class DbStack extends Stack {
|
|
139
139
|
vpc,
|
140
140
|
securityGroups: [securityGroup],
|
141
141
|
vpcSubnets: {
|
142
|
-
subnetType: SubnetType.
|
142
|
+
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
|
143
143
|
},
|
144
144
|
instanceType: configuration.dbInstanceType,
|
145
145
|
parameterGroup,
|