From 8b2a75029d3faa85c45a39eed9bae2f16aee2d0c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 19 Nov 2020 17:51:40 +0100 Subject: [PATCH 1/4] feat(lambda-nodejs): command hooks Add support for running additional commands before bundling, before node modules installation and after bundling. Closes #11468 --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 23 +++++++++++++++ .../aws-lambda-nodejs/lib/bundling.ts | 8 ++++- .../@aws-cdk/aws-lambda-nodejs/lib/types.ts | 29 +++++++++++++++++++ .../aws-lambda-nodejs/test/bundling.test.ts | 27 +++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 2797329f7de35..845c5166bc665 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -144,3 +144,26 @@ $ yarn add --dev esbuild@0 To force bundling in a Docker container, set the `forceDockerBundling` prop to `true`. This is useful if your function relies on node modules that should be installed (`nodeModules` prop, see [above](#install-modules)) in a Lambda compatible environment. This is usually the case with modules using native dependencies. + +### Command hooks +It is possible to run additional commands by specifying the `commandHooks` prop: + +```ts +new lambda.NodejsFunction(this, 'my-handler-with-commands', { + commandHooks: { + // Copy a file so that it will be included in the bundled asset + afterBundling(inputDir: string, outputDir: string): string { + return `cp ${inputDir}/important-file.node ${outputDir}` + } + } +}); +``` + +The following hooks are available: +- `beforeBundling`: runs before all bundling commands +- `afterInstall`: runs before node modules installation +- `afterBundling`: runs after all bundling commands + +They all receive the directory containing the lock file (`inputDir`) and the +directory where the bundled asset will be output (`outputDir`). They must return +the command to run as a `string`. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index be6789a1b3226..9128fb017f85a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -169,7 +169,13 @@ export class Bundling implements cdk.BundlingOptions { ]); } - return chain([esbuildCommand, depsCommand]); + return chain([ + this.props.commandHooks?.beforeBundling?.(inputDir, outputDir) ?? '', + esbuildCommand, + (this.props.nodeModules && this.props.commandHooks?.beforeInstall?.(inputDir, outputDir)) ?? '', + depsCommand, + this.props.commandHooks?.afterBundling?.(inputDir, outputDir) ?? '', + ]); } } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index 78bc72d40df22..4153c36b38e09 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -101,4 +101,33 @@ export interface BundlingOptions { * @default - use the Docker image provided by @aws-cdk/aws-lambda-nodejs */ readonly bundlingDockerImage?: BundlingDockerImage; + + /** + * Command hooks + * + * @default - do not run additional commands + */ + readonly commandHooks?: ICommandHooks; +} + +/** + * Command hooks + */ +export interface ICommandHooks { + /** + * Returns a command to run before bundling + */ + beforeBundling?(inputDir: string, outputDir: string): string; + + /** + * Returns a command to run before installing node modules. + * + * This hook only runs when node modules are installed + */ + beforeInstall?(inputDir: string, outputDir: string): string; + + /** + * Returns a command to run after bundling + */ + afterBundling?(inputDir: string, outputDir: string): string; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index a88f55a41e842..e35a0cc427701 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -255,3 +255,30 @@ test('Custom bundling docker image', () => { }), }); }); + +test('with command hooks', () => { + Bundling.bundle({ + entry, + depsLockFilePath, + runtime: Runtime.NODEJS_12_X, + commandHooks: { + beforeBundling(inputDir: string, outputDir: string): string { + return `cp ${inputDir}/a.txt ${outputDir}`; + }, + afterBundling(inputDir: string, outputDir: string): string { + return `cp ${inputDir}/b.txt ${outputDir}/txt`; + }, + }, + forceDockerBundling: true, + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + expect.stringMatching(/^cp \/asset-input\/a.txt \/asset-output && .+ && cp \/asset-input\/b.txt \/asset-output\/txt$/), + ], + }), + }); +}); From ebbef5b3f17fd992c5c92cf163bc01b799ca0599 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 23 Nov 2020 19:00:03 +0100 Subject: [PATCH 2/4] PR feedback --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 11 +++--- .../aws-lambda-nodejs/lib/bundling.ts | 6 ++-- .../@aws-cdk/aws-lambda-nodejs/lib/types.ts | 34 +++++++++++++++---- .../aws-lambda-nodejs/test/bundling.test.ts | 13 ++++--- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 845c5166bc665..a1f80a9197262 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -152,8 +152,8 @@ It is possible to run additional commands by specifying the `commandHooks` prop: new lambda.NodejsFunction(this, 'my-handler-with-commands', { commandHooks: { // Copy a file so that it will be included in the bundled asset - afterBundling(inputDir: string, outputDir: string): string { - return `cp ${inputDir}/important-file.node ${outputDir}` + afterBundling(inputDir: string, outputDir: string): string[] { + return [`cp ${inputDir}/my-binary.node ${outputDir}`]; } } }); @@ -161,9 +161,12 @@ new lambda.NodejsFunction(this, 'my-handler-with-commands', { The following hooks are available: - `beforeBundling`: runs before all bundling commands -- `afterInstall`: runs before node modules installation +- `beforeInstall`: runs before node modules installation - `afterBundling`: runs after all bundling commands They all receive the directory containing the lock file (`inputDir`) and the directory where the bundled asset will be output (`outputDir`). They must return -the command to run as a `string`. +an array of commands to run. Commands are chained with `&&`. + +The commands will run in the environment in which bundling occurs: inside the +container for Docker bundling or on the host OS for local bundling. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 9128fb017f85a..8c04c69a8ceb8 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -170,11 +170,11 @@ export class Bundling implements cdk.BundlingOptions { } return chain([ - this.props.commandHooks?.beforeBundling?.(inputDir, outputDir) ?? '', + ...this.props.commandHooks?.beforeBundling?.(inputDir, outputDir) ?? [], esbuildCommand, - (this.props.nodeModules && this.props.commandHooks?.beforeInstall?.(inputDir, outputDir)) ?? '', + ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall?.(inputDir, outputDir)) ?? [], depsCommand, - this.props.commandHooks?.afterBundling?.(inputDir, outputDir) ?? '', + ...this.props.commandHooks?.afterBundling?.(inputDir, outputDir) ?? [], ]); } } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index 4153c36b38e09..fd9776b93c7d9 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -112,22 +112,42 @@ export interface BundlingOptions { /** * Command hooks + * + * These commands will run in the environment in which bundling occurs: inside + * the container for Docker bundling or on the host OS for local bundling. + * + * Commands are chained with `&&`. + * + * @example + * { + * // Copy a file from the input directory to the output directory + * // to include it in the bundled asset + * afterBundling(inputDir: string, outputDir: string): string[] { + * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; + * } + * } */ export interface ICommandHooks { /** - * Returns a command to run before bundling + * Returns commands to run before bundling. + * + * Commands are chained with `&&`. */ - beforeBundling?(inputDir: string, outputDir: string): string; + beforeBundling?(inputDir: string, outputDir: string): string[]; /** - * Returns a command to run before installing node modules. + * Returns commands to run before installing node modules. + * + * This hook only runs when node modules are installed. * - * This hook only runs when node modules are installed + * Commands are chained with `&&`. */ - beforeInstall?(inputDir: string, outputDir: string): string; + beforeInstall?(inputDir: string, outputDir: string): string[]; /** - * Returns a command to run after bundling + * Returns commands to run after bundling. + * + * Commands are chained with `&&`. */ - afterBundling?(inputDir: string, outputDir: string): string; + afterBundling?(inputDir: string, outputDir: string): string[]; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index e35a0cc427701..34d2dfad84048 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -262,11 +262,14 @@ test('with command hooks', () => { depsLockFilePath, runtime: Runtime.NODEJS_12_X, commandHooks: { - beforeBundling(inputDir: string, outputDir: string): string { - return `cp ${inputDir}/a.txt ${outputDir}`; + beforeBundling(inputDir: string, outputDir: string): string[] { + return [ + `echo hello > ${inputDir}/a.txt`, + `cp ${inputDir}/a.txt ${outputDir}`, + ]; }, - afterBundling(inputDir: string, outputDir: string): string { - return `cp ${inputDir}/b.txt ${outputDir}/txt`; + afterBundling(inputDir: string, outputDir: string): string[] { + return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; }, }, forceDockerBundling: true, @@ -277,7 +280,7 @@ test('with command hooks', () => { bundling: expect.objectContaining({ command: [ 'bash', '-c', - expect.stringMatching(/^cp \/asset-input\/a.txt \/asset-output && .+ && cp \/asset-input\/b.txt \/asset-output\/txt$/), + expect.stringMatching(/^echo hello > \/asset-input\/a.txt && cp \/asset-input\/a.txt \/asset-output && .+ && cp \/asset-input\/b.txt \/asset-output\/txt$/), ], }), }); From 305c76b2e881316c04f2e523b387eacd96327fc2 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 25 Nov 2020 16:30:28 +0100 Subject: [PATCH 3/4] mandatory --- packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts | 6 +++--- packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts | 6 +++--- packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 8c04c69a8ceb8..0ed7586aa6944 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -170,11 +170,11 @@ export class Bundling implements cdk.BundlingOptions { } return chain([ - ...this.props.commandHooks?.beforeBundling?.(inputDir, outputDir) ?? [], + ...this.props.commandHooks?.beforeBundling(inputDir, outputDir) ?? [], esbuildCommand, - ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall?.(inputDir, outputDir)) ?? [], + ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(inputDir, outputDir)) ?? [], depsCommand, - ...this.props.commandHooks?.afterBundling?.(inputDir, outputDir) ?? [], + ...this.props.commandHooks?.afterBundling(inputDir, outputDir) ?? [], ]); } } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index fd9776b93c7d9..3fea8b5470abc 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -133,7 +133,7 @@ export interface ICommandHooks { * * Commands are chained with `&&`. */ - beforeBundling?(inputDir: string, outputDir: string): string[]; + beforeBundling(inputDir: string, outputDir: string): string[]; /** * Returns commands to run before installing node modules. @@ -142,12 +142,12 @@ export interface ICommandHooks { * * Commands are chained with `&&`. */ - beforeInstall?(inputDir: string, outputDir: string): string[]; + beforeInstall(inputDir: string, outputDir: string): string[]; /** * Returns commands to run after bundling. * * Commands are chained with `&&`. */ - afterBundling?(inputDir: string, outputDir: string): string[]; + afterBundling(inputDir: string, outputDir: string): string[]; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 34d2dfad84048..aa362c7ea9a8f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -271,6 +271,9 @@ test('with command hooks', () => { afterBundling(inputDir: string, outputDir: string): string[] { return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; }, + beforeInstall() { + return []; + }, }, forceDockerBundling: true, }); From 2fc0d32d40364241509550ebb889e63079a0099b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 25 Nov 2020 16:32:24 +0100 Subject: [PATCH 4/4] README --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 1 + packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index a1f80a9197262..646b996f75fcb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -155,6 +155,7 @@ new lambda.NodejsFunction(this, 'my-handler-with-commands', { afterBundling(inputDir: string, outputDir: string): string[] { return [`cp ${inputDir}/my-binary.node ${outputDir}`]; } + // ... } }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index 3fea8b5470abc..074d083cb0abe 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -125,6 +125,7 @@ export interface BundlingOptions { * afterBundling(inputDir: string, outputDir: string): string[] { * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; * } + * // ... * } */ export interface ICommandHooks {