Skip to content

Commit d3d071f

Browse files
authored
feat(aws-lambda-python): add command hooks for bundling to allow for execution of custom commands in the build container (#23330)
This brings `aws-lambda-python` the same functionality as the `go` and `nodejs` lambda functions which allows to execute custom commands before and after installing the dependencies within the bundling environment (container). This can be useful to run test and linters or add additional files. Did not add an integration test, as there seemed to be none for the other packages either. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6e4b4ea commit d3d071f

File tree

4 files changed

+112
-1
lines changed

4 files changed

+112
-1
lines changed

packages/@aws-cdk/aws-lambda-python/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,39 @@ new python.PythonFunction(this, 'function', {
198198
},
199199
});
200200
```
201+
202+
## Command hooks
203+
204+
It is possible to run additional commands by specifying the `commandHooks` prop:
205+
206+
```ts
207+
const entry = '/path/to/function';
208+
new python.PythonFunction(this, 'function', {
209+
entry,
210+
runtime: Runtime.PYTHON_3_8,
211+
bundling: {
212+
commandHooks: {
213+
// run tests
214+
beforeBundling(inputDir: string): string[] {
215+
return ['pytest'];
216+
},
217+
afterBundling(inputDir: string): string[] {
218+
return ['pylint'];
219+
},
220+
// ...
221+
},
222+
},
223+
});
224+
```
225+
226+
The following hooks are available:
227+
228+
- `beforeBundling`: runs before all bundling commands
229+
- `afterBundling`: runs after all bundling commands
230+
231+
They all receive the directory containing the dependencies file (`inputDir`) and the
232+
directory where the bundled asset will be output (`outputDir`). They must return
233+
an array of commands to run. Commands are chained with `&&`.
234+
235+
The commands will run in the environment in which bundling occurs: inside the
236+
container for Docker bundling or on the host OS for local bundling.

packages/@aws-cdk/aws-lambda-python/lib/bundling.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as path from 'path';
22
import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda';
33
import { AssetStaging, BundlingOptions as CdkBundlingOptions, DockerImage } from '@aws-cdk/core';
44
import { Packaging, DependenciesFile } from './packaging';
5-
import { BundlingOptions } from './types';
5+
import { BundlingOptions, ICommandHooks } from './types';
66

77
/**
88
* Dependency files to exclude from the asset hash.
@@ -68,6 +68,7 @@ export class Bundling implements CdkBundlingOptions {
6868
outputPathSuffix = '',
6969
image,
7070
poetryIncludeHashes,
71+
commandHooks,
7172
} = props;
7273

7374
const outputPath = path.posix.join(AssetStaging.BUNDLING_OUTPUT_DIR, outputPathSuffix);
@@ -77,6 +78,7 @@ export class Bundling implements CdkBundlingOptions {
7778
inputDir: AssetStaging.BUNDLING_INPUT_DIR,
7879
outputDir: outputPath,
7980
poetryIncludeHashes,
81+
commandHooks,
8082
});
8183

8284
this.image = image ?? DockerImage.fromBuild(path.join(__dirname, '../lib'), {
@@ -93,12 +95,14 @@ export class Bundling implements CdkBundlingOptions {
9395
private createBundlingCommand(options: BundlingCommandOptions): string[] {
9496
const packaging = Packaging.fromEntry(options.entry, options.poetryIncludeHashes);
9597
let bundlingCommands: string[] = [];
98+
bundlingCommands.push(...options.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? []);
9699
bundlingCommands.push(`cp -rTL ${options.inputDir}/ ${options.outputDir}`);
97100
bundlingCommands.push(`cd ${options.outputDir}`);
98101
bundlingCommands.push(packaging.exportCommand ?? '');
99102
if (packaging.dependenciesFile) {
100103
bundlingCommands.push(`python -m pip install -r ${DependenciesFile.PIP} -t ${options.outputDir}`);
101104
}
105+
bundlingCommands.push(...options.commandHooks?.afterBundling(options.inputDir, options.outputDir) ?? []);
102106
return bundlingCommands;
103107
}
104108
}
@@ -108,6 +112,7 @@ interface BundlingCommandOptions {
108112
readonly inputDir: string;
109113
readonly outputDir: string;
110114
readonly poetryIncludeHashes?: boolean;
115+
readonly commandHooks?: ICommandHooks
111116
}
112117

113118
/**

packages/@aws-cdk/aws-lambda-python/lib/types.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,45 @@ export interface BundlingOptions {
8686
* @default - Based on `assetHashType`
8787
*/
8888
readonly assetHash?: string;
89+
90+
/**
91+
* Command hooks
92+
*
93+
* @default - do not run additional commands
94+
*/
95+
readonly commandHooks?: ICommandHooks;
96+
}
97+
98+
/**
99+
* Command hooks
100+
*
101+
* These commands will run in the environment in which bundling occurs: inside
102+
* the container for Docker bundling or on the host OS for local bundling.
103+
*
104+
* Commands are chained with `&&`.
105+
*
106+
* ```text
107+
* {
108+
* // Run tests prior to bundling
109+
* beforeBundling(inputDir: string, outputDir: string): string[] {
110+
* return [`pytest`];
111+
* }
112+
* // ...
113+
* }
114+
* ```
115+
*/
116+
export interface ICommandHooks {
117+
/**
118+
* Returns commands to run before bundling.
119+
*
120+
* Commands are chained with `&&`.
121+
*/
122+
beforeBundling(inputDir: string, outputDir: string): string[];
123+
124+
/**
125+
* Returns commands to run after bundling.
126+
*
127+
* Commands are chained with `&&`.
128+
*/
129+
afterBundling(inputDir: string, outputDir: string): string[];
89130
}

packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,32 @@ test('Build docker image when bundling is not skipped', () => {
299299

300300
expect(DockerImage.fromBuild).toHaveBeenCalled();
301301
});
302+
303+
test('with command hooks', () => {
304+
const entry = path.join(__dirname, 'lambda-handler');
305+
Bundling.bundle({
306+
entry: entry,
307+
runtime: Runtime.PYTHON_3_7,
308+
skip: false,
309+
commandHooks: {
310+
beforeBundling(inputDir: string, outputDir: string): string[] {
311+
return [
312+
`echo hello > ${inputDir}/a.txt`,
313+
`cp ${inputDir}/a.txt ${outputDir}`,
314+
];
315+
},
316+
afterBundling(inputDir: string, outputDir: string): string[] {
317+
return [`cp ${inputDir}/b.txt ${outputDir}/txt`];
318+
},
319+
},
320+
});
321+
322+
expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({
323+
bundling: expect.objectContaining({
324+
command: [
325+
'bash', '-c',
326+
expect.stringMatching(/^echo hello > \/asset-input\/a.txt && cp \/asset-input\/a.txt \/asset-output && .+ && cp \/asset-input\/b.txt \/asset-output\/txt$/),
327+
],
328+
}),
329+
}));
330+
});

0 commit comments

Comments
 (0)