From a8b0723f63f86cafe7575995b7dfd6cf570ce4ec Mon Sep 17 00:00:00 2001 From: Andreas Sieferlinger Date: Wed, 14 Dec 2022 08:53:36 +0100 Subject: [PATCH 1/3] feat(aws-lambda-python): add command hooks for bundling to allow for execution of custom commands in the build container --- packages/@aws-cdk/aws-lambda-python/README.md | 34 +++++++++++++++ .../aws-lambda-python/lib/bundling.ts | 7 +++- .../@aws-cdk/aws-lambda-python/lib/types.ts | 41 +++++++++++++++++++ .../aws-lambda-python/test/bundling.test.ts | 29 +++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index fc68cbce9d837..b05399ac64bc2 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -198,3 +198,37 @@ new python.PythonFunction(this, 'function', { }, }); ``` + +## Command hooks + +It is possible to run additional commands by specifying the `commandHooks` prop: + +```ts +const entry = '/path/to/function'; +const image = DockerImage.fromBuild(entry); +new python.PythonFunction(this, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { + commandHooks: { + // run tests + beforeBundling(inputDir: string): string[] { + return ['pytest']; + }, + // ... + }, + }, +}); +``` + +The following hooks are available: + +- `beforeBundling`: runs before all bundling commands +- `afterBundling`: runs after all bundling commands + +They all receive the directory containing the dependencies file (`inputDir`) and the +directory where the bundled asset will be output (`outputDir`). They must return +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-python/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts index 726b72c5bc2b8..5302673a5e296 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; import { AssetStaging, BundlingOptions as CdkBundlingOptions, DockerImage } from '@aws-cdk/core'; import { Packaging, DependenciesFile } from './packaging'; -import { BundlingOptions } from './types'; +import { BundlingOptions, ICommandHooks } from './types'; /** * Dependency files to exclude from the asset hash. @@ -68,6 +68,7 @@ export class Bundling implements CdkBundlingOptions { outputPathSuffix = '', image, poetryIncludeHashes, + commandHooks, } = props; const outputPath = path.posix.join(AssetStaging.BUNDLING_OUTPUT_DIR, outputPathSuffix); @@ -77,6 +78,7 @@ export class Bundling implements CdkBundlingOptions { inputDir: AssetStaging.BUNDLING_INPUT_DIR, outputDir: outputPath, poetryIncludeHashes, + commandHooks, }); this.image = image ?? DockerImage.fromBuild(path.join(__dirname, '../lib'), { @@ -93,12 +95,14 @@ export class Bundling implements CdkBundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string[] { const packaging = Packaging.fromEntry(options.entry, options.poetryIncludeHashes); let bundlingCommands: string[] = []; + bundlingCommands.push(...options.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? []); bundlingCommands.push(`cp -rTL ${options.inputDir}/ ${options.outputDir}`); bundlingCommands.push(`cd ${options.outputDir}`); bundlingCommands.push(packaging.exportCommand ?? ''); if (packaging.dependenciesFile) { bundlingCommands.push(`python -m pip install -r ${DependenciesFile.PIP} -t ${options.outputDir}`); } + bundlingCommands.push(...options.commandHooks?.afterBundling(options.inputDir, options.outputDir) ?? []); return bundlingCommands; } } @@ -108,6 +112,7 @@ interface BundlingCommandOptions { readonly inputDir: string; readonly outputDir: string; readonly poetryIncludeHashes?: boolean; + readonly commandHooks?: ICommandHooks } /** diff --git a/packages/@aws-cdk/aws-lambda-python/lib/types.ts b/packages/@aws-cdk/aws-lambda-python/lib/types.ts index 3689c43335959..ff9608b97e2f1 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/types.ts @@ -86,4 +86,45 @@ export interface BundlingOptions { * @default - Based on `assetHashType` */ readonly assetHash?: string; + + /** + * Command hooks + * + * @default - do not run additional commands + */ + readonly commandHooks?: ICommandHooks; +} + +/** + * 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 `&&`. + * + * ```text + * { + * // Run tests prior to bundling + * beforeBundling(inputDir: string, outputDir: string): string[] { + * return [`pytest`]; + * } + * // ... + * } + * ``` + */ +export interface ICommandHooks { + /** + * Returns commands to run before bundling. + * + * Commands are chained with `&&`. + */ + beforeBundling(inputDir: string, outputDir: string): string[]; + + /** + * Returns commands to run after bundling. + * + * Commands are chained with `&&`. + */ + afterBundling(inputDir: string, outputDir: string): string[]; } diff --git a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts index 5626a3f02d2a4..402953c197c1c 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -299,3 +299,32 @@ test('Build docker image when bundling is not skipped', () => { expect(DockerImage.fromBuild).toHaveBeenCalled(); }); + +test('with command hooks', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + skip: false, + commandHooks: { + 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`]; + }, + }, + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + expect.stringMatching(/^echo hello > \/asset-input\/a.txt && cp \/asset-input\/a.txt \/asset-output && .+ && cp \/asset-input\/b.txt \/asset-output\/txt$/), + ], + }), + })); +}); From 4d8c0997fe7eb94b4f18420a6ea31d6466c0eeea Mon Sep 17 00:00:00 2001 From: Andreas Sieferlinger Date: Wed, 14 Dec 2022 09:40:08 +0100 Subject: [PATCH 2/3] add afterbundling to example --- packages/@aws-cdk/aws-lambda-python/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index b05399ac64bc2..8d3d645f2edb6 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -215,6 +215,9 @@ new python.PythonFunction(this, 'function', { beforeBundling(inputDir: string): string[] { return ['pytest']; }, + afterBundling(inputDir: string): string[] { + return ['pylint']; + }, // ... }, }, From d50901377b2d17c5fd7c14da72cdd40fd3553909 Mon Sep 17 00:00:00 2001 From: Andreas Sieferlinger Date: Wed, 14 Dec 2022 12:33:44 +0100 Subject: [PATCH 3/3] Update packages/@aws-cdk/aws-lambda-python/README.md Co-authored-by: Momo Kornher --- packages/@aws-cdk/aws-lambda-python/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 8d3d645f2edb6..04f092e5f2875 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -205,7 +205,6 @@ It is possible to run additional commands by specifying the `commandHooks` prop ```ts const entry = '/path/to/function'; -const image = DockerImage.fromBuild(entry); new python.PythonFunction(this, 'function', { entry, runtime: Runtime.PYTHON_3_8,