@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
@@ -6,19 +6,17 @@ const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
|
|
6
6
|
const aws_sns_1 = require("aws-cdk-lib/aws-sns");
|
7
7
|
class CanaryAlarm {
|
8
8
|
constructor(stack, canary, params) {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
alarm.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(aws_sns_1.Topic.fromTopicArn(stack, `${alarmName}-action`, params.alarm.topicArn)));
|
21
|
-
}
|
9
|
+
const alarmName = params.alarm?.alarmName ?? `${params.name}-alarm`;
|
10
|
+
const alarm = new aws_cloudwatch_1.Alarm(stack, alarmName, {
|
11
|
+
alarmName,
|
12
|
+
alarmDescription: params.alarm?.description ?? "",
|
13
|
+
metric: canary.metricSuccessPercent(),
|
14
|
+
evaluationPeriods: params.alarm?.evalutionPeriods ?? 1,
|
15
|
+
threshold: params.alarm?.threshold ?? 100,
|
16
|
+
comparisonOperator: aws_cloudwatch_1.ComparisonOperator.LESS_THAN_THRESHOLD,
|
17
|
+
});
|
18
|
+
if (params.alarm?.topicArn) {
|
19
|
+
alarm.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(aws_sns_1.Topic.fromTopicArn(stack, `${alarmName}-action`, params.alarm.topicArn)));
|
22
20
|
}
|
23
21
|
}
|
24
22
|
}
|
@@ -17,15 +17,13 @@ class DigitrafficCanary extends aws_synthetics_alpha_1.Canary {
|
|
17
17
|
}),
|
18
18
|
environmentVariables: {
|
19
19
|
...environmentVariables,
|
20
|
-
...params
|
20
|
+
...params.canaryEnv,
|
21
21
|
},
|
22
22
|
canaryName,
|
23
23
|
schedule: params.schedule ?? aws_synthetics_alpha_1.Schedule.rate(aws_cdk_lib_1.Duration.minutes(15)),
|
24
24
|
});
|
25
25
|
this.artifactsBucket.grantWrite(role);
|
26
|
-
|
27
|
-
new canary_alarm_1.CanaryAlarm(scope, this, params);
|
28
|
-
}
|
26
|
+
new canary_alarm_1.CanaryAlarm(scope, this, params);
|
29
27
|
}
|
30
28
|
}
|
31
29
|
exports.DigitrafficCanary = DigitrafficCanary;
|
@@ -5,7 +5,7 @@ const database_1 = require("../../../database/database");
|
|
5
5
|
const proxy_holder_1 = require("../../runtime/secrets/proxy-holder");
|
6
6
|
const rds_holder_1 = require("../../runtime/secrets/rds-holder");
|
7
7
|
const utils_1 = require("../../../utils/utils");
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
8
|
+
// 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
|
9
9
|
const synthetics = require("Synthetics");
|
10
10
|
class DatabaseCheck {
|
11
11
|
constructor(name, sql) {
|
@@ -58,7 +58,9 @@ class DatabaseCountChecker {
|
|
58
58
|
constructor(credentialsFunction) {
|
59
59
|
this.checks = [];
|
60
60
|
this.credentialsFunction = credentialsFunction;
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
61
62
|
synthetics.getConfiguration().disableRequestMetrics();
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
62
64
|
synthetics.getConfiguration().withFailedCanaryMetric(true);
|
63
65
|
}
|
64
66
|
static createForProxy() {
|
@@ -100,6 +102,7 @@ class DatabaseCountChecker {
|
|
100
102
|
const checkFunction = () => {
|
101
103
|
check.check(value);
|
102
104
|
};
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
103
106
|
synthetics.executeStep(check.name, checkFunction, stepConfig);
|
104
107
|
}
|
105
108
|
});
|
@@ -23,6 +23,7 @@ class UrlCanary extends canary_1.DigitrafficCanary {
|
|
23
23
|
static create(stack, role, publicApi, params) {
|
24
24
|
return new UrlCanary(stack, role, {
|
25
25
|
...{
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
26
27
|
handler: `${params.name}.handler`,
|
27
28
|
hostname: publicApi.hostname(),
|
28
29
|
apiKeyId: this.getApiKey(publicApi),
|
@@ -3,8 +3,8 @@ import { IncomingMessage } from "http";
|
|
3
3
|
import { MediaType } from "../../types/mediatypes";
|
4
4
|
import { FeatureCollection } from "geojson";
|
5
5
|
export declare const API_KEY_HEADER = "x-api-key";
|
6
|
-
type CheckerFunction = (Res: IncomingMessage) => void
|
7
|
-
type JsonCheckerFunction<T> = (json: T, body: string, message: IncomingMessage) => void
|
6
|
+
type CheckerFunction = (Res: IncomingMessage) => Promise<void>;
|
7
|
+
type JsonCheckerFunction<T> = (json: T, body: string, message: IncomingMessage) => Promise<void>;
|
8
8
|
export declare class UrlChecker {
|
9
9
|
private readonly requestOptions;
|
10
10
|
constructor(hostname: string, apiKey?: string);
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.HeaderChecker = exports.GeoJsonChecker = exports.ContentTypeChecker = exports.ContentChecker = exports.ResponseChecker = exports.UrlChecker = exports.API_KEY_HEADER = void 0;
|
4
4
|
const asserter_1 = require("../../../test/asserter");
|
5
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
5
|
+
// 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
|
6
6
|
const synthetics = require("Synthetics");
|
7
7
|
const zlib = require("zlib");
|
8
8
|
const mediatypes_1 = require("../../types/mediatypes");
|
@@ -28,7 +28,9 @@ class UrlChecker {
|
|
28
28
|
protocol: "https:",
|
29
29
|
headers,
|
30
30
|
};
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
31
32
|
synthetics.getConfiguration().disableRequestMetrics();
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
32
34
|
synthetics
|
33
35
|
.getConfiguration()
|
34
36
|
.withIncludeRequestBody(false)
|
@@ -52,6 +54,7 @@ class UrlChecker {
|
|
52
54
|
path: url,
|
53
55
|
},
|
54
56
|
};
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
55
58
|
return synthetics.executeHttpStep(`Verify ${statusCode} for ${url.replace(/auth=.*/, "")}`, requestOptions, callback);
|
56
59
|
}
|
57
60
|
expect200(url, ...callbacks) {
|
@@ -67,6 +70,7 @@ class UrlChecker {
|
|
67
70
|
path: url,
|
68
71
|
},
|
69
72
|
};
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
70
74
|
return synthetics.executeHttpStep(`Verify 404 for ${url}`, requestOptions, validateStatusCodeAndContentType(404, mediatypes_1.MediaType.TEXT_PLAIN));
|
71
75
|
}
|
72
76
|
expect400(url) {
|
@@ -76,10 +80,13 @@ class UrlChecker {
|
|
76
80
|
path: url,
|
77
81
|
},
|
78
82
|
};
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
79
84
|
return synthetics.executeHttpStep(`Verify 400 for ${url}`, requestOptions, validateStatusCodeAndContentType(400, mediatypes_1.MediaType.TEXT_PLAIN));
|
80
85
|
}
|
81
86
|
expect403WithoutApiKey(url, mediaType) {
|
82
|
-
if (
|
87
|
+
if (
|
88
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
89
|
+
!this.requestOptions.headers ||
|
83
90
|
!this.requestOptions.headers[exports.API_KEY_HEADER]) {
|
84
91
|
console.error("No api key defined");
|
85
92
|
}
|
@@ -90,6 +97,7 @@ class UrlChecker {
|
|
90
97
|
headers: baseHeaders,
|
91
98
|
},
|
92
99
|
};
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
93
101
|
return synthetics.executeHttpStep(`Verify 403 for ${url}`, requestOptions, validateStatusCodeAndContentType(403, mediaType ?? mediatypes_1.MediaType.APPLICATION_JSON));
|
94
102
|
}
|
95
103
|
done() {
|
@@ -129,10 +137,13 @@ function validateStatusCodeAndContentType(statusCode, contentType) {
|
|
129
137
|
return (res) => {
|
130
138
|
return new Promise((resolve) => {
|
131
139
|
if (res.statusCode !== statusCode) {
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
132
141
|
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
133
142
|
}
|
134
143
|
if (res.headers["content-type"] !== contentType) {
|
135
|
-
throw new Error(
|
144
|
+
throw new Error(
|
145
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
146
|
+
`Wrong content-type ${res.headers["content-type"]}`);
|
136
147
|
}
|
137
148
|
resolve();
|
138
149
|
});
|
@@ -172,13 +183,16 @@ class ResponseChecker {
|
|
172
183
|
throw new Error("statusCode missing");
|
173
184
|
}
|
174
185
|
if (res.statusCode < 200 || res.statusCode > 299) {
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
175
187
|
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
176
188
|
}
|
177
189
|
if (this.checkCors && !res.headers["access-control-allow-origin"]) {
|
178
190
|
throw new Error("CORS missing");
|
179
191
|
}
|
180
192
|
if (res.headers["content-type"] !== this.contentType) {
|
181
|
-
throw new Error(
|
193
|
+
throw new Error(
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
195
|
+
`Wrong content-type ${res.headers["content-type"]}`);
|
182
196
|
}
|
183
197
|
const body = await getResponseBody(res);
|
184
198
|
fn(body, res);
|
@@ -208,13 +222,16 @@ class ContentTypeChecker {
|
|
208
222
|
throw new Error("statusCode missing");
|
209
223
|
}
|
210
224
|
if (res.statusCode < 200 || res.statusCode > 299) {
|
225
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
211
226
|
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
212
227
|
}
|
213
228
|
if (!res.headers["access-control-allow-origin"]) {
|
214
229
|
throw new Error("CORS missing");
|
215
230
|
}
|
216
231
|
if (res.headers["content-type"] !== contentType) {
|
217
|
-
throw new Error(
|
232
|
+
throw new Error(
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
234
|
+
`Wrong content-type ${res.headers["content-type"]}`);
|
218
235
|
}
|
219
236
|
};
|
220
237
|
}
|
@@ -238,6 +255,7 @@ class HeaderChecker {
|
|
238
255
|
if (!res.headers[headerName]) {
|
239
256
|
throw new Error("Missing header: " + headerName);
|
240
257
|
}
|
258
|
+
return Promise.resolve();
|
241
259
|
};
|
242
260
|
}
|
243
261
|
static checkHeaderMissing(headerName) {
|
@@ -245,6 +263,7 @@ class HeaderChecker {
|
|
245
263
|
if (res.headers[headerName]) {
|
246
264
|
throw new Error("Header should not exist: " + headerName);
|
247
265
|
}
|
266
|
+
return Promise.resolve();
|
248
267
|
};
|
249
268
|
}
|
250
269
|
}
|
@@ -2,6 +2,4 @@ import { RequestValidator, Resource } from "aws-cdk-lib/aws-apigateway";
|
|
2
2
|
import { Queue } from "aws-cdk-lib/aws-sqs";
|
3
3
|
import { IModel } from "aws-cdk-lib/aws-apigateway/lib/model";
|
4
4
|
import { Construct } from "constructs";
|
5
|
-
export declare function attachQueueToApiGatewayResource(stack: Construct, queue: Queue, resource: Resource, requestValidator: RequestValidator, resourceName: string, apiKeyRequired: boolean, requestModels?:
|
6
|
-
[param: string]: IModel;
|
7
|
-
}): void;
|
5
|
+
export declare function attachQueueToApiGatewayResource(stack: Construct, queue: Queue, resource: Resource, requestValidator: RequestValidator, resourceName: string, apiKeyRequired: boolean, requestModels?: Record<string, IModel>): void;
|
@@ -7,83 +7,79 @@ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
|
|
7
7
|
function attachQueueToApiGatewayResource(stack, queue, resource, requestValidator, resourceName, apiKeyRequired, requestModels) {
|
8
8
|
// role for API Gateway
|
9
9
|
const apiGwRole = new aws_iam_1.Role(stack, `${resourceName}APIGatewayToSQSRole`, {
|
10
|
-
assumedBy: new aws_iam_1.ServicePrincipal(
|
10
|
+
assumedBy: new aws_iam_1.ServicePrincipal("apigateway.amazonaws.com"),
|
11
11
|
});
|
12
12
|
// grants API Gateway the right to send SQS messages
|
13
13
|
apiGwRole.addToPolicy(new aws_iam_1.PolicyStatement({
|
14
|
-
resources: [
|
15
|
-
|
16
|
-
],
|
17
|
-
actions: [
|
18
|
-
'sqs:SendMessage',
|
19
|
-
],
|
14
|
+
resources: [queue.queueArn],
|
15
|
+
actions: ["sqs:SendMessage"],
|
20
16
|
}));
|
21
17
|
// grants API Gateway the right write CloudWatch Logs
|
22
18
|
apiGwRole.addToPolicy(new aws_iam_1.PolicyStatement({
|
23
|
-
resources: [
|
24
|
-
'*',
|
25
|
-
],
|
19
|
+
resources: ["*"],
|
26
20
|
actions: [
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
21
|
+
"logs:CreateLogGroup",
|
22
|
+
"logs:CreateLogStream",
|
23
|
+
"logs:DescribeLogGroups",
|
24
|
+
"logs:DescribeLogStreams",
|
25
|
+
"logs:PutLogEvents",
|
26
|
+
"logs:GetLogEvents",
|
27
|
+
"logs:FilterLogEvents",
|
34
28
|
],
|
35
29
|
}));
|
36
30
|
// create an integration between API Gateway and an SQS queue
|
37
|
-
const fifoMessageGroupId = queue.fifo
|
31
|
+
const fifoMessageGroupId = queue.fifo
|
32
|
+
? "&MessageGroupId=AlwaysSameFifoGroup"
|
33
|
+
: "";
|
38
34
|
const sqsIntegration = new aws_apigateway_1.AwsIntegration({
|
39
|
-
service:
|
40
|
-
integrationHttpMethod:
|
35
|
+
service: "sqs",
|
36
|
+
integrationHttpMethod: "POST",
|
41
37
|
options: {
|
42
38
|
passthroughBehavior: aws_apigateway_1.PassthroughBehavior.NEVER,
|
43
39
|
credentialsRole: apiGwRole,
|
44
40
|
requestParameters: {
|
45
41
|
// SQS requires the Content-Type of the HTTP request to be application/x-www-form-urlencoded
|
46
|
-
|
42
|
+
"integration.request.header.Content-Type": "'application/x-www-form-urlencoded'",
|
47
43
|
},
|
48
44
|
requestTemplates: {
|
49
45
|
// map the JSON request to a form parameter, FIFO needs also MessageGroupId
|
50
46
|
// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
|
51
|
-
|
47
|
+
"application/json": `Action=SendMessage${fifoMessageGroupId}&MessageBody=$util.urlEncode($input.body)`,
|
52
48
|
},
|
53
49
|
// these are required by SQS
|
54
50
|
integrationResponses: [
|
55
51
|
{
|
56
|
-
statusCode:
|
52
|
+
statusCode: "200",
|
57
53
|
responseTemplates: {
|
58
|
-
|
54
|
+
"text/html": "Success",
|
59
55
|
},
|
60
56
|
},
|
61
57
|
{
|
62
|
-
statusCode:
|
58
|
+
statusCode: "500",
|
63
59
|
responseTemplates: {
|
64
|
-
|
60
|
+
"text/html": "Error",
|
65
61
|
},
|
66
|
-
selectionPattern:
|
62
|
+
selectionPattern: "500",
|
67
63
|
},
|
68
64
|
],
|
69
65
|
},
|
70
66
|
path: `${aws_cdk_lib_1.Aws.ACCOUNT_ID}/${queue.queueName}`,
|
71
67
|
});
|
72
|
-
resource.addMethod(
|
68
|
+
resource.addMethod("POST", sqsIntegration, {
|
73
69
|
requestValidator,
|
74
70
|
apiKeyRequired,
|
75
71
|
requestModels: requestModels ?? {},
|
76
72
|
methodResponses: [
|
77
73
|
{
|
78
|
-
statusCode:
|
74
|
+
statusCode: "200",
|
79
75
|
responseParameters: {
|
80
|
-
|
76
|
+
"method.response.header.Content-Type": true,
|
81
77
|
},
|
82
78
|
},
|
83
79
|
{
|
84
|
-
statusCode:
|
80
|
+
statusCode: "500",
|
85
81
|
responseParameters: {
|
86
|
-
|
82
|
+
"method.response.header.Content-Type": true,
|
87
83
|
},
|
88
84
|
},
|
89
85
|
],
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { Queue, QueueProps } from "aws-cdk-lib/aws-sqs";
|
2
|
-
import { Construct } from "constructs";
|
3
2
|
import { DigitrafficStack } from "./stack/stack";
|
4
3
|
/**
|
5
4
|
* Construct for creating SQS-queues.
|
@@ -8,7 +7,6 @@ import { DigitrafficStack } from "./stack/stack";
|
|
8
7
|
* and an alarm for the queue. Anything that goes to the dlq will be written into the bucket and the alarm is activated.
|
9
8
|
*/
|
10
9
|
export declare class DigitrafficSqsQueue extends Queue {
|
11
|
-
constructor(scope: Construct, name: string, props: QueueProps);
|
12
10
|
static create(stack: DigitrafficStack, name: string, props: QueueProps): DigitrafficSqsQueue;
|
13
11
|
}
|
14
12
|
export declare class DigitrafficDLQueue {
|
@@ -18,19 +18,19 @@ const monitoredfunction_1 = require("./stack/monitoredfunction");
|
|
18
18
|
* and an alarm for the queue. Anything that goes to the dlq will be written into the bucket and the alarm is activated.
|
19
19
|
*/
|
20
20
|
class DigitrafficSqsQueue extends aws_sqs_1.Queue {
|
21
|
-
constructor(scope, name, props) {
|
22
|
-
super(scope, name, props);
|
23
|
-
}
|
24
21
|
static create(stack, name, props) {
|
25
22
|
const queueName = `${stack.configuration.shortName}-${name}-Queue`;
|
26
|
-
const queueProps = {
|
23
|
+
const queueProps = {
|
24
|
+
...props,
|
25
|
+
...{
|
27
26
|
encryption: aws_sqs_1.QueueEncryption.KMS_MANAGED,
|
28
27
|
queueName,
|
29
|
-
deadLetterQueue: props.deadLetterQueue
|
28
|
+
deadLetterQueue: props.deadLetterQueue ?? {
|
30
29
|
maxReceiveCount: 2,
|
31
30
|
queue: DigitrafficDLQueue.create(stack, name),
|
32
31
|
},
|
33
|
-
}
|
32
|
+
},
|
33
|
+
};
|
34
34
|
return new DigitrafficSqsQueue(stack, queueName, queueProps);
|
35
35
|
}
|
36
36
|
}
|
@@ -53,14 +53,14 @@ class DigitrafficDLQueue {
|
|
53
53
|
functionName: dlqFunctionName,
|
54
54
|
code: getDlqCode(dlqBucket.bucketName),
|
55
55
|
timeout: aws_cdk_lib_1.Duration.seconds(10),
|
56
|
-
handler:
|
56
|
+
handler: "index.handler",
|
57
57
|
memorySize: 128,
|
58
58
|
reservedConcurrentExecutions: 1,
|
59
59
|
});
|
60
60
|
const statement = new aws_iam_1.PolicyStatement();
|
61
|
-
statement.addActions(
|
62
|
-
statement.addActions(
|
63
|
-
statement.addResources(dlqBucket.bucketArn +
|
61
|
+
statement.addActions("s3:PutObject");
|
62
|
+
statement.addActions("s3:PutObjectAcl");
|
63
|
+
statement.addResources(dlqBucket.bucketArn + "/*");
|
64
64
|
lambda.addToRolePolicy(statement);
|
65
65
|
lambda.addEventSource(new aws_lambda_event_sources_1.SqsEventSource(dlq));
|
66
66
|
addDLQAlarm(stack, dlqName, dlq);
|
@@ -72,17 +72,18 @@ function addDLQAlarm(stack, dlqName, dlq) {
|
|
72
72
|
const alarmName = `${dlqName}-Alarm`;
|
73
73
|
dlq.metricNumberOfMessagesReceived({
|
74
74
|
period: aws_cdk_lib_1.Duration.minutes(5),
|
75
|
-
})
|
75
|
+
})
|
76
|
+
.createAlarm(stack, alarmName, {
|
76
77
|
alarmName,
|
77
78
|
threshold: 0,
|
78
79
|
evaluationPeriods: 1,
|
79
80
|
treatMissingData: aws_cloudwatch_1.TreatMissingData.NOT_BREACHING,
|
80
81
|
comparisonOperator: aws_cloudwatch_1.ComparisonOperator.GREATER_THAN_THRESHOLD,
|
81
|
-
})
|
82
|
+
})
|
83
|
+
.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(stack.warningTopic));
|
82
84
|
}
|
83
85
|
function getDlqCode(bName) {
|
84
|
-
const functionBody = DLQ_LAMBDA_CODE
|
85
|
-
.replace("__bucketName__", bName)
|
86
|
+
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__", bName)
|
86
87
|
.replace("__upload__", uploadToS3.toString())
|
87
88
|
.replace("__doUpload__", doUpload.toString())
|
88
89
|
.replace("__handler__", createHandler().toString().substring(23)); // remove function handler() from signature
|
@@ -90,33 +91,39 @@ function getDlqCode(bName) {
|
|
90
91
|
}
|
91
92
|
async function uploadToS3(s3, bName, body, objectName) {
|
92
93
|
try {
|
93
|
-
console.info(
|
94
|
+
console.info("writing %s to %s", objectName, bName);
|
94
95
|
await doUpload(s3, bName, body, objectName);
|
95
96
|
}
|
96
97
|
catch (error) {
|
97
98
|
console.warn(error);
|
98
|
-
console.warn(
|
99
|
+
console.warn("method=uploadToS3 retrying upload to bucket %s", bName);
|
99
100
|
try {
|
100
101
|
await doUpload(s3, bName, body, objectName);
|
101
102
|
}
|
102
103
|
catch (e2) {
|
103
|
-
console.error(
|
104
|
+
console.error("method=uploadToS3 failed retrying upload to bucket %s", bName);
|
104
105
|
}
|
105
106
|
}
|
106
107
|
}
|
107
108
|
function doUpload(s3, bName, Body, Key) {
|
108
|
-
return s3
|
109
|
-
|
110
|
-
|
109
|
+
return s3
|
110
|
+
.upload({
|
111
|
+
Bucket: bName,
|
112
|
+
Body,
|
113
|
+
Key,
|
114
|
+
})
|
115
|
+
.promise();
|
111
116
|
}
|
112
117
|
// bucketName is unused, will be overridden in the actual lambda code below
|
113
|
-
const bucketName =
|
118
|
+
const bucketName = "";
|
114
119
|
function createHandler() {
|
115
120
|
return async function handler(event) {
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
117
|
-
const AWS = require(
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
|
122
|
+
const AWS = require("aws-sdk");
|
118
123
|
const millis = new Date().getTime();
|
119
|
-
await Promise.all(event.Records.map((e, idx) => uploadToS3(
|
124
|
+
await Promise.all(event.Records.map((e, idx) => uploadToS3(
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
126
|
+
new AWS.S3(), bucketName, e.body, `dlq-${millis}-${idx}.json`)));
|
120
127
|
};
|
121
128
|
}
|
122
129
|
const DLQ_LAMBDA_CODE = `const AWS = require('aws-sdk');
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Architecture, Code, FunctionProps, Runtime } from "aws-cdk-lib/aws-lambda";
|
2
|
-
import {
|
2
|
+
import { IVpc, SubnetSelection } from "aws-cdk-lib/aws-ec2";
|
3
3
|
import { Role } from "aws-cdk-lib/aws-iam";
|
4
4
|
import { DigitrafficStack } from "./stack";
|
5
5
|
import { MonitoredFunctionAlarmProps } from "./monitoredfunction";
|
@@ -8,34 +8,8 @@ export type DBLambdaEnvironment = LambdaEnvironment & {
|
|
8
8
|
SECRET_ID?: string;
|
9
9
|
DB_APPLICATION: string;
|
10
10
|
};
|
11
|
-
export interface LambdaConfiguration {
|
12
|
-
vpcId: string;
|
13
|
-
allowFromIpAddresses?: string[];
|
14
|
-
privateSubnetIds: string[];
|
15
|
-
availabilityZones: string[];
|
16
|
-
lambdaDbSgId: string;
|
17
|
-
dbProps?: DbProps;
|
18
|
-
defaultLambdaDurationSeconds?: number;
|
19
|
-
logsDestinationArn: string;
|
20
|
-
memorySize?: number;
|
21
|
-
runtime?: Runtime;
|
22
|
-
}
|
23
|
-
declare interface DbProps {
|
24
|
-
username: string;
|
25
|
-
password: string;
|
26
|
-
uri?: string;
|
27
|
-
ro_uri?: string;
|
28
|
-
}
|
29
11
|
export declare function databaseFunctionProps(stack: DigitrafficStack, environment: LambdaEnvironment, lambdaName: string, simpleLambdaName: string, config?: Partial<FunctionParameters>): FunctionProps;
|
30
12
|
export declare function lambdaFunctionProps(stack: DigitrafficStack, environment: LambdaEnvironment, lambdaName: string, simpleLambdaName: string, config?: Partial<FunctionParameters>): FunctionProps;
|
31
|
-
/**
|
32
|
-
* Creates a base configuration for a Lambda that uses an RDS database
|
33
|
-
* @param vpc "Private" Lambdas are associated with a VPC
|
34
|
-
* @param lambdaDbSg Security Group shared by Lambda and RDS
|
35
|
-
* @param props Database connection properties for the Lambda
|
36
|
-
* @param config Lambda configuration
|
37
|
-
*/
|
38
|
-
export declare function dbLambdaConfiguration(vpc: IVpc, lambdaDbSg: ISecurityGroup, props: LambdaConfiguration, config: FunctionParameters): FunctionProps;
|
39
13
|
export declare function defaultLambdaConfiguration(config: FunctionParameters): FunctionProps;
|
40
14
|
export interface FunctionParameters {
|
41
15
|
memorySize?: number;
|
@@ -44,9 +18,7 @@ export interface FunctionParameters {
|
|
44
18
|
code: Code;
|
45
19
|
handler: string;
|
46
20
|
readOnly?: boolean;
|
47
|
-
environment?:
|
48
|
-
[key: string]: string;
|
49
|
-
};
|
21
|
+
environment?: Record<string, string>;
|
50
22
|
reservedConcurrentExecutions?: number;
|
51
23
|
role?: Role;
|
52
24
|
vpc?: IVpc;
|
@@ -61,4 +33,3 @@ export type MonitoredFunctionParameters = FunctionParameters & {
|
|
61
33
|
readonly errorAlarmProps?: MonitoredFunctionAlarmProps;
|
62
34
|
readonly throttleAlarmProps?: MonitoredFunctionAlarmProps;
|
63
35
|
};
|
64
|
-
export {};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.defaultLambdaConfiguration = exports.
|
3
|
+
exports.defaultLambdaConfiguration = exports.lambdaFunctionProps = exports.databaseFunctionProps = void 0;
|
4
4
|
const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
|
5
5
|
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
6
6
|
const aws_logs_1 = require("aws-cdk-lib/aws-logs");
|
@@ -13,9 +13,9 @@ function databaseFunctionProps(stack, environment, lambdaName, simpleLambdaName,
|
|
13
13
|
return {
|
14
14
|
...lambdaFunctionProps(stack, environment, lambdaName, simpleLambdaName, config),
|
15
15
|
...{
|
16
|
-
vpc: stack.vpc
|
16
|
+
vpc: stack.vpc ?? undefined,
|
17
17
|
vpcSubnets,
|
18
|
-
securityGroup: stack.lambdaDbSg
|
18
|
+
securityGroup: stack.lambdaDbSg ?? undefined,
|
19
19
|
},
|
20
20
|
};
|
21
21
|
}
|
@@ -42,39 +42,6 @@ function getAssetCode(simpleLambdaName, isSingleLambda) {
|
|
42
42
|
: `dist/lambda/${simpleLambdaName}`;
|
43
43
|
return new aws_lambda_1.AssetCode(lambdaPath);
|
44
44
|
}
|
45
|
-
/**
|
46
|
-
* Creates a base configuration for a Lambda that uses an RDS database
|
47
|
-
* @param vpc "Private" Lambdas are associated with a VPC
|
48
|
-
* @param lambdaDbSg Security Group shared by Lambda and RDS
|
49
|
-
* @param props Database connection properties for the Lambda
|
50
|
-
* @param config Lambda configuration
|
51
|
-
*/
|
52
|
-
function dbLambdaConfiguration(vpc, lambdaDbSg, props, config) {
|
53
|
-
return {
|
54
|
-
runtime: props.runtime ?? aws_lambda_1.Runtime.NODEJS_16_X,
|
55
|
-
memorySize: props.memorySize ?? config.memorySize ?? 1024,
|
56
|
-
functionName: config.functionName,
|
57
|
-
code: config.code,
|
58
|
-
role: config.role,
|
59
|
-
handler: config.handler,
|
60
|
-
timeout: aws_cdk_lib_1.Duration.seconds(config.timeout ?? props.defaultLambdaDurationSeconds ?? 60),
|
61
|
-
environment: config.environment ?? {
|
62
|
-
DB_USER: props.dbProps?.username ?? "",
|
63
|
-
DB_PASS: props.dbProps?.password ?? "",
|
64
|
-
DB_URI: (config.readOnly
|
65
|
-
? props.dbProps?.ro_uri
|
66
|
-
: props.dbProps?.uri) ?? "",
|
67
|
-
},
|
68
|
-
logRetention: aws_logs_1.RetentionDays.ONE_YEAR,
|
69
|
-
vpc: vpc,
|
70
|
-
vpcSubnets: {
|
71
|
-
subnets: vpc.privateSubnets,
|
72
|
-
},
|
73
|
-
securityGroups: [lambdaDbSg],
|
74
|
-
reservedConcurrentExecutions: config.reservedConcurrentExecutions ?? 3,
|
75
|
-
};
|
76
|
-
}
|
77
|
-
exports.dbLambdaConfiguration = dbLambdaConfiguration;
|
78
45
|
function defaultLambdaConfiguration(config) {
|
79
46
|
const props = {
|
80
47
|
runtime: aws_lambda_1.Runtime.NODEJS_16_X,
|
@@ -86,7 +53,7 @@ function defaultLambdaConfiguration(config) {
|
|
86
53
|
reservedConcurrentExecutions: config.reservedConcurrentExecutions,
|
87
54
|
code: config.code,
|
88
55
|
role: config.role,
|
89
|
-
timeout: aws_cdk_lib_1.Duration.seconds(config.timeout
|
56
|
+
timeout: aws_cdk_lib_1.Duration.seconds(config.timeout ?? 10),
|
90
57
|
};
|
91
58
|
if (config.vpc) {
|
92
59
|
return {
|
@@ -94,7 +61,7 @@ function defaultLambdaConfiguration(config) {
|
|
94
61
|
...{
|
95
62
|
vpc: config.vpc,
|
96
63
|
vpcSubnets: {
|
97
|
-
subnets: config.vpc
|
64
|
+
subnets: config.vpc.privateSubnets,
|
98
65
|
},
|
99
66
|
},
|
100
67
|
};
|
@@ -22,7 +22,9 @@ class MonitoredFunction extends aws_lambda_1.Function {
|
|
22
22
|
static create(stack, id, functionProps, props) {
|
23
23
|
if (props === MonitoredFunction.DISABLE_ALARMS &&
|
24
24
|
stack.configuration.production) {
|
25
|
-
throw new Error(
|
25
|
+
throw new Error(
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
27
|
+
`Function ${functionProps.functionName} has DISABLE_ALARMS. Remove before installing to production or define your own properties!`);
|
26
28
|
}
|
27
29
|
return new MonitoredFunction(stack, id, functionProps, stack.alarmTopic, stack.warningTopic, stack.configuration.production, stack.configuration.trafficType, props);
|
28
30
|
}
|
@@ -67,7 +67,7 @@ class DbStack extends aws_cdk_lib_1.Stack {
|
|
67
67
|
vpc,
|
68
68
|
securityGroups: [securityGroup],
|
69
69
|
vpcSubnets: {
|
70
|
-
subnetType: aws_ec2_1.SubnetType.
|
70
|
+
subnetType: aws_ec2_1.SubnetType.PRIVATE_WITH_EGRESS,
|
71
71
|
},
|
72
72
|
instanceType: configuration.dbInstanceType,
|
73
73
|
parameterGroup,
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { Stack } from "aws-cdk-lib";
|
2
2
|
import { Construct } from "constructs";
|
3
|
-
import { Vpc } from "aws-cdk-lib/aws-ec2";
|
3
|
+
import { IVpc, Vpc } from "aws-cdk-lib/aws-ec2";
|
4
4
|
import { InfraStackConfiguration } from "./intra-stack-configuration";
|
5
5
|
export interface NetworkConfiguration {
|
6
6
|
readonly vpcName: string;
|
7
7
|
readonly cidr: string;
|
8
8
|
}
|
9
9
|
export declare class NetworkStack extends Stack {
|
10
|
+
readonly vpc: IVpc;
|
10
11
|
constructor(scope: Construct, id: string, isc: InfraStackConfiguration, configuration: NetworkConfiguration);
|
11
12
|
createVpc(configuration: NetworkConfiguration): Vpc;
|
12
13
|
}
|