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
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  <p>
4
4
  <a href="https://github.com/ferdinandfrank/cdk-nuxt/actions/workflows/publish.yml"><img alt="Build" src="https://img.shields.io/github/actions/workflow/status/ferdinandfrank/cdk-nuxt/publish.yml?logo=github" /></a>
5
5
  <a href="https://www.npmjs.com/package/cdk-nuxt"><img alt="Version" src="https://img.shields.io/npm/v/cdk-nuxt.svg" /></a>
6
+ <a href="https://www.npmjs.com/package/cdk-nuxt"><img alt="Downloads" src="https://img.shields.io/npm/dm/cdk-nuxt.svg"></a>
6
7
  <a href="https://www.npmjs.com/package/cdk-nuxt"><img alt="License" src="https://img.shields.io/npm/l/cdk-nuxt.svg" /></a>
7
8
  </p>
8
9
 
@@ -14,6 +15,7 @@ Easily deploy Nuxt 3 applications via CDK on AWS including the following feature
14
15
  - Automatic upload of the build files for CSR and static assets to [S3](https://aws.amazon.com/s3/) with optimized caching rules
15
16
  - Scheduled pings of the Nuxt app to keep the Lambda warm for fast responses via [EventBridge](https://aws.amazon.com/eventbridge/) rules
16
17
  - Automatic cleanup of outdated static assets and build files
18
+ - Access logs analysis via [Athena](https://aws.amazon.com/athena/) for the Nuxt app's CloudFront distribution
17
19
 
18
20
  ## Prerequisites
19
21
 
@@ -112,6 +114,13 @@ Whether to enable AWS X-Ray for the Nuxt Lambda function.
112
114
  ### enableSitemap?: boolean
113
115
  Whether to enable a global Sitemap bucket which is permanently accessible through multiple deployments.
114
116
 
117
+ ### enableAccessLogsAnalysis?: boolean
118
+ Whether to enable access logs analysis for the Nuxt app's CloudFront distribution via Athena.
119
+
120
+ ### accessLogCookies?: string[]
121
+ An array of cookies to include for reporting in the access logs analysis.
122
+ Only has an effect when `enableAccessLogsAnalysis` is set to `true`.
123
+
115
124
  ### outdatedAssetsRetentionDays?: boolean
116
125
  The number of days to retain static assets of outdated deployments in the S3 bucket.
117
126
  Useful to allow users to still access old assets after a new deployment when they are still browsing on an old version.
@@ -171,7 +180,42 @@ Afterwards, the CDK stack will be deployed to AWS.
171
180
  node_modules/.bin/cdk-nuxt-deploy-server
172
181
  ```
173
182
 
174
- ## Optional: Automatically deploy on every push (CD) via [GitHub Actions](https://github.com/features/actions)
183
+ ## Destroy the Stack
184
+
185
+ If you want to destroy the stack and all its resources (including storage, e.g., access logs), run the following script:
186
+
187
+ ```bash
188
+ node_modules/.bin/cdk-nuxt-destroy-server
189
+ ```
190
+
191
+ ## Reference: Created AWS Resources
192
+
193
+ In the following, you can find an overview of the AWS resources that will be created by this package for reference.
194
+
195
+ ### NuxtServerAppStack
196
+
197
+ This stack is responsible for deploying dynamic Nuxt 3 apps to AWS.
198
+ The following AWS resources will be created by this stack:
199
+
200
+ - [Lambda](https://aws.amazon.com/lambda/):
201
+ - A Lambda function to render the Nuxt app including a separated Lambda layer to provide the `node_modules` of the Nuxt app required for server-side rendering.
202
+ - A Lambda function that deletes the outdated static assets of the Nuxt app from S3.
203
+ - [S3](https://aws.amazon.com/s3/):
204
+ - A bucket to store the client files and static assets of the Nuxt build (`.nuxt/dist/client`) with optimized cache settings.
205
+ - A bucket to store the CloudFront access logs for analysis via Athena. Only created if `enableAccessLogsAnalysis` is set to `true`.
206
+ - [Route53](https://aws.amazon.com/route53/): Two DNS records (`A` for IPv4 and `AAAA` for IPv6) in the configured hosted zone to make the Nuxt app available on the internet via the configured custom domain.
207
+ - [API Gateway](https://aws.amazon.com/api-gateway/): An HTTP API to make the Nuxt Lambda function publicly available.
208
+ - [CloudFront](https://aws.amazon.com/cloudfront/): A distribution to route incoming requests to the Nuxt Lambda function (via the API Gateway) and the S3 bucket to serve the static assets for the Nuxt app.
209
+ - [EventBridge](https://aws.amazon.com/eventbridge/):
210
+ - A scheduled rule to ping the Nuxt app's Lambda function every 5 minutes in order to keep it warm and to speed up initial SSR requests.
211
+ - A scheduled rule to trigger the cleanup Lambda function for deleting the outdated static assets of the Nuxt app from S3 every tuesday at 03:30 AM GMT.
212
+ - [Athena](https://aws.amazon.com/athena/): A database and table to analyze the access logs of the Nuxt app's CloudFront distribution. Only created if `enableAccessLogsAnalysis` is set to `true`.
213
+
214
+ ## Guidelines
215
+
216
+ In the following, you can find some guidelines for the deployment and usages of this package.
217
+
218
+ ### Automatically deploy on every push (CD) via [GitHub Actions](https://github.com/features/actions)
175
219
 
176
220
  Feel free to copy the following GitHub Actions YAML file content into a YAML file at `.github/workflows/deploy.yml` to automatically build and deploy the Nuxt app to AWS on every push to a specific branch.<br/>This only works if you're using GitHub for the project's VCS repository.
177
221
 
@@ -221,21 +265,3 @@ jobs:
221
265
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
222
266
  AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
223
267
  ```
224
-
225
- ## Advanced: Used AWS Resources
226
-
227
- ### NuxtServerAppStack
228
-
229
- This stack is responsible for deploying dynamic Nuxt 3 apps to AWS.
230
- The following AWS resources will be created by this stack:
231
-
232
- - [Lambda](https://aws.amazon.com/lambda/):
233
- - A Lambda function to render the Nuxt app including a separated Lambda layer to provide the `node_modules` of the Nuxt app required for server-side rendering.
234
- - A Lambda function that deletes the outdated static assets of the Nuxt app from S3.
235
- - [S3](https://aws.amazon.com/s3/): A bucket to store the client files of the Nuxt build (`.nuxt/dist/client`) and the custom static files of the Nuxt app (`static`) with optimized cache settings.
236
- - [Route53](https://aws.amazon.com/route53/): Two DNS records (`A` for IPv4 and `AAAA` for IPv6) in the configured hosted zone to make the Nuxt app available on the internet via the configured custom domain.
237
- - [API Gateway](https://aws.amazon.com/api-gateway/): An HTTP API to make the Nuxt Lambda function publicly available.
238
- - [CloudFront](https://aws.amazon.com/cloudfront/): A distribution to route incoming requests to the Nuxt Lambda function (via the API Gateway) and the S3 bucket to serve the static assets for the Nuxt app.
239
- - [EventBridge](https://aws.amazon.com/eventbridge/):
240
- - A scheduled rule to ping the Nuxt app's Lambda function every 5 minutes in order to keep it warm and to speed up initial SSR requests.
241
- - A scheduled rule to trigger the cleanup Lambda function for deleting the outdated static assets of the Nuxt app from S3 every tuesday at 03:30 AM GMT.
package/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- export { NuxtServerAppStack, NuxtServerAppStackProps } from "./lib/stack/server/nuxt-server-app-stack";
1
+ export { NuxtServerAppStack } from "./lib/stack/server/NuxtServerAppStack";
2
+ export { NuxtServerAppStackProps } from "./lib/stack/server/NuxtServerAppStackProps";
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NuxtServerAppStack = void 0;
4
- var nuxt_server_app_stack_1 = require("./lib/stack/server/nuxt-server-app-stack");
5
- Object.defineProperty(exports, "NuxtServerAppStack", { enumerable: true, get: function () { return nuxt_server_app_stack_1.NuxtServerAppStack; } });
6
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxrRkFBb0c7QUFBNUYsMkhBQUEsa0JBQWtCLE9BQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQge051eHRTZXJ2ZXJBcHBTdGFjaywgTnV4dFNlcnZlckFwcFN0YWNrUHJvcHN9IGZyb20gXCIuL2xpYi9zdGFjay9zZXJ2ZXIvbnV4dC1zZXJ2ZXItYXBwLXN0YWNrXCJcbiJdfQ==
4
+ var NuxtServerAppStack_1 = require("./lib/stack/server/NuxtServerAppStack");
5
+ Object.defineProperty(exports, "NuxtServerAppStack", { enumerable: true, get: function () { return NuxtServerAppStack_1.NuxtServerAppStack; } });
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw0RUFBd0U7QUFBaEUsd0hBQUEsa0JBQWtCLE9BQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQge051eHRTZXJ2ZXJBcHBTdGFja30gZnJvbSBcIi4vbGliL3N0YWNrL3NlcnZlci9OdXh0U2VydmVyQXBwU3RhY2tcIlxuZXhwb3J0IHtOdXh0U2VydmVyQXBwU3RhY2tQcm9wc30gZnJvbSBcIi4vbGliL3N0YWNrL3NlcnZlci9OdXh0U2VydmVyQXBwU3RhY2tQcm9wc1wiXG4iXX0=
@@ -13,16 +13,44 @@ const deploymentSourceFolder = '.output/server';
13
13
  shell.echo(`${logPrefix}: Deleting outdated CDK files...`);
14
14
  shell.rm('-rf', 'cdk.out');
15
15
 
16
- // Preparing the assets cleanup lambda function
17
- shell.echo(`${logPrefix}: Installing the assets cleanup lambda function...`);
16
+ // Preparing the assets cleanup Lambda function
17
+ shell.echo(`${logPrefix}: Installing the assets cleanup Lambda function...`);
18
18
  shell.cd(path.join(__dirname, '../functions/assets-cleanup'));
19
19
  if (shell.exec('yarn install --frozen-lockfile && yarn install-layer').code !== 0) {
20
- shell.echo(`${logPrefix} Error: Installation of assets cleanup lambda function failed.`);
20
+ shell.echo(`${logPrefix} Error: Installation of assets cleanup Lambda function failed.`);
21
21
  shell.exit(1);
22
22
  }
23
- shell.echo(`${logPrefix}: Building the assets cleanup lambda function...`);
23
+ shell.echo(`${logPrefix}: Building the assets cleanup Lambda function...`);
24
24
  if (shell.exec('yarn build').code !== 0) {
25
- shell.echo(`${logPrefix} Error: Build of assets cleanup lambda function failed.`);
25
+ shell.echo(`${logPrefix} Error: Build of assets cleanup Lambda function failed.`);
26
+ shell.exit(1);
27
+ }
28
+ shell.cd(rootFolder);
29
+
30
+ // Preparing the access logs analysis group-by-date Lambda function
31
+ shell.echo(`${logPrefix}: Installing the access logs analysis group-by-date Lambda function...`);
32
+ shell.cd(path.join(__dirname, '../functions/access-logs-analysis/group-by-date'));
33
+ if (shell.exec('yarn install --frozen-lockfile && yarn install-layer').code !== 0) {
34
+ shell.echo(`${logPrefix} Error: Installation of access logs analysis group-by-date Lambda function failed.`);
35
+ shell.exit(1);
36
+ }
37
+ shell.echo(`${logPrefix}: Building the access logs analysis group-by-date Lambda function...`);
38
+ if (shell.exec('yarn build').code !== 0) {
39
+ shell.echo(`${logPrefix} Error: Build of access logs analysis group-by-date Lambda function failed.`);
40
+ shell.exit(1);
41
+ }
42
+ shell.cd(rootFolder);
43
+
44
+ // Preparing the access logs analysis partitioning Lambda function
45
+ shell.echo(`${logPrefix}: Installing the access logs analysis partitioning Lambda function...`);
46
+ shell.cd(path.join(__dirname, '../functions/access-logs-analysis/partitioning'));
47
+ if (shell.exec('yarn install --frozen-lockfile && yarn install-layer').code !== 0) {
48
+ shell.echo(`${logPrefix} Error: Installation of access logs analysis partitioning Lambda function failed.`);
49
+ shell.exit(1);
50
+ }
51
+ shell.echo(`${logPrefix}: Building the access logs analysis partitioning Lambda function...`);
52
+ if (shell.exec('yarn build').code !== 0) {
53
+ shell.echo(`${logPrefix} Error: Build of access logs analysis partitioning Lambda function failed.`);
26
54
  shell.exit(1);
27
55
  }
28
56
  shell.cd(rootFolder);
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ const shell = require("shelljs");
4
+ const logPrefix = 'CDK Nuxt Destroy';
5
+
6
+ shell.echo(`${logPrefix}: Destroying stack on AWS via CDK...`);
7
+ if (shell.exec('yarn cdk destroy --require-approval never --all --app="yarn ts-node stack/index.ts" ' + process.argv.slice(2)).code !== 0) {
8
+ shell.echo(`${logPrefix} Error: CDK destroy failed.`);
9
+ shell.exit(1);
10
+ }
11
+
12
+ shell.echo(`${logPrefix}: CDK destroy successful.`);
13
+
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ build
@@ -0,0 +1,2 @@
1
+ import type { S3Event } from 'aws-lambda';
2
+ export declare const handler: (event: S3Event) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handler = void 0;
4
+ /**
5
+ * This script moves access logs into a target folder hierarchy, structured by year, month, day, and hour (UTC).
6
+ * That way, the logs can easier be processed with Athena.
7
+ * Taken and adjusted from https://github.com/aws-samples/amazon-cloudfront-access-logs-queries.
8
+ * This Lambda supports access logs from both: CloudFront and S3.
9
+ */
10
+ const client_s3_1 = require("@aws-sdk/client-s3");
11
+ // without leading and trailing slashes
12
+ const targetFolder = process.env.TARGET_FOLDER;
13
+ if (!targetFolder) {
14
+ throw new Error('Required environment variable TARGET_FOLDER missing!');
15
+ }
16
+ if (!process.env.RAW_ACCESS_LOG_FILE_PATTERN) {
17
+ throw new Error('Required environment variable RAW_ACCESS_LOG_FILE_PATTERN missing!');
18
+ }
19
+ // unfortunately, CloudFront logs and S3 logs are completely different. That's why the pattern to identify
20
+ // unprocessed log files need to be configurable
21
+ const rawAccessLogFilePattern = new RegExp(process.env.RAW_ACCESS_LOG_FILE_PATTERN);
22
+ // matches for everything after the last slash
23
+ const filenamePattern = /[^/]+$/;
24
+ const s3Client = new client_s3_1.S3Client({ region: process.env.AWS_REGION });
25
+ const internalHandler = async (event) => {
26
+ try {
27
+ const pendingMoves = event.Records.map(async (record) => {
28
+ const bucket = record.s3.bucket.name;
29
+ const sourceKey = record.s3.object.key;
30
+ const match = rawAccessLogFilePattern.exec(sourceKey);
31
+ // skip other files/folder
32
+ if (match?.groups) {
33
+ const { year, month, day, hour } = match.groups;
34
+ const filename = filenamePattern.exec(sourceKey)[0];
35
+ const targetKey = `${targetFolder}/year=${year}/month=${month}/day=${day}/hour=${hour}/${filename}`;
36
+ const copyCommand = new client_s3_1.CopyObjectCommand({
37
+ Bucket: bucket,
38
+ Key: targetKey,
39
+ CopySource: bucket + '/' + sourceKey,
40
+ });
41
+ await s3Client.send(copyCommand);
42
+ const deleteSourceCommand = new client_s3_1.DeleteObjectCommand({
43
+ Bucket: bucket,
44
+ Key: sourceKey,
45
+ });
46
+ await s3Client.send(deleteSourceCommand);
47
+ }
48
+ });
49
+ const processed = await Promise.all(pendingMoves);
50
+ console.log(`successfully moved ${processed.length} files`);
51
+ }
52
+ catch (error) {
53
+ console.error('### unexpected runtime error ###', error);
54
+ }
55
+ };
56
+ exports.handler = internalHandler;
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,kDAAoF;AAGpF,uCAAuC;AACvC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAC/C,IAAI,CAAC,YAAY,EAAE;IACjB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;CACzE;AACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE;IAC5C,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;CACvF;AACD,0GAA0G;AAC1G,gDAAgD;AAChD,MAAM,uBAAuB,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAEpF,8CAA8C;AAC9C,MAAM,eAAe,GAAG,QAAQ,CAAC;AAEjC,MAAM,QAAQ,GAAG,IAAI,oBAAQ,CAAC,EAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAC,CAAC,CAAC;AAEhE,MAAM,eAAe,GAAG,KAAK,EAAE,KAAc,EAAE,EAAE;IAC/C,IAAI;QACF,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE;YACpD,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;YACrC,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC;YACvC,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAEtD,0BAA0B;YAC1B,IAAI,KAAK,EAAE,MAAM,EAAE;gBACjB,MAAM,EAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAC,GAAG,KAAK,CAAC,MAAM,CAAC;gBAC9C,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAE,CAAC,CAAC,CAAC,CAAC;gBACrD,MAAM,SAAS,GAAG,GAAG,YAAY,SAAS,IAAI,UAAU,KAAK,QAAQ,GAAG,SAAS,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAEpG,MAAM,WAAW,GAAG,IAAI,6BAAiB,CAAC;oBACxC,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,SAAS;oBACd,UAAU,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;iBACrC,CAAC,CAAC;gBAEH,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAEjC,MAAM,mBAAmB,GAAG,IAAI,+BAAmB,CAAC;oBAClD,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,SAAS;iBACf,CAAC,CAAC;gBAEH,MAAM,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;aAC1C;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,sBAAsB,SAAS,CAAC,MAAM,QAAQ,CAAC,CAAC;KAC7D;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;KAC1D;AACH,CAAC,CAAC;AAEW,QAAA,OAAO,GAAG,eAAe,CAAC","sourcesContent":["/**\n * This script moves access logs into a target folder hierarchy, structured by year, month, day, and hour (UTC).\n * That way, the logs can easier be processed with Athena.\n * Taken and adjusted from https://github.com/aws-samples/amazon-cloudfront-access-logs-queries.\n * This Lambda supports access logs from both: CloudFront and S3.\n */\nimport {S3Client, CopyObjectCommand, DeleteObjectCommand} from '@aws-sdk/client-s3';\nimport type {S3Event} from 'aws-lambda';\n\n// without leading and trailing slashes\nconst targetFolder = process.env.TARGET_FOLDER;\nif (!targetFolder) {\n throw new Error('Required environment variable TARGET_FOLDER missing!');\n}\nif (!process.env.RAW_ACCESS_LOG_FILE_PATTERN) {\n throw new Error('Required environment variable RAW_ACCESS_LOG_FILE_PATTERN missing!');\n}\n// unfortunately, CloudFront logs and S3 logs are completely different. That's why the pattern to identify\n// unprocessed log files need to be configurable\nconst rawAccessLogFilePattern = new RegExp(process.env.RAW_ACCESS_LOG_FILE_PATTERN);\n\n// matches for everything after the last slash\nconst filenamePattern = /[^/]+$/;\n\nconst s3Client = new S3Client({region: process.env.AWS_REGION});\n\nconst internalHandler = async (event: S3Event) => {\n try {\n const pendingMoves = event.Records.map(async record => {\n const bucket = record.s3.bucket.name;\n const sourceKey = record.s3.object.key;\n const match = rawAccessLogFilePattern.exec(sourceKey);\n\n // skip other files/folder\n if (match?.groups) {\n const {year, month, day, hour} = match.groups;\n const filename = filenamePattern.exec(sourceKey)![0];\n const targetKey = `${targetFolder}/year=${year}/month=${month}/day=${day}/hour=${hour}/${filename}`;\n\n const copyCommand = new CopyObjectCommand({\n Bucket: bucket,\n Key: targetKey,\n CopySource: bucket + '/' + sourceKey,\n });\n\n await s3Client.send(copyCommand);\n\n const deleteSourceCommand = new DeleteObjectCommand({\n Bucket: bucket,\n Key: sourceKey,\n });\n\n await s3Client.send(deleteSourceCommand);\n }\n });\n\n const processed = await Promise.all(pendingMoves);\n console.log(`successfully moved ${processed.length} files`);\n } catch (error) {\n console.error('### unexpected runtime error ###', error);\n }\n};\n\nexport const handler = internalHandler;\n"]}
@@ -0,0 +1,2 @@
1
+ import type { S3Event } from 'aws-lambda';
2
+ export declare const handler: (event: S3Event) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handler = void 0;
4
+ /**
5
+ * This script moves access logs into a target folder hierarchy, structured by year, month, day, and hour (UTC).
6
+ * That way, the logs can easier be processed with Athena.
7
+ * Taken and adjusted from https://github.com/aws-samples/amazon-cloudfront-access-logs-queries.
8
+ * This Lambda supports access logs from both: CloudFront and S3.
9
+ */
10
+ const client_s3_1 = require("@aws-sdk/client-s3");
11
+ // without leading and trailing slashes
12
+ const targetFolder = process.env.TARGET_FOLDER;
13
+ if (!targetFolder) {
14
+ throw new Error('Required environment variable TARGET_FOLDER missing!');
15
+ }
16
+ if (!process.env.RAW_ACCESS_LOG_FILE_PATTERN) {
17
+ throw new Error('Required environment variable RAW_ACCESS_LOG_FILE_PATTERN missing!');
18
+ }
19
+ // unfortunately, CloudFront logs and S3 logs are completely different. That's why the pattern to identify
20
+ // unprocessed log files need to be configurable
21
+ const rawAccessLogFilePattern = new RegExp(process.env.RAW_ACCESS_LOG_FILE_PATTERN);
22
+ // matches for everything after the last slash
23
+ const filenamePattern = /[^/]+$/;
24
+ const s3Client = new client_s3_1.S3Client({ region: process.env.AWS_REGION });
25
+ const internalHandler = async (event) => {
26
+ try {
27
+ const pendingMoves = event.Records.map(async (record) => {
28
+ const bucket = record.s3.bucket.name;
29
+ const sourceKey = record.s3.object.key;
30
+ const match = rawAccessLogFilePattern.exec(sourceKey);
31
+ // skip other files/folder
32
+ if (match === null || match === void 0 ? void 0 : match.groups) {
33
+ const { year, month, day, hour } = match.groups;
34
+ const filename = filenamePattern.exec(sourceKey)[0];
35
+ const targetKey = `${targetFolder}/year=${year}/month=${month}/day=${day}/hour=${hour}/${filename}`;
36
+ const copyCommand = new client_s3_1.CopyObjectCommand({
37
+ Bucket: bucket,
38
+ Key: targetKey,
39
+ CopySource: bucket + '/' + sourceKey,
40
+ });
41
+ await s3Client.send(copyCommand);
42
+ const deleteSourceCommand = new client_s3_1.DeleteObjectCommand({
43
+ Bucket: bucket,
44
+ Key: sourceKey,
45
+ });
46
+ await s3Client.send(deleteSourceCommand);
47
+ }
48
+ });
49
+ const processed = await Promise.all(pendingMoves);
50
+ console.log(`successfully moved ${processed.length} files`);
51
+ }
52
+ catch (error) {
53
+ console.error('### unexpected runtime error ###', error);
54
+ }
55
+ };
56
+ exports.handler = internalHandler;
57
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQTs7Ozs7R0FLRztBQUNILGtEQUFvRjtBQUdwRix1Q0FBdUM7QUFDdkMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUM7QUFDL0MsSUFBSSxDQUFDLFlBQVksRUFBRTtJQUNqQixNQUFNLElBQUksS0FBSyxDQUFDLHNEQUFzRCxDQUFDLENBQUM7Q0FDekU7QUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQkFBMkIsRUFBRTtJQUM1QyxNQUFNLElBQUksS0FBSyxDQUFDLG9FQUFvRSxDQUFDLENBQUM7Q0FDdkY7QUFDRCwwR0FBMEc7QUFDMUcsZ0RBQWdEO0FBQ2hELE1BQU0sdUJBQXVCLEdBQUcsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO0FBRXBGLDhDQUE4QztBQUM5QyxNQUFNLGVBQWUsR0FBRyxRQUFRLENBQUM7QUFFakMsTUFBTSxRQUFRLEdBQUcsSUFBSSxvQkFBUSxDQUFDLEVBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFDLENBQUMsQ0FBQztBQUVoRSxNQUFNLGVBQWUsR0FBRyxLQUFLLEVBQUUsS0FBYyxFQUFFLEVBQUU7SUFDL0MsSUFBSTtRQUNGLE1BQU0sWUFBWSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBQyxNQUFNLEVBQUMsRUFBRTtZQUNwRCxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUM7WUFDckMsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDO1lBQ3ZDLE1BQU0sS0FBSyxHQUFHLHVCQUF1QixDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUV0RCwwQkFBMEI7WUFDMUIsSUFBSSxLQUFLLGFBQUwsS0FBSyx1QkFBTCxLQUFLLENBQUUsTUFBTSxFQUFFO2dCQUNqQixNQUFNLEVBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQztnQkFDOUMsTUFBTSxRQUFRLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDckQsTUFBTSxTQUFTLEdBQUcsR0FBRyxZQUFZLFNBQVMsSUFBSSxVQUFVLEtBQUssUUFBUSxHQUFHLFNBQVMsSUFBSSxJQUFJLFFBQVEsRUFBRSxDQUFDO2dCQUVwRyxNQUFNLFdBQVcsR0FBRyxJQUFJLDZCQUFpQixDQUFDO29CQUN4QyxNQUFNLEVBQUUsTUFBTTtvQkFDZCxHQUFHLEVBQUUsU0FBUztvQkFDZCxVQUFVLEVBQUUsTUFBTSxHQUFHLEdBQUcsR0FBRyxTQUFTO2lCQUNyQyxDQUFDLENBQUM7Z0JBRUgsTUFBTSxRQUFRLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO2dCQUVqQyxNQUFNLG1CQUFtQixHQUFHLElBQUksK0JBQW1CLENBQUM7b0JBQ2xELE1BQU0sRUFBRSxNQUFNO29CQUNkLEdBQUcsRUFBRSxTQUFTO2lCQUNmLENBQUMsQ0FBQztnQkFFSCxNQUFNLFFBQVEsQ0FBQyxJQUFJLENBQUMsbUJBQW1CLENBQUMsQ0FBQzthQUMxQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxTQUFTLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ2xELE9BQU8sQ0FBQyxHQUFHLENBQUMsc0JBQXNCLFNBQVMsQ0FBQyxNQUFNLFFBQVEsQ0FBQyxDQUFDO0tBQzdEO0lBQUMsT0FBTyxLQUFLLEVBQUU7UUFDZCxPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0tBQzFEO0FBQ0gsQ0FBQyxDQUFDO0FBRVcsUUFBQSxPQUFPLEdBQUcsZUFBZSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGlzIHNjcmlwdCBtb3ZlcyBhY2Nlc3MgbG9ncyBpbnRvIGEgdGFyZ2V0IGZvbGRlciBoaWVyYXJjaHksIHN0cnVjdHVyZWQgYnkgeWVhciwgbW9udGgsIGRheSwgYW5kIGhvdXIgKFVUQykuXG4gKiBUaGF0IHdheSwgdGhlIGxvZ3MgY2FuIGVhc2llciBiZSBwcm9jZXNzZWQgd2l0aCBBdGhlbmEuXG4gKiBUYWtlbiBhbmQgYWRqdXN0ZWQgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vYXdzLXNhbXBsZXMvYW1hem9uLWNsb3VkZnJvbnQtYWNjZXNzLWxvZ3MtcXVlcmllcy5cbiAqIFRoaXMgTGFtYmRhIHN1cHBvcnRzIGFjY2VzcyBsb2dzIGZyb20gYm90aDogQ2xvdWRGcm9udCBhbmQgUzMuXG4gKi9cbmltcG9ydCB7UzNDbGllbnQsIENvcHlPYmplY3RDb21tYW5kLCBEZWxldGVPYmplY3RDb21tYW5kfSBmcm9tICdAYXdzLXNkay9jbGllbnQtczMnO1xuaW1wb3J0IHR5cGUge1MzRXZlbnR9IGZyb20gJ2F3cy1sYW1iZGEnO1xuXG4vLyB3aXRob3V0IGxlYWRpbmcgYW5kIHRyYWlsaW5nIHNsYXNoZXNcbmNvbnN0IHRhcmdldEZvbGRlciA9IHByb2Nlc3MuZW52LlRBUkdFVF9GT0xERVI7XG5pZiAoIXRhcmdldEZvbGRlcikge1xuICB0aHJvdyBuZXcgRXJyb3IoJ1JlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlIFRBUkdFVF9GT0xERVIgbWlzc2luZyEnKTtcbn1cbmlmICghcHJvY2Vzcy5lbnYuUkFXX0FDQ0VTU19MT0dfRklMRV9QQVRURVJOKSB7XG4gIHRocm93IG5ldyBFcnJvcignUmVxdWlyZWQgZW52aXJvbm1lbnQgdmFyaWFibGUgUkFXX0FDQ0VTU19MT0dfRklMRV9QQVRURVJOIG1pc3NpbmchJyk7XG59XG4vLyB1bmZvcnR1bmF0ZWx5LCBDbG91ZEZyb250IGxvZ3MgYW5kIFMzIGxvZ3MgYXJlIGNvbXBsZXRlbHkgZGlmZmVyZW50LiBUaGF0J3Mgd2h5IHRoZSBwYXR0ZXJuIHRvIGlkZW50aWZ5XG4vLyB1bnByb2Nlc3NlZCBsb2cgZmlsZXMgbmVlZCB0byBiZSBjb25maWd1cmFibGVcbmNvbnN0IHJhd0FjY2Vzc0xvZ0ZpbGVQYXR0ZXJuID0gbmV3IFJlZ0V4cChwcm9jZXNzLmVudi5SQVdfQUNDRVNTX0xPR19GSUxFX1BBVFRFUk4pO1xuXG4vLyBtYXRjaGVzIGZvciBldmVyeXRoaW5nIGFmdGVyIHRoZSBsYXN0IHNsYXNoXG5jb25zdCBmaWxlbmFtZVBhdHRlcm4gPSAvW14vXSskLztcblxuY29uc3QgczNDbGllbnQgPSBuZXcgUzNDbGllbnQoe3JlZ2lvbjogcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTn0pO1xuXG5jb25zdCBpbnRlcm5hbEhhbmRsZXIgPSBhc3luYyAoZXZlbnQ6IFMzRXZlbnQpID0+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBwZW5kaW5nTW92ZXMgPSBldmVudC5SZWNvcmRzLm1hcChhc3luYyByZWNvcmQgPT4ge1xuICAgICAgY29uc3QgYnVja2V0ID0gcmVjb3JkLnMzLmJ1Y2tldC5uYW1lO1xuICAgICAgY29uc3Qgc291cmNlS2V5ID0gcmVjb3JkLnMzLm9iamVjdC5rZXk7XG4gICAgICBjb25zdCBtYXRjaCA9IHJhd0FjY2Vzc0xvZ0ZpbGVQYXR0ZXJuLmV4ZWMoc291cmNlS2V5KTtcblxuICAgICAgLy8gc2tpcCBvdGhlciBmaWxlcy9mb2xkZXJcbiAgICAgIGlmIChtYXRjaD8uZ3JvdXBzKSB7XG4gICAgICAgIGNvbnN0IHt5ZWFyLCBtb250aCwgZGF5LCBob3VyfSA9IG1hdGNoLmdyb3VwcztcbiAgICAgICAgY29uc3QgZmlsZW5hbWUgPSBmaWxlbmFtZVBhdHRlcm4uZXhlYyhzb3VyY2VLZXkpIVswXTtcbiAgICAgICAgY29uc3QgdGFyZ2V0S2V5ID0gYCR7dGFyZ2V0Rm9sZGVyfS95ZWFyPSR7eWVhcn0vbW9udGg9JHttb250aH0vZGF5PSR7ZGF5fS9ob3VyPSR7aG91cn0vJHtmaWxlbmFtZX1gO1xuXG4gICAgICAgIGNvbnN0IGNvcHlDb21tYW5kID0gbmV3IENvcHlPYmplY3RDb21tYW5kKHtcbiAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldCxcbiAgICAgICAgICBLZXk6IHRhcmdldEtleSxcbiAgICAgICAgICBDb3B5U291cmNlOiBidWNrZXQgKyAnLycgKyBzb3VyY2VLZXksXG4gICAgICAgIH0pO1xuXG4gICAgICAgIGF3YWl0IHMzQ2xpZW50LnNlbmQoY29weUNvbW1hbmQpO1xuXG4gICAgICAgIGNvbnN0IGRlbGV0ZVNvdXJjZUNvbW1hbmQgPSBuZXcgRGVsZXRlT2JqZWN0Q29tbWFuZCh7XG4gICAgICAgICAgQnVja2V0OiBidWNrZXQsXG4gICAgICAgICAgS2V5OiBzb3VyY2VLZXksXG4gICAgICAgIH0pO1xuXG4gICAgICAgIGF3YWl0IHMzQ2xpZW50LnNlbmQoZGVsZXRlU291cmNlQ29tbWFuZCk7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICBjb25zdCBwcm9jZXNzZWQgPSBhd2FpdCBQcm9taXNlLmFsbChwZW5kaW5nTW92ZXMpO1xuICAgIGNvbnNvbGUubG9nKGBzdWNjZXNzZnVsbHkgbW92ZWQgJHtwcm9jZXNzZWQubGVuZ3RofSBmaWxlc2ApO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGNvbnNvbGUuZXJyb3IoJyMjIyB1bmV4cGVjdGVkIHJ1bnRpbWUgZXJyb3IgIyMjJywgZXJyb3IpO1xuICB9XG59O1xuXG5leHBvcnQgY29uc3QgaGFuZGxlciA9IGludGVybmFsSGFuZGxlcjtcbiJdfQ==
@@ -0,0 +1,64 @@
1
+ /**
2
+ * This script moves access logs into a target folder hierarchy, structured by year, month, day, and hour (UTC).
3
+ * That way, the logs can easier be processed with Athena.
4
+ * Taken and adjusted from https://github.com/aws-samples/amazon-cloudfront-access-logs-queries.
5
+ * This Lambda supports access logs from both: CloudFront and S3.
6
+ */
7
+ import {S3Client, CopyObjectCommand, DeleteObjectCommand} from '@aws-sdk/client-s3';
8
+ import type {S3Event} from 'aws-lambda';
9
+
10
+ // without leading and trailing slashes
11
+ const targetFolder = process.env.TARGET_FOLDER;
12
+ if (!targetFolder) {
13
+ throw new Error('Required environment variable TARGET_FOLDER missing!');
14
+ }
15
+ if (!process.env.RAW_ACCESS_LOG_FILE_PATTERN) {
16
+ throw new Error('Required environment variable RAW_ACCESS_LOG_FILE_PATTERN missing!');
17
+ }
18
+ // unfortunately, CloudFront logs and S3 logs are completely different. That's why the pattern to identify
19
+ // unprocessed log files need to be configurable
20
+ const rawAccessLogFilePattern = new RegExp(process.env.RAW_ACCESS_LOG_FILE_PATTERN);
21
+
22
+ // matches for everything after the last slash
23
+ const filenamePattern = /[^/]+$/;
24
+
25
+ const s3Client = new S3Client({region: process.env.AWS_REGION});
26
+
27
+ const internalHandler = async (event: S3Event) => {
28
+ try {
29
+ const pendingMoves = event.Records.map(async record => {
30
+ const bucket = record.s3.bucket.name;
31
+ const sourceKey = record.s3.object.key;
32
+ const match = rawAccessLogFilePattern.exec(sourceKey);
33
+
34
+ // skip other files/folder
35
+ if (match?.groups) {
36
+ const {year, month, day, hour} = match.groups;
37
+ const filename = filenamePattern.exec(sourceKey)![0];
38
+ const targetKey = `${targetFolder}/year=${year}/month=${month}/day=${day}/hour=${hour}/${filename}`;
39
+
40
+ const copyCommand = new CopyObjectCommand({
41
+ Bucket: bucket,
42
+ Key: targetKey,
43
+ CopySource: bucket + '/' + sourceKey,
44
+ });
45
+
46
+ await s3Client.send(copyCommand);
47
+
48
+ const deleteSourceCommand = new DeleteObjectCommand({
49
+ Bucket: bucket,
50
+ Key: sourceKey,
51
+ });
52
+
53
+ await s3Client.send(deleteSourceCommand);
54
+ }
55
+ });
56
+
57
+ const processed = await Promise.all(pendingMoves);
58
+ console.log(`successfully moved ${processed.length} files`);
59
+ } catch (error) {
60
+ console.error('### unexpected runtime error ###', error);
61
+ }
62
+ };
63
+
64
+ export const handler = internalHandler;
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "cdk-nuxt-access-log-analysis-group-by-date",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "license": "UNLICENSED",
6
+ "private": true,
7
+ "scripts": {
8
+ "install-layer": "yarn install --frozen-lockfile --production --modules-folder build/layer/nodejs/node_modules",
9
+ "build": "tsc --project ./tsconfig.json"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^16.11.0",
13
+ "@types/aws-lambda": "^8.10.101",
14
+ "ts-node": "^10.9.1",
15
+ "typescript": "^4.7.4"
16
+ },
17
+ "dependencies": {
18
+ "@aws-sdk/client-s3": "^3.131.0"
19
+ }
20
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./build/app",
4
+ "target": "ES2021",
5
+ "module": "CommonJS",
6
+ "lib": ["es2021"],
7
+ "declaration": true,
8
+ "alwaysStrict": true,
9
+ "sourceMap": true,
10
+ "inlineSources": true,
11
+ "inlineSourceMap": false,
12
+ "sourceRoot": "/",
13
+ "noEmitHelpers": true,
14
+ "importHelpers": true,
15
+ "noImplicitAny": false,
16
+ "noUnusedLocals": false,
17
+ "noUnusedParameters": false,
18
+ "noImplicitReturns": true,
19
+ "noFallthroughCasesInSwitch": false,
20
+ "emitDecoratorMetadata": true,
21
+ "experimentalDecorators": true,
22
+ "strictPropertyInitialization": false,
23
+ "moduleResolution": "node",
24
+ "baseUrl": "./",
25
+ "paths": {
26
+ "*": ["./node_modules/*"]
27
+ },
28
+ "typeRoots": ["./node_modules/@types"],
29
+ "skipLibCheck": true
30
+ },
31
+ "include": ["./"],
32
+ "exclude": ["./build", "./node_modules"]
33
+ }