Skip to content

Commit 5c258a3

Browse files
authored
feat(lambda): docker platform for architecture (#16858)
Add a `dockerPlatform` property in `Architecture` and use it to pass the correct `platform` when bundling in a container in `aws-lambda-nodejs`, `aws-lambda-go` and `aws-lambda-python`. Note that the SAM build images (`public.ecr.aws/sam/build-<runtime>`) are now multi-arch. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f8550d7 commit 5c258a3

File tree

15 files changed

+144
-44
lines changed

15 files changed

+144
-44
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd
106106
By default the following environment variables are set for you:
107107

108108
* `GOOS=linux`
109-
* `GOARCH=amd64`
109+
* `GOARCH`: based on the target architecture of the Lambda function
110110
* `GO111MODULE=on`
111111

112112
Use the `environment` prop to define additional environment variables when go runs:
@@ -124,7 +124,7 @@ new lambda.GoFunction(this, 'handler', {
124124

125125
## Local Bundling
126126

127-
If `Go` is installed locally and the version is >= `go1.11` then it will be used to bundle your code in your environment. Otherwise, bundling will happen in a [Lambda compatible Docker container](https://hub.docker.com/layers/lambci/lambda/build-go1.x/images/sha256-e14dab718ed0bb06b2243825c5993e494a6969de7c01754ad7e80dacfce9b0cf?context=explore).
127+
If `Go` is installed locally and the version is >= `go1.11` then it will be used to bundle your code in your environment. Otherwise, bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-go1.x) with the Docker platform based on the target architecture of the Lambda function.
128128

129129
For macOS the recommended approach is to install `Go` as Docker volume performance is really poor.
130130

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as os from 'os';
22
import * as path from 'path';
3-
import { AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda';
3+
import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda';
44
import * as cdk from '@aws-cdk/core';
55
import { BundlingOptions } from './types';
66
import { exec, findUp, getGoBuildVersion } from './util';
@@ -55,6 +55,11 @@ export interface BundlingProps extends BundlingOptions {
5555
* The runtime of the lambda function
5656
*/
5757
readonly runtime: Runtime;
58+
59+
/**
60+
* The system architecture of the lambda function
61+
*/
62+
readonly architecture: Architecture;
5863
}
5964

6065
/**
@@ -104,7 +109,7 @@ export class Bundling implements cdk.BundlingOptions {
104109
const environment = {
105110
CGO_ENABLED: cgoEnabled,
106111
GO111MODULE: 'on',
107-
GOARCH: 'amd64',
112+
GOARCH: props.architecture.dockerPlatform.split('/')[1],
108113
GOOS: 'linux',
109114
...props.environment,
110115
};
@@ -117,6 +122,7 @@ export class Bundling implements cdk.BundlingOptions {
117122
...props.buildArgs ?? {},
118123
IMAGE: Runtime.GO_1_X.bundlingImage.image, // always use the GO_1_X build image
119124
},
125+
platform: props.architecture.dockerPlatform,
120126
})
121127
: cdk.DockerImage.fromRegistry('dummy'); // Do not build if we don't need to
122128

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export class GoFunction extends lambda.Function {
104104
}
105105

106106
const runtime = props.runtime ?? lambda.Runtime.PROVIDED_AL2;
107+
const architecture = props.architecture ?? lambda.Architecture.X86_64;
107108

108109
super(scope, id, {
109110
...props,
@@ -112,6 +113,7 @@ export class GoFunction extends lambda.Function {
112113
...props.bundling ?? {},
113114
entry,
114115
runtime,
116+
architecture,
115117
moduleDir,
116118
}),
117119
handler: 'bootstrap', // setting name to bootstrap so that the 'provided' runtime can also be used

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import * as child_process from 'child_process';
22
import * as os from 'os';
33
import * as path from 'path';
4-
import { Code, Runtime } from '@aws-cdk/aws-lambda';
4+
import { Architecture, Code, Runtime } from '@aws-cdk/aws-lambda';
55
import { AssetHashType, DockerImage } from '@aws-cdk/core';
66
import { Bundling } from '../lib/bundling';
77
import * as util from '../lib/util';
88

9-
jest.spyOn(Code, 'fromAsset');
10-
const fromAssetMock = jest.spyOn(DockerImage, 'fromBuild');
119
let getGoBuildVersionMock = jest.spyOn(util, 'getGoBuildVersion');
1210

1311
beforeEach(() => {
1412
jest.clearAllMocks();
1513
jest.resetAllMocks();
1614
Bundling.clearRunsLocallyCache();
17-
getGoBuildVersionMock.mockReturnValue(true);
18-
fromAssetMock.mockReturnValue({
15+
16+
jest.spyOn(Code, 'fromAsset');
17+
18+
jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({
1919
image: 'built-image',
2020
cp: () => 'built-image',
2121
run: () => {},
2222
toJSON: () => 'build-image',
2323
});
24+
25+
getGoBuildVersionMock.mockReturnValue(true);
2426
});
2527

2628
const moduleDir = '/project/go.mod';
@@ -30,6 +32,7 @@ test('bundling', () => {
3032
Bundling.bundle({
3133
entry,
3234
runtime: Runtime.GO_1_X,
35+
architecture: Architecture.X86_64,
3336
moduleDir,
3437
forcedDockerBundling: true,
3538
environment: {
@@ -55,12 +58,20 @@ test('bundling', () => {
5558
],
5659
}),
5760
});
61+
62+
expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/aws-lambda-go\/lib$/), expect.objectContaining({
63+
buildArgs: expect.objectContaining({
64+
IMAGE: expect.stringMatching(/build-go/),
65+
}),
66+
platform: 'linux/amd64',
67+
}));
5868
});
5969

6070
test('bundling with file as entry', () => {
6171
Bundling.bundle({
6272
entry: '/project/main.go',
6373
runtime: Runtime.GO_1_X,
74+
architecture: Architecture.X86_64,
6475
moduleDir,
6576
});
6677

@@ -81,6 +92,7 @@ test('bundling with file in subdirectory as entry', () => {
8192
Bundling.bundle({
8293
entry: '/project/cmd/api/main.go',
8394
runtime: Runtime.GO_1_X,
95+
architecture: Architecture.X86_64,
8496
moduleDir,
8597
});
8698

@@ -101,6 +113,7 @@ test('bundling with file other than main.go in subdirectory as entry', () => {
101113
Bundling.bundle({
102114
entry: '/project/cmd/api/api.go',
103115
runtime: Runtime.GO_1_X,
116+
architecture: Architecture.X86_64,
104117
moduleDir,
105118
});
106119

@@ -122,6 +135,7 @@ test('go with Windows paths', () => {
122135
Bundling.bundle({
123136
entry: 'C:\\my-project\\cmd\\api',
124137
runtime: Runtime.GO_1_X,
138+
architecture: Architecture.X86_64,
125139
moduleDir: 'C:\\my-project\\go.mod',
126140
forcedDockerBundling: true,
127141
});
@@ -141,13 +155,14 @@ test('with Docker build args', () => {
141155
Bundling.bundle({
142156
entry,
143157
runtime: Runtime.GO_1_X,
158+
architecture: Architecture.X86_64,
144159
moduleDir,
145160
forcedDockerBundling: true,
146161
buildArgs: {
147162
HELLO: 'WORLD',
148163
},
149164
});
150-
expect(fromAssetMock).toHaveBeenCalledWith(expect.stringMatching(/lib$/), expect.objectContaining({
165+
expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/aws-lambda-go\/lib$/), expect.objectContaining({
151166
buildArgs: expect.objectContaining({
152167
HELLO: 'WORLD',
153168
}),
@@ -171,6 +186,7 @@ test('Local bundling', () => {
171186
KEY: 'value',
172187
},
173188
runtime: Runtime.PROVIDED_AL2,
189+
architecture: Architecture.X86_64,
174190
});
175191

176192
expect(bundler.local).toBeDefined();
@@ -188,7 +204,7 @@ test('Local bundling', () => {
188204
);
189205

190206
// Docker image is not built
191-
expect(fromAssetMock).not.toHaveBeenCalled();
207+
expect(DockerImage.fromBuild).not.toHaveBeenCalled();
192208
});
193209

194210
test('Incorrect go version', () => {
@@ -198,6 +214,7 @@ test('Incorrect go version', () => {
198214
entry,
199215
moduleDir,
200216
runtime: Runtime.PROVIDED_AL2,
217+
architecture: Architecture.X86_64,
201218
});
202219

203220
const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.GO_1_X.bundlingDockerImage });
@@ -211,6 +228,7 @@ test('Custom bundling docker image', () => {
211228
entry,
212229
moduleDir,
213230
runtime: Runtime.GO_1_X,
231+
architecture: Architecture.X86_64,
214232
forcedDockerBundling: true,
215233
dockerImage: DockerImage.fromRegistry('my-custom-image'),
216234
});
@@ -227,6 +245,7 @@ test('Go build flags can be passed', () => {
227245
Bundling.bundle({
228246
entry,
229247
runtime: Runtime.GO_1_X,
248+
architecture: Architecture.X86_64,
230249
moduleDir,
231250
environment: {
232251
KEY: 'value',
@@ -258,6 +277,7 @@ test('AssetHashType can be specified', () => {
258277
Bundling.bundle({
259278
entry,
260279
runtime: Runtime.GO_1_X,
280+
architecture: Architecture.X86_64,
261281
moduleDir,
262282
environment: {
263283
KEY: 'value',
@@ -291,6 +311,7 @@ test('with command hooks', () => {
291311
entry,
292312
moduleDir,
293313
runtime: Runtime.PROVIDED_AL2,
314+
architecture: Architecture.X86_64,
294315
commandHooks: {
295316
beforeBundling(inputDir: string, outputDir: string): string[] {
296317
return [

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ used by your function. Otherwise bundling will fail.
9999
## Local bundling
100100

101101
If `esbuild` is available it will be used to bundle your code in your environment. Otherwise,
102-
bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x).
102+
bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x)
103+
with the Docker platform based on the target architecture of the Lambda function.
103104

104105
For macOS the recommendend approach is to install `esbuild` as Docker volume performance is really poor.
105106

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as os from 'os';
22
import * as path from 'path';
3-
import { AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda';
3+
import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda';
44
import * as cdk from '@aws-cdk/core';
55
import { EsbuildInstallation } from './esbuild-installation';
66
import { PackageManager } from './package-manager';
@@ -28,6 +28,11 @@ export interface BundlingProps extends BundlingOptions {
2828
*/
2929
readonly runtime: Runtime;
3030

31+
/**
32+
* The system architecture of the lambda function
33+
*/
34+
readonly architecture: Architecture;
35+
3136
/**
3237
* Path to project root
3338
*/
@@ -99,6 +104,7 @@ export class Bundling implements cdk.BundlingOptions {
99104
IMAGE: props.runtime.bundlingImage.image,
100105
ESBUILD_VERSION: props.esbuildVersion ?? ESBUILD_MAJOR_VERSION,
101106
},
107+
platform: props.architecture.dockerPlatform,
102108
})
103109
: cdk.DockerImage.fromRegistry('dummy'); // Do not build if we don't need to
104110

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

Lines changed: 3 additions & 0 deletions
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 * as lambda from '@aws-cdk/aws-lambda';
4+
import { Architecture } from '@aws-cdk/aws-lambda';
45
import { Bundling } from './bundling';
56
import { PackageManager } from './package-manager';
67
import { BundlingOptions } from './types';
@@ -95,6 +96,7 @@ export class NodejsFunction extends lambda.Function {
9596
const entry = path.resolve(findEntry(id, props.entry));
9697
const handler = props.handler ?? 'handler';
9798
const runtime = props.runtime ?? lambda.Runtime.NODEJS_14_X;
99+
const architecture = props.architecture ?? Architecture.X86_64;
98100
const depsLockFilePath = findLockFile(props.depsLockFilePath);
99101
const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath);
100102

@@ -105,6 +107,7 @@ export class NodejsFunction extends lambda.Function {
105107
...props.bundling ?? {},
106108
entry,
107109
runtime,
110+
architecture,
108111
depsLockFilePath,
109112
projectRoot,
110113
}),

0 commit comments

Comments
 (0)