Skip to content

Commit 4b4a0e8

Browse files
authored
Merge pull request #5 from fourTheorem/cmg/fix-skip-behaviour
fix: skip behaviour and recursive copying
2 parents b7ffb01 + d387d9c commit 4b4a0e8

File tree

3 files changed

+97
-30
lines changed

3 files changed

+97
-30
lines changed

src/bundling.ts

+20-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const DEFAULT_ASSET_EXCLUDES = [
2121
'node_modules/',
2222
'cdk.out/',
2323
'.git/',
24+
'cdk',
2425
];
2526

2627
interface BundlingCommandOptions {
@@ -71,7 +72,7 @@ export class Bundling {
7172
return Code.fromAsset(options.rootDir, {
7273
assetHashType: AssetHashType.SOURCE,
7374
exclude: HASHABLE_DEPENDENCIES_EXCLUDE,
74-
bundling: options.skip ? undefined : new Bundling(options),
75+
bundling: new Bundling(options),
7576
});
7677
}
7778

@@ -92,13 +93,11 @@ export class Bundling {
9293
rootDir,
9394
workspacePackage,
9495
image,
95-
runtime,
9696
commandHooks,
9797
assetExcludes = DEFAULT_ASSET_EXCLUDES,
98-
architecture = Architecture.ARM_64,
9998
} = props;
10099

101-
const bundlingCommands = this.createBundlingCommands({
100+
const bundlingCommands = props.skip ? [] : this.createBundlingCommands({
102101
rootDir,
103102
workspacePackage,
104103
assetExcludes,
@@ -107,15 +106,7 @@ export class Bundling {
107106
outputDir: AssetStaging.BUNDLING_OUTPUT_DIR,
108107
});
109108

110-
this.image =
111-
image ??
112-
DockerImage.fromBuild(path.resolve(__dirname, '..', 'resources'), {
113-
buildArgs: {
114-
...props.buildArgs,
115-
IMAGE: runtime.bundlingImage.image,
116-
},
117-
platform: architecture.dockerPlatform,
118-
});
109+
this.image = image ?? this.createDockerImage(props);
119110

120111
this.command = props.command ?? [
121112
'bash',
@@ -133,6 +124,22 @@ export class Bundling {
133124
this.bundlingFileAccess = props.bundlingFileAccess;
134125
}
135126

127+
private createDockerImage(props: BundlingProps): DockerImage {
128+
// If skip is true then don't call DockerImage.fromBuild as that calls dockerExec.
129+
// Return a dummy object of the right type as it's not going to be used.
130+
if (props.skip) {
131+
return new DockerImage('skipped');
132+
}
133+
134+
return DockerImage.fromBuild(path.resolve(__dirname, '..', 'resources'), {
135+
buildArgs: {
136+
...props.buildArgs,
137+
IMAGE: props.runtime.bundlingImage.image,
138+
},
139+
platform: (props.architecture ?? Architecture.ARM_64).dockerPlatform,
140+
});
141+
}
142+
136143
private createBundlingCommands(options: BundlingCommandOptions): string[] {
137144
const excludeArgs = options.assetExcludes.map((exclude) => `--exclude="${exclude}"`);
138145
const workspacePackage = options.workspacePackage;

src/function.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ export class PythonFunction extends Function {
7575
throw new Error('Only Python runtimes are supported');
7676
}
7777

78+
const skip = !Stack.of(scope).bundlingRequired;
79+
7880
const code = Bundling.bundle({
7981
rootDir,
8082
runtime,
81-
skip: !Stack.of(scope).bundlingRequired,
83+
skip: skip,
8284
architecture,
8385
workspacePackage,
8486
...props.bundling,
@@ -95,18 +97,23 @@ export class PythonFunction extends Function {
9597
handler: resolvedHandler,
9698
});
9799

100+
if (skip) {
101+
return;
102+
}
103+
98104
const assetPath = ((this.node.defaultChild) as CfnFunction).getMetadata('aws:asset:path');
99-
if (assetPath) { // TODO - remove - we always need one
100-
const codePath = path.join(process.env.CDK_OUTDIR as string, assetPath);
105+
if (!assetPath) {
106+
return;
107+
}
101108

102-
const pythonPaths = getPthFilePaths(codePath);
109+
const codePath = path.join(process.env.CDK_OUTDIR as string, assetPath);
110+
const pythonPaths = getPthFilePaths(codePath);
103111

104-
if (pythonPaths.length > 0) {
105-
let pythonPathValue = environment.PYTHONPATH;
106-
const addedPaths = pythonPaths.join(':');
107-
pythonPathValue = pythonPathValue ? `${pythonPathValue}:${addedPaths}` : addedPaths;
108-
this.addEnvironment('PYTHONPATH', pythonPathValue);
109-
}
112+
if (pythonPaths.length > 0) {
113+
let pythonPathValue = environment.PYTHONPATH;
114+
const addedPaths = pythonPaths.join(':');
115+
pythonPathValue = pythonPathValue ? `${pythonPathValue}:${addedPaths}` : addedPaths;
116+
this.addEnvironment('PYTHONPATH', pythonPathValue);
110117
}
111118
}
112119
}

test/function.test.ts

+60-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { exec } from 'node:child_process';
22
import * as fs from 'node:fs/promises';
3+
import * as os from 'node:os';
34
import * as path from 'node:path';
45
import { promisify } from 'node:util';
56
import { App, Stack } from 'aws-cdk-lib';
67
import { Match, Template } from 'aws-cdk-lib/assertions';
78
import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda';
9+
import * as cxapi from 'aws-cdk-lib/cx-api';
810
import { PythonFunction } from '../src';
911
const execAsync = promisify(exec);
1012

@@ -27,9 +29,41 @@ async function getDockerHostArch(): Promise<Architecture> {
2729
}
2830
}
2931

30-
test('Create a function from basic_app', async () => {
32+
/**
33+
* Create a new CDK App and Stack with the given name and set the context to ensure
34+
* that the 'aws:asset:path' metadata is set.
35+
*
36+
* @returns The App and Stack
37+
*/
38+
async function createStack(name = 'test'): Promise<{ app: App; stack: Stack }> {
3139
const app = new App({});
32-
const stack = new Stack(app, 'test');
40+
const stack = new Stack(app, name);
41+
42+
// This ensures that the 'aws:asset:path' metadata is set
43+
stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true);
44+
45+
return { app, stack };
46+
}
47+
48+
// Need to have CDK_OUTDIR set to something sensible as it's used to create the codePath when aws:asset:path is set
49+
const OLD_ENV = process.env;
50+
51+
beforeEach(async () => {
52+
jest.resetModules();
53+
process.env = { ...OLD_ENV };
54+
process.env.CDK_OUTDIR = await fs.mkdtemp(path.join(os.tmpdir(), 'uv-python-lambda-test-'));
55+
});
56+
57+
afterEach(async () => {
58+
if (process.env.CDK_OUTDIR) {
59+
await fs.rm(process.env.CDK_OUTDIR, { recursive: true });
60+
}
61+
process.env = OLD_ENV;
62+
});
63+
64+
test('Create a function from basic_app', async () => {
65+
const { app, stack } = await createStack();
66+
3367
new PythonFunction(stack, 'basic_app', {
3468
rootDir: path.join(resourcesPath, 'basic_app'),
3569
index: 'handler.py',
@@ -55,8 +89,8 @@ test('Create a function from basic_app', async () => {
5589
});
5690

5791
test('Create a function from basic_app with no .py index extension', async () => {
58-
const app = new App({});
59-
const stack = new Stack(app, 'test');
92+
const { stack } = await createStack();
93+
6094
new PythonFunction(stack, 'basic_app', {
6195
rootDir: path.join(resourcesPath, 'basic_app'),
6296
index: 'handler',
@@ -77,9 +111,29 @@ test('Create a function from basic_app with no .py index extension', async () =>
77111
});
78112
});
79113

114+
test('Create a function from basic_app when skip is true', async () => {
115+
const { stack } = await createStack();
116+
117+
const bundlingSpy = jest.spyOn(stack, 'bundlingRequired', 'get').mockReturnValue(false);
118+
const architecture = await getDockerHostArch();
119+
120+
// To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor
121+
expect(() => {
122+
new PythonFunction(stack, 'basic_app', {
123+
rootDir: path.join(resourcesPath, 'basic_app'),
124+
index: 'handler',
125+
handler: 'lambda_handler',
126+
runtime: Runtime.PYTHON_3_12,
127+
architecture,
128+
});
129+
}).not.toThrow();
130+
131+
bundlingSpy.mockRestore();
132+
});
133+
80134
test('Create a function with workspaces_app', async () => {
81-
const app = new App({});
82-
const stack = new Stack(app, 'wstest');
135+
const { app, stack } = await createStack('wstest');
136+
83137
new PythonFunction(stack, 'workspaces_app', {
84138
rootDir: path.join(resourcesPath, 'workspaces_app'),
85139
workspacePackage: 'app',
@@ -122,4 +176,3 @@ async function getFunctionAssetContents(functionResource: any, app: App) {
122176
const contents = await fs.readdir(assetPath);
123177
return contents;
124178
}
125-

0 commit comments

Comments
 (0)