@beesolve/cdk-constructs 0.1.0

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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # CDK constructs
2
+
3
+ This repository contains various CDK construct built at BeeSolve.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm i @beesolve/cdk-constructs
9
+ ```
@@ -0,0 +1,213 @@
1
+ import { DistributionProps } from "aws-cdk-lib/aws-cloudfront";
2
+ import { Construct } from "constructs";
3
+ /**
4
+ * List of all CloudFront access log columns.
5
+ *
6
+ * @link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#BasicDistributionFileFormat
7
+ */
8
+ declare const cloudFrontAccessLogColumns: readonly [{
9
+ readonly name: "date";
10
+ readonly type: "date";
11
+ }, {
12
+ readonly name: "time";
13
+ readonly type: "string";
14
+ }, {
15
+ readonly name: "x-edge-location";
16
+ readonly type: "string";
17
+ }, {
18
+ readonly name: "sc-bytes";
19
+ readonly type: "string";
20
+ }, {
21
+ readonly name: "c-ip";
22
+ readonly type: "string";
23
+ }, {
24
+ readonly name: "cs-method";
25
+ readonly type: "string";
26
+ }, {
27
+ readonly name: "cs(Host)";
28
+ readonly type: "string";
29
+ }, {
30
+ readonly name: "cs-uri-stem";
31
+ readonly type: "string";
32
+ }, {
33
+ readonly name: "sc-status";
34
+ readonly type: "string";
35
+ }, {
36
+ readonly name: "cs(Referer)";
37
+ readonly type: "string";
38
+ }, {
39
+ readonly name: "cs(User-Agent)";
40
+ readonly type: "string";
41
+ }, {
42
+ readonly name: "cs-uri-query";
43
+ readonly type: "string";
44
+ }, {
45
+ readonly name: "cs(Cookie)";
46
+ readonly type: "string";
47
+ }, {
48
+ readonly name: "x-edge-result-type";
49
+ readonly type: "string";
50
+ }, {
51
+ readonly name: "x-edge-request-id";
52
+ readonly type: "string";
53
+ }, {
54
+ readonly name: "x-host-header";
55
+ readonly type: "string";
56
+ }, {
57
+ readonly name: "cs-protocol";
58
+ readonly type: "string";
59
+ }, {
60
+ readonly name: "cs-bytes";
61
+ readonly type: "string";
62
+ }, {
63
+ readonly name: "time-taken";
64
+ readonly type: "string";
65
+ }, {
66
+ readonly name: "x-forwarded-for";
67
+ readonly type: "string";
68
+ }, {
69
+ readonly name: "ssl-protocol";
70
+ readonly type: "string";
71
+ }, {
72
+ readonly name: "ssl-cipher";
73
+ readonly type: "string";
74
+ }, {
75
+ readonly name: "x-edge-response-result-type";
76
+ readonly type: "string";
77
+ }, {
78
+ readonly name: "cs-protocol-version";
79
+ readonly type: "string";
80
+ }, {
81
+ readonly name: "fle-status";
82
+ readonly type: "string";
83
+ }, {
84
+ readonly name: "fle-encrypted-fields";
85
+ readonly type: "string";
86
+ }, {
87
+ readonly name: "c-port";
88
+ readonly type: "string";
89
+ }, {
90
+ readonly name: "time-to-first-byte";
91
+ readonly type: "string";
92
+ }, {
93
+ readonly name: "x-edge-detailed-result-type";
94
+ readonly type: "string";
95
+ }, {
96
+ readonly name: "sc-content-type";
97
+ readonly type: "string";
98
+ }, {
99
+ readonly name: "sc-content-len";
100
+ readonly type: "string";
101
+ }, {
102
+ readonly name: "sc-range-start";
103
+ readonly type: "string";
104
+ }, {
105
+ readonly name: "sc-range-end";
106
+ readonly type: "string";
107
+ }];
108
+ type CloudFrontAccessLogColumn = (typeof cloudFrontAccessLogColumns)[number]["name"];
109
+ interface CloudFrontAccessLoggingSettingsProps extends Pick<DistributionProps, "logFilePrefix" | "logIncludesCookies"> {
110
+ /**
111
+ * Athena related settings.
112
+ *
113
+ * When not defined, no glue nor athena are being deployed.
114
+ *
115
+ * @default undefined
116
+ */
117
+ readonly athena?: {
118
+ /**
119
+ * Current account number.
120
+ */
121
+ readonly account: string;
122
+ /**
123
+ * Provide custom glue database name.
124
+ *
125
+ * @default cf_logs_db
126
+ */
127
+ readonly glueDbName?: string;
128
+ /**
129
+ * Provide custom glue table name.
130
+ *
131
+ * @default cf_logs_table
132
+ */
133
+ readonly glueTableName?: string;
134
+ /**
135
+ * Provide custom athena workgroup name.
136
+ *
137
+ * @default cf_workgroup
138
+ */
139
+ readonly workGroupName?: string;
140
+ /**
141
+ * You can pick individual columns from the access logs which will be stored inside the glue table.
142
+ *
143
+ * @default all available columns
144
+ */
145
+ readonly columns?: CloudFrontAccessLogColumn[];
146
+ /**
147
+ * When `true` sample query is created within the Athena.
148
+ *
149
+ * @default true
150
+ */
151
+ readonly createSampleQuery?: boolean;
152
+ };
153
+ }
154
+ declare class CloudFrontAccessLoggingSettings extends Construct {
155
+ readonly cloudFrontLoggingSettings: Pick<DistributionProps, "logBucket" | "logFilePrefix" | "enableLogging" | "logIncludesCookies">;
156
+ constructor(scope: Construct, id: string, props: CloudFrontAccessLoggingSettingsProps);
157
+ }
158
+ import { Duration } from "aws-cdk-lib";
159
+ import { Function } from "aws-cdk-lib/aws-lambda";
160
+ import { Queue } from "aws-cdk-lib/aws-sqs";
161
+ import { Construct as Construct2 } from "constructs";
162
+ declare class EmailAlarms extends Construct2 {
163
+ private emailSubscription;
164
+ constructor(scope: Construct2, id: string, props: {
165
+ readonly emailAddress: string;
166
+ });
167
+ readonly reportLambdaErrors: (handler: Function) => void;
168
+ readonly reportSqsErrors: (props: {
169
+ readonly queue: Queue;
170
+ readonly dlq: Queue;
171
+ readonly noMessagesPeriod?: Duration;
172
+ readonly noConsumersPeriod?: Duration;
173
+ }) => void;
174
+ }
175
+ import { Function as Function2 } from "aws-cdk-lib/aws-lambda";
176
+ import { SqsEventSourceProps } from "aws-cdk-lib/aws-lambda-event-sources";
177
+ import { Queue as Queue2, QueueProps } from "aws-cdk-lib/aws-sqs";
178
+ import { Construct as Construct3 } from "constructs";
179
+ interface SqsWithDlqProps {
180
+ readonly queue?: QueueProps;
181
+ readonly dlq?: Partial<Omit<QueueProps, "fifo" | "contentBasedDeduplication" | "deduplicationScope">>;
182
+ }
183
+ declare class SqsWithDlq extends Construct3 {
184
+ readonly queue: Queue2;
185
+ readonly dlq: Queue2;
186
+ constructor(scope: Construct3, id: string, props?: SqsWithDlqProps);
187
+ /**
188
+ * Uses the queue as an input for the given lambda function.
189
+ *
190
+ * Allows to batch and throttle the lambda invocations.
191
+ */
192
+ static asLambdaInput(props: SqsWithDlqLambdaInputProps): SqsWithDlq;
193
+ }
194
+ interface SqsWithDlqLambdaInputProps extends SqsWithDlqProps {
195
+ /**
196
+ * Lambda function used to handle the SQS messages.
197
+ */
198
+ readonly lambda: Function2;
199
+ /**
200
+ * SQS message batching configuration.
201
+ */
202
+ readonly batching?: BatchingConfig;
203
+ /**
204
+ * Set to true to disable the consumption of messages.
205
+ *
206
+ * Useful when the processing needs to be paused during maintenance.
207
+ *
208
+ * @default false
209
+ */
210
+ readonly disabled?: boolean;
211
+ }
212
+ type BatchingConfig = Pick<SqsEventSourceProps, "batchSize" | "maxBatchingWindow" | "reportBatchItemFailures" | "maxConcurrency">;
213
+ export { SqsWithDlqProps, SqsWithDlqLambdaInputProps, SqsWithDlq, EmailAlarms, CloudFrontAccessLoggingSettingsProps, CloudFrontAccessLoggingSettings };
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ // packages/cdk-constructs/src/cloudFrontAccessLoggingSettings.ts
2
+ import { isNotNil } from "@beesolve/helpers";
3
+ import { CfnNamedQuery, CfnWorkGroup } from "aws-cdk-lib/aws-athena";
4
+ import { CfnDatabase, CfnTable } from "aws-cdk-lib/aws-glue";
5
+ import { Bucket, ObjectOwnership } from "aws-cdk-lib/aws-s3";
6
+ import { Construct } from "constructs";
7
+ var cloudFrontAccessLogColumns = [
8
+ { name: "date", type: "date" },
9
+ { name: "time", type: "string" },
10
+ { name: "x-edge-location", type: "string" },
11
+ { name: "sc-bytes", type: "string" },
12
+ { name: "c-ip", type: "string" },
13
+ { name: "cs-method", type: "string" },
14
+ { name: "cs(Host)", type: "string" },
15
+ { name: "cs-uri-stem", type: "string" },
16
+ { name: "sc-status", type: "string" },
17
+ { name: "cs(Referer)", type: "string" },
18
+ { name: "cs(User-Agent)", type: "string" },
19
+ { name: "cs-uri-query", type: "string" },
20
+ { name: "cs(Cookie)", type: "string" },
21
+ { name: "x-edge-result-type", type: "string" },
22
+ { name: "x-edge-request-id", type: "string" },
23
+ { name: "x-host-header", type: "string" },
24
+ { name: "cs-protocol", type: "string" },
25
+ { name: "cs-bytes", type: "string" },
26
+ { name: "time-taken", type: "string" },
27
+ { name: "x-forwarded-for", type: "string" },
28
+ { name: "ssl-protocol", type: "string" },
29
+ { name: "ssl-cipher", type: "string" },
30
+ { name: "x-edge-response-result-type", type: "string" },
31
+ { name: "cs-protocol-version", type: "string" },
32
+ { name: "fle-status", type: "string" },
33
+ { name: "fle-encrypted-fields", type: "string" },
34
+ { name: "c-port", type: "string" },
35
+ { name: "time-to-first-byte", type: "string" },
36
+ { name: "x-edge-detailed-result-type", type: "string" },
37
+ { name: "sc-content-type", type: "string" },
38
+ { name: "sc-content-len", type: "string" },
39
+ { name: "sc-range-start", type: "string" },
40
+ { name: "sc-range-end", type: "string" }
41
+ ];
42
+ var columnDefinitionByKey = Object.fromEntries(cloudFrontAccessLogColumns.map((value) => [value.name, value]));
43
+
44
+ class CloudFrontAccessLoggingSettings extends Construct {
45
+ cloudFrontLoggingSettings;
46
+ constructor(scope, id, props) {
47
+ super(scope, id);
48
+ const logBucket = new Bucket(this, "AccessLogs", {
49
+ objectOwnership: ObjectOwnership.OBJECT_WRITER
50
+ });
51
+ if (props.athena != null) {
52
+ const databaseName = props.athena.glueDbName ?? "cf_logs_db";
53
+ const tableName = props.athena.glueTableName ?? "cf_logs_table";
54
+ const workgroupName = props.athena.workGroupName ?? "cf_workgroup";
55
+ const glueDatabase = new CfnDatabase(this, "AccessLogsGlueDb", {
56
+ catalogId: props.athena.account,
57
+ databaseInput: {
58
+ description: "Glue DB for CloudFront access logs",
59
+ name: databaseName
60
+ }
61
+ });
62
+ const glueTable = new CfnTable(this, "AccessLogsGlueTable", {
63
+ catalogId: props.athena.account,
64
+ databaseName,
65
+ tableInput: {
66
+ name: tableName,
67
+ description: "Glue table for CloudFront access logs",
68
+ storageDescriptor: {
69
+ columns: props.athena.columns == null ? [...cloudFrontAccessLogColumns] : Array.from(new Set(props.athena.columns)).map((column) => columnDefinitionByKey[column]).filter(isNotNil),
70
+ location: `s3://${logBucket.bucketName}/${props.logFilePrefix ?? ""}`,
71
+ inputFormat: "org.apache.hadoop.mapred.TextInputFormat",
72
+ outputFormat: "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat",
73
+ serdeInfo: {
74
+ serializationLibrary: "org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe",
75
+ parameters: {
76
+ "serialization.format": "\t",
77
+ "field.delim": "\t"
78
+ }
79
+ }
80
+ },
81
+ parameters: {
82
+ "skip.header.line.count": 2
83
+ },
84
+ tableType: "EXTERNAL_TABLE"
85
+ }
86
+ });
87
+ glueTable.addDependency(glueDatabase);
88
+ const athenaBucket = new Bucket(this, "AthenaBucket");
89
+ const workgroup = new CfnWorkGroup(this, "Workgroup", {
90
+ name: workgroupName,
91
+ workGroupConfiguration: {
92
+ resultConfiguration: {
93
+ outputLocation: `s3://${athenaBucket.bucketName}/`
94
+ }
95
+ }
96
+ });
97
+ if (props.athena.createSampleQuery !== false) {
98
+ const sampleQuery = new CfnNamedQuery(this, "SampleNamedQuery", {
99
+ name: "sample_query",
100
+ queryString: `SELECT * FROM "${databaseName}"."${tableName}" limit 25;`,
101
+ database: databaseName,
102
+ workGroup: workgroup.name
103
+ });
104
+ sampleQuery.addDependency(workgroup);
105
+ }
106
+ }
107
+ this.cloudFrontLoggingSettings = {
108
+ enableLogging: true,
109
+ logBucket,
110
+ logFilePrefix: props.logFilePrefix,
111
+ logIncludesCookies: props.logIncludesCookies ?? false
112
+ };
113
+ }
114
+ }
115
+ // packages/cdk-constructs/src/emailAlarms.ts
116
+ import {
117
+ Alarm,
118
+ ComparisonOperator,
119
+ TreatMissingData
120
+ } from "aws-cdk-lib/aws-cloudwatch";
121
+ import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
122
+ import { Topic } from "aws-cdk-lib/aws-sns";
123
+ import { EmailSubscription } from "aws-cdk-lib/aws-sns-subscriptions";
124
+ import { Construct as Construct2 } from "constructs";
125
+
126
+ class EmailAlarms extends Construct2 {
127
+ emailSubscription;
128
+ constructor(scope, id, props) {
129
+ super(scope, id);
130
+ this.emailSubscription = new EmailSubscription(props.emailAddress);
131
+ }
132
+ reportLambdaErrors = (handler) => {
133
+ const topic = new Topic(handler, "ErrorAlarmTopic");
134
+ topic.addSubscription(this.emailSubscription);
135
+ const alarm = new Alarm(handler, `ErrorAlarm`, {
136
+ metric: handler.metricErrors(),
137
+ threshold: 1,
138
+ evaluationPeriods: 1,
139
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
140
+ treatMissingData: TreatMissingData.IGNORE
141
+ });
142
+ alarm.addOkAction(new SnsAction(topic));
143
+ alarm.addAlarmAction(new SnsAction(topic));
144
+ };
145
+ reportSqsErrors = (props) => {
146
+ const dlqTopic = new Topic(props.dlq, "DlqAlarmTopic");
147
+ dlqTopic.addSubscription(this.emailSubscription);
148
+ const dlqAlarm = new Alarm(this, "DlqAlarm", {
149
+ alarmDescription: `DLQ for ${props.queue.queueName} is not empty.`,
150
+ metric: props.dlq.metricApproximateNumberOfMessagesVisible(),
151
+ threshold: 1,
152
+ evaluationPeriods: 1,
153
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
154
+ treatMissingData: TreatMissingData.IGNORE
155
+ });
156
+ dlqAlarm.addAlarmAction(new SnsAction(dlqTopic));
157
+ if (props.noMessagesPeriod || props.noConsumersPeriod) {
158
+ const queueTopic = new Topic(props.queue, "QueueAlarmTopic");
159
+ queueTopic.addSubscription(this.emailSubscription);
160
+ if (props.noMessagesPeriod) {
161
+ const noMessages = new Alarm(this, "NoMessagesAlarm", {
162
+ alarmDescription: `Queue received no messages for ${props.noMessagesPeriod.toHumanString()}.`,
163
+ metric: props.queue.metric("NumberOfMessagesSent", {
164
+ statistic: "Sum",
165
+ period: props.noMessagesPeriod
166
+ }),
167
+ threshold: 0,
168
+ evaluationPeriods: 1,
169
+ comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
170
+ treatMissingData: TreatMissingData.BREACHING
171
+ });
172
+ noMessages.addAlarmAction(new SnsAction(queueTopic));
173
+ }
174
+ if (props.noConsumersPeriod) {
175
+ const noConsumers = new Alarm(this, "NoConsumersAlarm", {
176
+ alarmDescription: `Queue had no consumers for ${props.noConsumersPeriod.toHumanString()}.`,
177
+ metric: props.queue.metric("NumberOfMessagesReceived", {
178
+ statistic: "Sum",
179
+ period: props.noConsumersPeriod
180
+ }),
181
+ threshold: 0,
182
+ evaluationPeriods: 1,
183
+ comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
184
+ treatMissingData: TreatMissingData.BREACHING
185
+ });
186
+ noConsumers.addAlarmAction(new SnsAction(queueTopic));
187
+ }
188
+ }
189
+ };
190
+ }
191
+ // packages/cdk-constructs/src/sqsWithDlq.ts
192
+ import { Duration } from "aws-cdk-lib";
193
+ import {
194
+ SqsEventSource
195
+ } from "aws-cdk-lib/aws-lambda-event-sources";
196
+ import { Queue, QueueEncryption } from "aws-cdk-lib/aws-sqs";
197
+ import { Construct as Construct3 } from "constructs";
198
+
199
+ class SqsWithDlq extends Construct3 {
200
+ queue;
201
+ dlq;
202
+ constructor(scope, id, props = {}) {
203
+ super(scope, id);
204
+ this.dlq = new Queue(this, "Dlq", {
205
+ fifo: props.queue?.fifo,
206
+ contentBasedDeduplication: props.queue?.contentBasedDeduplication,
207
+ deduplicationScope: props.queue?.deduplicationScope,
208
+ encryption: QueueEncryption.SQS_MANAGED,
209
+ enforceSSL: true,
210
+ retentionPeriod: Duration.days(14),
211
+ ...props?.dlq
212
+ });
213
+ this.queue = new Queue(this, "Queue", {
214
+ encryption: QueueEncryption.SQS_MANAGED,
215
+ enforceSSL: true,
216
+ retentionPeriod: Duration.days(14),
217
+ ...props.queue,
218
+ deadLetterQueue: {
219
+ queue: this.dlq,
220
+ maxReceiveCount: 5,
221
+ ...props.queue?.deadLetterQueue
222
+ }
223
+ });
224
+ }
225
+ static asLambdaInput(props) {
226
+ const { lambda, batching, ...sqsProps } = props;
227
+ const handlerTimeout = lambda.timeout ?? Duration.seconds(3);
228
+ const sqs = new SqsWithDlq(lambda, "Input", {
229
+ ...sqsProps,
230
+ queue: {
231
+ visibilityTimeout: Duration.seconds(Math.min(handlerTimeout.toSeconds() * 6, 43200)),
232
+ ...sqsProps.queue
233
+ }
234
+ });
235
+ lambda.addEventSource(new SqsEventSource(sqs.queue, {
236
+ ...batching,
237
+ reportBatchItemFailures: true,
238
+ enabled: props.disabled !== true
239
+ }));
240
+ return sqs;
241
+ }
242
+ }
243
+ export {
244
+ SqsWithDlq,
245
+ EmailAlarms,
246
+ CloudFrontAccessLoggingSettings
247
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@beesolve/cdk-constructs",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "homepage": "https://github.com/beesolve/packages/tree/main/packages/cdk-constructs#readme",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/beesolve/packages.git"
13
+ },
14
+ "scripts": {
15
+ "type-check": "tsc --noEmit"
16
+ },
17
+ "peerDependencies": {
18
+ "typescript": "catalog:"
19
+ },
20
+ "peerDependenciesMeta": {
21
+ "typescript": {
22
+ "optional": true
23
+ }
24
+ },
25
+ "dependencies": {
26
+ "aws-cdk-lib": "catalog:",
27
+ "constructs": "catalog:",
28
+ "@beesolve/helpers": "workspace:*"
29
+ },
30
+ "type": "module",
31
+ "exports": {
32
+ ".": {
33
+ "import": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ }
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "module": "./dist/index.js",
41
+ "types": "./dist/index.d.ts"
42
+ }