From 0a360fb6e3a75659a993866679f59e2735505216 Mon Sep 17 00:00:00 2001 From: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:43:00 +0100 Subject: [PATCH 1/6] fix: extract generic cdk asset publish failures (#2271) --- .changeset/poor-phones-attend.md | 5 +++++ package-lock.json | 1 + packages/backend-deployer/package.json | 3 ++- .../src/cdk_error_mapper.test.ts | 18 ++++++++++++++++++ .../backend-deployer/src/cdk_error_mapper.ts | 17 +++++++++++++++-- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 .changeset/poor-phones-attend.md diff --git a/.changeset/poor-phones-attend.md b/.changeset/poor-phones-attend.md new file mode 100644 index 0000000000..b3dbdabe6a --- /dev/null +++ b/.changeset/poor-phones-attend.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-deployer': patch +--- + +extract generic cdk asset publish failures diff --git a/package-lock.json b/package-lock.json index 25adc2dc0a..a89d39fc1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31647,6 +31647,7 @@ "@aws-amplify/platform-core": "^1.2.0", "@aws-amplify/plugin-types": "^1.4.0", "execa": "^8.0.1", + "strip-ansi": "^6.0.1", "tsx": "^4.6.1" }, "peerDependencies": { diff --git a/packages/backend-deployer/package.json b/packages/backend-deployer/package.json index 36e80d5817..adc2a77a22 100644 --- a/packages/backend-deployer/package.json +++ b/packages/backend-deployer/package.json @@ -22,7 +22,8 @@ "@aws-amplify/platform-core": "^1.2.2", "@aws-amplify/plugin-types": "^1.4.0", "execa": "^8.0.1", - "tsx": "^4.6.1" + "tsx": "^4.6.1", + "strip-ansi": "^6.0.1" }, "peerDependencies": { "aws-cdk": "^2.158.0", diff --git a/packages/backend-deployer/src/cdk_error_mapper.test.ts b/packages/backend-deployer/src/cdk_error_mapper.test.ts index f1d7d9653c..be08eedc17 100644 --- a/packages/backend-deployer/src/cdk_error_mapper.test.ts +++ b/packages/backend-deployer/src/cdk_error_mapper.test.ts @@ -429,6 +429,24 @@ npm error A complete log of this run can be found in: /home/some-path/.npm/_logs errorName: 'AccessDeniedError', expectedDownstreamErrorMessage: undefined, }, + { + // eslint-disable-next-line spellcheck/spell-checker + errorMessage: `[31mamplify-stack-sandbox-11[22m: fail: Bucket named 'cdk-abc-assets-11-us-west-2' exists, but we do not have access to it. +amplify-stack-sandbox-11: fail: Bucket named 'cdk-abc-assets-11-us-west-2' exists, but we do not have access to it. +Failed to publish asset abc:current_account-current_region`, + expectedTopLevelErrorMessage: `CDK failed to publish assets due to 'Bucket named 'cdk-abc-assets-11-us-west-2' exists, but we do not have access to it.'`, + errorName: 'CDKAssetPublishError', + expectedDownstreamErrorMessage: undefined, + }, + { + // eslint-disable-next-line spellcheck/spell-checker + errorMessage: `[31mamplify-user-sandbox-c71414864a: fail: socket hang up + +Failed to publish asset abc:current_account-current_region`, + expectedTopLevelErrorMessage: `CDK failed to publish assets due to 'socket hang up'`, + errorName: 'CDKAssetPublishError', + expectedDownstreamErrorMessage: undefined, + }, { errorMessage: `Error: Transform failed with 1 error:` + diff --git a/packages/backend-deployer/src/cdk_error_mapper.ts b/packages/backend-deployer/src/cdk_error_mapper.ts index 9ed05d9d7e..67d5159d50 100644 --- a/packages/backend-deployer/src/cdk_error_mapper.ts +++ b/packages/backend-deployer/src/cdk_error_mapper.ts @@ -4,6 +4,7 @@ import { AmplifyFault, AmplifyUserError, } from '@aws-amplify/platform-core'; +import stripANSI from 'strip-ansi'; import { BackendDeployerOutputFormatter } from './types.js'; /** @@ -27,13 +28,14 @@ export class CdkErrorMapper { return amplifyError; } + const errorMessage = stripANSI(error.message); const matchingError = this.getKnownErrors().find((knownError) => - knownError.errorRegex.test(error.message) + knownError.errorRegex.test(errorMessage) ); if (matchingError) { // Extract meaningful contextual information if available - const matchGroups = error.message.match(matchingError.errorRegex); + const matchGroups = errorMessage.match(matchingError.errorRegex); if (matchGroups && matchGroups.length > 1) { // If the contextual information can be used in the error message use it, else consider it as a downstream cause @@ -400,6 +402,16 @@ export class CdkErrorMapper { errorName: 'AppSyncResolverSyntaxError', classification: 'ERROR', }, + { + errorRegex: new RegExp( + `amplify-.*-(branch|sandbox)-.*fail: (?.*)${this.multiLineEolRegex}.*Failed to publish asset`, + 'm' + ), + humanReadableErrorMessage: `CDK failed to publish assets due to '{publishFailure}'`, + resolutionMessage: `Check the error message for more details.`, + errorName: 'CDKAssetPublishError', + classification: 'ERROR', + }, // Generic error printed by CDK. Order matters so keep this towards the bottom of this list { // Error: .* is printed to stderr during cdk synth @@ -447,6 +459,7 @@ export type CDKDeploymentError = | 'BootstrapNotDetectedError' | 'BootstrapDetectionError' | 'BootstrapOutdatedError' + | 'CDKAssetPublishError' | 'CDKResolveAWSAccountError' | 'CDKVersionMismatchError' | 'CFNUpdateNotSupportedError' From 72b2fe0d763392641d1001ebccc57c98191c24e4 Mon Sep 17 00:00:00 2001 From: Andrew Taylor Date: Tue, 26 Nov 2024 10:10:26 -0800 Subject: [PATCH 2/6] Allow Node 22 functions (#2269) * Add support to `@aws-amplify/backend-function` for Node 22 Add support to `@aws-amplify/backend-function` for Node 22, which is a [supported Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtime-deprecation-levels) that was added in [`aws-cdk-lib/aws-lambda` version `2.168.0`](https://github.com/aws/aws-cdk/releases/tag/v2.168.0) on November 20th, 2024 * Regen API docs * npm clean * Update aws-cdk-lib and aws-cdk to 2.168.0 * Update aws-cdk-lib and aws-cdk to 2.168.0 * Update test * Changeset for aws-cdk-lib upgrade * Update .changeset/forty-bulldogs-end.md --------- Co-authored-by: Kamil Sobol --- .changeset/forty-bulldogs-end.md | 8 +++ .changeset/new-rings-suffer.md | 20 ++++++ package-lock.json | 68 +++++++++---------- packages/ai-constructs/package.json | 2 +- packages/auth-construct/package.json | 2 +- packages/backend-ai/package.json | 2 +- packages/backend-auth/package.json | 2 +- packages/backend-data/package.json | 2 +- packages/backend-deployer/package.json | 2 +- packages/backend-function/API.md | 2 +- packages/backend-function/package.json | 2 +- packages/backend-function/src/factory.test.ts | 6 +- packages/backend-function/src/factory.ts | 3 +- packages/backend-output-storage/package.json | 2 +- .../backend-platform-test-stubs/package.json | 2 +- packages/backend-storage/package.json | 2 +- packages/backend/package.json | 2 +- packages/integration-tests/package.json | 2 +- packages/plugin-types/package.json | 2 +- packages/sandbox/package.json | 2 +- 20 files changed, 82 insertions(+), 53 deletions(-) create mode 100644 .changeset/forty-bulldogs-end.md create mode 100644 .changeset/new-rings-suffer.md diff --git a/.changeset/forty-bulldogs-end.md b/.changeset/forty-bulldogs-end.md new file mode 100644 index 0000000000..99aa5a81e2 --- /dev/null +++ b/.changeset/forty-bulldogs-end.md @@ -0,0 +1,8 @@ +--- +'@aws-amplify/backend-function': minor +'@aws-amplify/backend': minor +--- + +Add support to `@aws-amplify/backend-function` for Node 22 + +Add support to `@aws-amplify/backend-function` for Node 22, which is a [supported Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtime-deprecation-levels) that was added in [`aws-cdk-lib/aws-lambda` version `2.168.0`](https://github.com/aws/aws-cdk/releases/tag/v2.168.0) on November 20th, 2024 diff --git a/.changeset/new-rings-suffer.md b/.changeset/new-rings-suffer.md new file mode 100644 index 0000000000..f3df724642 --- /dev/null +++ b/.changeset/new-rings-suffer.md @@ -0,0 +1,20 @@ +--- +'@aws-amplify/backend-platform-test-stubs': patch +'@aws-amplify/backend-output-storage': patch +'@aws-amplify/integration-tests': patch +'@aws-amplify/backend-deployer': patch +'@aws-amplify/backend-function': patch +'@aws-amplify/schema-generator': patch +'@aws-amplify/backend-storage': patch +'@aws-amplify/auth-construct': patch +'@aws-amplify/ai-constructs': patch +'@aws-amplify/client-config': patch +'@aws-amplify/backend-auth': patch +'@aws-amplify/backend-data': patch +'@aws-amplify/plugin-types': patch +'@aws-amplify/backend-ai': patch +'@aws-amplify/backend': patch +'@aws-amplify/sandbox': patch +--- + +update aws-cdk lib to ^2.168.0 diff --git a/package-lock.json b/package-lock.json index a89d39fc1d..abf7f39339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6159,15 +6159,15 @@ } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==", + "version": "2.2.213", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.213.tgz", + "integrity": "sha512-crm1yDJmORJF2Y9gDvNUX4Q3iQXVhWrL7oaZfpx3QDqrvVz5UEgWGpJdysqDuWFZTmIgtrI5Svq3UfdwCNNpsg==", "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.3.tgz", + "integrity": "sha512-cDG1w3ieM6eOT9mTefRuTypk95+oyD7P5X/wRltwmYxU7nZc3+076YEVS6vrjDKr3ADYbfn0lDKpfB1FBtO9CQ==", "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { @@ -19581,9 +19581,9 @@ "license": "0BSD" }, "node_modules/aws-cdk": { - "version": "2.164.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.164.1.tgz", - "integrity": "sha512-dWRViQgHLe7GHkPIQGA+8EQSm8TBcxemyCC3HHW3wbLMWUDbspio9Dktmw5EmWxlFjjWh86Dk1JWf1zKQo8C5g==", + "version": "2.171.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.171.0.tgz", + "integrity": "sha512-tVo4hYS0iAbiCFxUh2/7KoDL6EHEIUAurCJaBs2BOUAB9DfBuUAPp8DGUK4iVF8XzrQQ4f3a5ivN7DteQrGBEQ==", "license": "Apache-2.0", "peer": true, "bin": { @@ -19597,9 +19597,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.164.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.164.1.tgz", - "integrity": "sha512-jNvVmfZJbZoAYU94b5dzTlF2z6JXJ204NgcYY5haOa6mq3m2bzdYPXnPtB5kpAX3oBi++yoRdmLhqgckdEhUZA==", + "version": "2.171.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.171.0.tgz", + "integrity": "sha512-N0O0mWI+S8PAbiED7eV05qVfbJgHkdh+jQS4BOG6CUAc/i2s9U4w+XDRkK8auO0HgTM9+ahEaFfucMuQ4abRWQ==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -19615,10 +19615,10 @@ ], "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-awscli-v1": "^2.2.208", + "@aws-cdk/asset-kubectl-v20": "^2.1.3", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^38.0.0", + "@aws-cdk/cloud-assembly-schema": "^38.0.1", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.2.0", @@ -19742,9 +19742,9 @@ "license": "MIT" }, "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.1", + "version": "3.0.3", "inBundle": true, - "license": "MIT" + "license": "BSD-3-Clause" }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { "version": "11.2.0", @@ -31526,7 +31526,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31545,7 +31545,7 @@ "@aws-sdk/util-arn-parser": "^3.568.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31574,7 +31574,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31591,7 +31591,7 @@ "@aws-amplify/plugin-types": "^1.5.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31614,7 +31614,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31635,23 +31635,23 @@ "@aws-amplify/platform-core": "^1.2.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, "packages/backend-deployer": { "name": "@aws-amplify/backend-deployer", - "version": "1.1.9", + "version": "1.1.10", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/platform-core": "^1.2.0", + "@aws-amplify/platform-core": "^1.2.2", "@aws-amplify/plugin-types": "^1.4.0", "execa": "^8.0.1", "strip-ansi": "^6.0.1", "tsx": "^4.6.1" }, "peerDependencies": { - "aws-cdk": "^2.158.0", + "aws-cdk": "^2.168.0", "typescript": "^5.0.0" } }, @@ -31673,7 +31673,7 @@ "uuid": "^9.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31712,7 +31712,7 @@ "@aws-amplify/plugin-types": "^1.3.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0" + "aws-cdk-lib": "^2.168.0" } }, "packages/backend-platform-test-stubs": { @@ -31721,7 +31721,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.3.1", - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -31752,7 +31752,7 @@ "@aws-amplify/platform-core": "^1.2.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -32129,7 +32129,7 @@ "@zip.js/zip.js": "^2.7.52", "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0", "execa": "^8.0.1", "fs-extra": "^11.1.1", @@ -32180,7 +32180,7 @@ }, "packages/platform-core": { "name": "@aws-amplify/platform-core", - "version": "1.2.1", + "version": "1.2.2", "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.5.0", @@ -32219,7 +32219,7 @@ }, "peerDependencies": { "@aws-sdk/types": "^3.609.0", - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } }, @@ -32367,7 +32367,7 @@ "@types/parse-gitignore": "^1.0.0" }, "peerDependencies": { - "aws-cdk": "^2.158.0" + "aws-cdk": "^2.168.0" } }, "packages/schema-generator": { diff --git a/packages/ai-constructs/package.json b/packages/ai-constructs/package.json index 73fdf04e27..584d3d361c 100644 --- a/packages/ai-constructs/package.json +++ b/packages/ai-constructs/package.json @@ -38,7 +38,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/auth-construct/package.json b/packages/auth-construct/package.json index 42c20143ee..cb70f43074 100644 --- a/packages/auth-construct/package.json +++ b/packages/auth-construct/package.json @@ -25,7 +25,7 @@ "@aws-sdk/util-arn-parser": "^3.568.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-ai/package.json b/packages/backend-ai/package.json index 6699cc19a5..1cf30fb297 100644 --- a/packages/backend-ai/package.json +++ b/packages/backend-ai/package.json @@ -30,7 +30,7 @@ "@aws-amplify/plugin-types": "^1.5.0" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-auth/package.json b/packages/backend-auth/package.json index 43ea82bfdc..054d782dc6 100644 --- a/packages/backend-auth/package.json +++ b/packages/backend-auth/package.json @@ -33,7 +33,7 @@ "aws-lambda": "^1.0.7" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-data/package.json b/packages/backend-data/package.json index ba3ed7e55a..0ec2b12a4c 100644 --- a/packages/backend-data/package.json +++ b/packages/backend-data/package.json @@ -24,7 +24,7 @@ "@aws-amplify/platform-core": "^1.2.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" }, "dependencies": { diff --git a/packages/backend-deployer/package.json b/packages/backend-deployer/package.json index adc2a77a22..3a57923c2b 100644 --- a/packages/backend-deployer/package.json +++ b/packages/backend-deployer/package.json @@ -26,7 +26,7 @@ "strip-ansi": "^6.0.1" }, "peerDependencies": { - "aws-cdk": "^2.158.0", + "aws-cdk": "^2.168.0", "typescript": "^5.0.0" } } diff --git a/packages/backend-function/API.md b/packages/backend-function/API.md index c87d9469ef..8e0ad66d85 100644 --- a/packages/backend-function/API.md +++ b/packages/backend-function/API.md @@ -46,7 +46,7 @@ export type FunctionProps = { export type FunctionSchedule = TimeInterval | CronSchedule; // @public (undocumented) -export type NodeVersion = 16 | 18 | 20; +export type NodeVersion = 16 | 18 | 20 | 22; // @public (undocumented) export type TimeInterval = `every ${number}m` | `every ${number}h` | `every day` | `every week` | `every month` | `every year`; diff --git a/packages/backend-function/package.json b/packages/backend-function/package.json index 412c6be0be..212f8567ed 100644 --- a/packages/backend-function/package.json +++ b/packages/backend-function/package.json @@ -32,7 +32,7 @@ "uuid": "^9.0.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-function/src/factory.test.ts b/packages/backend-function/src/factory.test.ts index 95624b523b..c7ce48a2cf 100644 --- a/packages/backend-function/src/factory.test.ts +++ b/packages/backend-function/src/factory.test.ts @@ -308,12 +308,12 @@ void describe('AmplifyFunctionFactory', () => { void it('sets valid runtime', () => { const lambda = defineFunction({ entry: './test-assets/default-lambda/handler.ts', - runtime: 16, + runtime: 22, }).getInstance(getInstanceProps); const template = Template.fromStack(lambda.stack); template.hasResourceProperties('AWS::Lambda::Function', { - Runtime: Runtime.NODEJS_16_X.name, + Runtime: Runtime.NODEJS_22_X.name, }); }); @@ -335,7 +335,7 @@ void describe('AmplifyFunctionFactory', () => { entry: './test-assets/default-lambda/handler.ts', runtime: 14 as NodeVersion, }).getInstance(getInstanceProps), - new Error('runtime must be one of the following: 16, 18, 20') + new Error('runtime must be one of the following: 16, 18, 20, 22') ); }); diff --git a/packages/backend-function/src/factory.ts b/packages/backend-function/src/factory.ts index 5a62fcf36f..9c29357c21 100644 --- a/packages/backend-function/src/factory.ts +++ b/packages/backend-function/src/factory.ts @@ -570,10 +570,11 @@ const isWholeNumberBetweenInclusive = ( max: number ) => min <= test && test <= max && test % 1 === 0; -export type NodeVersion = 16 | 18 | 20; +export type NodeVersion = 16 | 18 | 20 | 22; const nodeVersionMap: Record = { 16: Runtime.NODEJS_16_X, 18: Runtime.NODEJS_18_X, 20: Runtime.NODEJS_20_X, + 22: Runtime.NODEJS_22_X, }; diff --git a/packages/backend-output-storage/package.json b/packages/backend-output-storage/package.json index 839a1351da..0269f794b6 100644 --- a/packages/backend-output-storage/package.json +++ b/packages/backend-output-storage/package.json @@ -24,6 +24,6 @@ "@aws-amplify/plugin-types": "^1.3.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0" + "aws-cdk-lib": "^2.168.0" } } diff --git a/packages/backend-platform-test-stubs/package.json b/packages/backend-platform-test-stubs/package.json index 4d8ab06e8f..8d86327dc9 100644 --- a/packages/backend-platform-test-stubs/package.json +++ b/packages/backend-platform-test-stubs/package.json @@ -17,7 +17,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^1.3.1", - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend-storage/package.json b/packages/backend-storage/package.json index c56134608f..051a4c975d 100644 --- a/packages/backend-storage/package.json +++ b/packages/backend-storage/package.json @@ -28,7 +28,7 @@ "@aws-amplify/platform-core": "^1.2.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } } diff --git a/packages/backend/package.json b/packages/backend/package.json index b436de0b74..106acaac42 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -40,7 +40,7 @@ "lodash.snakecase": "^4.1.1" }, "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" }, "devDependencies": { diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 7f394b28b8..9943548811 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -33,7 +33,7 @@ "@zip.js/zip.js": "^2.7.52", "aws-amplify": "^6.0.16", "aws-appsync-auth-link": "^3.0.7", - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0", "execa": "^8.0.1", "fs-extra": "^11.1.1", diff --git a/packages/plugin-types/package.json b/packages/plugin-types/package.json index d60ad1902c..b5aa9f6995 100644 --- a/packages/plugin-types/package.json +++ b/packages/plugin-types/package.json @@ -11,7 +11,7 @@ }, "license": "Apache-2.0", "peerDependencies": { - "aws-cdk-lib": "^2.158.0", + "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0", "@aws-sdk/types": "^3.609.0" }, diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index ff96f11959..fbb7da66ab 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -42,6 +42,6 @@ "@types/parse-gitignore": "^1.0.0" }, "peerDependencies": { - "aws-cdk": "^2.158.0" + "aws-cdk": "^2.168.0" } } From 65abf6a22ed9591bf11678a3798c459b767955db Mon Sep 17 00:00:00 2001 From: Kamil Sobol Date: Wed, 27 Nov 2024 09:44:10 -0800 Subject: [PATCH 3/6] Functions logging (#2245) * Functions logging * changeset * more * conversation handler * conversation handler * this works * more tests * more tests * more tests * fix build * fix build * pr feedback * pr feedback * api * pr feedback --- .changeset/poor-moons-refuse.md | 9 + package-lock.json | 4 + packages/ai-constructs/API.md | 6 + .../conversation_handler_construct.test.ts | 39 +++++ .../conversation_handler_construct.ts | 10 +- packages/backend-ai/API.md | 18 ++ .../src/conversation/factory.test.ts | 37 ++++ .../backend-ai/src/conversation/factory.ts | 28 +++ packages/backend-ai/src/conversation/index.ts | 6 + packages/backend-function/API.md | 19 ++ packages/backend-function/src/factory.test.ts | 33 ++++ packages/backend-function/src/factory.ts | 25 +++ .../src/logging_options_parser.test.ts | 56 ++++++ .../src/logging_options_parser.ts | 48 ++++++ packages/platform-core/API.md | 24 +++ packages/platform-core/api-extractor.json | 3 +- packages/platform-core/package.json | 9 + packages/platform-core/src/.eslintrc.json | 15 ++ packages/platform-core/src/cdk/.eslintrc.json | 5 + .../src/cdk/enum_converters.test.ts | 162 ++++++++++++++++++ .../platform-core/src/cdk/enum_converters.ts | 97 +++++++++++ packages/platform-core/src/cdk/index.ts | 3 + packages/platform-core/src/index.internal.ts | 12 ++ packages/plugin-types/API.md | 6 + packages/plugin-types/src/index.ts | 2 + packages/plugin-types/src/log_level.ts | 1 + packages/plugin-types/src/log_retention.ts | 24 +++ 27 files changed, 699 insertions(+), 2 deletions(-) create mode 100644 .changeset/poor-moons-refuse.md create mode 100644 packages/backend-function/src/logging_options_parser.test.ts create mode 100644 packages/backend-function/src/logging_options_parser.ts create mode 100644 packages/platform-core/src/.eslintrc.json create mode 100644 packages/platform-core/src/cdk/.eslintrc.json create mode 100644 packages/platform-core/src/cdk/enum_converters.test.ts create mode 100644 packages/platform-core/src/cdk/enum_converters.ts create mode 100644 packages/platform-core/src/cdk/index.ts create mode 100644 packages/platform-core/src/index.internal.ts create mode 100644 packages/plugin-types/src/log_level.ts create mode 100644 packages/plugin-types/src/log_retention.ts diff --git a/.changeset/poor-moons-refuse.md b/.changeset/poor-moons-refuse.md new file mode 100644 index 0000000000..4da456357c --- /dev/null +++ b/.changeset/poor-moons-refuse.md @@ -0,0 +1,9 @@ +--- +'@aws-amplify/ai-constructs': minor +'@aws-amplify/backend-ai': minor +'@aws-amplify/backend-function': minor +'@aws-amplify/backend': minor +'@aws-amplify/platform-core': minor +--- + +Add options to control log settings diff --git a/package-lock.json b/package-lock.json index abf7f39339..c60a804d40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32195,6 +32195,10 @@ "@types/is-ci": "^3.0.4", "@types/lodash.mergewith": "^4.6.2", "@types/uuid": "9.0.7" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.168.0", + "constructs": "^10.0.0" } }, "packages/platform-core/node_modules/uuid": { diff --git a/packages/ai-constructs/API.md b/packages/ai-constructs/API.md index a5621d37df..0f1550885f 100644 --- a/packages/ai-constructs/API.md +++ b/packages/ai-constructs/API.md @@ -7,12 +7,14 @@ /// import { AIConversationOutput } from '@aws-amplify/backend-output-schemas'; +import { ApplicationLogLevel } from 'aws-cdk-lib/aws-lambda'; import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types'; import * as bedrock from '@aws-sdk/client-bedrock-runtime'; import { Construct } from 'constructs'; import { FunctionResources } from '@aws-amplify/plugin-types'; import * as jsonSchemaToTypeScript from 'json-schema-to-ts'; import { ResourceProvider } from '@aws-amplify/plugin-types'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; declare namespace __export__conversation { export { @@ -55,6 +57,10 @@ type ConversationHandlerFunctionProps = { region?: string; }>; memoryMB?: number; + logging?: { + level?: ApplicationLogLevel; + retention?: RetentionDays; + }; outputStorageStrategy?: BackendOutputStorageStrategy; }; diff --git a/packages/ai-constructs/src/conversation/conversation_handler_construct.test.ts b/packages/ai-constructs/src/conversation/conversation_handler_construct.test.ts index b0130e711f..284024a89e 100644 --- a/packages/ai-constructs/src/conversation/conversation_handler_construct.test.ts +++ b/packages/ai-constructs/src/conversation/conversation_handler_construct.test.ts @@ -5,6 +5,8 @@ import { ConversationHandlerFunction } from './conversation_handler_construct'; import { Template } from 'aws-cdk-lib/assertions'; import path from 'path'; import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage'; +import { ApplicationLogLevel } from 'aws-cdk-lib/aws-lambda'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; void describe('Conversation Handler Function construct', () => { void it('creates handler with log group with JWT token redacting policy', () => { @@ -284,4 +286,41 @@ void describe('Conversation Handler Function construct', () => { }, new Error('memoryMB must be a whole number between 128 and 10240 inclusive')); }); }); + + void describe('logging options', () => { + void it('sets log level', () => { + const app = new App(); + const stack = new Stack(app); + new ConversationHandlerFunction(stack, 'conversationHandler', { + models: [], + logging: { + level: ApplicationLogLevel.DEBUG, + }, + }); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + ApplicationLogLevel: 'DEBUG', + LogFormat: 'JSON', + }, + }); + }); + + void it('sets log retention', () => { + const app = new App(); + const stack = new Stack(app); + new ConversationHandlerFunction(stack, 'conversationHandler', { + models: [], + logging: { + retention: RetentionDays.ONE_YEAR, + }, + }); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: 365, + }); + }); + }); }); diff --git a/packages/ai-constructs/src/conversation/conversation_handler_construct.ts b/packages/ai-constructs/src/conversation/conversation_handler_construct.ts index 995b92fed6..e5b563a6df 100644 --- a/packages/ai-constructs/src/conversation/conversation_handler_construct.ts +++ b/packages/ai-constructs/src/conversation/conversation_handler_construct.ts @@ -6,6 +6,7 @@ import { import { Duration, Stack, Tags } from 'aws-cdk-lib'; import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { + ApplicationLogLevel, CfnFunction, Runtime as LambdaRuntime, LoggingFormat, @@ -40,6 +41,12 @@ export type ConversationHandlerFunctionProps = { * Default is 512MB. */ memoryMB?: number; + + logging?: { + level?: ApplicationLogLevel; + retention?: RetentionDays; + }; + /** * @internal */ @@ -100,8 +107,9 @@ export class ConversationHandlerFunction bundleAwsSDK: !!this.props.entry, }, loggingFormat: LoggingFormat.JSON, + applicationLogLevelV2: this.props.logging?.level, logGroup: new LogGroup(this, 'conversationHandlerFunctionLogGroup', { - retention: RetentionDays.INFINITE, + retention: this.props.logging?.retention ?? RetentionDays.INFINITE, dataProtectionPolicy: new DataProtectionPolicy({ identifiers: [ new CustomDataIdentifier( diff --git a/packages/backend-ai/API.md b/packages/backend-ai/API.md index 2b75a01c57..fcacb75c61 100644 --- a/packages/backend-ai/API.md +++ b/packages/backend-ai/API.md @@ -8,12 +8,17 @@ import { AiModel } from '@aws-amplify/data-schema-types'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { ConversationTurnEventVersion } from '@aws-amplify/ai-constructs/conversation'; import { FunctionResources } from '@aws-amplify/plugin-types'; +import { LogLevel } from '@aws-amplify/plugin-types'; +import { LogRetention } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; import * as runtime from '@aws-amplify/ai-constructs/conversation/runtime'; declare namespace __export__conversation { export { ConversationHandlerFunctionFactory, + ConversationHandlerFunctionLogLevel, + ConversationHandlerFunctionLogRetention, + ConversationHandlerFunctionLoggingOptions, DefineConversationHandlerFunctionProps, defineConversationHandlerFunction } @@ -36,6 +41,18 @@ type ConversationHandlerFunctionFactory = ConstructFactory; memoryMB?: number; + logging?: ConversationHandlerFunctionLoggingOptions; }; // @public (undocumented) diff --git a/packages/backend-ai/src/conversation/factory.test.ts b/packages/backend-ai/src/conversation/factory.test.ts index 9802e4944b..a264731ffb 100644 --- a/packages/backend-ai/src/conversation/factory.test.ts +++ b/packages/backend-ai/src/conversation/factory.test.ts @@ -203,4 +203,41 @@ void describe('ConversationHandlerFactory', () => { MemorySize: 271, }); }); + + void it('passes log level to construct', () => { + const factory = defineConversationHandlerFunction({ + entry: './test-assets/with-default-entry/handler.ts', + name: 'testHandlerName', + models: [], + logging: { + level: 'debug', + }, + }); + const lambda = factory.getInstance(getInstanceProps); + const template = Template.fromStack(Stack.of(lambda.resources.lambda)); + template.resourceCountIs('AWS::Lambda::Function', 1); + template.hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + ApplicationLogLevel: 'DEBUG', + LogFormat: 'JSON', + }, + }); + }); + + void it('passes log retention to construct', () => { + const factory = defineConversationHandlerFunction({ + entry: './test-assets/with-default-entry/handler.ts', + name: 'testHandlerName', + models: [], + logging: { + retention: '1 day', + }, + }); + const lambda = factory.getInstance(getInstanceProps); + const template = Template.fromStack(Stack.of(lambda.resources.lambda)); + template.resourceCountIs('AWS::Lambda::Function', 1); + template.hasResourceProperties('AWS::Logs::LogGroup', { + RetentionInDays: 1, + }); + }); }); diff --git a/packages/backend-ai/src/conversation/factory.ts b/packages/backend-ai/src/conversation/factory.ts index 6b4242288f..d5a88c3f03 100644 --- a/packages/backend-ai/src/conversation/factory.ts +++ b/packages/backend-ai/src/conversation/factory.ts @@ -7,6 +7,8 @@ import { ConstructFactoryGetInstanceProps, FunctionResources, GenerateContainerEntryProps, + LogLevel, + LogRetention, ResourceProvider, } from '@aws-amplify/plugin-types'; import { @@ -17,6 +19,10 @@ import { import path from 'path'; import { CallerDirectoryExtractor } from '@aws-amplify/platform-core'; import { AiModel } from '@aws-amplify/data-schema-types'; +import { + LogLevelConverter, + LogRetentionConverter, +} from '@aws-amplify/platform-core/cdk'; class ConversationHandlerFunctionGenerator implements ConstructContainerEntryGenerator @@ -47,6 +53,18 @@ class ConversationHandlerFunctionGenerator outputStorageStrategy: this.outputStorageStrategy, memoryMB: this.props.memoryMB, }; + const logging: typeof constructProps.logging = {}; + if (this.props.logging?.level) { + logging.level = new LogLevelConverter().toCDKLambdaApplicationLogLevel( + this.props.logging.level + ); + } + if (this.props.logging?.retention) { + logging.retention = new LogRetentionConverter().toCDKRetentionDays( + this.props.logging.retention + ); + } + constructProps.logging = logging; const conversationHandlerFunction = new ConversationHandlerFunction( scope, this.props.name, @@ -115,6 +133,15 @@ class DefaultConversationHandlerFunctionFactory }; } +export type ConversationHandlerFunctionLogLevel = LogLevel; + +export type ConversationHandlerFunctionLogRetention = LogRetention; + +export type ConversationHandlerFunctionLoggingOptions = { + retention?: ConversationHandlerFunctionLogRetention; + level?: ConversationHandlerFunctionLogLevel; +}; + export type DefineConversationHandlerFunctionProps = { name: string; entry?: string; @@ -128,6 +155,7 @@ export type DefineConversationHandlerFunctionProps = { * Default is 512MB. */ memoryMB?: number; + logging?: ConversationHandlerFunctionLoggingOptions; }; /** diff --git a/packages/backend-ai/src/conversation/index.ts b/packages/backend-ai/src/conversation/index.ts index 489209c219..69eb8d3c5c 100644 --- a/packages/backend-ai/src/conversation/index.ts +++ b/packages/backend-ai/src/conversation/index.ts @@ -1,11 +1,17 @@ import { ConversationHandlerFunctionFactory, + ConversationHandlerFunctionLogLevel, + ConversationHandlerFunctionLogRetention, + ConversationHandlerFunctionLoggingOptions, DefineConversationHandlerFunctionProps, defineConversationHandlerFunction, } from './factory.js'; export { ConversationHandlerFunctionFactory, + ConversationHandlerFunctionLogLevel, + ConversationHandlerFunctionLogRetention, + ConversationHandlerFunctionLoggingOptions, DefineConversationHandlerFunctionProps, defineConversationHandlerFunction, }; diff --git a/packages/backend-function/API.md b/packages/backend-function/API.md index 8e0ad66d85..500972914d 100644 --- a/packages/backend-function/API.md +++ b/packages/backend-function/API.md @@ -8,6 +8,8 @@ import { AmplifyResourceGroupName } from '@aws-amplify/plugin-types'; import { BackendSecret } from '@aws-amplify/plugin-types'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { FunctionResources } from '@aws-amplify/plugin-types'; +import { LogLevel } from '@aws-amplify/plugin-types'; +import { LogRetention } from '@aws-amplify/plugin-types'; import { ResourceAccessAcceptorFactory } from '@aws-amplify/plugin-types'; import { ResourceProvider } from '@aws-amplify/plugin-types'; import { StackProvider } from '@aws-amplify/plugin-types'; @@ -28,6 +30,22 @@ export type FunctionBundlingOptions = { minify?: boolean; }; +// @public (undocumented) +export type FunctionLoggingOptions = ({ + format: 'json'; + level?: FunctionLogLevel; +} | { + format?: 'text'; +}) & { + retention?: FunctionLogRetention; +}; + +// @public (undocumented) +export type FunctionLogLevel = LogLevel; + +// @public (undocumented) +export type FunctionLogRetention = LogRetention; + // @public (undocumented) export type FunctionProps = { name?: string; @@ -40,6 +58,7 @@ export type FunctionProps = { layers?: Record; bundling?: FunctionBundlingOptions; resourceGroupName?: AmplifyResourceGroupName; + logging?: FunctionLoggingOptions; }; // @public (undocumented) diff --git a/packages/backend-function/src/factory.test.ts b/packages/backend-function/src/factory.test.ts index c7ce48a2cf..8b964f336c 100644 --- a/packages/backend-function/src/factory.test.ts +++ b/packages/backend-function/src/factory.test.ts @@ -440,6 +440,39 @@ void describe('AmplifyFunctionFactory', () => { }); }); + void describe('logging options', () => { + void it('sets logging options', () => { + const lambda = defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + bundling: { + minify: false, + }, + logging: { + format: 'json', + level: 'warn', + retention: '13 months', + }, + }).getInstance(getInstanceProps); + const template = Template.fromStack(lambda.stack); + // Enabling log retention adds extra lambda. + template.resourceCountIs('AWS::Lambda::Function', 2); + const lambdas = template.findResources('AWS::Lambda::Function'); + assert.ok( + Object.keys(lambdas).some((key) => key.startsWith('LogRetention')) + ); + template.hasResourceProperties('Custom::LogRetention', { + RetentionInDays: 400, + }); + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'index.handler', + LoggingConfig: { + ApplicationLogLevel: 'WARN', + LogFormat: 'JSON', + }, + }); + }); + }); + void describe('resourceAccessAcceptor', () => { void it('attaches policy to execution role and configures ssm environment context', () => { const functionFactory = defineFunction({ diff --git a/packages/backend-function/src/factory.ts b/packages/backend-function/src/factory.ts index 9c29357c21..e752b46d44 100644 --- a/packages/backend-function/src/factory.ts +++ b/packages/backend-function/src/factory.ts @@ -18,6 +18,8 @@ import { ConstructFactoryGetInstanceProps, FunctionResources, GenerateContainerEntryProps, + LogLevel, + LogRetention, ResourceAccessAcceptorFactory, ResourceNameValidator, ResourceProvider, @@ -45,6 +47,7 @@ import { FunctionEnvironmentTranslator } from './function_env_translator.js'; import { FunctionEnvironmentTypeGenerator } from './function_env_type_generator.js'; import { FunctionLayerArnParser } from './layer_parser.js'; import { convertFunctionSchedulesToRuleSchedules } from './schedule_parser.js'; +import { convertLoggingOptionsToCDK } from './logging_options_parser.js'; const functionStackType = 'function-Lambda'; @@ -64,6 +67,9 @@ export type TimeInterval = | `every year`; export type FunctionSchedule = TimeInterval | CronSchedule; +export type FunctionLogLevel = LogLevel; +export type FunctionLogRetention = LogRetention; + /** * Entry point for defining a function in the Amplify ecosystem */ @@ -159,6 +165,8 @@ export type FunctionProps = { * resourceGroupName: 'auth' // to group an auth trigger with an auth resource */ resourceGroupName?: AmplifyResourceGroupName; + + logging?: FunctionLoggingOptions; }; export type FunctionBundlingOptions = { @@ -170,6 +178,18 @@ export type FunctionBundlingOptions = { minify?: boolean; }; +export type FunctionLoggingOptions = ( + | { + format: 'json'; + level?: FunctionLogLevel; + } + | { + format?: 'text'; + } +) & { + retention?: FunctionLogRetention; +}; + /** * Create Lambda functions in the context of an Amplify backend definition */ @@ -218,6 +238,7 @@ class FunctionFactory implements ConstructFactory { bundling: this.resolveBundling(), layers, resourceGroupName: this.props.resourceGroupName ?? 'function', + logging: this.props.logging ?? {}, }; }; @@ -438,6 +459,7 @@ class AmplifyFunction functionEnvironmentTypeGenerator.generateProcessEnvShim(); let functionLambda: NodejsFunction; + const cdkLoggingOptions = convertLoggingOptionsToCDK(props.logging); try { functionLambda = new NodejsFunction(scope, `${id}-lambda`, { entry: props.entry, @@ -451,6 +473,9 @@ class AmplifyFunction inject: shims, externalModules: Object.keys(props.layers), }, + logRetention: cdkLoggingOptions.retention, + applicationLogLevelV2: cdkLoggingOptions.level, + loggingFormat: cdkLoggingOptions.format, }); } catch (error) { // If the error is from ES Bundler which is executed as a child process by CDK, diff --git a/packages/backend-function/src/logging_options_parser.test.ts b/packages/backend-function/src/logging_options_parser.test.ts new file mode 100644 index 0000000000..4d6abec958 --- /dev/null +++ b/packages/backend-function/src/logging_options_parser.test.ts @@ -0,0 +1,56 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { FunctionLoggingOptions } from './factory.js'; +import { + CDKLoggingOptions, + convertLoggingOptionsToCDK, +} from './logging_options_parser.js'; +import { ApplicationLogLevel, LoggingFormat } from 'aws-cdk-lib/aws-lambda'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; + +type TestCase = { + input: FunctionLoggingOptions; + expectedOutput: CDKLoggingOptions; +}; + +const testCases: Array = [ + { + input: {}, + expectedOutput: { + format: undefined, + level: undefined, + retention: undefined, + }, + }, + { + input: { + format: 'text', + retention: '13 months', + }, + expectedOutput: { + format: LoggingFormat.TEXT, + retention: RetentionDays.THIRTEEN_MONTHS, + level: undefined, + }, + }, + { + input: { + format: 'json', + level: 'debug', + }, + expectedOutput: { + format: LoggingFormat.JSON, + retention: undefined, + level: ApplicationLogLevel.DEBUG, + }, + }, +]; + +void describe('LoggingOptions converter', () => { + testCases.forEach((testCase, index) => { + void it(`converts to cdk options[${index}]`, () => { + const convertedOptions = convertLoggingOptionsToCDK(testCase.input); + assert.deepStrictEqual(convertedOptions, testCase.expectedOutput); + }); + }); +}); diff --git a/packages/backend-function/src/logging_options_parser.ts b/packages/backend-function/src/logging_options_parser.ts new file mode 100644 index 0000000000..ce5693d625 --- /dev/null +++ b/packages/backend-function/src/logging_options_parser.ts @@ -0,0 +1,48 @@ +import { FunctionLoggingOptions } from './factory.js'; +import { ApplicationLogLevel, LoggingFormat } from 'aws-cdk-lib/aws-lambda'; +import { + LogLevelConverter, + LogRetentionConverter, +} from '@aws-amplify/platform-core/cdk'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; + +export type CDKLoggingOptions = { + level?: ApplicationLogLevel; + retention?: RetentionDays; + format?: LoggingFormat; +}; + +/** + * Converts logging options to CDK. + */ +export const convertLoggingOptionsToCDK = ( + loggingOptions: FunctionLoggingOptions +): CDKLoggingOptions => { + let level: ApplicationLogLevel | undefined = undefined; + if ('level' in loggingOptions) { + level = new LogLevelConverter().toCDKLambdaApplicationLogLevel( + loggingOptions.level + ); + } + const retention = new LogRetentionConverter().toCDKRetentionDays( + loggingOptions.retention + ); + const format = convertFormat(loggingOptions.format); + + return { + level, + retention, + format, + }; +}; + +const convertFormat = (format: 'json' | 'text' | undefined) => { + switch (format) { + case undefined: + return undefined; + case 'json': + return LoggingFormat.JSON; + case 'text': + return LoggingFormat.TEXT; + } +}; diff --git a/packages/platform-core/API.md b/packages/platform-core/API.md index 286ae41620..55997c8747 100644 --- a/packages/platform-core/API.md +++ b/packages/platform-core/API.md @@ -5,10 +5,22 @@ ```ts import { AppId } from '@aws-amplify/plugin-types'; +import { ApplicationLogLevel } from 'aws-cdk-lib/aws-lambda'; import { BackendIdentifier } from '@aws-amplify/plugin-types'; import { DeepPartialAmplifyGeneratedConfigs } from '@aws-amplify/plugin-types'; +import { LogLevel } from '@aws-amplify/plugin-types'; +import { LogRetention } from '@aws-amplify/plugin-types'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import z from 'zod'; +declare namespace __export__cdk { + export { + LogLevelConverter, + LogRetentionConverter + } +} +export { __export__cdk } + // @public export abstract class AmplifyError extends Error { constructor(name: T, classification: AmplifyErrorClassification, options: AmplifyErrorOptions, cause?: Error | undefined); @@ -121,6 +133,18 @@ export class FilePathExtractor { // @public (undocumented) export type LocalConfigurationFileName = 'usage_data_preferences.json'; +// @public +class LogLevelConverter { + // (undocumented) + toCDKLambdaApplicationLogLevel: (logLevel: LogLevel | undefined) => ApplicationLogLevel | undefined; +} + +// @public +class LogRetentionConverter { + // (undocumented) + toCDKRetentionDays: (retention: LogRetention | undefined) => RetentionDays | undefined; +} + // @public export class ObjectAccumulator { constructor(accumulator: DeepPartialAmplifyGeneratedConfigs, versionKey?: string); diff --git a/packages/platform-core/api-extractor.json b/packages/platform-core/api-extractor.json index 0f56de03f6..cc2ebea8cf 100644 --- a/packages/platform-core/api-extractor.json +++ b/packages/platform-core/api-extractor.json @@ -1,3 +1,4 @@ { - "extends": "../../api-extractor.base.json" + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "/lib/index.internal.d.ts" } diff --git a/packages/platform-core/package.json b/packages/platform-core/package.json index 890f108a4b..131980b90a 100644 --- a/packages/platform-core/package.json +++ b/packages/platform-core/package.json @@ -10,6 +10,11 @@ "types": "./lib/index.d.ts", "import": "./lib/index.js", "require": "./lib/index.js" + }, + "./cdk": { + "types": "./lib/cdk/index.d.ts", + "import": "./lib/cdk/index.js", + "require": "./lib/cdk/index.js" } }, "main": "lib/index.js", @@ -31,5 +36,9 @@ "semver": "^7.6.3", "uuid": "^9.0.1", "zod": "^3.22.2" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.168.0", + "constructs": "^10.0.0" } } diff --git a/packages/platform-core/src/.eslintrc.json b/packages/platform-core/src/.eslintrc.json new file mode 100644 index 0000000000..0da8b97bbb --- /dev/null +++ b/packages/platform-core/src/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["aws-cdk-lib", "aws-cdk-lib/*", "constructs"], + "message": "Usage of CDK lib is not allowed in platform-core. Except /cdk entry point. This is to ensure that we don't load CDK eagerly from package root." + } + ] + } + ] + } +} diff --git a/packages/platform-core/src/cdk/.eslintrc.json b/packages/platform-core/src/cdk/.eslintrc.json new file mode 100644 index 0000000000..c3220a910c --- /dev/null +++ b/packages/platform-core/src/cdk/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-restricted-imports": "off" + } +} diff --git a/packages/platform-core/src/cdk/enum_converters.test.ts b/packages/platform-core/src/cdk/enum_converters.test.ts new file mode 100644 index 0000000000..19edc0d144 --- /dev/null +++ b/packages/platform-core/src/cdk/enum_converters.test.ts @@ -0,0 +1,162 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { LogLevel, LogRetention } from '@aws-amplify/plugin-types'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { LogLevelConverter, LogRetentionConverter } from './enum_converters'; +import { ApplicationLogLevel } from 'aws-cdk-lib/aws-lambda'; + +type TestCase = { + input: TSource | undefined; + expectedOutput: TTarget | undefined; +}; + +void describe('LogRetentionConverter', () => { + const testCases: Array> = [ + { + input: undefined, + expectedOutput: undefined, + }, + { + input: '1 day', + expectedOutput: RetentionDays.ONE_DAY, + }, + { + input: '3 days', + expectedOutput: RetentionDays.THREE_DAYS, + }, + { + input: '5 days', + expectedOutput: RetentionDays.FIVE_DAYS, + }, + { + input: '1 week', + expectedOutput: RetentionDays.ONE_WEEK, + }, + { + input: '2 weeks', + expectedOutput: RetentionDays.TWO_WEEKS, + }, + { + input: '1 month', + expectedOutput: RetentionDays.ONE_MONTH, + }, + { + input: '2 months', + expectedOutput: RetentionDays.TWO_MONTHS, + }, + { + input: '3 months', + expectedOutput: RetentionDays.THREE_MONTHS, + }, + { + input: '4 months', + expectedOutput: RetentionDays.FOUR_MONTHS, + }, + { + input: '5 months', + expectedOutput: RetentionDays.FIVE_MONTHS, + }, + { + input: '6 months', + expectedOutput: RetentionDays.SIX_MONTHS, + }, + { + input: '13 months', + expectedOutput: RetentionDays.THIRTEEN_MONTHS, + }, + { + input: '18 months', + expectedOutput: RetentionDays.EIGHTEEN_MONTHS, + }, + { + input: '1 year', + expectedOutput: RetentionDays.ONE_YEAR, + }, + { + input: '2 years', + expectedOutput: RetentionDays.TWO_YEARS, + }, + { + input: '3 years', + expectedOutput: RetentionDays.THREE_YEARS, + }, + { + input: '5 years', + expectedOutput: RetentionDays.FIVE_YEARS, + }, + { + input: '6 years', + expectedOutput: RetentionDays.SIX_YEARS, + }, + { + input: '7 years', + expectedOutput: RetentionDays.SEVEN_YEARS, + }, + { + input: '8 years', + expectedOutput: RetentionDays.EIGHT_YEARS, + }, + { + input: '9 years', + expectedOutput: RetentionDays.NINE_YEARS, + }, + { + input: '10 years', + expectedOutput: RetentionDays.TEN_YEARS, + }, + { + input: 'infinite', + expectedOutput: RetentionDays.INFINITE, + }, + ]; + + testCases.forEach((testCase, index) => { + void it(`converts log retention[${index}]`, () => { + const convertedValue = new LogRetentionConverter().toCDKRetentionDays( + testCase.input + ); + assert.strictEqual(convertedValue, testCase.expectedOutput); + }); + }); +}); + +void describe('LogLevelConverter', () => { + const testCases: Array> = [ + { + input: undefined, + expectedOutput: undefined, + }, + { + input: 'info', + expectedOutput: ApplicationLogLevel.INFO, + }, + { + input: 'debug', + expectedOutput: ApplicationLogLevel.DEBUG, + }, + { + input: 'error', + expectedOutput: ApplicationLogLevel.ERROR, + }, + { + input: 'warn', + expectedOutput: ApplicationLogLevel.WARN, + }, + { + input: 'trace', + expectedOutput: ApplicationLogLevel.TRACE, + }, + { + input: 'fatal', + expectedOutput: ApplicationLogLevel.FATAL, + }, + ]; + + testCases.forEach((testCase, index) => { + void it(`converts log retention[${index}]`, () => { + const convertedValue = + new LogLevelConverter().toCDKLambdaApplicationLogLevel(testCase.input); + assert.strictEqual(convertedValue, testCase.expectedOutput); + }); + }); +}); diff --git a/packages/platform-core/src/cdk/enum_converters.ts b/packages/platform-core/src/cdk/enum_converters.ts new file mode 100644 index 0000000000..3f467785a0 --- /dev/null +++ b/packages/platform-core/src/cdk/enum_converters.ts @@ -0,0 +1,97 @@ +import { LogLevel, LogRetention } from '@aws-amplify/plugin-types'; +import { ApplicationLogLevel } from 'aws-cdk-lib/aws-lambda'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; + +/** + * Converts LogRetention to CDK types. + */ +export class LogRetentionConverter { + toCDKRetentionDays = ( + retention: LogRetention | undefined + ): RetentionDays | undefined => { + switch (retention) { + case undefined: + return undefined; + + case '1 day': + return RetentionDays.ONE_DAY; + case '3 days': + return RetentionDays.THREE_DAYS; + case '5 days': + return RetentionDays.FIVE_DAYS; + case '1 week': + return RetentionDays.ONE_WEEK; + case '2 weeks': + return RetentionDays.TWO_WEEKS; + case '1 month': + return RetentionDays.ONE_MONTH; + case '2 months': + return RetentionDays.TWO_MONTHS; + case '3 months': + return RetentionDays.THREE_MONTHS; + case '4 months': + return RetentionDays.FOUR_MONTHS; + case '5 months': + return RetentionDays.FIVE_MONTHS; + case '6 months': + return RetentionDays.SIX_MONTHS; + case '1 year': + return RetentionDays.ONE_YEAR; + case '13 months': + return RetentionDays.THIRTEEN_MONTHS; + case '18 months': + return RetentionDays.EIGHTEEN_MONTHS; + case '2 years': + return RetentionDays.TWO_YEARS; + case '3 years': + return RetentionDays.THREE_YEARS; + case '5 years': + return RetentionDays.FIVE_YEARS; + case '6 years': + return RetentionDays.SIX_YEARS; + case '7 years': + return RetentionDays.SEVEN_YEARS; + case '8 years': + return RetentionDays.EIGHT_YEARS; + case '9 years': + return RetentionDays.NINE_YEARS; + case '10 years': + return RetentionDays.TEN_YEARS; + case 'infinite': + return RetentionDays.INFINITE; + } + }; +} + +/** + * Converts LogLevel to CDK types. + */ +export class LogLevelConverter { + toCDKLambdaApplicationLogLevel = ( + logLevel: LogLevel | undefined + ): ApplicationLogLevel | undefined => { + switch (logLevel) { + case undefined: { + return undefined; + } + case 'info': { + return ApplicationLogLevel.INFO; + } + case 'debug': { + return ApplicationLogLevel.DEBUG; + } + case 'warn': { + return ApplicationLogLevel.WARN; + } + case 'error': { + return ApplicationLogLevel.ERROR; + } + case 'fatal': { + return ApplicationLogLevel.FATAL; + } + case 'trace': { + return ApplicationLogLevel.TRACE; + } + } + }; +} diff --git a/packages/platform-core/src/cdk/index.ts b/packages/platform-core/src/cdk/index.ts new file mode 100644 index 0000000000..d5c9d704c9 --- /dev/null +++ b/packages/platform-core/src/cdk/index.ts @@ -0,0 +1,3 @@ +import { LogLevelConverter, LogRetentionConverter } from './enum_converters.js'; + +export { LogLevelConverter, LogRetentionConverter }; diff --git a/packages/platform-core/src/index.internal.ts b/packages/platform-core/src/index.internal.ts new file mode 100644 index 0000000000..336a22d74e --- /dev/null +++ b/packages/platform-core/src/index.internal.ts @@ -0,0 +1,12 @@ +// Suppressing to allow special prefix __export__ that is recognized by API checks. +// eslint-disable-next-line @typescript-eslint/naming-convention +import * as __export__cdk from './cdk/index.js'; + +export * from './index.js'; + +/* + Api-extractor does not ([yet](https://github.com/microsoft/rushstack/issues/1596)) support multiple package entry points + Because this package has a submodule export, we are working around this issue by including that export here and directing api-extract to this entry point instead + This allows api-extractor to pick up the submodule exports in its analysis + */ +export { __export__cdk }; diff --git a/packages/plugin-types/API.md b/packages/plugin-types/API.md index 2b78dd2ae1..832ba6c782 100644 --- a/packages/plugin-types/API.md +++ b/packages/plugin-types/API.md @@ -175,6 +175,12 @@ export type ImportPathVerifier = { verify: (importStack: string | undefined, expectedImportingFile: string, errorMessage: string) => void; }; +// @public (undocumented) +export type LogLevel = 'info' | 'debug' | 'warn' | 'error' | 'fatal' | 'trace'; + +// @public (undocumented) +export type LogRetention = '1 day' | '3 days' | '5 days' | '1 week' | '2 weeks' | '1 month' | '2 months' | '3 months' | '4 months' | '5 months' | '6 months' | '1 year' | '13 months' | '18 months' | '2 years' | '3 years' | '5 years' | '6 years' | '7 years' | '8 years' | '9 years' | '10 years' | 'infinite'; + // @public export type MainStackCreator = { getOrCreateMainStack: () => Stack; diff --git a/packages/plugin-types/src/index.ts b/packages/plugin-types/src/index.ts index 780c52ec02..8d738f7b2f 100644 --- a/packages/plugin-types/src/index.ts +++ b/packages/plugin-types/src/index.ts @@ -22,3 +22,5 @@ export * from './resource_name_validator.js'; export * from './aws_client_provider.js'; export * from './stack_provider.js'; export * from './amplify_resource_group_name.js'; +export * from './log_level.js'; +export * from './log_retention.js'; diff --git a/packages/plugin-types/src/log_level.ts b/packages/plugin-types/src/log_level.ts new file mode 100644 index 0000000000..5f4d23c008 --- /dev/null +++ b/packages/plugin-types/src/log_level.ts @@ -0,0 +1 @@ +export type LogLevel = 'info' | 'debug' | 'warn' | 'error' | 'fatal' | 'trace'; diff --git a/packages/plugin-types/src/log_retention.ts b/packages/plugin-types/src/log_retention.ts new file mode 100644 index 0000000000..60210c721b --- /dev/null +++ b/packages/plugin-types/src/log_retention.ts @@ -0,0 +1,24 @@ +export type LogRetention = + | '1 day' + | '3 days' + | '5 days' + | '1 week' + | '2 weeks' + | '1 month' + | '2 months' + | '3 months' + | '4 months' + | '5 months' + | '6 months' + | '1 year' + | '13 months' + | '18 months' + | '2 years' + | '3 years' + | '5 years' + | '6 years' + | '7 years' + | '8 years' + | '9 years' + | '10 years' + | 'infinite'; From 37d8564014934f681d24c05e86ce00c457ef0932 Mon Sep 17 00:00:00 2001 From: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:38:52 +0100 Subject: [PATCH 4/6] fix: handle cdk error mapping for more generic invalid credentials (#2279) --- .changeset/fair-ghosts-wave.md | 5 +++++ packages/backend-deployer/src/cdk_error_mapper.test.ts | 9 +++++++++ packages/backend-deployer/src/cdk_error_mapper.ts | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/fair-ghosts-wave.md diff --git a/.changeset/fair-ghosts-wave.md b/.changeset/fair-ghosts-wave.md new file mode 100644 index 0000000000..41ace980f0 --- /dev/null +++ b/.changeset/fair-ghosts-wave.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-deployer': patch +--- + +handle cdk error mapping for more generic invalid credentials diff --git a/packages/backend-deployer/src/cdk_error_mapper.test.ts b/packages/backend-deployer/src/cdk_error_mapper.test.ts index be08eedc17..3850467a35 100644 --- a/packages/backend-deployer/src/cdk_error_mapper.test.ts +++ b/packages/backend-deployer/src/cdk_error_mapper.test.ts @@ -31,6 +31,15 @@ const testErrorMappings = [ expectedDownstreamErrorMessage: 'Error: The security token included in the request is expired', }, + { + errorMessage: + 'InvalidClientTokenId: The security token included in the request is invalid', + expectedTopLevelErrorMessage: + 'The security token included in the request is invalid.', + errorName: 'ExpiredTokenError', + expectedDownstreamErrorMessage: + 'InvalidClientTokenId: The security token included in the request is invalid', + }, { errorMessage: 'Access Denied', expectedTopLevelErrorMessage: diff --git a/packages/backend-deployer/src/cdk_error_mapper.ts b/packages/backend-deployer/src/cdk_error_mapper.ts index 67d5159d50..949a9212f3 100644 --- a/packages/backend-deployer/src/cdk_error_mapper.ts +++ b/packages/backend-deployer/src/cdk_error_mapper.ts @@ -99,7 +99,7 @@ export class CdkErrorMapper { }> => [ { errorRegex: - /ExpiredToken|Error: The security token included in the request is expired/, + /ExpiredToken|(Error|InvalidClientTokenId): The security token included in the request is (expired|invalid)/, humanReadableErrorMessage: 'The security token included in the request is invalid.', resolutionMessage: From ec4a7da61f2db4625f8a041f4a139b29258d3330 Mon Sep 17 00:00:00 2001 From: Kamil Sobol Date: Wed, 27 Nov 2024 12:11:54 -0800 Subject: [PATCH 5/6] bump cdk (#2280) --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c60a804d40..97fb224519 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19581,9 +19581,9 @@ "license": "0BSD" }, "node_modules/aws-cdk": { - "version": "2.171.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.171.0.tgz", - "integrity": "sha512-tVo4hYS0iAbiCFxUh2/7KoDL6EHEIUAurCJaBs2BOUAB9DfBuUAPp8DGUK4iVF8XzrQQ4f3a5ivN7DteQrGBEQ==", + "version": "2.171.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.171.1.tgz", + "integrity": "sha512-IWENyT4F5UcLr1szLsbipUdjIHn8FD3d/RvaIvhs2+qCamkfEV5mqv/ChMvRJ8H2jebhIZ2iz74or9O5Ismp+Q==", "license": "Apache-2.0", "peer": true, "bin": { @@ -19597,9 +19597,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.171.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.171.0.tgz", - "integrity": "sha512-N0O0mWI+S8PAbiED7eV05qVfbJgHkdh+jQS4BOG6CUAc/i2s9U4w+XDRkK8auO0HgTM9+ahEaFfucMuQ4abRWQ==", + "version": "2.171.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.171.1.tgz", + "integrity": "sha512-BmXodHmeOWu7EZMwXFA+Mp+SnlZgIwhMxfOmqpdGa5dXF4BWOrs0cm4YgrzcJkg0XK713eXPj5IWGj8YeRIU3g==", "bundleDependencies": [ "@balena/dockerignore", "case", From cfdc8540fddba9df47d702f51fa1192fdc3d3125 Mon Sep 17 00:00:00 2001 From: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:51:41 +0100 Subject: [PATCH 6/6] fix: return amplify user error as it is from `AmplifyError.fromError` (#2288) * fix: return amplify user error as it is from 'AmplifyError.fromError' * PR Updates --- .changeset/five-comics-beg.md | 5 +++++ packages/platform-core/API.md | 2 +- .../src/errors/amplify_error.test.ts | 9 +++++++++ .../platform-core/src/errors/amplify_error.ts | 15 +++++---------- 4 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 .changeset/five-comics-beg.md diff --git a/.changeset/five-comics-beg.md b/.changeset/five-comics-beg.md new file mode 100644 index 0000000000..10a4c35dd0 --- /dev/null +++ b/.changeset/five-comics-beg.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/platform-core': patch +--- + +return amplify user error as it is from `AmplifyError.fromError` diff --git a/packages/platform-core/API.md b/packages/platform-core/API.md index 55997c8747..f40d1c5364 100644 --- a/packages/platform-core/API.md +++ b/packages/platform-core/API.md @@ -33,7 +33,7 @@ export abstract class AmplifyError extends Error { // (undocumented) readonly details?: string; // (undocumented) - static fromError: (error: unknown) => AmplifyError<'UnknownFault' | 'CredentialsError' | 'InsufficientDiskSpaceError' | 'InvalidCommandInputError' | 'DomainNotFoundError' | 'SyntaxError'>; + static fromError: (error: unknown) => AmplifyError; // (undocumented) static fromStderr: (_stderr: string) => AmplifyError | undefined; static isAmplifyError: (error: unknown) => error is AmplifyError; diff --git a/packages/platform-core/src/errors/amplify_error.test.ts b/packages/platform-core/src/errors/amplify_error.test.ts index 94980ab213..c4f0c974f4 100644 --- a/packages/platform-core/src/errors/amplify_error.test.ts +++ b/packages/platform-core/src/errors/amplify_error.test.ts @@ -205,4 +205,13 @@ void describe('AmplifyError.fromError', async () => { ); }); }); + void it('return amplify user errors as it is', () => { + const error = new AmplifyUserError('DeploymentInProgressError', { + message: 'Deployment already in progress', + resolution: 'wait for it', + }); + const actual = AmplifyError.fromError(error); + assert.deepStrictEqual(error, actual); + assert.strictEqual(actual.resolution, error.resolution); + }); }); diff --git a/packages/platform-core/src/errors/amplify_error.ts b/packages/platform-core/src/errors/amplify_error.ts index 9ea73c8ff1..314444252e 100644 --- a/packages/platform-core/src/errors/amplify_error.ts +++ b/packages/platform-core/src/errors/amplify_error.ts @@ -118,16 +118,11 @@ export abstract class AmplifyError extends Error { ); }; - static fromError = ( - error: unknown - ): AmplifyError< - | 'UnknownFault' - | 'CredentialsError' - | 'InsufficientDiskSpaceError' - | 'InvalidCommandInputError' - | 'DomainNotFoundError' - | 'SyntaxError' - > => { + static fromError = (error: unknown): AmplifyError => { + if (AmplifyError.isAmplifyError(error)) { + return error; + } + const errorMessage = error instanceof Error ? `${error.name}: ${error.message}`