@digitraffic/common 2023.12.15-1 → 2024.1.10-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/sqs-queue.js +24 -18
- package/dist/aws/infra/stack/lambda-configs.js +2 -2
- package/dist/aws/infra/stack/stack-checking-aspect.js +6 -10
- package/dist/aws/infra/stack/stack.d.ts +2 -2
- package/dist/aws/infra/stacks/network-stack.js +1 -1
- package/dist/aws/runtime/environment.js +1 -1
- package/dist/aws/runtime/secrets/dbsecret.js +2 -2
- package/dist/aws/types/mediatypes.js +1 -1
- package/dist/database/cached.js +1 -1
- package/dist/database/database.js +1 -1
- package/dist/database/last-updated.js +1 -1
- package/dist/test/httpserver.d.ts +1 -1
- package/dist/test/httpserver.js +9 -4
- package/dist/types/language.js +1 -1
- package/dist/types/traffictype.js +1 -1
- package/dist/utils/retry.js +1 -1
- package/package.json +28 -31
- package/src/aws/infra/sqs-queue.ts +27 -42
- package/src/aws/infra/stack/lambda-configs.ts +7 -7
- package/src/aws/infra/stack/stack-checking-aspect.ts +19 -71
- package/src/test/httpserver.ts +13 -7
@@ -10,6 +10,7 @@ const aws_logs_1 = require("aws-cdk-lib/aws-logs");
|
|
10
10
|
const aws_lambda_event_sources_1 = require("aws-cdk-lib/aws-lambda-event-sources");
|
11
11
|
const aws_cloudwatch_1 = require("aws-cdk-lib/aws-cloudwatch");
|
12
12
|
const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
|
13
|
+
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
13
14
|
const monitoredfunction_1 = require("./stack/monitoredfunction");
|
14
15
|
/**
|
15
16
|
* Construct for creating SQS-queues.
|
@@ -48,7 +49,7 @@ class DigitrafficDLQueue {
|
|
48
49
|
});
|
49
50
|
const dlqFunctionName = `${dlqName}-Function`;
|
50
51
|
const lambda = monitoredfunction_1.MonitoredFunction.create(stack, dlqFunctionName, {
|
51
|
-
runtime: aws_lambda_1.Runtime.
|
52
|
+
runtime: aws_lambda_1.Runtime.NODEJS_20_X,
|
52
53
|
logRetention: aws_logs_1.RetentionDays.ONE_YEAR,
|
53
54
|
functionName: dlqFunctionName,
|
54
55
|
code: getDlqCode(dlqBucket.bucketName),
|
@@ -82,37 +83,40 @@ function addDLQAlarm(stack, dlqName, dlq) {
|
|
82
83
|
})
|
83
84
|
.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(stack.warningTopic));
|
84
85
|
}
|
85
|
-
function getDlqCode(
|
86
|
-
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__",
|
86
|
+
function getDlqCode(Bucket) {
|
87
|
+
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__", Bucket)
|
87
88
|
.replace("__upload__", uploadToS3.toString())
|
88
89
|
.replace("__doUpload__", doUpload.toString())
|
89
90
|
.replace("__handler__", createHandler().toString().substring(23)); // remove function handler() from signature
|
90
91
|
return new aws_lambda_1.InlineCode(functionBody);
|
91
92
|
}
|
92
|
-
async function uploadToS3(s3,
|
93
|
+
async function uploadToS3(s3, Bucket, Body, Key) {
|
93
94
|
try {
|
94
|
-
console.info("writing %s to %s",
|
95
|
-
await doUpload(s3,
|
95
|
+
console.info("writing %s to %s", Key, Bucket);
|
96
|
+
await doUpload(s3, Bucket, Body, Key);
|
96
97
|
}
|
97
98
|
catch (error) {
|
98
99
|
console.warn(error);
|
99
|
-
console.warn("method=uploadToS3 retrying upload to bucket %s",
|
100
|
+
console.warn("method=uploadToS3 retrying upload to bucket %s", Bucket);
|
100
101
|
try {
|
101
|
-
await doUpload(s3,
|
102
|
+
await doUpload(s3, Bucket, Body, Key);
|
102
103
|
}
|
103
104
|
catch (e2) {
|
104
|
-
console.error("method=uploadToS3 failed retrying upload to bucket %s",
|
105
|
+
console.error("method=uploadToS3 failed retrying upload to bucket %s", Bucket);
|
105
106
|
}
|
106
107
|
}
|
107
108
|
}
|
108
|
-
function doUpload(s3,
|
109
|
-
|
110
|
-
.
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
109
|
+
async function doUpload(s3, Bucket, Body, Key) {
|
110
|
+
try {
|
111
|
+
const upload = new lib_storage_1.Upload({
|
112
|
+
client: s3,
|
113
|
+
params: { Bucket, Key, Body },
|
114
|
+
});
|
115
|
+
await upload.done();
|
116
|
+
}
|
117
|
+
catch (error) {
|
118
|
+
console.error(error);
|
119
|
+
}
|
116
120
|
}
|
117
121
|
// bucketName is unused, will be overridden in the actual lambda code below
|
118
122
|
const bucketName = "";
|
@@ -126,7 +130,9 @@ function createHandler() {
|
|
126
130
|
new AWS.S3(), bucketName, e.body, `dlq-${millis}-${idx}.json`)));
|
127
131
|
};
|
128
132
|
}
|
129
|
-
const DLQ_LAMBDA_CODE = `
|
133
|
+
const DLQ_LAMBDA_CODE = `
|
134
|
+
import { S3, S3Client } from "@aws-sdk/client-s3";
|
135
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
130
136
|
const bucketName = "__bucketName__";
|
131
137
|
|
132
138
|
__upload__
|
@@ -22,7 +22,7 @@ function databaseFunctionProps(stack, environment, lambdaName, simpleLambdaName,
|
|
22
22
|
exports.databaseFunctionProps = databaseFunctionProps;
|
23
23
|
function lambdaFunctionProps(stack, environment, lambdaName, simpleLambdaName, config) {
|
24
24
|
return {
|
25
|
-
runtime: config?.runtime ?? aws_lambda_1.Runtime.
|
25
|
+
runtime: config?.runtime ?? aws_lambda_1.Runtime.NODEJS_20_X,
|
26
26
|
architecture: config?.architecture ?? aws_lambda_1.Architecture.ARM_64,
|
27
27
|
memorySize: config?.memorySize ?? 128,
|
28
28
|
functionName: lambdaName,
|
@@ -44,7 +44,7 @@ function getAssetCode(simpleLambdaName, isSingleLambda) {
|
|
44
44
|
}
|
45
45
|
function defaultLambdaConfiguration(config) {
|
46
46
|
const props = {
|
47
|
-
runtime: aws_lambda_1.Runtime.
|
47
|
+
runtime: aws_lambda_1.Runtime.NODEJS_20_X,
|
48
48
|
memorySize: config.memorySize ?? 128,
|
49
49
|
functionName: config.functionName,
|
50
50
|
handler: config.handler,
|
@@ -14,7 +14,7 @@ const aws_logs_1 = require("aws-cdk-lib/aws-logs");
|
|
14
14
|
const change_case_1 = require("change-case");
|
15
15
|
const lodash_1 = __importDefault(require("lodash"));
|
16
16
|
const MAX_CONCURRENCY_LIMIT = 100;
|
17
|
-
const NODE_RUNTIMES = [aws_lambda_1.Runtime.
|
17
|
+
const NODE_RUNTIMES = [aws_lambda_1.Runtime.NODEJS_20_X.name, aws_lambda_1.Runtime.NODEJS_18_X.name];
|
18
18
|
var ResourceType;
|
19
19
|
(function (ResourceType) {
|
20
20
|
ResourceType["stackName"] = "STACK_NAME";
|
@@ -67,13 +67,11 @@ class StackCheckingAspect {
|
|
67
67
|
}
|
68
68
|
checkStack(node) {
|
69
69
|
if (node instanceof stack_1.DigitrafficStack) {
|
70
|
-
if ((node.stackName.includes("Test") ||
|
71
|
-
node.stackName.includes("Tst")) &&
|
70
|
+
if ((node.stackName.includes("Test") || node.stackName.includes("Tst")) &&
|
72
71
|
node.configuration.production) {
|
73
72
|
this.addAnnotation(node, ResourceType.stackName, "Production is set for Test-stack");
|
74
73
|
}
|
75
|
-
if ((node.stackName.includes("Prod") ||
|
76
|
-
node.stackName.includes("Prd")) &&
|
74
|
+
if ((node.stackName.includes("Prod") || node.stackName.includes("Prd")) &&
|
77
75
|
!node.configuration.production) {
|
78
76
|
this.addAnnotation(node, ResourceType.stackName, "Production is not set for Production-stack");
|
79
77
|
}
|
@@ -93,8 +91,7 @@ class StackCheckingAspect {
|
|
93
91
|
if (!node.memorySize) {
|
94
92
|
this.addAnnotation(node, ResourceType.functionMemorySize, "Function must have memorySize");
|
95
93
|
}
|
96
|
-
if (node.runtime !== undefined &&
|
97
|
-
!NODE_RUNTIMES.includes(node.runtime)) {
|
94
|
+
if (node.runtime !== undefined && !NODE_RUNTIMES.includes(node.runtime)) {
|
98
95
|
this.addAnnotation(node, ResourceType.functionRuntime, `Function has wrong runtime ${node.runtime}!`);
|
99
96
|
}
|
100
97
|
if (this.stackShortName &&
|
@@ -131,7 +128,7 @@ class StackCheckingAspect {
|
|
131
128
|
if (path.includes("{")) {
|
132
129
|
return this.isValidPath(path.split("{")[0]);
|
133
130
|
}
|
134
|
-
return (0, change_case_1.
|
131
|
+
return (0, change_case_1.kebabCase)(path) === path;
|
135
132
|
}
|
136
133
|
static isValidQueryString(name) {
|
137
134
|
return lodash_1.default.snakeCase(name) === name;
|
@@ -149,8 +146,7 @@ class StackCheckingAspect {
|
|
149
146
|
const split = key.split(".");
|
150
147
|
const type = split[2];
|
151
148
|
const name = split[3];
|
152
|
-
if (type === "querystring" &&
|
153
|
-
!StackCheckingAspect.isValidQueryString(name)) {
|
149
|
+
if (type === "querystring" && !StackCheckingAspect.isValidQueryString(name)) {
|
154
150
|
this.addAnnotation(node, name, "Querystring should be in snake_case");
|
155
151
|
}
|
156
152
|
});
|
@@ -8,8 +8,8 @@ import { Construct } from "constructs";
|
|
8
8
|
import { TrafficType } from "../../../types/traffictype";
|
9
9
|
import { DBLambdaEnvironment } from "./lambda-configs";
|
10
10
|
export declare const SOLUTION_KEY = "Solution";
|
11
|
-
export declare const SSM_KEY_WARNING_TOPIC
|
12
|
-
export declare const SSM_KEY_ALARM_TOPIC
|
11
|
+
export declare const SSM_KEY_WARNING_TOPIC = "/digitraffic/monitoring/warning-topic";
|
12
|
+
export declare const SSM_KEY_ALARM_TOPIC = "/digitraffic/monitoring/alarm-topic";
|
13
13
|
export interface StackConfiguration {
|
14
14
|
readonly shortName: string;
|
15
15
|
readonly secretId?: string;
|
@@ -21,7 +21,7 @@ class NetworkStack extends core_1.Stack {
|
|
21
21
|
vpcName: configuration.vpcName,
|
22
22
|
availabilityZones: core_1.Stack.of(this)
|
23
23
|
.availabilityZones.sort()
|
24
|
-
.slice(0, 2),
|
24
|
+
.slice(0, 2), // take two first azs
|
25
25
|
enableDnsHostnames: true,
|
26
26
|
enableDnsSupport: true,
|
27
27
|
ipAddresses: aws_ec2_1.IpAddresses.cidr(configuration.cidr),
|
@@ -6,7 +6,7 @@ var EnvKeys;
|
|
6
6
|
EnvKeys["AWS_REGION"] = "AWS_REGION";
|
7
7
|
EnvKeys["SECRET_ID"] = "SECRET_ID";
|
8
8
|
EnvKeys["SECRET_OVERRIDE_AWS_REGION"] = "SECRET_OVERRIDE_AWS_REGION";
|
9
|
-
})(EnvKeys
|
9
|
+
})(EnvKeys || (exports.EnvKeys = EnvKeys = {}));
|
10
10
|
/**
|
11
11
|
* @deprecated Use digitraffic/common/utils/utils#getEnvVariable
|
12
12
|
*/
|
@@ -7,14 +7,14 @@ var RdsProxySecretKey;
|
|
7
7
|
RdsProxySecretKey["password"] = "password";
|
8
8
|
RdsProxySecretKey["proxy_host"] = "proxy_host";
|
9
9
|
RdsProxySecretKey["proxy_ro_host"] = "proxy_ro_host";
|
10
|
-
})(RdsProxySecretKey
|
10
|
+
})(RdsProxySecretKey || (exports.RdsProxySecretKey = RdsProxySecretKey = {}));
|
11
11
|
var RdsSecretKey;
|
12
12
|
(function (RdsSecretKey) {
|
13
13
|
RdsSecretKey["username"] = "username";
|
14
14
|
RdsSecretKey["password"] = "password";
|
15
15
|
RdsSecretKey["host"] = "host";
|
16
16
|
RdsSecretKey["ro_host"] = "ro_host";
|
17
|
-
})(RdsSecretKey
|
17
|
+
})(RdsSecretKey || (exports.RdsSecretKey = RdsSecretKey = {}));
|
18
18
|
function checkExpectedSecretKeys(keys, secret) {
|
19
19
|
const missingKeys = keys.filter((key) => !(key in secret));
|
20
20
|
if (missingKeys.length) {
|
@@ -13,5 +13,5 @@ var MediaType;
|
|
13
13
|
MediaType["TEXT_HTML"] = "text/html";
|
14
14
|
MediaType["TEXT_CSV"] = "text/csv";
|
15
15
|
MediaType["APPLICATION_JSON_UTF8"] = "application/json;charset=UTF-8";
|
16
|
-
})(MediaType
|
16
|
+
})(MediaType || (exports.MediaType = MediaType = {}));
|
17
17
|
//# sourceMappingURL=mediatypes.js.map
|
package/dist/database/cached.js
CHANGED
@@ -17,7 +17,7 @@ var JSON_CACHE_KEY;
|
|
17
17
|
(function (JSON_CACHE_KEY) {
|
18
18
|
JSON_CACHE_KEY["NAUTICAL_WARNINGS_ACTIVE"] = "nautical-warnings-active";
|
19
19
|
JSON_CACHE_KEY["NAUTICAL_WARNINGS_ARCHIVED"] = "nautical-warnings-archived";
|
20
|
-
})(JSON_CACHE_KEY
|
20
|
+
})(JSON_CACHE_KEY || (exports.JSON_CACHE_KEY = JSON_CACHE_KEY = {}));
|
21
21
|
/**
|
22
22
|
*
|
23
23
|
* @param db
|
@@ -11,7 +11,7 @@ var DatabaseEnvironmentKeys;
|
|
11
11
|
DatabaseEnvironmentKeys["DB_URI"] = "DB_URI";
|
12
12
|
DatabaseEnvironmentKeys["DB_RO_URI"] = "DB_RO_URI";
|
13
13
|
DatabaseEnvironmentKeys["DB_APPLICATION"] = "DB_APPLICATION";
|
14
|
-
})(DatabaseEnvironmentKeys
|
14
|
+
})(DatabaseEnvironmentKeys || (exports.DatabaseEnvironmentKeys = DatabaseEnvironmentKeys = {}));
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
16
16
|
const pgp = require("pg-promise")();
|
17
17
|
// convert numeric types to number instead of string
|
@@ -10,7 +10,7 @@ var DataType;
|
|
10
10
|
DataType["MAINTENANCE_TRACKING_DATA_CHECKED"] = "MAINTENANCE_TRACKING_DATA_CHECKED";
|
11
11
|
DataType["PERMIT_DATA"] = "PERMIT_DATA";
|
12
12
|
DataType["PERMIT_DATA_CHECK"] = "PERMIT_DATA_CHECK";
|
13
|
-
})(DataType
|
13
|
+
})(DataType || (exports.DataType = DataType = {}));
|
14
14
|
const UNSET_SUBTYPE = "-";
|
15
15
|
function getLastUpdated(db, datatype) {
|
16
16
|
return db.oneOrNone("select updated from data_updated where data_type=$(datatype) and subtype=$(subtype)", {
|
@@ -11,7 +11,7 @@ export declare class TestHttpServer {
|
|
11
11
|
getCallCount(): number;
|
12
12
|
getRequestBody(callNumber: number): string;
|
13
13
|
listen(port: number, props: ListenProperties, debug?: boolean, statusCode?: number): void;
|
14
|
-
close():
|
14
|
+
close(): Promise<boolean>;
|
15
15
|
private debuglog;
|
16
16
|
}
|
17
17
|
export type ListenProperties = Record<string, (url?: string, data?: string) => string>;
|
package/dist/test/httpserver.js
CHANGED
@@ -65,10 +65,15 @@ class TestHttpServer {
|
|
65
65
|
this.server.listen(port);
|
66
66
|
}
|
67
67
|
close() {
|
68
|
-
|
69
|
-
|
70
|
-
this.server
|
71
|
-
|
68
|
+
return new Promise((resolve, reject) => {
|
69
|
+
this.debuglog("Closing test server");
|
70
|
+
if (this.server !== undefined) {
|
71
|
+
this.server.close((error) => error != null ? reject(false) : resolve(true));
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
resolve(true);
|
75
|
+
}
|
76
|
+
});
|
72
77
|
}
|
73
78
|
debuglog(str) {
|
74
79
|
if (this.debug) {
|
package/dist/types/language.js
CHANGED
@@ -9,5 +9,5 @@ var TrafficType;
|
|
9
9
|
TrafficType["AVIATION"] = "Aviation";
|
10
10
|
TrafficType["MCP"] = "MCP";
|
11
11
|
TrafficType["OTHER"] = "Other";
|
12
|
-
})(TrafficType
|
12
|
+
})(TrafficType || (exports.TrafficType = TrafficType = {}));
|
13
13
|
//# sourceMappingURL=traffictype.js.map
|
package/dist/utils/retry.js
CHANGED
@@ -9,7 +9,7 @@ var RetryLogError;
|
|
9
9
|
RetryLogError[RetryLogError["LOG_ALL_AS_ERRORS"] = 0] = "LOG_ALL_AS_ERRORS";
|
10
10
|
RetryLogError[RetryLogError["LOG_LAST_RETRY_AS_ERROR_OTHERS_AS_WARNS"] = 1] = "LOG_LAST_RETRY_AS_ERROR_OTHERS_AS_WARNS";
|
11
11
|
RetryLogError[RetryLogError["NO_LOGGING"] = 2] = "NO_LOGGING";
|
12
|
-
})(RetryLogError
|
12
|
+
})(RetryLogError || (exports.RetryLogError = RetryLogError = {}));
|
13
13
|
/**
|
14
14
|
* Utility timeout functions for "retry" function.
|
15
15
|
*/
|
package/package.json
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@digitraffic/common",
|
3
|
-
"version": "
|
3
|
+
"version": "2024.1.10-1",
|
4
4
|
"description": "",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
7
7
|
"url": "https://github.com/tmfg/digitraffic-common.git"
|
8
8
|
},
|
9
9
|
"engines": {
|
10
|
-
"node": ">=
|
10
|
+
"node": ">=20 <21"
|
11
11
|
},
|
12
12
|
"license": "EUPL-1.2",
|
13
13
|
"private": false,
|
@@ -21,10 +21,10 @@
|
|
21
21
|
"aws-cdk-lib": "^2.103.0",
|
22
22
|
"aws-sdk": "^2.1481.0",
|
23
23
|
"axios": "^1.6.2",
|
24
|
-
"change-case": "
|
24
|
+
"change-case": "5.0.0",
|
25
25
|
"constructs": "^10.3.0",
|
26
|
-
"date-fns-tz": "~2.0.0",
|
27
26
|
"date-fns": "~2.30.0",
|
27
|
+
"date-fns-tz": "~2.0.0",
|
28
28
|
"etag": "^1.8.1",
|
29
29
|
"geojson-validation": "^1.0.2",
|
30
30
|
"node-ttl": "^0.2.0",
|
@@ -32,51 +32,48 @@
|
|
32
32
|
"pg-promise": "^11.5.4"
|
33
33
|
},
|
34
34
|
"devDependencies": {
|
35
|
-
"@types/aws-lambda": "8.10.
|
36
|
-
"@types/
|
37
|
-
"@types/
|
38
|
-
"@types/jest": "29.5.
|
39
|
-
"@types/lodash": "4.14.
|
40
|
-
"@types/node": "20.
|
41
|
-
"@types/sinon": "
|
42
|
-
"@typescript-eslint/eslint-plugin": "~6.
|
43
|
-
"@typescript-eslint/parser": "^6.
|
44
|
-
"aws-cdk-lib": "~2.
|
45
|
-
"aws-sdk": "~2.
|
46
|
-
"axios": "^1.6.
|
47
|
-
"change-case": "
|
35
|
+
"@types/aws-lambda": "8.10.131",
|
36
|
+
"@types/etag": "1.8.3",
|
37
|
+
"@types/geojson": "7946.0.13",
|
38
|
+
"@types/jest": "29.5.11",
|
39
|
+
"@types/lodash": "4.14.202",
|
40
|
+
"@types/node": "20.10.7",
|
41
|
+
"@types/sinon": "17.0.2",
|
42
|
+
"@typescript-eslint/eslint-plugin": "~6.18.1",
|
43
|
+
"@typescript-eslint/parser": "^6.18.1",
|
44
|
+
"aws-cdk-lib": "~2.118.0",
|
45
|
+
"aws-sdk": "~2.1531.0",
|
46
|
+
"axios": "^1.6.5",
|
47
|
+
"change-case": "5.3.0",
|
48
48
|
"constructs": "10.3.0",
|
49
|
-
"date-fns-tz": "~2.0.0",
|
50
49
|
"date-fns": "~2.30.0",
|
51
|
-
"
|
52
|
-
"eslint
|
50
|
+
"date-fns-tz": "~2.0.0",
|
51
|
+
"eslint": "~8.56.0",
|
52
|
+
"eslint-config-prettier": "^9.1.0",
|
53
53
|
"eslint-plugin-deprecation": "~2.0.0",
|
54
54
|
"etag": "^1.8.1",
|
55
55
|
"geojson-validation": "^1.0.2",
|
56
|
-
"husky": "^8.0.3",
|
57
56
|
"jest": "^29.7.0",
|
58
57
|
"jest-junit": "^16.0.0",
|
59
|
-
"lint-staged": "^15.0
|
58
|
+
"lint-staged": "^15.2.0",
|
60
59
|
"lodash": "~4.17.21",
|
61
60
|
"node-ttl": "^0.2.0",
|
62
61
|
"pg-native": "^3.0.1",
|
63
62
|
"pg-promise": "^11.5.4",
|
64
|
-
"prettier": "^
|
63
|
+
"prettier": "^3.1.1",
|
65
64
|
"rimraf": "^5.0.5",
|
66
|
-
"sinon": "17.0.
|
65
|
+
"sinon": "17.0.1",
|
67
66
|
"ts-jest": "^29.1.1",
|
68
|
-
"typescript": "~
|
67
|
+
"typescript": "~5.3.3",
|
69
68
|
"velocityjs": "2.0.6"
|
70
69
|
},
|
71
|
-
"
|
72
|
-
"aws-sdk",
|
73
|
-
"
|
74
|
-
],
|
75
|
-
"lint-staged": {
|
76
|
-
"*.{js,ts,css,md,yml,yaml,json}": "prettier --write"
|
70
|
+
"dependencies": {
|
71
|
+
"@aws-sdk/client-s3": "~3.472.0",
|
72
|
+
"@aws-sdk/lib-storage": "~3.472.0"
|
77
73
|
},
|
78
74
|
"scripts": {
|
79
75
|
"build": "tsc",
|
76
|
+
"build:watch": "tsc --watch",
|
80
77
|
"lint": "eslint --cache .",
|
81
78
|
"eslint-report": "eslint . --format html",
|
82
79
|
"ci:eslint-report": "eslint . --format html -o report.html || true",
|
@@ -5,13 +5,10 @@ import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
5
5
|
import { InlineCode, Runtime } from "aws-cdk-lib/aws-lambda";
|
6
6
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
7
7
|
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
8
|
-
import {
|
9
|
-
ComparisonOperator,
|
10
|
-
TreatMissingData,
|
11
|
-
} from "aws-cdk-lib/aws-cloudwatch";
|
8
|
+
import { ComparisonOperator, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
12
9
|
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
13
|
-
import {
|
14
|
-
import {
|
10
|
+
import { S3, S3Client } from "@aws-sdk/client-s3";
|
11
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
15
12
|
import { SQSEvent, SQSHandler, SQSRecord } from "aws-lambda";
|
16
13
|
import { DigitrafficStack } from "./stack/stack";
|
17
14
|
import { MonitoredFunction } from "./stack/monitoredfunction";
|
@@ -23,11 +20,7 @@ import { MonitoredFunction } from "./stack/monitoredfunction";
|
|
23
20
|
* and an alarm for the queue. Anything that goes to the dlq will be written into the bucket and the alarm is activated.
|
24
21
|
*/
|
25
22
|
export class DigitrafficSqsQueue extends Queue {
|
26
|
-
static create(
|
27
|
-
stack: DigitrafficStack,
|
28
|
-
name: string,
|
29
|
-
props: QueueProps
|
30
|
-
): DigitrafficSqsQueue {
|
23
|
+
static create(stack: DigitrafficStack, name: string, props: QueueProps): DigitrafficSqsQueue {
|
31
24
|
const queueName = `${stack.configuration.shortName}-${name}-Queue`;
|
32
25
|
const queueProps = {
|
33
26
|
...props,
|
@@ -61,7 +54,7 @@ export class DigitrafficDLQueue {
|
|
61
54
|
|
62
55
|
const dlqFunctionName = `${dlqName}-Function`;
|
63
56
|
const lambda = MonitoredFunction.create(stack, dlqFunctionName, {
|
64
|
-
runtime: Runtime.
|
57
|
+
runtime: Runtime.NODEJS_20_X,
|
65
58
|
logRetention: RetentionDays.ONE_YEAR,
|
66
59
|
functionName: dlqFunctionName,
|
67
60
|
code: getDlqCode(dlqBucket.bucketName),
|
@@ -100,8 +93,8 @@ function addDLQAlarm(stack: DigitrafficStack, dlqName: string, dlq: Queue) {
|
|
100
93
|
.addAlarmAction(new SnsAction(stack.warningTopic));
|
101
94
|
}
|
102
95
|
|
103
|
-
function getDlqCode(
|
104
|
-
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__",
|
96
|
+
function getDlqCode(Bucket: string): InlineCode {
|
97
|
+
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__", Bucket)
|
105
98
|
.replace("__upload__", uploadToS3.toString())
|
106
99
|
.replace("__doUpload__", doUpload.toString())
|
107
100
|
.replace("__handler__", createHandler().toString().substring(23)); // remove function handler() from signature
|
@@ -109,42 +102,32 @@ function getDlqCode(bName: string): InlineCode {
|
|
109
102
|
return new InlineCode(functionBody);
|
110
103
|
}
|
111
104
|
|
112
|
-
async function uploadToS3(
|
113
|
-
s3: S3,
|
114
|
-
bName: string,
|
115
|
-
body: string,
|
116
|
-
objectName: string
|
117
|
-
): Promise<void> {
|
105
|
+
async function uploadToS3(s3: S3 | S3Client, Bucket: string, Body: string, Key: string): Promise<void> {
|
118
106
|
try {
|
119
|
-
console.info("writing %s to %s",
|
120
|
-
await doUpload(s3,
|
107
|
+
console.info("writing %s to %s", Key, Bucket);
|
108
|
+
await doUpload(s3, Bucket, Body, Key);
|
121
109
|
} catch (error) {
|
122
110
|
console.warn(error);
|
123
|
-
console.warn("method=uploadToS3 retrying upload to bucket %s",
|
111
|
+
console.warn("method=uploadToS3 retrying upload to bucket %s", Bucket);
|
124
112
|
try {
|
125
|
-
await doUpload(s3,
|
113
|
+
await doUpload(s3, Bucket, Body, Key);
|
126
114
|
} catch (e2) {
|
127
|
-
console.error(
|
128
|
-
"method=uploadToS3 failed retrying upload to bucket %s",
|
129
|
-
bName
|
130
|
-
);
|
115
|
+
console.error("method=uploadToS3 failed retrying upload to bucket %s", Bucket);
|
131
116
|
}
|
132
117
|
}
|
133
118
|
}
|
134
119
|
|
135
|
-
function doUpload(
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
)
|
141
|
-
|
142
|
-
.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
})
|
147
|
-
.promise();
|
120
|
+
async function doUpload(s3: S3 | S3Client, Bucket: string, Body: string, Key: string) {
|
121
|
+
try {
|
122
|
+
const upload = new Upload({
|
123
|
+
client: s3,
|
124
|
+
params: { Bucket, Key, Body },
|
125
|
+
});
|
126
|
+
|
127
|
+
await upload.done();
|
128
|
+
} catch (error) {
|
129
|
+
console.error(error);
|
130
|
+
}
|
148
131
|
}
|
149
132
|
|
150
133
|
// bucketName is unused, will be overridden in the actual lambda code below
|
@@ -170,7 +153,9 @@ function createHandler(): SQSHandler {
|
|
170
153
|
};
|
171
154
|
}
|
172
155
|
|
173
|
-
const DLQ_LAMBDA_CODE = `
|
156
|
+
const DLQ_LAMBDA_CODE = `
|
157
|
+
import { S3, S3Client } from "@aws-sdk/client-s3";
|
158
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
174
159
|
const bucketName = "__bucketName__";
|
175
160
|
|
176
161
|
__upload__
|
@@ -24,7 +24,7 @@ export function databaseFunctionProps(
|
|
24
24
|
environment: LambdaEnvironment,
|
25
25
|
lambdaName: string,
|
26
26
|
simpleLambdaName: string,
|
27
|
-
config?: Partial<FunctionParameters
|
27
|
+
config?: Partial<FunctionParameters>,
|
28
28
|
): FunctionProps {
|
29
29
|
const vpcSubnets = stack.vpc
|
30
30
|
? {
|
@@ -38,7 +38,7 @@ export function databaseFunctionProps(
|
|
38
38
|
environment,
|
39
39
|
lambdaName,
|
40
40
|
simpleLambdaName,
|
41
|
-
config
|
41
|
+
config,
|
42
42
|
),
|
43
43
|
...{
|
44
44
|
vpc: stack.vpc ?? undefined,
|
@@ -53,10 +53,10 @@ export function lambdaFunctionProps(
|
|
53
53
|
environment: LambdaEnvironment,
|
54
54
|
lambdaName: string,
|
55
55
|
simpleLambdaName: string,
|
56
|
-
config?: Partial<FunctionParameters
|
56
|
+
config?: Partial<FunctionParameters>,
|
57
57
|
): FunctionProps {
|
58
58
|
return {
|
59
|
-
runtime: config?.runtime ?? Runtime.
|
59
|
+
runtime: config?.runtime ?? Runtime.NODEJS_20_X,
|
60
60
|
architecture: config?.architecture ?? Architecture.ARM_64,
|
61
61
|
memorySize: config?.memorySize ?? 128,
|
62
62
|
functionName: lambdaName,
|
@@ -72,7 +72,7 @@ export function lambdaFunctionProps(
|
|
72
72
|
|
73
73
|
function getAssetCode(
|
74
74
|
simpleLambdaName: string,
|
75
|
-
isSingleLambda: boolean
|
75
|
+
isSingleLambda: boolean,
|
76
76
|
): AssetCode {
|
77
77
|
const lambdaPath = isSingleLambda
|
78
78
|
? `dist/lambda/`
|
@@ -82,10 +82,10 @@ function getAssetCode(
|
|
82
82
|
}
|
83
83
|
|
84
84
|
export function defaultLambdaConfiguration(
|
85
|
-
config: FunctionParameters
|
85
|
+
config: FunctionParameters,
|
86
86
|
): FunctionProps {
|
87
87
|
const props: FunctionProps = {
|
88
|
-
runtime: Runtime.
|
88
|
+
runtime: Runtime.NODEJS_20_X,
|
89
89
|
memorySize: config.memorySize ?? 128,
|
90
90
|
functionName: config.functionName,
|
91
91
|
handler: config.handler,
|
@@ -6,12 +6,12 @@ import { IConstruct } from "constructs";
|
|
6
6
|
import { CfnMethod, CfnResource } from "aws-cdk-lib/aws-apigateway";
|
7
7
|
import { CfnQueue } from "aws-cdk-lib/aws-sqs";
|
8
8
|
import { LogRetention } from "aws-cdk-lib/aws-logs";
|
9
|
-
import {
|
9
|
+
import { kebabCase } from "change-case";
|
10
10
|
import _ from "lodash";
|
11
11
|
import IntegrationProperty = CfnMethod.IntegrationProperty;
|
12
12
|
|
13
13
|
const MAX_CONCURRENCY_LIMIT = 100;
|
14
|
-
const NODE_RUNTIMES = [Runtime.
|
14
|
+
const NODE_RUNTIMES = [Runtime.NODEJS_20_X.name, Runtime.NODEJS_18_X.name];
|
15
15
|
|
16
16
|
enum ResourceType {
|
17
17
|
stackName = "STACK_NAME",
|
@@ -61,12 +61,7 @@ export class StackCheckingAspect implements IAspect {
|
|
61
61
|
});
|
62
62
|
}
|
63
63
|
|
64
|
-
private addAnnotation(
|
65
|
-
node: IConstruct,
|
66
|
-
key: ResourceType | string,
|
67
|
-
message: string,
|
68
|
-
isError = true
|
69
|
-
) {
|
64
|
+
private addAnnotation(node: IConstruct, key: ResourceType | string, message: string, isError = true) {
|
70
65
|
const resourceKey = `${node.node.path}/${key}`;
|
71
66
|
const isWhiteListed = this.isWhitelisted(resourceKey);
|
72
67
|
const annotationMessage = `${resourceKey}:${message}`;
|
@@ -83,20 +78,14 @@ export class StackCheckingAspect implements IAspect {
|
|
83
78
|
private checkStack(node: IConstruct) {
|
84
79
|
if (node instanceof DigitrafficStack) {
|
85
80
|
if (
|
86
|
-
(node.stackName.includes("Test") ||
|
87
|
-
node.stackName.includes("Tst")) &&
|
81
|
+
(node.stackName.includes("Test") || node.stackName.includes("Tst")) &&
|
88
82
|
node.configuration.production
|
89
83
|
) {
|
90
|
-
this.addAnnotation(
|
91
|
-
node,
|
92
|
-
ResourceType.stackName,
|
93
|
-
"Production is set for Test-stack"
|
94
|
-
);
|
84
|
+
this.addAnnotation(node, ResourceType.stackName, "Production is set for Test-stack");
|
95
85
|
}
|
96
86
|
|
97
87
|
if (
|
98
|
-
(node.stackName.includes("Prod") ||
|
99
|
-
node.stackName.includes("Prd")) &&
|
88
|
+
(node.stackName.includes("Prod") || node.stackName.includes("Prd")) &&
|
100
89
|
!node.configuration.production
|
101
90
|
) {
|
102
91
|
this.addAnnotation(
|
@@ -116,9 +105,7 @@ export class StackCheckingAspect implements IAspect {
|
|
116
105
|
ResourceType.reservedConcurrentConcurrency,
|
117
106
|
"Function must have reservedConcurrentConcurrency"
|
118
107
|
);
|
119
|
-
} else if (
|
120
|
-
node.reservedConcurrentExecutions > MAX_CONCURRENCY_LIMIT
|
121
|
-
) {
|
108
|
+
} else if (node.reservedConcurrentExecutions > MAX_CONCURRENCY_LIMIT) {
|
122
109
|
this.addAnnotation(
|
123
110
|
node,
|
124
111
|
ResourceType.reservedConcurrentConcurrency,
|
@@ -127,25 +114,14 @@ export class StackCheckingAspect implements IAspect {
|
|
127
114
|
}
|
128
115
|
|
129
116
|
if (!node.timeout) {
|
130
|
-
this.addAnnotation(
|
131
|
-
node,
|
132
|
-
ResourceType.functionTimeout,
|
133
|
-
"Function must have timeout"
|
134
|
-
);
|
117
|
+
this.addAnnotation(node, ResourceType.functionTimeout, "Function must have timeout");
|
135
118
|
}
|
136
119
|
|
137
120
|
if (!node.memorySize) {
|
138
|
-
this.addAnnotation(
|
139
|
-
node,
|
140
|
-
ResourceType.functionMemorySize,
|
141
|
-
"Function must have memorySize"
|
142
|
-
);
|
121
|
+
this.addAnnotation(node, ResourceType.functionMemorySize, "Function must have memorySize");
|
143
122
|
}
|
144
123
|
|
145
|
-
if (
|
146
|
-
node.runtime !== undefined &&
|
147
|
-
!NODE_RUNTIMES.includes(node.runtime)
|
148
|
-
) {
|
124
|
+
if (node.runtime !== undefined && !NODE_RUNTIMES.includes(node.runtime)) {
|
149
125
|
this.addAnnotation(
|
150
126
|
node,
|
151
127
|
ResourceType.functionRuntime,
|
@@ -170,11 +146,7 @@ export class StackCheckingAspect implements IAspect {
|
|
170
146
|
private checkTags(node: IConstruct) {
|
171
147
|
if (node instanceof Stack) {
|
172
148
|
if (!node.tags.tagValues()[SOLUTION_KEY]) {
|
173
|
-
this.addAnnotation(
|
174
|
-
node,
|
175
|
-
ResourceType.tagSolution,
|
176
|
-
"Solution tag is missing"
|
177
|
-
);
|
149
|
+
this.addAnnotation(node, ResourceType.tagSolution, "Solution tag is missing");
|
178
150
|
}
|
179
151
|
}
|
180
152
|
}
|
@@ -192,11 +164,7 @@ export class StackCheckingAspect implements IAspect {
|
|
192
164
|
!c.ignorePublicAcls ||
|
193
165
|
!c.restrictPublicBuckets)
|
194
166
|
) {
|
195
|
-
this.addAnnotation(
|
196
|
-
node,
|
197
|
-
ResourceType.bucketPublicity,
|
198
|
-
"Check bucket publicity"
|
199
|
-
);
|
167
|
+
this.addAnnotation(node, ResourceType.bucketPublicity, "Check bucket publicity");
|
200
168
|
}
|
201
169
|
}
|
202
170
|
}
|
@@ -211,7 +179,7 @@ export class StackCheckingAspect implements IAspect {
|
|
211
179
|
return this.isValidPath(path.split("{")[0]);
|
212
180
|
}
|
213
181
|
|
214
|
-
return
|
182
|
+
return kebabCase(path) === path;
|
215
183
|
}
|
216
184
|
|
217
185
|
private static isValidQueryString(name: string) {
|
@@ -221,16 +189,10 @@ export class StackCheckingAspect implements IAspect {
|
|
221
189
|
private checkResourceCasing(node: IConstruct) {
|
222
190
|
if (node instanceof CfnResource) {
|
223
191
|
if (!StackCheckingAspect.isValidPath(node.pathPart)) {
|
224
|
-
this.addAnnotation(
|
225
|
-
node,
|
226
|
-
ResourceType.resourcePath,
|
227
|
-
"Path part should be in kebab-case"
|
228
|
-
);
|
192
|
+
this.addAnnotation(node, ResourceType.resourcePath, "Path part should be in kebab-case");
|
229
193
|
}
|
230
194
|
} else if (node instanceof CfnMethod) {
|
231
|
-
const integration = node.integration as
|
232
|
-
| IntegrationProperty
|
233
|
-
| undefined;
|
195
|
+
const integration = node.integration as IntegrationProperty | undefined;
|
234
196
|
|
235
197
|
if (integration?.requestParameters) {
|
236
198
|
Object.keys(integration.requestParameters).forEach((key) => {
|
@@ -238,15 +200,8 @@ export class StackCheckingAspect implements IAspect {
|
|
238
200
|
const type = split[2];
|
239
201
|
const name = split[3];
|
240
202
|
|
241
|
-
if (
|
242
|
-
|
243
|
-
!StackCheckingAspect.isValidQueryString(name)
|
244
|
-
) {
|
245
|
-
this.addAnnotation(
|
246
|
-
node,
|
247
|
-
name,
|
248
|
-
"Querystring should be in snake_case"
|
249
|
-
);
|
203
|
+
if (type === "querystring" && !StackCheckingAspect.isValidQueryString(name)) {
|
204
|
+
this.addAnnotation(node, name, "Querystring should be in snake_case");
|
250
205
|
}
|
251
206
|
});
|
252
207
|
}
|
@@ -256,21 +211,14 @@ export class StackCheckingAspect implements IAspect {
|
|
256
211
|
private checkQueueEncryption(node: IConstruct) {
|
257
212
|
if (node instanceof CfnQueue) {
|
258
213
|
if (!node.kmsMasterKeyId) {
|
259
|
-
this.addAnnotation(
|
260
|
-
node,
|
261
|
-
ResourceType.queueEncryption,
|
262
|
-
"Queue must have encryption enabled"
|
263
|
-
);
|
214
|
+
this.addAnnotation(node, ResourceType.queueEncryption, "Queue must have encryption enabled");
|
264
215
|
}
|
265
216
|
}
|
266
217
|
}
|
267
218
|
|
268
219
|
private checkLogGroupRetention(node: IConstruct) {
|
269
220
|
if (node instanceof LogRetention) {
|
270
|
-
const child = node.node.defaultChild as unknown as Record<
|
271
|
-
string,
|
272
|
-
Record<string, string>
|
273
|
-
>;
|
221
|
+
const child = node.node.defaultChild as unknown as Record<string, Record<string, string>>;
|
274
222
|
const retention = child._cfnProperties.RetentionInDays;
|
275
223
|
|
276
224
|
if (!retention) {
|
package/src/test/httpserver.ts
CHANGED
@@ -30,7 +30,7 @@ export class TestHttpServer {
|
|
30
30
|
port: number,
|
31
31
|
props: ListenProperties,
|
32
32
|
debug = false,
|
33
|
-
statusCode = 200
|
33
|
+
statusCode = 200,
|
34
34
|
) {
|
35
35
|
this.debug = debug;
|
36
36
|
this.messageStack = [];
|
@@ -62,7 +62,7 @@ export class TestHttpServer {
|
|
62
62
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
63
63
|
res.setHeader(
|
64
64
|
"Access-Control-Allow-Headers",
|
65
|
-
"Authorization,X-User-Id,X-Auth-Token"
|
65
|
+
"Authorization,X-User-Id,X-Auth-Token",
|
66
66
|
);
|
67
67
|
res.writeHead(statusCode);
|
68
68
|
|
@@ -85,11 +85,17 @@ export class TestHttpServer {
|
|
85
85
|
this.server.listen(port);
|
86
86
|
}
|
87
87
|
|
88
|
-
close() {
|
89
|
-
|
90
|
-
|
91
|
-
this.server
|
92
|
-
|
88
|
+
close(): Promise<boolean> {
|
89
|
+
return new Promise((resolve, reject) => {
|
90
|
+
this.debuglog("Closing test server");
|
91
|
+
if (this.server !== undefined) {
|
92
|
+
this.server.close((error) =>
|
93
|
+
error != null ? reject(false) : resolve(true),
|
94
|
+
);
|
95
|
+
} else {
|
96
|
+
resolve(true);
|
97
|
+
}
|
98
|
+
});
|
93
99
|
}
|
94
100
|
|
95
101
|
private debuglog(str: string) {
|