Skip to content

Commit 5dc61ea

Browse files
authored
fix(lambda-python): docker image gets built even when we don't need to bundle assets (#16192)
`DockerImage.fromBuild` was being called for bundling regardless of whether the stack needed bundling or not. With this change, we will check if the stack needs bundling before proceeding to build the docker image. Fixes #14747 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent fe9f750 commit 5dc61ea

File tree

9 files changed

+144
-6
lines changed

9 files changed

+144
-6
lines changed

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export interface BundlingProps extends BundlingOptions {
3434
* @default Architecture.X86_64
3535
*/
3636
readonly architecture?: Architecture;
37+
38+
/**
39+
* Whether or not the bundling process should be skipped
40+
*
41+
* @default - Does not skip bundling
42+
*/
43+
readonly skip?: boolean;
3744
}
3845

3946
/**
@@ -45,7 +52,7 @@ export class Bundling implements CdkBundlingOptions {
4552
assetHash: options.assetHash,
4653
assetHashType: options.assetHashType,
4754
exclude: DEPENDENCY_EXCLUDES,
48-
bundling: new Bundling(options),
55+
bundling: options.skip ? undefined : new Bundling(options),
4956
});
5057
}
5158

@@ -72,7 +79,7 @@ export class Bundling implements CdkBundlingOptions {
7279

7380
this.image = image ?? DockerImage.fromBuild(path.join(__dirname, '../lib'), {
7481
buildArgs: {
75-
...props.buildArgs ?? {},
82+
...props.buildArgs,
7683
IMAGE: runtime.bundlingImage.image,
7784
},
7885
platform: architecture.dockerPlatform,

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

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import { Function, FunctionOptions, Runtime, RuntimeFamily } from '@aws-cdk/aws-lambda';
4+
import { Stack } from '@aws-cdk/core';
45
import { Bundling } from './bundling';
56
import { BundlingOptions } from './types';
67

@@ -79,6 +80,7 @@ export class PythonFunction extends Function {
7980
code: Bundling.bundle({
8081
entry,
8182
runtime,
83+
skip: !Stack.of(scope).bundlingRequired,
8284
...props.bundling,
8385
}),
8486
handler: resolvedHandler,

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

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as path from 'path';
22
import * as lambda from '@aws-cdk/aws-lambda';
3+
import { Stack } from '@aws-cdk/core';
34
import { Bundling } from './bundling';
45
import { BundlingOptions } from './types';
56

@@ -67,6 +68,7 @@ export class PythonLayerVersion extends lambda.LayerVersion {
6768
runtime,
6869
architecture,
6970
outputPathSuffix: 'python',
71+
skip: !Stack.of(scope).bundlingRequired,
7072
...props.bundling,
7173
}),
7274
});

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

+22
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,25 @@ test('Bundling with custom environment vars`', () => {
249249
}),
250250
}));
251251
});
252+
253+
test('Do not build docker image when skipping bundling', () => {
254+
const entry = path.join(__dirname, 'lambda-handler');
255+
Bundling.bundle({
256+
entry: entry,
257+
runtime: Runtime.PYTHON_3_7,
258+
skip: true,
259+
});
260+
261+
expect(DockerImage.fromBuild).not.toHaveBeenCalled();
262+
});
263+
264+
test('Build docker image when bundling is not skipped', () => {
265+
const entry = path.join(__dirname, 'lambda-handler');
266+
Bundling.bundle({
267+
entry: entry,
268+
runtime: Runtime.PYTHON_3_7,
269+
skip: false,
270+
});
271+
272+
expect(DockerImage.fromBuild).toHaveBeenCalled();
273+
});

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

+32
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,35 @@ test('Allows use of custom bundling image', () => {
167167
image,
168168
}));
169169
});
170+
171+
test('Skip bundling when stack does not require it', () => {
172+
const spy = jest.spyOn(stack, 'bundlingRequired', 'get').mockReturnValue(false);
173+
const entry = path.join(__dirname, 'lambda-handler');
174+
175+
new PythonFunction(stack, 'function', {
176+
entry,
177+
runtime: Runtime.PYTHON_3_8,
178+
});
179+
180+
expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
181+
skip: true,
182+
}));
183+
184+
spy.mockRestore();
185+
});
186+
187+
test('Do not skip bundling when stack requires it', () => {
188+
const spy = jest.spyOn(stack, 'bundlingRequired', 'get').mockReturnValue(true);
189+
const entry = path.join(__dirname, 'lambda-handler');
190+
191+
new PythonFunction(stack, 'function', {
192+
entry,
193+
runtime: Runtime.PYTHON_3_8,
194+
});
195+
196+
expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
197+
skip: false,
198+
}));
199+
200+
spy.mockRestore();
201+
});

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

+30
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,33 @@ test('Allows use of custom bundling image', () => {
6363
image,
6464
}));
6565
});
66+
67+
test('Skip bundling when stack does not require it', () => {
68+
const spy = jest.spyOn(stack, 'bundlingRequired', 'get').mockReturnValue(false);
69+
const entry = path.join(__dirname, 'lambda-handler-project');
70+
71+
new PythonLayerVersion(stack, 'layer', {
72+
entry,
73+
});
74+
75+
expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
76+
skip: true,
77+
}));
78+
79+
spy.mockRestore();
80+
});
81+
82+
test('Do not skip bundling when stack requires it', () => {
83+
const spy = jest.spyOn(stack, 'bundlingRequired', 'get').mockReturnValue(true);
84+
const entry = path.join(__dirname, 'lambda-handler-project');
85+
86+
new PythonLayerVersion(stack, 'layer', {
87+
entry,
88+
});
89+
90+
expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
91+
skip: false,
92+
}));
93+
94+
spy.mockRestore();
95+
});

packages/@aws-cdk/core/lib/asset-staging.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as path from 'path';
44
import * as cxapi from '@aws-cdk/cx-api';
55
import { Construct } from 'constructs';
66
import * as fs from 'fs-extra';
7-
import * as minimatch from 'minimatch';
87
import { AssetHashType, AssetOptions, FileAssetPackaging } from './assets';
98
import { BundlingOptions, BundlingOutput } from './bundling';
109
import { FileSystem, FingerprintOptions } from './fs';
@@ -188,9 +187,7 @@ export class AssetStaging extends CoreConstruct {
188187
let skip = false;
189188
if (props.bundling) {
190189
// Check if we actually have to bundle for this stack
191-
const bundlingStacks: string[] = this.node.tryGetContext(cxapi.BUNDLING_STACKS) ?? ['*'];
192-
// bundlingStacks is of the form `Stage/Stack`, convert it to `Stage-Stack` before comparing to stack name
193-
skip = !bundlingStacks.find(pattern => minimatch(Stack.of(this).stackName, pattern.replace('/', '-')));
190+
skip = !Stack.of(this).bundlingRequired;
194191
const bundling = props.bundling;
195192
stageThisAsset = () => this.stageByBundling(bundling, skip);
196193
} else {

packages/@aws-cdk/core/lib/stack.ts

+14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'path';
33
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
44
import * as cxapi from '@aws-cdk/cx-api';
55
import { IConstruct, Construct, Node } from 'constructs';
6+
import * as minimatch from 'minimatch';
67
import { Annotations } from './annotations';
78
import { App } from './app';
89
import { Arn, ArnComponents, ArnFormat } from './arn';
@@ -1153,6 +1154,19 @@ export class Stack extends CoreConstruct implements ITaggable {
11531154

11541155
return makeStackName(ids);
11551156
}
1157+
1158+
/**
1159+
* Indicates whether the stack requires bundling or not
1160+
*/
1161+
public get bundlingRequired() {
1162+
const bundlingStacks: string[] = this.node.tryGetContext(cxapi.BUNDLING_STACKS) ?? ['*'];
1163+
1164+
// bundlingStacks is of the form `Stage/Stack`, convert it to `Stage-Stack` before comparing to stack name
1165+
return bundlingStacks.some(pattern => minimatch(
1166+
this.stackName,
1167+
pattern.replace('/', '-'),
1168+
));
1169+
}
11561170
}
11571171

11581172
function merge(template: any, fragment: any): void {

packages/@aws-cdk/core/test/stack.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,38 @@ describe('stack', () => {
11911191
expect(new Stack(app, 'Stack', { analyticsReporting: true })._versionReportingEnabled).toBeDefined();
11921192

11931193
});
1194+
1195+
test('requires bundling when wildcard is specified in BUNDLING_STACKS', () => {
1196+
const app = new App();
1197+
const stack = new Stack(app, 'Stack');
1198+
stack.node.setContext(cxapi.BUNDLING_STACKS, ['*']);
1199+
expect(stack.bundlingRequired).toBe(true);
1200+
1201+
});
1202+
1203+
test('requires bundling when stackName has an exact match in BUNDLING_STACKS', () => {
1204+
const app = new App();
1205+
const stack = new Stack(app, 'Stack');
1206+
stack.node.setContext(cxapi.BUNDLING_STACKS, ['Stack']);
1207+
expect(stack.bundlingRequired).toBe(true);
1208+
1209+
});
1210+
1211+
test('does not require bundling when no item from BUILDING_STACKS matches stackName', () => {
1212+
const app = new App();
1213+
const stack = new Stack(app, 'Stack');
1214+
stack.node.setContext(cxapi.BUNDLING_STACKS, ['Stac']);
1215+
expect(stack.bundlingRequired).toBe(false);
1216+
1217+
});
1218+
1219+
test('does not require bundling when BUNDLING_STACKS is empty', () => {
1220+
const app = new App();
1221+
const stack = new Stack(app, 'Stack');
1222+
stack.node.setContext(cxapi.BUNDLING_STACKS, []);
1223+
expect(stack.bundlingRequired).toBe(false);
1224+
1225+
});
11941226
});
11951227

11961228
describe('regionalFact', () => {

0 commit comments

Comments
 (0)