cdk-nuxt 2.0.0 → 2.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.
Files changed (85) hide show
  1. package/README.md +45 -19
  2. package/index.d.ts +2 -1
  3. package/index.js +3 -3
  4. package/lib/cli/deploy-server.js +33 -5
  5. package/lib/cli/destroy-server.js +13 -0
  6. package/lib/functions/access-logs-analysis/group-by-date/.gitignore +2 -0
  7. package/lib/functions/access-logs-analysis/group-by-date/build/app/index.d.ts +2 -0
  8. package/lib/functions/access-logs-analysis/group-by-date/build/app/index.js +57 -0
  9. package/lib/functions/access-logs-analysis/group-by-date/build/app/index.js.map +1 -0
  10. package/lib/functions/access-logs-analysis/group-by-date/index.d.ts +2 -0
  11. package/lib/functions/access-logs-analysis/group-by-date/index.js +57 -0
  12. package/lib/functions/access-logs-analysis/group-by-date/index.ts +64 -0
  13. package/lib/functions/access-logs-analysis/group-by-date/package.json +20 -0
  14. package/lib/functions/access-logs-analysis/group-by-date/tsconfig.json +33 -0
  15. package/lib/functions/access-logs-analysis/group-by-date/yarn.lock +1203 -0
  16. package/lib/functions/access-logs-analysis/partitioning/.gitignore +2 -0
  17. package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.d.ts +1 -0
  18. package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.js +57 -0
  19. package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.js.map +1 -0
  20. package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.d.ts +2 -0
  21. package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.js +72 -0
  22. package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.js.map +1 -0
  23. package/lib/functions/access-logs-analysis/partitioning/build/app/types.d.ts +7 -0
  24. package/lib/functions/access-logs-analysis/partitioning/build/app/types.js +3 -0
  25. package/lib/functions/access-logs-analysis/partitioning/build/app/types.js.map +1 -0
  26. package/lib/functions/access-logs-analysis/partitioning/build/app/util.d.ts +9 -0
  27. package/lib/functions/access-logs-analysis/partitioning/build/app/util.js +44 -0
  28. package/lib/functions/access-logs-analysis/partitioning/build/app/util.js.map +1 -0
  29. package/lib/functions/access-logs-analysis/partitioning/create-partition.d.ts +1 -0
  30. package/lib/functions/access-logs-analysis/partitioning/create-partition.js +57 -0
  31. package/lib/functions/access-logs-analysis/partitioning/create-partition.ts +58 -0
  32. package/lib/functions/access-logs-analysis/partitioning/package.json +20 -0
  33. package/lib/functions/access-logs-analysis/partitioning/transform-partition.d.ts +2 -0
  34. package/lib/functions/access-logs-analysis/partitioning/transform-partition.js +72 -0
  35. package/lib/functions/access-logs-analysis/partitioning/transform-partition.ts +83 -0
  36. package/lib/functions/access-logs-analysis/partitioning/tsconfig.json +33 -0
  37. package/lib/functions/access-logs-analysis/partitioning/types.d.ts +7 -0
  38. package/lib/functions/access-logs-analysis/partitioning/types.js +3 -0
  39. package/lib/functions/access-logs-analysis/partitioning/types.ts +8 -0
  40. package/lib/functions/access-logs-analysis/partitioning/util.d.ts +9 -0
  41. package/lib/functions/access-logs-analysis/partitioning/util.js +44 -0
  42. package/lib/functions/access-logs-analysis/partitioning/util.ts +52 -0
  43. package/lib/functions/access-logs-analysis/partitioning/yarn.lock +951 -0
  44. package/lib/functions/assets-cleanup/build/app/index.js +1 -1
  45. package/lib/functions/assets-cleanup/build/app/index.js.map +1 -1
  46. package/lib/functions/assets-cleanup/index.js +2 -2
  47. package/lib/functions/assets-cleanup/index.ts +2 -2
  48. package/lib/stack/NuxtAppStackProps.js +3 -0
  49. package/lib/stack/{nuxt-app-static-assets.d.ts → NuxtAppStaticAssets.d.ts} +9 -4
  50. package/lib/stack/NuxtAppStaticAssets.js +71 -0
  51. package/lib/stack/{nuxt-app-static-assets.ts → NuxtAppStaticAssets.ts} +31 -24
  52. package/lib/stack/access-logs-analysis/AccessLogsAnalysis.d.ts +66 -0
  53. package/lib/stack/access-logs-analysis/AccessLogsAnalysis.js +270 -0
  54. package/lib/stack/access-logs-analysis/AccessLogsAnalysis.ts +330 -0
  55. package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.d.ts +21 -0
  56. package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.js +3 -0
  57. package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.ts +26 -0
  58. package/lib/stack/access-logs-analysis/AccessLogsParquetTable.d.ts +9 -0
  59. package/lib/stack/access-logs-analysis/AccessLogsParquetTable.js +25 -0
  60. package/lib/stack/access-logs-analysis/AccessLogsParquetTable.ts +23 -0
  61. package/lib/stack/access-logs-analysis/AccessLogsTableConfig.d.ts +8 -0
  62. package/lib/stack/access-logs-analysis/AccessLogsTableConfig.js +167 -0
  63. package/lib/stack/access-logs-analysis/AccessLogsTableConfig.ts +164 -0
  64. package/lib/stack/access-logs-analysis/AccessLogsTableProps.d.ts +7 -0
  65. package/lib/stack/access-logs-analysis/AccessLogsTableProps.js +3 -0
  66. package/lib/stack/access-logs-analysis/AccessLogsTableProps.ts +8 -0
  67. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.d.ts +36 -0
  68. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.js +60 -0
  69. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.ts +73 -0
  70. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.d.ts +9 -0
  71. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.js +37 -0
  72. package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.ts +44 -0
  73. package/lib/stack/server/{nuxt-server-app-stack.d.ts → NuxtServerAppStack.d.ts} +13 -75
  74. package/lib/stack/server/NuxtServerAppStack.js +483 -0
  75. package/lib/stack/server/{nuxt-server-app-stack.ts → NuxtServerAppStack.ts} +53 -91
  76. package/lib/stack/server/NuxtServerAppStackProps.d.ts +80 -0
  77. package/lib/stack/server/NuxtServerAppStackProps.js +3 -0
  78. package/lib/stack/server/NuxtServerAppStackProps.ts +94 -0
  79. package/lib/templates/stack-index-server.ts +108 -15
  80. package/package.json +3 -1
  81. package/lib/stack/nuxt-app-stack-props.js +0 -3
  82. package/lib/stack/nuxt-app-static-assets.js +0 -72
  83. package/lib/stack/server/nuxt-server-app-stack.js +0 -447
  84. /package/lib/stack/{nuxt-app-stack-props.d.ts → NuxtAppStackProps.d.ts} +0 -0
  85. /package/lib/stack/{nuxt-app-stack-props.ts → NuxtAppStackProps.ts} +0 -0
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccessLogsAnalysis = void 0;
4
+ const aws_s3_1 = require("aws-cdk-lib/aws-s3");
5
+ const constructs_1 = require("constructs");
6
+ const aws_athena_1 = require("aws-cdk-lib/aws-athena");
7
+ const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
8
+ const aws_events_1 = require("aws-cdk-lib/aws-events");
9
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
10
+ const aws_glue_alpha_1 = require("@aws-cdk/aws-glue-alpha");
11
+ const aws_lambda_event_sources_1 = require("aws-cdk-lib/aws-lambda-event-sources");
12
+ const aws_logs_1 = require("aws-cdk-lib/aws-logs");
13
+ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
14
+ const aws_events_targets_1 = require("aws-cdk-lib/aws-events-targets");
15
+ const path = require("path");
16
+ /**
17
+ * Provides the AWS resources to analyze access logs. This construct is derived from the official AWS sample
18
+ * CloudFormation stack:
19
+ * {@link https://github.com/aws-samples/amazon-cloudfront-access-logs-queries/blob/mainline/template.yaml}
20
+ */
21
+ class AccessLogsAnalysis extends constructs_1.Construct {
22
+ constructor(scope, id, props) {
23
+ super(scope, id);
24
+ this.resourceIdPrefix = props.resourcePrefix;
25
+ this.bucket = props.bucket;
26
+ this.setupLifecycleRules(props);
27
+ this.workgroup = this.createWorkgroup();
28
+ this.database = this.createGlueDatabase();
29
+ this.accessLogsByDateTable = this.createAccessLogsByDateTable();
30
+ this.accessLogsParquetTable = this.createAccessLogsParquetTable();
31
+ this.groupByDateLayer = this.createGroupByDateLayer();
32
+ this.groupByDateLambda = this.createGroupByDateLambda();
33
+ this.partitioningLayer = this.createPartitioningLayer();
34
+ this.createPartitionLambda = this.createCreatePartitionLambda();
35
+ this.transformPartitionLambda = this.createTransformPartitionLambda();
36
+ this.createPartitionsScheduler = this.createCreatePartitionsScheduler();
37
+ this.transformPartitionsScheduler = this.createTransformPartitionsScheduler(props);
38
+ }
39
+ setupLifecycleRules(props) {
40
+ // cleanup raw and partitioned access logs after a configurable time range
41
+ var _a, _b, _c;
42
+ this.bucket.addLifecycleRule({
43
+ id: `${this.resourceIdPrefix}-cleanup-unprocessed`,
44
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_UNPROCESSED}/*`,
45
+ expiration: (_a = props.expireRawLogsAfter) !== null && _a !== void 0 ? _a : aws_cdk_lib_1.Duration.days(7),
46
+ });
47
+ this.bucket.addLifecycleRule({
48
+ id: `${this.resourceIdPrefix}-cleanup-grouped`,
49
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_GROUPED_BY_DATE}/*`,
50
+ expiration: (_b = props.expireIntermediateLogsAfter) !== null && _b !== void 0 ? _b : aws_cdk_lib_1.Duration.days(7),
51
+ });
52
+ this.bucket.addLifecycleRule({
53
+ id: `${this.resourceIdPrefix}-cleanup-transformed`,
54
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_TRANSFORMED}/*`,
55
+ expiration: (_c = props.expireTransformedLogsAfter) !== null && _c !== void 0 ? _c : aws_cdk_lib_1.Duration.days(180),
56
+ });
57
+ /* CHECKME: delete query results after 1 week
58
+ this.bucket.addLifecycleRule({
59
+ id: `${this.resourceIdPrefix}-cleanup-query-results`,
60
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_ATHENA_RESULTS}/*`,
61
+ expiration: Duration.days(7),
62
+ });
63
+ */
64
+ }
65
+ createWorkgroup() {
66
+ const workgroupName = `${this.resourceIdPrefix}-workgroup`;
67
+ // due to a current bug, the tags are not automatically assigned to the workgroup
68
+ // note, that the keys must be converted to lower case
69
+ const tags = aws_cdk_lib_1.Stack.of(this)
70
+ .tags.renderTags()
71
+ .map((tag) => ({ key: tag.Key, value: tag.Value }));
72
+ return new aws_athena_1.CfnWorkGroup(this, workgroupName, {
73
+ name: workgroupName,
74
+ workGroupConfiguration: {
75
+ publishCloudWatchMetricsEnabled: false,
76
+ resultConfiguration: {
77
+ outputLocation: `s3://${this.bucket.bucketName}/${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_ATHENA_RESULTS}`,
78
+ },
79
+ enforceWorkGroupConfiguration: true
80
+ },
81
+ tags,
82
+ });
83
+ }
84
+ createGlueDatabase() {
85
+ const databaseName = `${this.resourceIdPrefix}-database`;
86
+ return new aws_glue_alpha_1.Database(this, databaseName, {
87
+ // Athena doesn't support dashes in database/table names
88
+ databaseName: databaseName.replace(/-/g, '_'),
89
+ });
90
+ }
91
+ createGroupByDateLayer() {
92
+ const layerVersionName = `${this.resourceIdPrefix}-group-by-date-dependencies`;
93
+ return new aws_lambda_1.LayerVersion(this, layerVersionName, {
94
+ layerVersionName,
95
+ compatibleArchitectures: [aws_lambda_1.Architecture.ARM_64, aws_lambda_1.Architecture.X86_64],
96
+ compatibleRuntimes: [aws_lambda_1.Runtime.NODEJS_14_X, aws_lambda_1.Runtime.NODEJS_16_X],
97
+ code: aws_lambda_1.Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/group-by-date/build/layer')),
98
+ removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
99
+ });
100
+ }
101
+ createPartitioningLayer() {
102
+ const layerVersionName = `${this.resourceIdPrefix}-partitioning-dependencies`;
103
+ return new aws_lambda_1.LayerVersion(this, layerVersionName, {
104
+ layerVersionName,
105
+ compatibleArchitectures: [aws_lambda_1.Architecture.ARM_64, aws_lambda_1.Architecture.X86_64],
106
+ compatibleRuntimes: [aws_lambda_1.Runtime.NODEJS_14_X, aws_lambda_1.Runtime.NODEJS_16_X],
107
+ code: aws_lambda_1.Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/layer')),
108
+ removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
109
+ });
110
+ }
111
+ /**
112
+ * A Lambda function to be triggered whenever a new access log is created.
113
+ * It moves the raw access logs into a sub folder hierarchy by year, month, day and hour.
114
+ */
115
+ createGroupByDateLambda() {
116
+ const functionName = `${this.resourceIdPrefix}-group-by-date`;
117
+ const lambda = new aws_lambda_1.Function(this, functionName, {
118
+ functionName,
119
+ architecture: aws_lambda_1.Architecture.ARM_64,
120
+ runtime: aws_lambda_1.Runtime.NODEJS_16_X,
121
+ code: aws_lambda_1.Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/group-by-date/build/app')),
122
+ memorySize: 512,
123
+ timeout: aws_cdk_lib_1.Duration.seconds(20),
124
+ handler: 'index.handler',
125
+ layers: [this.groupByDateLayer],
126
+ events: [
127
+ new aws_lambda_event_sources_1.S3EventSource(this.bucket, {
128
+ events: [aws_s3_1.EventType.OBJECT_CREATED],
129
+ filters: [this.getUnprocessedObjectsFilter()],
130
+ }),
131
+ ],
132
+ logRetention: aws_logs_1.RetentionDays.TWO_WEEKS,
133
+ environment: {
134
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
135
+ NODE_OPTIONS: '--enable-source-maps',
136
+ TARGET_FOLDER: AccessLogsAnalysis.ACCESS_LOGS_FOLDER_GROUPED_BY_DATE,
137
+ RAW_ACCESS_LOG_FILE_PATTERN: this.getRawAccessLogFilePattern().source,
138
+ },
139
+ });
140
+ this.bucket.grantReadWrite(lambda);
141
+ this.bucket.grantDelete(lambda, `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_UNPROCESSED}/*`);
142
+ return lambda;
143
+ }
144
+ /**
145
+ * Creates a new partition for the upcoming hour in the access log database.
146
+ */
147
+ createCreatePartitionLambda() {
148
+ const functionName = `${this.resourceIdPrefix}-create-part`;
149
+ const lambda = new aws_lambda_1.Function(this, functionName, {
150
+ functionName,
151
+ architecture: aws_lambda_1.Architecture.ARM_64,
152
+ runtime: aws_lambda_1.Runtime.NODEJS_16_X,
153
+ code: aws_lambda_1.Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/app'), {
154
+ exclude: ['transform-partition*', '*.d.ts'],
155
+ }),
156
+ memorySize: 128,
157
+ timeout: aws_cdk_lib_1.Duration.seconds(20),
158
+ handler: 'create-partition.handler',
159
+ layers: [this.partitioningLayer],
160
+ logRetention: aws_logs_1.RetentionDays.TWO_WEEKS,
161
+ environment: {
162
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
163
+ NODE_OPTIONS: '--enable-source-maps',
164
+ WORKGROUP: this.workgroup.name,
165
+ DATABASE: this.database.databaseName,
166
+ TABLE: this.accessLogsByDateTable.tableName,
167
+ },
168
+ });
169
+ // grant access to S3 bucket and to execute Athena queries which create new partitions
170
+ this.bucket.grantReadWrite(lambda);
171
+ lambda.addToRolePolicy(new aws_iam_1.PolicyStatement({
172
+ effect: aws_iam_1.Effect.ALLOW,
173
+ actions: [
174
+ 'athena:StartQueryExecution',
175
+ 'athena:GetQueryExecution',
176
+ 'glue:CreateDatabase',
177
+ 'glue:CreatePartition',
178
+ 'glue:GetDatabase',
179
+ 'glue:GetTable',
180
+ 'glue:BatchCreatePartition',
181
+ ],
182
+ resources: ['*'],
183
+ }));
184
+ return lambda;
185
+ }
186
+ /**
187
+ * Transforms partitions from the Hive format to Parquet.
188
+ */
189
+ createTransformPartitionLambda() {
190
+ const functionName = `${this.resourceIdPrefix}-transform-part`;
191
+ const lambda = new aws_lambda_1.Function(this, functionName, {
192
+ functionName,
193
+ architecture: aws_lambda_1.Architecture.ARM_64,
194
+ runtime: aws_lambda_1.Runtime.NODEJS_16_X,
195
+ code: aws_lambda_1.Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/app'), {
196
+ exclude: ['create-partition*', '*.d.ts'],
197
+ }),
198
+ memorySize: 128,
199
+ timeout: aws_cdk_lib_1.Duration.seconds(20),
200
+ handler: 'transform-partition.handler',
201
+ layers: [this.partitioningLayer],
202
+ logRetention: aws_logs_1.RetentionDays.TWO_WEEKS,
203
+ environment: {
204
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
205
+ NODE_OPTIONS: '--enable-source-maps',
206
+ WORKGROUP: this.workgroup.name,
207
+ DATABASE: this.database.databaseName,
208
+ SOURCE_TABLE: this.accessLogsByDateTable.tableName,
209
+ TARGET_TABLE: this.accessLogsParquetTable.tableName,
210
+ },
211
+ });
212
+ // grant access to S3 bucket and to execute Athena queries which create new partitions
213
+ this.bucket.grantReadWrite(lambda);
214
+ lambda.addToRolePolicy(new aws_iam_1.PolicyStatement({
215
+ effect: aws_iam_1.Effect.ALLOW,
216
+ actions: [
217
+ 'athena:StartQueryExecution',
218
+ 'athena:GetQueryExecution',
219
+ 'glue:CreateDatabase',
220
+ 'glue:CreatePartition',
221
+ 'glue:GetDatabase',
222
+ 'glue:GetTable',
223
+ 'glue:BatchCreatePartition',
224
+ 'glue:GetPartition',
225
+ 'glue:GetPartitions',
226
+ 'glue:CreateTable',
227
+ 'glue:DeleteTable',
228
+ 'glue:DeletePartition',
229
+ ],
230
+ resources: ['*'],
231
+ }));
232
+ return lambda;
233
+ }
234
+ createCreatePartitionsScheduler() {
235
+ const ruleName = `${this.resourceIdPrefix}-create-part-sched`;
236
+ return new aws_events_1.Rule(this, ruleName, {
237
+ ruleName,
238
+ schedule: aws_events_1.Schedule.cron({ minute: '55' }),
239
+ targets: [new aws_events_targets_1.LambdaFunction(this.createPartitionLambda, { retryAttempts: 10 })],
240
+ });
241
+ }
242
+ createTransformPartitionsScheduler(props) {
243
+ const ruleName = `${this.resourceIdPrefix}-transform-part-sched`;
244
+ return new aws_events_1.Rule(this, ruleName, {
245
+ ruleName,
246
+ schedule: aws_events_1.Schedule.cron({ minute: '1' }),
247
+ targets: [
248
+ new aws_events_targets_1.LambdaFunction(this.transformPartitionLambda, {
249
+ event: this.getTransformPartitionInvocationProps(props),
250
+ retryAttempts: 10,
251
+ }),
252
+ ],
253
+ });
254
+ }
255
+ getTransformPartitionInvocationProps(props) {
256
+ return aws_events_1.RuleTargetInput.fromObject({
257
+ columnNames: [
258
+ ...this.accessLogsParquetTable.columns,
259
+ ...this.accessLogsParquetTable.partitionKeys,
260
+ ].map(column => column.name),
261
+ columnTransformations: this.getColumnTransformationRules(props),
262
+ });
263
+ }
264
+ }
265
+ exports.AccessLogsAnalysis = AccessLogsAnalysis;
266
+ AccessLogsAnalysis.ACCESS_LOGS_FOLDER_UNPROCESSED = 'unprocessed';
267
+ AccessLogsAnalysis.ACCESS_LOGS_FOLDER_GROUPED_BY_DATE = 'by-date';
268
+ AccessLogsAnalysis.ACCESS_LOGS_FOLDER_TRANSFORMED = 'transformed';
269
+ AccessLogsAnalysis.ACCESS_LOGS_FOLDER_ATHENA_RESULTS = 'athena-query-results';
270
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWNjZXNzTG9nc0FuYWx5c2lzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiQWNjZXNzTG9nc0FuYWx5c2lzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLCtDQUE0RTtBQUM1RSwyQ0FBcUM7QUFDckMsdURBQW9EO0FBR3BELHVEQUEyRjtBQUMzRix1REFBdUU7QUFDdkUsNkNBQW1FO0FBQ25FLDREQUF5RDtBQUN6RCxtRkFBbUU7QUFDbkUsbURBQW1EO0FBQ25ELGlEQUE0RDtBQUM1RCx1RUFBOEQ7QUFDOUQsNkJBQTZCO0FBSTdCOzs7O0dBSUc7QUFDSCxNQUFzQixrQkFBbUIsU0FBUSxzQkFBUztJQXFCdEQsWUFBc0IsS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBOEI7UUFDOUUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsS0FBSyxDQUFDLGNBQWMsQ0FBQztRQUM3QyxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDM0IsSUFBSSxDQUFDLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1FBQ3hDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDMUMsSUFBSSxDQUFDLHFCQUFxQixHQUFHLElBQUksQ0FBQywyQkFBMkIsRUFBRSxDQUFDO1FBQ2hFLElBQUksQ0FBQyxzQkFBc0IsR0FBRyxJQUFJLENBQUMsNEJBQTRCLEVBQUUsQ0FBQztRQUNsRSxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7UUFDdEQsSUFBSSxDQUFDLGlCQUFpQixHQUFHLElBQUksQ0FBQyx1QkFBdUIsRUFBRSxDQUFDO1FBQ3hELElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMs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QyxJQUFJLENBQUMsd0JBQXdCLEVBQUU7b0JBQzlDLEtBQUssRUFBRSxJQUFJLENBQUMsb0NBQW9DLENBQUMsS0FBSyxDQUFDO29CQUN2RCxhQUFhLEVBQUUsRUFBRTtpQkFDcEIsQ0FBQzthQUNMO1NBQ0osQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVPLG9DQUFvQyxDQUFDLEtBQThCO1FBQ3ZFLE9BQU8sNEJBQWUsQ0FBQyxVQUFVLENBQUM7WUFDOUIsV0FBVyxFQUFFO2dCQUNULEdBQUcsSUFBSSxDQUFDLHNCQUFzQixDQUFDLE9BQU87Z0JBQ3RDLEdBQUksSUFBSSxDQUFDLHNCQUFzQixDQUFDLGFBQTBCO2FBQzdELENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztZQUM1QixxQkFBcUIsRUFBRSxJQUFJLENBQUMsNEJBQTRCLENBQUMsS0FBSyxDQUFDO1NBQ2xFLENBQUMsQ0FBQztJQUNQLENBQUM7O0FBNVNMLGdEQW1UQztBQWpUNkIsaURBQThCLEdBQUcsYUFBYSxDQUFDO0FBQy9DLHFEQUFrQyxHQUFHLFNBQVMsQ0FBQztBQUMvQyxpREFBOEIsR0FBRyxhQUFhLENBQUM7QUFDL0Msb0RBQWlDLEdBQUcsc0JBQXNCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge0J1Y2tldCwgRXZlbnRUeXBlLCBOb3RpZmljYXRpb25LZXlGaWx0ZXJ9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1zMyc7XG5pbXBvcnQge0NvbnN0cnVjdH0gZnJvbSAnY29uc3RydWN0cyc7XG5pbXBvcnQge0NmbldvcmtHcm91cH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWF0aGVuYSc7XG5pbXBvcnQge0Nsb3VkRnJvbnRBY2Nlc3NMb2dzQnlEYXRlVGFibGV9IGZyb20gJy4vQ2xvdWRGcm9udEFjY2Vzc0xvZ3NCeURhdGVUYWJsZSc7XG5pbXBvcnQge0FjY2Vzc0xvZ3NQYXJxdWV0VGFibGV9IGZyb20gJy4vQWNjZXNzTG9nc1BhcnF1ZXRUYWJsZSc7XG5pbXBvcnQge0FyY2hpdGVjdHVyZSwgQ29kZSwgRnVuY3Rpb24sIExheWVyVmVyc2lvbiwgUnVudGltZX0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWxhbWJkYSc7XG5pbXBvcnQge1J1bGUsIFJ1bGVUYXJnZXRJbnB1dCwgU2NoZWR1bGV9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1ldmVudHMnO1xuaW1wb3J0IHtDZm5UYWcsIER1cmF0aW9uLCBSZW1vdmFsUG9saWN5LCBTdGFja30gZnJvbSAnYXdzLWNkay1saWInO1xuaW1wb3J0IHtDb2x1bW4sIERhdGFiYXNlfSBmcm9tICdAYXdzLWNkay9hd3MtZ2x1ZS1hbHBoYSc7XG5pbXBvcnQge1MzRXZlbnRTb3VyY2V9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1sYW1iZGEtZXZlbnQtc291cmNlcyc7XG5pbXBvcnQge1JldGVudGlvbkRheXN9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1sb2dzJztcbmltcG9ydCB7RWZmZWN0LCBQb2xpY3lTdGF0ZW1lbnR9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1pYW0nO1xuaW1wb3J0IHtMYW1iZGFGdW5jdGlvbn0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWV2ZW50cy10YXJnZXRzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQge0FjY2Vzc0xvZ3NBbmFseXNpc1Byb3BzfSBmcm9tIFwiLi9BY2Nlc3NMb2dzQW5hbHlzaXNQcm9wc1wiO1xuaW1wb3J0IHtDb2x1bW5UcmFuc2Zvcm1hdGlvblJ1bGVzfSBmcm9tIFwiLi4vLi4vZnVuY3Rpb25zL2FjY2Vzcy1sb2dzLWFuYWx5c2lzL3BhcnRpdGlvbmluZy90eXBlc1wiO1xuXG4vKipcbiAqIFByb3ZpZGVzIHRoZSBBV1MgcmVzb3VyY2VzIHRvIGFuYWx5emUgYWNjZXNzIGxvZ3MuIFRoaXMgY29uc3RydWN0IGlzIGRlcml2ZWQgZnJvbSB0aGUgb2ZmaWNpYWwgQVdTIHNhbXBsZVxuICogQ2xvdWRGb3JtYXRpb24gc3RhY2s6XG4gKiB7QGxpbmsgaHR0cHM6Ly9naXRodWIuY29tL2F3cy1zYW1wbGVzL2FtYXpvbi1jbG91ZGZyb250LWFjY2Vzcy1sb2dzLXF1ZXJpZXMvYmxvYi9tYWlubGluZS90ZW1wbGF0ZS55YW1sfVxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQWNjZXNzTG9nc0FuYWx5c2lzIGV4dGVuZHMgQ29uc3RydWN0IHtcblxuICAgIHByb3RlY3RlZCBzdGF0aWMgcmVhZG9ubHkgQUNDRVNTX0xPR1NfRk9MREVSX1VOUFJPQ0VTU0VEID0gJ3VucHJvY2Vzc2VkJztcbiAgICBwcm90ZWN0ZWQgc3RhdGljIHJlYWRvbmx5IEFDQ0VTU19MT0dTX0ZPTERFUl9HUk9VUEVEX0JZX0RBVEUgPSAnYnktZGF0ZSc7XG4gICAgcHJvdGVjdGVkIHN0YXRpYyByZWFkb25seSBBQ0NFU1NfTE9HU19GT0xERVJfVFJBTlNGT1JNRUQgPSAndHJhbnNmb3JtZWQnO1xuICAgIHByb3RlY3RlZCBzdGF0aWMgcmVhZG9ubHkgQUNDRVNTX0xPR1NfRk9MREVSX0FUSEVOQV9SRVNVTFRTID0gJ2F0aGVuYS1xdWVyeS1yZXN1bHRzJztcblxuICAgIHByb3RlY3RlZCByZWFkb25seSByZXNvdXJjZUlkUHJlZml4OiBzdHJpbmc7XG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGJ1Y2tldDogQnVja2V0O1xuICAgIHByb3RlY3RlZCByZWFkb25seSB3b3JrZ3JvdXA6IENmbldvcmtHcm91cDtcbiAgICBwcm90ZWN0ZWQgcmVhZG9ubHkgZGF0YWJhc2U6IERhdGFiYXNlO1xuICAgIHByb3RlY3RlZCByZWFkb25seSBhY2Nlc3NMb2dzQnlEYXRlVGFibGU6IENsb3VkRnJvbnRBY2Nlc3NMb2dzQnlEYXRlVGFibGU7XG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGFjY2Vzc0xvZ3NQYXJxdWV0VGFibGU6IEFjY2Vzc0xvZ3NQYXJxdWV0VGFibGU7XG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGdyb3VwQnlEYXRlTGF5ZXI6IExheWVyVmVyc2lvbjtcbiAgICBwcm90ZWN0ZWQgcmVhZG9ubHkgZ3JvdXBCeURhdGVMYW1iZGE6IEZ1bmN0aW9uO1xuICAgIHByb3RlY3RlZCByZWFkb25seSBwYXJ0aXRpb25pbmdMYXllcjogTGF5ZXJWZXJzaW9uO1xuICAgIHByb3RlY3RlZCByZWFkb25seSBjcmVhdGVQYXJ0aXRpb25MYW1iZGE6IEZ1bmN0aW9uO1xuICAgIHByb3RlY3RlZCByZWFkb25seSB0cmFuc2Zvcm1QYXJ0aXRpb25MYW1iZGE6IEZ1bmN0aW9uO1xuICAgIHByb3RlY3RlZCByZWFkb25seSBjcmVhdGVQYXJ0aXRpb25zU2NoZWR1bGVyOiBSdWxlO1xuICAgIHByb3RlY3RlZCByZWFkb25seSB0cmFuc2Zvcm1QYXJ0aXRpb25zU2NoZWR1bGVyOiBSdWxlO1xuXG4gICAgcHJvdGVjdGVkIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBBY2Nlc3NMb2dzQW5hbHlzaXNQcm9wcykge1xuICAgICAgICBzdXBlcihzY29wZSwgaWQpO1xuICAgICAgICB0aGlzLnJlc291cmNlSWRQcmVmaXggPSBwcm9wcy5yZXNvdXJjZVByZWZpeDtcbiAgICAgICAgdGhpcy5idWNrZXQgPSBwcm9wcy5idWNrZXQ7XG4gICAgICAgIHRoaXMuc2V0dXBMaWZlY3ljbGVSdWxlcyhwcm9wcyk7XG4gICAgICAgIHRoaXMud29ya2dyb3VwID0gdGhpcy5jcmVhdGVXb3JrZ3JvdXAoKTtcbiAgICAgICAgdGhpcy5kYXRhYmFzZSA9IHRoaXMuY3JlYXRlR2x1ZURhdGFiYXNlKCk7XG4gICAgICAgIHRoaXMuYWNjZXNzTG9nc0J5RGF0ZVRhYmxlID0gdGhpcy5jcmVhdGVBY2Nlc3NMb2dzQnlEYXRlVGFibGUoKTtcbiAgICAgICAgdGhpcy5hY2Nlc3NMb2dzUGFycXVldFRhYmxlID0gdGhpcy5jcmVhdGVBY2Nlc3NMb2dzUGFycXVldFRhYmxlKCk7XG4gICAgICAgIHRoaXMuZ3JvdXBCeURhdGVMYXllciA9IHRoaXMuY3JlYXRlR3JvdXBCeURhdGVMYXllcigpO1xuICAgICAgICB0aGlzLmdyb3VwQnlEYXRlTGFtYmRhID0gdGhpcy5jcmVhdGVHcm91cEJ5RGF0ZUxhbWJkYSgpO1xuICAgICAgICB0aGlzLnBhcnRpdGlvbmluZ0xheWVyID0gdGhpcy5jcmVhdGVQYXJ0aXRpb25pbmdMYXllcigpO1xuICAgICAgICB0aGlzLmNyZWF0ZVBhcnRpdGlvbkxhbWJkYSA9IHRoaXMuY3JlYXRlQ3JlYXRlUGFydGl0aW9uTGFtYmRhKCk7XG4gICAgICAgIHRoaXMudHJhbnNmb3JtUGFydGl0aW9uTGFtYmRhID0gdGhpcy5jcmVhdGVUcmFuc2Zvcm1QYXJ0aXRpb25MYW1iZGEoKTtcbiAgICAgICAgdGhpcy5jcmVhdGVQYXJ0aXRpb25zU2NoZWR1bGVyID0gdGhpcy5jcmVhdGVDcmVhdGVQYXJ0aXRpb25zU2NoZWR1bGVyKCk7XG4gICAgICAgIHRoaXMudHJhbnNmb3JtUGFydGl0aW9uc1NjaGVkdWxlciA9IHRoaXMuY3JlYXRlVHJhbnNmb3JtUGFydGl0aW9uc1NjaGVkdWxlcihwcm9wcyk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBzZXR1cExpZmVjeWNsZVJ1bGVzKHByb3BzOiBBY2Nlc3NMb2dzQW5hbHlzaXNQcm9wcyk6IHZvaWQge1xuICAgICAgICAvLyBjbGVhbnVwIHJhdyBhbmQgcGFydGl0aW9uZWQgYWNjZXNzIGxvZ3MgYWZ0ZXIgYSBjb25maWd1cmFibGUgdGltZSByYW5nZVxuXG4gICAgICAgIHRoaXMuYnVja2V0LmFkZExpZmVjeWNsZVJ1bGUoe1xuICAgICAgICAgICAgaWQ6IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tY2xlYW51cC11bnByb2Nlc3NlZGAsXG4gICAgICAgICAgICBwcmVmaXg6IGAke0FjY2Vzc0xvZ3NBbmFseXNpcy5BQ0NFU1NfTE9HU19GT0xERVJfVU5QUk9DRVNTRUR9LypgLFxuICAgICAgICAgICAgZXhwaXJhdGlvbjogcHJvcHMuZXhwaXJlUmF3TG9nc0FmdGVyID8/IER1cmF0aW9uLmRheXMoNyksXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHRoaXMuYnVja2V0LmFkZExpZmVjeWNsZVJ1bGUoe1xuICAgICAgICAgICAgaWQ6IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tY2xlYW51cC1ncm91cGVkYCxcbiAgICAgICAgICAgIHByZWZpeDogYCR7QWNjZXNzTG9nc0FuYWx5c2lzLkFDQ0VTU19MT0dTX0ZPTERFUl9HUk9VUEVEX0JZX0RBVEV9LypgLFxuICAgICAgICAgICAgZXhwaXJhdGlvbjogcHJvcHMuZXhwaXJlSW50ZXJtZWRpYXRlTG9nc0FmdGVyID8/IER1cmF0aW9uLmRheXMoNyksXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHRoaXMuYnVja2V0LmFkZExpZmVjeWNsZVJ1bGUoe1xuICAgICAgICAgICAgaWQ6IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tY2xlYW51cC10cmFuc2Zvcm1lZGAsXG4gICAgICAgICAgICBwcmVmaXg6IGAke0FjY2Vzc0xvZ3NBbmFseXNpcy5BQ0NFU1NfTE9HU19GT0xERVJfVFJBTlNGT1JNRUR9LypgLFxuICAgICAgICAgICAgZXhwaXJhdGlvbjogcHJvcHMuZXhwaXJlVHJhbnNmb3JtZWRMb2dzQWZ0ZXIgPz8gRHVyYXRpb24uZGF5cygxODApLFxuICAgICAgICB9KTtcblxuICAgICAgICAvKiBDSEVDS01FOiBkZWxldGUgcXVlcnkgcmVzdWx0cyBhZnRlciAxIHdlZWtcbiAgICAgICAgdGhpcy5idWNrZXQuYWRkTGlmZWN5Y2xlUnVsZSh7XG4gICAgICAgICAgaWQ6IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tY2xlYW51cC1xdWVyeS1yZXN1bHRzYCxcbiAgICAgICAgICBwcmVmaXg6IGAke0FjY2Vzc0xvZ3NBbmFseXNpcy5BQ0NFU1NfTE9HU19GT0xERVJfQVRIRU5BX1JFU1VMVFN9LypgLFxuICAgICAgICAgIGV4cGlyYXRpb246IER1cmF0aW9uLmRheXMoNyksXG4gICAgICAgIH0pO1xuICAgICAgICAqL1xuICAgIH1cblxuICAgIHByaXZhdGUgY3JlYXRlV29ya2dyb3VwKCk6IENmbldvcmtHcm91cCB7XG4gICAgICAgIGNvbnN0IHdvcmtncm91cE5hbWUgPSBgJHt0aGlzLnJlc291cmNlSWRQcmVmaXh9LXdvcmtncm91cGA7XG5cbiAgICAgICAgLy8gZHVlIHRvIGEgY3VycmVudCBidWcsIHRoZSB0YWdzIGFyZSBub3QgYXV0b21hdGljYWxseSBhc3NpZ25lZCB0byB0aGUgd29ya2dyb3VwXG4gICAgICAgIC8vIG5vdGUsIHRoYXQgdGhlIGtleXMgbXVzdCBiZSBjb252ZXJ0ZWQgdG8gbG93ZXIgY2FzZVxuICAgICAgICBjb25zdCB0YWdzOiBDZm5UYWdbXSA9IFN0YWNrLm9mKHRoaXMpXG4gICAgICAgICAgICAudGFncy5yZW5kZXJUYWdzKClcbiAgICAgICAgICAgIC5tYXAoKHRhZzogeyBLZXk6IHN0cmluZzsgVmFsdWU6IHN0cmluZyB9KSA9PiAoe2tleTogdGFnLktleSwgdmFsdWU6IHRhZy5WYWx1ZX0pKTtcblxuICAgICAgICByZXR1cm4gbmV3IENmbldvcmtHcm91cCh0aGlzLCB3b3JrZ3JvdXBOYW1lLCB7XG4gICAgICAgICAgICBuYW1lOiB3b3JrZ3JvdXBOYW1lLFxuICAgICAgICAgICAgd29ya0dyb3VwQ29uZmlndXJhdGlvbjoge1xuICAgICAgICAgICAgICAgIHB1Ymxpc2hDbG91ZFdhdGNoTWV0cmljc0VuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHJlc3VsdENvbmZpZ3VyYXRpb246IHtcbiAgICAgICAgICAgICAgICAgICAgb3V0cHV0TG9jYXRpb246IGBzMzovLyR7dGhpcy5idWNrZXQuYnVja2V0TmFtZX0vJHtBY2Nlc3NMb2dzQW5hbHlzaXMuQUNDRVNTX0xPR1NfRk9MREVSX0FUSEVOQV9SRVNVTFRTfWAsXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICBlbmZvcmNlV29ya0dyb3VwQ29uZmlndXJhdGlvbjogdHJ1ZVxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHRhZ3MsXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHByaXZhdGUgY3JlYXRlR2x1ZURhdGFiYXNlKCk6IERhdGFiYXNlIHtcbiAgICAgICAgY29uc3QgZGF0YWJhc2VOYW1lID0gYCR7dGhpcy5yZXNvdXJjZUlkUHJlZml4fS1kYXRhYmFzZWA7XG4gICAgICAgIHJldHVybiBuZXcgRGF0YWJhc2UodGhpcywgZGF0YWJhc2VOYW1lLCB7XG4gICAgICAgICAgICAvLyBBdGhlbmEgZG9lc24ndCBzdXBwb3J0IGRhc2hlcyBpbiBkYXRhYmFzZS90YWJsZSBuYW1lc1xuICAgICAgICAgICAgZGF0YWJhc2VOYW1lOiBkYXRhYmFzZU5hbWUucmVwbGFjZSgvLS9nLCAnXycpLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgYWJzdHJhY3QgY3JlYXRlQWNjZXNzTG9nc0J5RGF0ZVRhYmxlKCk6IENsb3VkRnJvbnRBY2Nlc3NMb2dzQnlEYXRlVGFibGU7XG5cbiAgICBwcm90ZWN0ZWQgYWJzdHJhY3QgY3JlYXRlQWNjZXNzTG9nc1BhcnF1ZXRUYWJsZSgpOiBDbG91ZEZyb250QWNjZXNzTG9nc0J5RGF0ZVRhYmxlO1xuXG4gICAgcHJpdmF0ZSBjcmVhdGVHcm91cEJ5RGF0ZUxheWVyKCk6IExheWVyVmVyc2lvbiB7XG4gICAgICAgIGNvbnN0IGxheWVyVmVyc2lvbk5hbWUgPSBgJHt0aGlzLnJlc291cmNlSWRQcmVmaXh9LWdyb3VwLWJ5LWRhdGUtZGVwZW5kZW5jaWVzYDtcbiAgICAgICAgcmV0dXJuIG5ldyBMYXllclZlcnNpb24odGhpcywgbGF5ZXJWZXJzaW9uTmFtZSwge1xuICAgICAgICAgICAgbGF5ZXJWZXJzaW9uTmFtZSxcbiAgICAgICAgICAgIGNvbXBhdGlibGVBcmNoaXRlY3R1cmVzOiBbQXJjaGl0ZWN0dXJlLkFSTV82NCwgQXJjaGl0ZWN0dXJlLlg4Nl82NF0sXG4gICAgICAgICAgICBjb21wYXRpYmxlUnVudGltZXM6IFtSdW50aW1lLk5PREVKU18xNF9YLCBSdW50aW1lLk5PREVKU18xNl9YXSxcbiAgICAgICAgICAgIGNvZGU6IENvZGUuZnJvbUFzc2V0KHBhdGguam9pbihfX2Rpcm5hbWUsICcuLi8uLi9mdW5jdGlvbnMvYWNjZXNzLWxvZ3MtYW5hbHlzaXMvZ3JvdXAtYnktZGF0ZS9idWlsZC9sYXllcicpKSxcbiAgICAgICAgICAgIHJlbW92YWxQb2xpY3k6IFJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjcmVhdGVQYXJ0aXRpb25pbmdMYXllcigpOiBMYXllclZlcnNpb24ge1xuICAgICAgICBjb25zdCBsYXllclZlcnNpb25OYW1lID0gYCR7dGhpcy5yZXNvdXJjZUlkUHJlZml4fS1wYXJ0aXRpb25pbmctZGVwZW5kZW5jaWVzYDtcbiAgICAgICAgcmV0dXJuIG5ldyBMYXllclZlcnNpb24odGhpcywgbGF5ZXJWZXJzaW9uTmFtZSwge1xuICAgICAgICAgICAgbGF5ZXJWZXJzaW9uTmFtZSxcbiAgICAgICAgICAgIGNvbXBhdGlibGVBcmNoaXRlY3R1cmVzOiBbQXJjaGl0ZWN0dXJlLkFSTV82NCwgQXJjaGl0ZWN0dXJlLlg4Nl82NF0sXG4gICAgICAgICAgICBjb21wYXRpYmxlUnVudGltZXM6IFtSdW50aW1lLk5PREVKU18xNF9YLCBSdW50aW1lLk5PREVKU18xNl9YXSxcbiAgICAgICAgICAgIGNvZGU6IENvZGUuZnJvbUFzc2V0KHBhdGguam9pbihfX2Rpcm5hbWUsICcuLi8uLi9mdW5jdGlvbnMvYWNjZXNzLWxvZ3MtYW5hbHlzaXMvcGFydGl0aW9uaW5nL2J1aWxkL2xheWVyJykpLFxuICAgICAgICAgICAgcmVtb3ZhbFBvbGljeTogUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBIExhbWJkYSBmdW5jdGlvbiB0byBiZSB0cmlnZ2VyZWQgd2hlbmV2ZXIgYSBuZXcgYWNjZXNzIGxvZyBpcyBjcmVhdGVkLlxuICAgICAqIEl0IG1vdmVzIHRoZSByYXcgYWNjZXNzIGxvZ3MgaW50byBhIHN1YiBmb2xkZXIgaGllcmFyY2h5IGJ5IHllYXIsIG1vbnRoLCBkYXkgYW5kIGhvdXIuXG4gICAgICovXG4gICAgcHJpdmF0ZSBjcmVhdGVHcm91cEJ5RGF0ZUxhbWJkYSgpOiBGdW5jdGlvbiB7XG4gICAgICAgIGNvbnN0IGZ1bmN0aW9uTmFtZSA9IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tZ3JvdXAtYnktZGF0ZWA7XG5cbiAgICAgICAgY29uc3QgbGFtYmRhID0gbmV3IEZ1bmN0aW9uKHRoaXMsIGZ1bmN0aW9uTmFtZSwge1xuICAgICAgICAgICAgZnVuY3Rpb25OYW1lLFxuICAgICAgICAgICAgYXJjaGl0ZWN0dXJlOiBBcmNoaXRlY3R1cmUuQVJNXzY0LFxuICAgICAgICAgICAgcnVudGltZTogUnVudGltZS5OT0RFSlNfMTZfWCxcbiAgICAgICAgICAgIGNvZGU6IENvZGUuZnJvbUFzc2V0KHBhdGguam9pbihfX2Rpcm5hbWUsICcuLi8uLi9mdW5jdGlvbnMvYWNjZXNzLWxvZ3MtYW5hbHlzaXMvZ3JvdXAtYnktZGF0ZS9idWlsZC9hcHAnKSksXG4gICAgICAgICAgICBtZW1vcnlTaXplOiA1MTIsXG4gICAgICAgICAgICB0aW1lb3V0OiBEdXJhdGlvbi5zZWNvbmRzKDIwKSxcbiAgICAgICAgICAgIGhhbmRsZXI6ICdpbmRleC5oYW5kbGVyJyxcbiAgICAgICAgICAgIGxheWVyczogW3RoaXMuZ3JvdXBCeURhdGVMYXllcl0sXG4gICAgICAgICAgICBldmVudHM6IFtcbiAgICAgICAgICAgICAgICBuZXcgUzNFdmVudFNvdXJjZSh0aGlzLmJ1Y2tldCwge1xuICAgICAgICAgICAgICAgICAgICBldmVudHM6IFtFdmVudFR5cGUuT0JKRUNUX0NSRUFURURdLFxuICAgICAgICAgICAgICAgICAgICBmaWx0ZXJzOiBbdGhpcy5nZXRVbnByb2Nlc3NlZE9iamVjdHNGaWx0ZXIoKV0sXG4gICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBdLFxuICAgICAgICAgICAgbG9nUmV0ZW50aW9uOiBSZXRlbnRpb25EYXlzLlRXT19XRUVLUyxcbiAgICAgICAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgICAgICAgICAgQVdTX05PREVKU19DT05ORUNUSU9OX1JFVVNFX0VOQUJMRUQ6ICcxJyxcbiAgICAgICAgICAgICAgICBOT0RFX09QVElPTlM6ICctLWVuYWJsZS1zb3VyY2UtbWFwcycsXG4gICAgICAgICAgICAgICAgVEFSR0VUX0ZPTERFUjogQWNjZXNzTG9nc0FuYWx5c2lzLkFDQ0VTU19MT0dTX0ZPTERFUl9HUk9VUEVEX0JZX0RBVEUsXG4gICAgICAgICAgICAgICAgUkFXX0FDQ0VTU19MT0dfRklMRV9QQVRURVJOOiB0aGlzLmdldFJhd0FjY2Vzc0xvZ0ZpbGVQYXR0ZXJuKCkuc291cmNlLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5idWNrZXQuZ3JhbnRSZWFkV3JpdGUobGFtYmRhKTtcbiAgICAgICAgdGhpcy5idWNrZXQuZ3JhbnREZWxldGUobGFtYmRhLCBgJHtBY2Nlc3NMb2dzQW5hbHlzaXMuQUNDRVNTX0xPR1NfRk9MREVSX1VOUFJPQ0VTU0VEfS8qYCk7XG5cbiAgICAgICAgcmV0dXJuIGxhbWJkYTtcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgYWJzdHJhY3QgZ2V0VW5wcm9jZXNzZWRPYmplY3RzRmlsdGVyKCk6IE5vdGlmaWNhdGlvbktleUZpbHRlcjtcblxuICAgIC8qKiBUaGUgcGF0dGVybiB0byBpZGVudGlmeSB1bnByb2Nlc3NlZCBhY2Nlc3MgbG9ncyBkZXBlbmRzIG9uIHRoZSBwcm9kdWNpbmcgc291cmNlICovXG4gICAgcHJvdGVjdGVkIGFic3RyYWN0IGdldFJhd0FjY2Vzc0xvZ0ZpbGVQYXR0ZXJuKCk6IFJlZ0V4cDtcblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcGFydGl0aW9uIGZvciB0aGUgdXBjb21pbmcgaG91ciBpbiB0aGUgYWNjZXNzIGxvZyBkYXRhYmFzZS5cbiAgICAgKi9cbiAgICBwcml2YXRlIGNyZWF0ZUNyZWF0ZVBhcnRpdGlvbkxhbWJkYSgpOiBGdW5jdGlvbiB7XG4gICAgICAgIGNvbnN0IGZ1bmN0aW9uTmFtZSA9IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tY3JlYXRlLXBhcnRgO1xuXG4gICAgICAgIGNvbnN0IGxhbWJkYSA9IG5ldyBGdW5jdGlvbih0aGlzLCBmdW5jdGlvbk5hbWUsIHtcbiAgICAgICAgICAgIGZ1bmN0aW9uTmFtZSxcbiAgICAgICAgICAgIGFyY2hpdGVjdHVyZTogQXJjaGl0ZWN0dXJlLkFSTV82NCxcbiAgICAgICAgICAgIHJ1bnRpbWU6IFJ1bnRpbWUuTk9ERUpTXzE2X1gsXG4gICAgICAgICAgICBjb2RlOiBDb2RlLmZyb21Bc3NldChwYXRoLmpvaW4oX19kaXJuYW1lLCAnLi4vLi4vZnVuY3Rpb25zL2FjY2Vzcy1sb2dzLWFuYWx5c2lzL3BhcnRpdGlvbmluZy9idWlsZC9hcHAnKSwge1xuICAgICAgICAgICAgICAgIGV4Y2x1ZGU6IFsndHJhbnNmb3JtLXBhcnRpdGlvbionLCAnKi5kLnRzJ10sXG4gICAgICAgICAgICB9KSxcbiAgICAgICAgICAgIG1lbW9yeVNpemU6IDEyOCxcbiAgICAgICAgICAgIHRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHMoMjApLFxuICAgICAgICAgICAgaGFuZGxlcjogJ2NyZWF0ZS1wYXJ0aXRpb24uaGFuZGxlcicsXG4gICAgICAgICAgICBsYXllcnM6IFt0aGlzLnBhcnRpdGlvbmluZ0xheWVyXSxcbiAgICAgICAgICAgIGxvZ1JldGVudGlvbjogUmV0ZW50aW9uRGF5cy5UV09fV0VFS1MsXG4gICAgICAgICAgICBlbnZpcm9ubWVudDoge1xuICAgICAgICAgICAgICAgIEFXU19OT0RFSlNfQ09OTkVDVElPTl9SRVVTRV9FTkFCTEVEOiAnMScsXG4gICAgICAgICAgICAgICAgTk9ERV9PUFRJT05TOiAnLS1lbmFibGUtc291cmNlLW1hcHMnLFxuICAgICAgICAgICAgICAgIFdPUktHUk9VUDogdGhpcy53b3JrZ3JvdXAubmFtZSxcbiAgICAgICAgICAgICAgICBEQVRBQkFTRTogdGhpcy5kYXRhYmFzZS5kYXRhYmFzZU5hbWUsXG4gICAgICAgICAgICAgICAgVEFCTEU6IHRoaXMuYWNjZXNzTG9nc0J5RGF0ZVRhYmxlLnRhYmxlTmFtZSxcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIGdyYW50IGFjY2VzcyB0byBTMyBidWNrZXQgYW5kIHRvIGV4ZWN1dGUgQXRoZW5hIHF1ZXJpZXMgd2hpY2ggY3JlYXRlIG5ldyBwYXJ0aXRpb25zXG4gICAgICAgIHRoaXMuYnVja2V0LmdyYW50UmVhZFdyaXRlKGxhbWJkYSk7XG4gICAgICAgIGxhbWJkYS5hZGRUb1JvbGVQb2xpY3koXG4gICAgICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICAgICAgICBlZmZlY3Q6IEVmZmVjdC5BTExPVyxcbiAgICAgICAgICAgICAgICBhY3Rpb25zOiBbXG4gICAgICAgICAgICAgICAgICAgICdhdGhlbmE6U3RhcnRRdWVyeUV4ZWN1dGlvbicsXG4gICAgICAgICAgICAgICAgICAgICdhdGhlbmE6R2V0UXVlcnlFeGVjdXRpb24nLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpDcmVhdGVEYXRhYmFzZScsXG4gICAgICAgICAgICAgICAgICAgICdnbHVlOkNyZWF0ZVBhcnRpdGlvbicsXG4gICAgICAgICAgICAgICAgICAgICdnbHVlOkdldERhdGFiYXNlJyxcbiAgICAgICAgICAgICAgICAgICAgJ2dsdWU6R2V0VGFibGUnLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpCYXRjaENyZWF0ZVBhcnRpdGlvbicsXG4gICAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgICAgICByZXNvdXJjZXM6IFsnKiddLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcblxuICAgICAgICByZXR1cm4gbGFtYmRhO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRyYW5zZm9ybXMgcGFydGl0aW9ucyBmcm9tIHRoZSBIaXZlIGZvcm1hdCB0byBQYXJxdWV0LlxuICAgICAqL1xuICAgIHByaXZhdGUgY3JlYXRlVHJhbnNmb3JtUGFydGl0aW9uTGFtYmRhKCk6IEZ1bmN0aW9uIHtcbiAgICAgICAgY29uc3QgZnVuY3Rpb25OYW1lID0gYCR7dGhpcy5yZXNvdXJjZUlkUHJlZml4fS10cmFuc2Zvcm0tcGFydGA7XG5cbiAgICAgICAgY29uc3QgbGFtYmRhID0gbmV3IEZ1bmN0aW9uKHRoaXMsIGZ1bmN0aW9uTmFtZSwge1xuICAgICAgICAgICAgZnVuY3Rpb25OYW1lLFxuICAgICAgICAgICAgYXJjaGl0ZWN0dXJlOiBBcmNoaXRlY3R1cmUuQVJNXzY0LFxuICAgICAgICAgICAgcnVudGltZTogUnVudGltZS5OT0RFSlNfMTZfWCxcbiAgICAgICAgICAgIGNvZGU6IENvZGUuZnJvbUFzc2V0KHBhdGguam9pbihfX2Rpcm5hbWUsICcuLi8uLi9mdW5jdGlvbnMvYWNjZXNzLWxvZ3MtYW5hbHlzaXMvcGFydGl0aW9uaW5nL2J1aWxkL2FwcCcpLCB7XG4gICAgICAgICAgICAgICAgZXhjbHVkZTogWydjcmVhdGUtcGFydGl0aW9uKicsICcqLmQudHMnXSxcbiAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgbWVtb3J5U2l6ZTogMTI4LFxuICAgICAgICAgICAgdGltZW91dDogRHVyYXRpb24uc2Vjb25kcygyMCksXG4gICAgICAgICAgICBoYW5kbGVyOiAndHJhbnNmb3JtLXBhcnRpdGlvbi5oYW5kbGVyJyxcbiAgICAgICAgICAgIGxheWVyczogW3RoaXMucGFydGl0aW9uaW5nTGF5ZXJdLFxuICAgICAgICAgICAgbG9nUmV0ZW50aW9uOiBSZXRlbnRpb25EYXlzLlRXT19XRUVLUyxcbiAgICAgICAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgICAgICAgICAgQVdTX05PREVKU19DT05ORUNUSU9OX1JFVVNFX0VOQUJMRUQ6ICcxJyxcbiAgICAgICAgICAgICAgICBOT0RFX09QVElPTlM6ICctLWVuYWJsZS1zb3VyY2UtbWFwcycsXG4gICAgICAgICAgICAgICAgV09SS0dST1VQOiB0aGlzLndvcmtncm91cC5uYW1lLFxuICAgICAgICAgICAgICAgIERBVEFCQVNFOiB0aGlzLmRhdGFiYXNlLmRhdGFiYXNlTmFtZSxcbiAgICAgICAgICAgICAgICBTT1VSQ0VfVEFCTEU6IHRoaXMuYWNjZXNzTG9nc0J5RGF0ZVRhYmxlLnRhYmxlTmFtZSxcbiAgICAgICAgICAgICAgICBUQVJHRVRfVEFCTEU6IHRoaXMuYWNjZXNzTG9nc1BhcnF1ZXRUYWJsZS50YWJsZU5hbWUsXG4gICAgICAgICAgICB9LFxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBncmFudCBhY2Nlc3MgdG8gUzMgYnVja2V0IGFuZCB0byBleGVjdXRlIEF0aGVuYSBxdWVyaWVzIHdoaWNoIGNyZWF0ZSBuZXcgcGFydGl0aW9uc1xuICAgICAgICB0aGlzLmJ1Y2tldC5ncmFudFJlYWRXcml0ZShsYW1iZGEpO1xuICAgICAgICBsYW1iZGEuYWRkVG9Sb2xlUG9saWN5KFxuICAgICAgICAgICAgbmV3IFBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgICAgICAgZWZmZWN0OiBFZmZlY3QuQUxMT1csXG4gICAgICAgICAgICAgICAgYWN0aW9uczogW1xuICAgICAgICAgICAgICAgICAgICAnYXRoZW5hOlN0YXJ0UXVlcnlFeGVjdXRpb24nLFxuICAgICAgICAgICAgICAgICAgICAnYXRoZW5hOkdldFF1ZXJ5RXhlY3V0aW9uJyxcbiAgICAgICAgICAgICAgICAgICAgJ2dsdWU6Q3JlYXRlRGF0YWJhc2UnLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpDcmVhdGVQYXJ0aXRpb24nLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpHZXREYXRhYmFzZScsXG4gICAgICAgICAgICAgICAgICAgICdnbHVlOkdldFRhYmxlJyxcbiAgICAgICAgICAgICAgICAgICAgJ2dsdWU6QmF0Y2hDcmVhdGVQYXJ0aXRpb24nLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpHZXRQYXJ0aXRpb24nLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpHZXRQYXJ0aXRpb25zJyxcbiAgICAgICAgICAgICAgICAgICAgJ2dsdWU6Q3JlYXRlVGFibGUnLFxuICAgICAgICAgICAgICAgICAgICAnZ2x1ZTpEZWxldGVUYWJsZScsXG4gICAgICAgICAgICAgICAgICAgICdnbHVlOkRlbGV0ZVBhcnRpdGlvbicsXG4gICAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgICAgICByZXNvdXJjZXM6IFsnKiddLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcblxuICAgICAgICByZXR1cm4gbGFtYmRhO1xuICAgIH1cblxuICAgIHByaXZhdGUgY3JlYXRlQ3JlYXRlUGFydGl0aW9uc1NjaGVkdWxlcigpOiBSdWxlIHtcbiAgICAgICAgY29uc3QgcnVsZU5hbWUgPSBgJHt0aGlzLnJlc291cmNlSWRQcmVmaXh9LWNyZWF0ZS1wYXJ0LXNjaGVkYDtcbiAgICAgICAgcmV0dXJuIG5ldyBSdWxlKHRoaXMsIHJ1bGVOYW1lLCB7XG4gICAgICAgICAgICBydWxlTmFtZSxcbiAgICAgICAgICAgIHNjaGVkdWxlOiBTY2hlZHVsZS5jcm9uKHttaW51dGU6ICc1NSd9KSxcbiAgICAgICAgICAgIHRhcmdldHM6IFtuZXcgTGFtYmRhRnVuY3Rpb24odGhpcy5jcmVhdGVQYXJ0aXRpb25MYW1iZGEsIHtyZXRyeUF0dGVtcHRzOiAxMH0pXSxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjcmVhdGVUcmFuc2Zvcm1QYXJ0aXRpb25zU2NoZWR1bGVyKHByb3BzOiBBY2Nlc3NMb2dzQW5hbHlzaXNQcm9wcyk6IFJ1bGUge1xuICAgICAgICBjb25zdCBydWxlTmFtZSA9IGAke3RoaXMucmVzb3VyY2VJZFByZWZpeH0tdHJhbnNmb3JtLXBhcnQtc2NoZWRgO1xuICAgICAgICByZXR1cm4gbmV3IFJ1bGUodGhpcywgcnVsZU5hbWUsIHtcbiAgICAgICAgICAgIHJ1bGVOYW1lLFxuICAgICAgICAgICAgc2NoZWR1bGU6IFNjaGVkdWxlLmNyb24oe21pbnV0ZTogJzEnfSksXG4gICAgICAgICAgICB0YXJnZXRzOiBbXG4gICAgICAgICAgICAgICAgbmV3IExhbWJkYUZ1bmN0aW9uKHRoaXMudHJhbnNmb3JtUGFydGl0aW9uTGFtYmRhLCB7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50OiB0aGlzLmdldFRyYW5zZm9ybVBhcnRpdGlvbkludm9jYXRpb25Qcm9wcyhwcm9wcyksXG4gICAgICAgICAgICAgICAgICAgIHJldHJ5QXR0ZW1wdHM6IDEwLFxuICAgICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgXSxcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBnZXRUcmFuc2Zvcm1QYXJ0aXRpb25JbnZvY2F0aW9uUHJvcHMocHJvcHM6IEFjY2Vzc0xvZ3NBbmFseXNpc1Byb3BzKTogUnVsZVRhcmdldElucHV0IHtcbiAgICAgICAgcmV0dXJuIFJ1bGVUYXJnZXRJbnB1dC5mcm9tT2JqZWN0KHtcbiAgICAgICAgICAgIGNvbHVtbk5hbWVzOiBbXG4gICAgICAgICAgICAgICAgLi4udGhpcy5hY2Nlc3NMb2dzUGFycXVldFRhYmxlLmNvbHVtbnMsXG4gICAgICAgICAgICAgICAgLi4uKHRoaXMuYWNjZXNzTG9nc1BhcnF1ZXRUYWJsZS5wYXJ0aXRpb25LZXlzIGFzIENvbHVtbltdKSxcbiAgICAgICAgICAgIF0ubWFwKGNvbHVtbiA9PiBjb2x1bW4ubmFtZSksXG4gICAgICAgICAgICBjb2x1bW5UcmFuc2Zvcm1hdGlvbnM6IHRoaXMuZ2V0Q29sdW1uVHJhbnNmb3JtYXRpb25SdWxlcyhwcm9wcyksXG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE5lZWRzIHRvIHJldHVybiB0aGUgbG9nIHNvdXJjZSBzcGVjaWZpYyBjb2x1bW4gdHJhbnNmb3JtYXRpb24gcnVsZXNcbiAgICAgKiAoZS5nLiB0byBhbm9ueW1pemUgSVAgYWRkcmVzc2VzKS5cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgYWJzdHJhY3QgZ2V0Q29sdW1uVHJhbnNmb3JtYXRpb25SdWxlcyhwcm9wczogQWNjZXNzTG9nc0FuYWx5c2lzUHJvcHMpOiBDb2x1bW5UcmFuc2Zvcm1hdGlvblJ1bGVzO1xufVxuIl19
@@ -0,0 +1,330 @@
1
+ import {Bucket, EventType, NotificationKeyFilter} from 'aws-cdk-lib/aws-s3';
2
+ import {Construct} from 'constructs';
3
+ import {CfnWorkGroup} from 'aws-cdk-lib/aws-athena';
4
+ import {CloudFrontAccessLogsByDateTable} from './CloudFrontAccessLogsByDateTable';
5
+ import {AccessLogsParquetTable} from './AccessLogsParquetTable';
6
+ import {Architecture, Code, Function, LayerVersion, Runtime} from 'aws-cdk-lib/aws-lambda';
7
+ import {Rule, RuleTargetInput, Schedule} from 'aws-cdk-lib/aws-events';
8
+ import {CfnTag, Duration, RemovalPolicy, Stack} from 'aws-cdk-lib';
9
+ import {Column, Database} from '@aws-cdk/aws-glue-alpha';
10
+ import {S3EventSource} from 'aws-cdk-lib/aws-lambda-event-sources';
11
+ import {RetentionDays} from 'aws-cdk-lib/aws-logs';
12
+ import {Effect, PolicyStatement} from 'aws-cdk-lib/aws-iam';
13
+ import {LambdaFunction} from 'aws-cdk-lib/aws-events-targets';
14
+ import * as path from 'path';
15
+ import {AccessLogsAnalysisProps} from "./AccessLogsAnalysisProps";
16
+ import {ColumnTransformationRules} from "../../functions/access-logs-analysis/partitioning/types";
17
+
18
+ /**
19
+ * Provides the AWS resources to analyze access logs. This construct is derived from the official AWS sample
20
+ * CloudFormation stack:
21
+ * {@link https://github.com/aws-samples/amazon-cloudfront-access-logs-queries/blob/mainline/template.yaml}
22
+ */
23
+ export abstract class AccessLogsAnalysis extends Construct {
24
+
25
+ protected static readonly ACCESS_LOGS_FOLDER_UNPROCESSED = 'unprocessed';
26
+ protected static readonly ACCESS_LOGS_FOLDER_GROUPED_BY_DATE = 'by-date';
27
+ protected static readonly ACCESS_LOGS_FOLDER_TRANSFORMED = 'transformed';
28
+ protected static readonly ACCESS_LOGS_FOLDER_ATHENA_RESULTS = 'athena-query-results';
29
+
30
+ protected readonly resourceIdPrefix: string;
31
+ protected readonly bucket: Bucket;
32
+ protected readonly workgroup: CfnWorkGroup;
33
+ protected readonly database: Database;
34
+ protected readonly accessLogsByDateTable: CloudFrontAccessLogsByDateTable;
35
+ protected readonly accessLogsParquetTable: AccessLogsParquetTable;
36
+ protected readonly groupByDateLayer: LayerVersion;
37
+ protected readonly groupByDateLambda: Function;
38
+ protected readonly partitioningLayer: LayerVersion;
39
+ protected readonly createPartitionLambda: Function;
40
+ protected readonly transformPartitionLambda: Function;
41
+ protected readonly createPartitionsScheduler: Rule;
42
+ protected readonly transformPartitionsScheduler: Rule;
43
+
44
+ protected constructor(scope: Construct, id: string, props: AccessLogsAnalysisProps) {
45
+ super(scope, id);
46
+ this.resourceIdPrefix = props.resourcePrefix;
47
+ this.bucket = props.bucket;
48
+ this.setupLifecycleRules(props);
49
+ this.workgroup = this.createWorkgroup();
50
+ this.database = this.createGlueDatabase();
51
+ this.accessLogsByDateTable = this.createAccessLogsByDateTable();
52
+ this.accessLogsParquetTable = this.createAccessLogsParquetTable();
53
+ this.groupByDateLayer = this.createGroupByDateLayer();
54
+ this.groupByDateLambda = this.createGroupByDateLambda();
55
+ this.partitioningLayer = this.createPartitioningLayer();
56
+ this.createPartitionLambda = this.createCreatePartitionLambda();
57
+ this.transformPartitionLambda = this.createTransformPartitionLambda();
58
+ this.createPartitionsScheduler = this.createCreatePartitionsScheduler();
59
+ this.transformPartitionsScheduler = this.createTransformPartitionsScheduler(props);
60
+ }
61
+
62
+ private setupLifecycleRules(props: AccessLogsAnalysisProps): void {
63
+ // cleanup raw and partitioned access logs after a configurable time range
64
+
65
+ this.bucket.addLifecycleRule({
66
+ id: `${this.resourceIdPrefix}-cleanup-unprocessed`,
67
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_UNPROCESSED}/*`,
68
+ expiration: props.expireRawLogsAfter ?? Duration.days(7),
69
+ });
70
+
71
+ this.bucket.addLifecycleRule({
72
+ id: `${this.resourceIdPrefix}-cleanup-grouped`,
73
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_GROUPED_BY_DATE}/*`,
74
+ expiration: props.expireIntermediateLogsAfter ?? Duration.days(7),
75
+ });
76
+
77
+ this.bucket.addLifecycleRule({
78
+ id: `${this.resourceIdPrefix}-cleanup-transformed`,
79
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_TRANSFORMED}/*`,
80
+ expiration: props.expireTransformedLogsAfter ?? Duration.days(180),
81
+ });
82
+
83
+ /* CHECKME: delete query results after 1 week
84
+ this.bucket.addLifecycleRule({
85
+ id: `${this.resourceIdPrefix}-cleanup-query-results`,
86
+ prefix: `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_ATHENA_RESULTS}/*`,
87
+ expiration: Duration.days(7),
88
+ });
89
+ */
90
+ }
91
+
92
+ private createWorkgroup(): CfnWorkGroup {
93
+ const workgroupName = `${this.resourceIdPrefix}-workgroup`;
94
+
95
+ // due to a current bug, the tags are not automatically assigned to the workgroup
96
+ // note, that the keys must be converted to lower case
97
+ const tags: CfnTag[] = Stack.of(this)
98
+ .tags.renderTags()
99
+ .map((tag: { Key: string; Value: string }) => ({key: tag.Key, value: tag.Value}));
100
+
101
+ return new CfnWorkGroup(this, workgroupName, {
102
+ name: workgroupName,
103
+ workGroupConfiguration: {
104
+ publishCloudWatchMetricsEnabled: false,
105
+ resultConfiguration: {
106
+ outputLocation: `s3://${this.bucket.bucketName}/${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_ATHENA_RESULTS}`,
107
+ },
108
+ enforceWorkGroupConfiguration: true
109
+ },
110
+ tags,
111
+ });
112
+ }
113
+
114
+ private createGlueDatabase(): Database {
115
+ const databaseName = `${this.resourceIdPrefix}-database`;
116
+ return new Database(this, databaseName, {
117
+ // Athena doesn't support dashes in database/table names
118
+ databaseName: databaseName.replace(/-/g, '_'),
119
+ });
120
+ }
121
+
122
+ protected abstract createAccessLogsByDateTable(): CloudFrontAccessLogsByDateTable;
123
+
124
+ protected abstract createAccessLogsParquetTable(): CloudFrontAccessLogsByDateTable;
125
+
126
+ private createGroupByDateLayer(): LayerVersion {
127
+ const layerVersionName = `${this.resourceIdPrefix}-group-by-date-dependencies`;
128
+ return new LayerVersion(this, layerVersionName, {
129
+ layerVersionName,
130
+ compatibleArchitectures: [Architecture.ARM_64, Architecture.X86_64],
131
+ compatibleRuntimes: [Runtime.NODEJS_14_X, Runtime.NODEJS_16_X],
132
+ code: Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/group-by-date/build/layer')),
133
+ removalPolicy: RemovalPolicy.DESTROY,
134
+ });
135
+ }
136
+
137
+ private createPartitioningLayer(): LayerVersion {
138
+ const layerVersionName = `${this.resourceIdPrefix}-partitioning-dependencies`;
139
+ return new LayerVersion(this, layerVersionName, {
140
+ layerVersionName,
141
+ compatibleArchitectures: [Architecture.ARM_64, Architecture.X86_64],
142
+ compatibleRuntimes: [Runtime.NODEJS_14_X, Runtime.NODEJS_16_X],
143
+ code: Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/layer')),
144
+ removalPolicy: RemovalPolicy.DESTROY,
145
+ });
146
+ }
147
+
148
+ /**
149
+ * A Lambda function to be triggered whenever a new access log is created.
150
+ * It moves the raw access logs into a sub folder hierarchy by year, month, day and hour.
151
+ */
152
+ private createGroupByDateLambda(): Function {
153
+ const functionName = `${this.resourceIdPrefix}-group-by-date`;
154
+
155
+ const lambda = new Function(this, functionName, {
156
+ functionName,
157
+ architecture: Architecture.ARM_64,
158
+ runtime: Runtime.NODEJS_16_X,
159
+ code: Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/group-by-date/build/app')),
160
+ memorySize: 512,
161
+ timeout: Duration.seconds(20),
162
+ handler: 'index.handler',
163
+ layers: [this.groupByDateLayer],
164
+ events: [
165
+ new S3EventSource(this.bucket, {
166
+ events: [EventType.OBJECT_CREATED],
167
+ filters: [this.getUnprocessedObjectsFilter()],
168
+ }),
169
+ ],
170
+ logRetention: RetentionDays.TWO_WEEKS,
171
+ environment: {
172
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
173
+ NODE_OPTIONS: '--enable-source-maps',
174
+ TARGET_FOLDER: AccessLogsAnalysis.ACCESS_LOGS_FOLDER_GROUPED_BY_DATE,
175
+ RAW_ACCESS_LOG_FILE_PATTERN: this.getRawAccessLogFilePattern().source,
176
+ },
177
+ });
178
+
179
+ this.bucket.grantReadWrite(lambda);
180
+ this.bucket.grantDelete(lambda, `${AccessLogsAnalysis.ACCESS_LOGS_FOLDER_UNPROCESSED}/*`);
181
+
182
+ return lambda;
183
+ }
184
+
185
+ protected abstract getUnprocessedObjectsFilter(): NotificationKeyFilter;
186
+
187
+ /** The pattern to identify unprocessed access logs depends on the producing source */
188
+ protected abstract getRawAccessLogFilePattern(): RegExp;
189
+
190
+ /**
191
+ * Creates a new partition for the upcoming hour in the access log database.
192
+ */
193
+ private createCreatePartitionLambda(): Function {
194
+ const functionName = `${this.resourceIdPrefix}-create-part`;
195
+
196
+ const lambda = new Function(this, functionName, {
197
+ functionName,
198
+ architecture: Architecture.ARM_64,
199
+ runtime: Runtime.NODEJS_16_X,
200
+ code: Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/app'), {
201
+ exclude: ['transform-partition*', '*.d.ts'],
202
+ }),
203
+ memorySize: 128,
204
+ timeout: Duration.seconds(20),
205
+ handler: 'create-partition.handler',
206
+ layers: [this.partitioningLayer],
207
+ logRetention: RetentionDays.TWO_WEEKS,
208
+ environment: {
209
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
210
+ NODE_OPTIONS: '--enable-source-maps',
211
+ WORKGROUP: this.workgroup.name,
212
+ DATABASE: this.database.databaseName,
213
+ TABLE: this.accessLogsByDateTable.tableName,
214
+ },
215
+ });
216
+
217
+ // grant access to S3 bucket and to execute Athena queries which create new partitions
218
+ this.bucket.grantReadWrite(lambda);
219
+ lambda.addToRolePolicy(
220
+ new PolicyStatement({
221
+ effect: Effect.ALLOW,
222
+ actions: [
223
+ 'athena:StartQueryExecution',
224
+ 'athena:GetQueryExecution',
225
+ 'glue:CreateDatabase',
226
+ 'glue:CreatePartition',
227
+ 'glue:GetDatabase',
228
+ 'glue:GetTable',
229
+ 'glue:BatchCreatePartition',
230
+ ],
231
+ resources: ['*'],
232
+ })
233
+ );
234
+
235
+ return lambda;
236
+ }
237
+
238
+ /**
239
+ * Transforms partitions from the Hive format to Parquet.
240
+ */
241
+ private createTransformPartitionLambda(): Function {
242
+ const functionName = `${this.resourceIdPrefix}-transform-part`;
243
+
244
+ const lambda = new Function(this, functionName, {
245
+ functionName,
246
+ architecture: Architecture.ARM_64,
247
+ runtime: Runtime.NODEJS_16_X,
248
+ code: Code.fromAsset(path.join(__dirname, '../../functions/access-logs-analysis/partitioning/build/app'), {
249
+ exclude: ['create-partition*', '*.d.ts'],
250
+ }),
251
+ memorySize: 128,
252
+ timeout: Duration.seconds(20),
253
+ handler: 'transform-partition.handler',
254
+ layers: [this.partitioningLayer],
255
+ logRetention: RetentionDays.TWO_WEEKS,
256
+ environment: {
257
+ AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
258
+ NODE_OPTIONS: '--enable-source-maps',
259
+ WORKGROUP: this.workgroup.name,
260
+ DATABASE: this.database.databaseName,
261
+ SOURCE_TABLE: this.accessLogsByDateTable.tableName,
262
+ TARGET_TABLE: this.accessLogsParquetTable.tableName,
263
+ },
264
+ });
265
+
266
+ // grant access to S3 bucket and to execute Athena queries which create new partitions
267
+ this.bucket.grantReadWrite(lambda);
268
+ lambda.addToRolePolicy(
269
+ new PolicyStatement({
270
+ effect: Effect.ALLOW,
271
+ actions: [
272
+ 'athena:StartQueryExecution',
273
+ 'athena:GetQueryExecution',
274
+ 'glue:CreateDatabase',
275
+ 'glue:CreatePartition',
276
+ 'glue:GetDatabase',
277
+ 'glue:GetTable',
278
+ 'glue:BatchCreatePartition',
279
+ 'glue:GetPartition',
280
+ 'glue:GetPartitions',
281
+ 'glue:CreateTable',
282
+ 'glue:DeleteTable',
283
+ 'glue:DeletePartition',
284
+ ],
285
+ resources: ['*'],
286
+ })
287
+ );
288
+
289
+ return lambda;
290
+ }
291
+
292
+ private createCreatePartitionsScheduler(): Rule {
293
+ const ruleName = `${this.resourceIdPrefix}-create-part-sched`;
294
+ return new Rule(this, ruleName, {
295
+ ruleName,
296
+ schedule: Schedule.cron({minute: '55'}),
297
+ targets: [new LambdaFunction(this.createPartitionLambda, {retryAttempts: 10})],
298
+ });
299
+ }
300
+
301
+ private createTransformPartitionsScheduler(props: AccessLogsAnalysisProps): Rule {
302
+ const ruleName = `${this.resourceIdPrefix}-transform-part-sched`;
303
+ return new Rule(this, ruleName, {
304
+ ruleName,
305
+ schedule: Schedule.cron({minute: '1'}),
306
+ targets: [
307
+ new LambdaFunction(this.transformPartitionLambda, {
308
+ event: this.getTransformPartitionInvocationProps(props),
309
+ retryAttempts: 10,
310
+ }),
311
+ ],
312
+ });
313
+ }
314
+
315
+ private getTransformPartitionInvocationProps(props: AccessLogsAnalysisProps): RuleTargetInput {
316
+ return RuleTargetInput.fromObject({
317
+ columnNames: [
318
+ ...this.accessLogsParquetTable.columns,
319
+ ...(this.accessLogsParquetTable.partitionKeys as Column[]),
320
+ ].map(column => column.name),
321
+ columnTransformations: this.getColumnTransformationRules(props),
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Needs to return the log source specific column transformation rules
327
+ * (e.g. to anonymize IP addresses).
328
+ */
329
+ protected abstract getColumnTransformationRules(props: AccessLogsAnalysisProps): ColumnTransformationRules;
330
+ }