cdk-nuxt 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -19
- package/index.d.ts +2 -1
- package/index.js +3 -3
- package/lib/cli/deploy-server.js +33 -5
- package/lib/cli/destroy-server.js +13 -0
- package/lib/functions/access-logs-analysis/group-by-date/.gitignore +2 -0
- package/lib/functions/access-logs-analysis/group-by-date/build/app/index.d.ts +2 -0
- package/lib/functions/access-logs-analysis/group-by-date/build/app/index.js +57 -0
- package/lib/functions/access-logs-analysis/group-by-date/build/app/index.js.map +1 -0
- package/lib/functions/access-logs-analysis/group-by-date/index.d.ts +2 -0
- package/lib/functions/access-logs-analysis/group-by-date/index.js +57 -0
- package/lib/functions/access-logs-analysis/group-by-date/index.ts +64 -0
- package/lib/functions/access-logs-analysis/group-by-date/package.json +20 -0
- package/lib/functions/access-logs-analysis/group-by-date/tsconfig.json +33 -0
- package/lib/functions/access-logs-analysis/group-by-date/yarn.lock +1203 -0
- package/lib/functions/access-logs-analysis/partitioning/.gitignore +2 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.d.ts +1 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.js +57 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/create-partition.js.map +1 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.d.ts +2 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.js +72 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/transform-partition.js.map +1 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/types.d.ts +7 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/types.js +3 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/types.js.map +1 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/util.d.ts +9 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/util.js +44 -0
- package/lib/functions/access-logs-analysis/partitioning/build/app/util.js.map +1 -0
- package/lib/functions/access-logs-analysis/partitioning/create-partition.d.ts +1 -0
- package/lib/functions/access-logs-analysis/partitioning/create-partition.js +57 -0
- package/lib/functions/access-logs-analysis/partitioning/create-partition.ts +58 -0
- package/lib/functions/access-logs-analysis/partitioning/package.json +20 -0
- package/lib/functions/access-logs-analysis/partitioning/transform-partition.d.ts +2 -0
- package/lib/functions/access-logs-analysis/partitioning/transform-partition.js +72 -0
- package/lib/functions/access-logs-analysis/partitioning/transform-partition.ts +83 -0
- package/lib/functions/access-logs-analysis/partitioning/tsconfig.json +33 -0
- package/lib/functions/access-logs-analysis/partitioning/types.d.ts +7 -0
- package/lib/functions/access-logs-analysis/partitioning/types.js +3 -0
- package/lib/functions/access-logs-analysis/partitioning/types.ts +8 -0
- package/lib/functions/access-logs-analysis/partitioning/util.d.ts +9 -0
- package/lib/functions/access-logs-analysis/partitioning/util.js +44 -0
- package/lib/functions/access-logs-analysis/partitioning/util.ts +52 -0
- package/lib/functions/access-logs-analysis/partitioning/yarn.lock +951 -0
- package/lib/stack/NuxtAppStackProps.js +3 -0
- package/lib/stack/{nuxt-app-static-assets.d.ts → NuxtAppStaticAssets.d.ts} +9 -4
- package/lib/stack/NuxtAppStaticAssets.js +71 -0
- package/lib/stack/{nuxt-app-static-assets.ts → NuxtAppStaticAssets.ts} +30 -23
- package/lib/stack/access-logs-analysis/AccessLogsAnalysis.d.ts +66 -0
- package/lib/stack/access-logs-analysis/AccessLogsAnalysis.js +271 -0
- package/lib/stack/access-logs-analysis/AccessLogsAnalysis.ts +331 -0
- package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.d.ts +21 -0
- package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.js +3 -0
- package/lib/stack/access-logs-analysis/AccessLogsAnalysisProps.ts +26 -0
- package/lib/stack/access-logs-analysis/AccessLogsParquetTable.d.ts +9 -0
- package/lib/stack/access-logs-analysis/AccessLogsParquetTable.js +25 -0
- package/lib/stack/access-logs-analysis/AccessLogsParquetTable.ts +23 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableConfig.d.ts +8 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableConfig.js +167 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableConfig.ts +164 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableProps.d.ts +7 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableProps.js +3 -0
- package/lib/stack/access-logs-analysis/AccessLogsTableProps.ts +8 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.d.ts +36 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.js +60 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsAnalysis.ts +73 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.d.ts +9 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.js +37 -0
- package/lib/stack/access-logs-analysis/CloudFrontAccessLogsByDateTable.ts +44 -0
- package/lib/stack/server/{nuxt-server-app-stack.d.ts → NuxtServerAppStack.d.ts} +32 -82
- package/lib/stack/server/NuxtServerAppStack.js +506 -0
- package/lib/stack/server/{nuxt-server-app-stack.ts → NuxtServerAppStack.ts} +105 -106
- package/lib/stack/server/NuxtServerAppStackProps.d.ts +85 -0
- package/lib/stack/server/NuxtServerAppStackProps.js +3 -0
- package/lib/stack/server/NuxtServerAppStackProps.ts +100 -0
- package/lib/templates/stack-index-server.ts +108 -15
- package/package.json +3 -1
- package/lib/stack/nuxt-app-stack-props.js +0 -3
- package/lib/stack/nuxt-app-static-assets.js +0 -72
- package/lib/stack/server/nuxt-server-app-stack.js +0 -447
- /package/lib/stack/{nuxt-app-stack-props.d.ts → NuxtAppStackProps.d.ts} +0 -0
- /package/lib/stack/{nuxt-app-stack-props.ts → NuxtAppStackProps.ts} +0 -0
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Easily deploy Nuxt 3 applications via CDK on AWS including the following feature
|
|
|
15
15
|
- Automatic upload of the build files for CSR and static assets to [S3](https://aws.amazon.com/s3/) with optimized caching rules
|
|
16
16
|
- Scheduled pings of the Nuxt app to keep the Lambda warm for fast responses via [EventBridge](https://aws.amazon.com/eventbridge/) rules
|
|
17
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
|
|
18
19
|
|
|
19
20
|
## Prerequisites
|
|
20
21
|
|
|
@@ -110,9 +111,20 @@ Defaults to 1792MB (optimized for costs and performance for standard Nuxt apps).
|
|
|
110
111
|
### enableTracing?: boolean
|
|
111
112
|
Whether to enable AWS X-Ray for the Nuxt Lambda function.
|
|
112
113
|
|
|
114
|
+
### enableApi?: boolean
|
|
115
|
+
Whether to enable (HTTPS only) API access to the Nuxt app via the `/api` path which support all HTTP methods.
|
|
116
|
+
See https://nuxt.com/docs/guide/directory-structure/server#recipes for details.
|
|
117
|
+
|
|
113
118
|
### enableSitemap?: boolean
|
|
114
119
|
Whether to enable a global Sitemap bucket which is permanently accessible through multiple deployments.
|
|
115
120
|
|
|
121
|
+
### enableAccessLogsAnalysis?: boolean
|
|
122
|
+
Whether to enable access logs analysis for the Nuxt app's CloudFront distribution via Athena.
|
|
123
|
+
|
|
124
|
+
### accessLogCookies?: string[]
|
|
125
|
+
An array of cookies to include for reporting in the access logs analysis.
|
|
126
|
+
Only has an effect when `enableAccessLogsAnalysis` is set to `true`.
|
|
127
|
+
|
|
116
128
|
### outdatedAssetsRetentionDays?: boolean
|
|
117
129
|
The number of days to retain static assets of outdated deployments in the S3 bucket.
|
|
118
130
|
Useful to allow users to still access old assets after a new deployment when they are still browsing on an old version.
|
|
@@ -172,7 +184,42 @@ Afterwards, the CDK stack will be deployed to AWS.
|
|
|
172
184
|
node_modules/.bin/cdk-nuxt-deploy-server
|
|
173
185
|
```
|
|
174
186
|
|
|
175
|
-
##
|
|
187
|
+
## Destroy the Stack
|
|
188
|
+
|
|
189
|
+
If you want to destroy the stack and all its resources (including storage, e.g., access logs), run the following script:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
node_modules/.bin/cdk-nuxt-destroy-server
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Reference: Created AWS Resources
|
|
196
|
+
|
|
197
|
+
In the following, you can find an overview of the AWS resources that will be created by this package for reference.
|
|
198
|
+
|
|
199
|
+
### NuxtServerAppStack
|
|
200
|
+
|
|
201
|
+
This stack is responsible for deploying dynamic Nuxt 3 apps to AWS.
|
|
202
|
+
The following AWS resources will be created by this stack:
|
|
203
|
+
|
|
204
|
+
- [Lambda](https://aws.amazon.com/lambda/):
|
|
205
|
+
- 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.
|
|
206
|
+
- A Lambda function that deletes the outdated static assets of the Nuxt app from S3.
|
|
207
|
+
- [S3](https://aws.amazon.com/s3/):
|
|
208
|
+
- A bucket to store the client files and static assets of the Nuxt build (`.nuxt/dist/client`) with optimized cache settings.
|
|
209
|
+
- A bucket to store the CloudFront access logs for analysis via Athena. Only created if `enableAccessLogsAnalysis` is set to `true`.
|
|
210
|
+
- [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.
|
|
211
|
+
- [API Gateway](https://aws.amazon.com/api-gateway/): An HTTP API to make the Nuxt Lambda function publicly available.
|
|
212
|
+
- [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.
|
|
213
|
+
- [EventBridge](https://aws.amazon.com/eventbridge/):
|
|
214
|
+
- 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.
|
|
215
|
+
- 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.
|
|
216
|
+
- [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`.
|
|
217
|
+
|
|
218
|
+
## Guidelines
|
|
219
|
+
|
|
220
|
+
In the following, you can find some guidelines for the deployment and usages of this package.
|
|
221
|
+
|
|
222
|
+
### Automatically deploy on every push (CD) via [GitHub Actions](https://github.com/features/actions)
|
|
176
223
|
|
|
177
224
|
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.
|
|
178
225
|
|
|
@@ -222,21 +269,3 @@ jobs:
|
|
|
222
269
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
223
270
|
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
|
|
224
271
|
```
|
|
225
|
-
|
|
226
|
-
## Advanced: Used AWS Resources
|
|
227
|
-
|
|
228
|
-
### NuxtServerAppStack
|
|
229
|
-
|
|
230
|
-
This stack is responsible for deploying dynamic Nuxt 3 apps to AWS.
|
|
231
|
-
The following AWS resources will be created by this stack:
|
|
232
|
-
|
|
233
|
-
- [Lambda](https://aws.amazon.com/lambda/):
|
|
234
|
-
- 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.
|
|
235
|
-
- A Lambda function that deletes the outdated static assets of the Nuxt app from S3.
|
|
236
|
-
- [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.
|
|
237
|
-
- [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.
|
|
238
|
-
- [API Gateway](https://aws.amazon.com/api-gateway/): An HTTP API to make the Nuxt Lambda function publicly available.
|
|
239
|
-
- [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.
|
|
240
|
-
- [EventBridge](https://aws.amazon.com/eventbridge/):
|
|
241
|
-
- 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.
|
|
242
|
-
- 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
|
|
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
|
|
5
|
-
Object.defineProperty(exports, "NuxtServerAppStack", { enumerable: true, get: function () { return
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
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=
|
package/lib/cli/deploy-server.js
CHANGED
|
@@ -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
|
|
17
|
-
shell.echo(`${logPrefix}: Installing the assets cleanup
|
|
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
|
|
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
|
|
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
|
|
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,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,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
|
+
}
|