diff --git a/.changeset/nasty-tables-heal.md b/.changeset/nasty-tables-heal.md new file mode 100644 index 0000000000..777a807a34 --- /dev/null +++ b/.changeset/nasty-tables-heal.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-function': minor +--- + +Add ephemeralStorageSizeMB option to defineFunction diff --git a/.eslint_dictionary.json b/.eslint_dictionary.json index 71e74fbd53..708bec2391 100644 --- a/.eslint_dictionary.json +++ b/.eslint_dictionary.json @@ -100,6 +100,7 @@ "lstat", "macos", "matchers", + "mebibytes", "mfas", "minify", "mkdtemp", diff --git a/packages/backend-function/API.md b/packages/backend-function/API.md index fed504b411..4c44cd115d 100644 --- a/packages/backend-function/API.md +++ b/packages/backend-function/API.md @@ -93,6 +93,7 @@ export type FunctionProps = { entry?: string; timeoutSeconds?: number; memoryMB?: number; + ephemeralStorageSizeMB?: number; environment?: Record; runtime?: NodeVersion; schedule?: FunctionSchedule | FunctionSchedule[]; diff --git a/packages/backend-function/src/factory.test.ts b/packages/backend-function/src/factory.test.ts index abf1613354..6bb63f14d2 100644 --- a/packages/backend-function/src/factory.test.ts +++ b/packages/backend-function/src/factory.test.ts @@ -683,4 +683,71 @@ void describe('AmplifyFunctionFactory', () => { 'function-Lambda' ); }); + + void describe('ephemeralStorageSizeMB property', () => { + void it('sets valid ephemeralStorageSize', () => { + const lambda = defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + ephemeralStorageSizeMB: 1024, + }).getInstance(getInstanceProps); + const template = Template.fromStack(lambda.stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + EphemeralStorage: { Size: 1024 }, + }); + }); + + void it('sets default ephemeralStorageSizeMB', () => { + const lambda = defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + }).getInstance(getInstanceProps); + const template = Template.fromStack(lambda.stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + EphemeralStorage: { Size: 512 }, + }); + }); + + void it('throws on ephemeralStorageSizeMB below 512 MB', () => { + assert.throws( + () => + defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + ephemeralStorageSizeMB: 511, + }).getInstance(getInstanceProps), + new AmplifyUserError('InvalidEphemeralStorageSizeMBError', { + message: `Invalid function ephemeralStorageSizeMB of 511`, + resolution: `ephemeralStorageSizeMB must be a whole number between 512 and 10240 inclusive`, + }) + ); + }); + + void it('throws on ephemeralStorageSizeMB above 10240 MB', () => { + assert.throws( + () => + defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + ephemeralStorageSizeMB: 10241, + }).getInstance(getInstanceProps), + new AmplifyUserError('InvalidEphemeralStorageSizeMBError', { + message: `Invalid function ephemeralStorageSizeMB of 10241`, + resolution: `ephemeralStorageSizeMB must be a whole number between 512 and 10240 inclusive`, + }) + ); + }); + + void it('throws on fractional ephemeralStorageSizeMB', () => { + assert.throws( + () => + defineFunction({ + entry: './test-assets/default-lambda/handler.ts', + ephemeralStorageSizeMB: 512.5, + }).getInstance(getInstanceProps), + new AmplifyUserError('InvalidEphemeralStorageSizeMBError', { + message: `Invalid function ephemeralStorageSizeMB of 512.5`, + resolution: `ephemeralStorageSizeMB must be a whole number between 512 and 10240 inclusive`, + }) + ); + }); + }); }); diff --git a/packages/backend-function/src/factory.ts b/packages/backend-function/src/factory.ts index e0c8abb4a4..7f2f04b081 100644 --- a/packages/backend-function/src/factory.ts +++ b/packages/backend-function/src/factory.ts @@ -26,7 +26,7 @@ import { SsmEnvironmentEntry, StackProvider, } from '@aws-amplify/plugin-types'; -import { Duration, Stack, Tags } from 'aws-cdk-lib'; +import { Duration, Size, Stack, Tags } from 'aws-cdk-lib'; import { Rule } from 'aws-cdk-lib/aws-events'; import * as targets from 'aws-cdk-lib/aws-events-targets'; import { Policy } from 'aws-cdk-lib/aws-iam'; @@ -115,6 +115,13 @@ export type FunctionProps = { */ memoryMB?: number; + /** + * The size of the function's /tmp directory in MB. + * Must be a whole number. + * @default 512 + */ + ephemeralStorageSizeMB?: number; + /** * Environment variables that will be available during function execution */ @@ -236,6 +243,7 @@ class FunctionFactory implements ConstructFactory { entry: this.resolveEntry(), timeoutSeconds: this.resolveTimeout(), memoryMB: this.resolveMemory(), + ephemeralStorageSizeMB: this.resolveEphemeralStorageSize(), environment: this.resolveEnvironment(), runtime: this.resolveRuntime(), schedule: this.resolveSchedule(), @@ -324,6 +332,28 @@ class FunctionFactory implements ConstructFactory { return this.props.memoryMB; }; + private resolveEphemeralStorageSize = () => { + const ephemeralStorageSizeMin = 512; + const ephemeralStorageSizeMax = 10240; + const ephemeralStorageSizeDefault = 512; + if (this.props.ephemeralStorageSizeMB === undefined) { + return ephemeralStorageSizeDefault; + } + if ( + !isWholeNumberBetweenInclusive( + this.props.ephemeralStorageSizeMB, + ephemeralStorageSizeMin, + ephemeralStorageSizeMax + ) + ) { + throw new AmplifyUserError('InvalidEphemeralStorageSizeMBError', { + message: `Invalid function ephemeralStorageSizeMB of ${this.props.ephemeralStorageSizeMB}`, + resolution: `ephemeralStorageSizeMB must be a whole number between ${ephemeralStorageSizeMin} and ${ephemeralStorageSizeMax} inclusive`, + }); + } + return this.props.ephemeralStorageSizeMB; + }; + private resolveEnvironment = () => { if (this.props.environment === undefined) { return {}; @@ -509,6 +539,7 @@ class AmplifyFunction entry: props.entry, timeout: Duration.seconds(props.timeoutSeconds), memorySize: props.memoryMB, + ephemeralStorageSize: Size.mebibytes(props.ephemeralStorageSizeMB), runtime: nodeVersionMap[props.runtime], layers: props.resolvedLayers, bundling: {