diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index dc40e77e1363d..f12afc0c3d809 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -81,6 +81,16 @@ new lambda.DockerImageFunction(this, 'ECRFunction', { The props for these docker image resources allow overriding the image's `CMD`, `ENTRYPOINT`, and `WORKDIR` configurations as well as choosing a specific tag or digest. See their docs for more information. +To deploy a `DockerImageFunction` on Lambda `arm64` architecture, specify `Architecture.ARM_64` in `architecture`. +This will bundle docker image assets for `arm64` architecture with `--platform linux/arm64` even if build within an `x86_64` host. + +```ts +new DockerImageFunction(this, 'AssetFunction', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler')), + architecture: Architecture.ARM_64, +}); +``` + ## Execution Role Lambda functions assume an IAM role during execution. In CDK by default, Lambda diff --git a/packages/@aws-cdk/aws-lambda/lib/image-function.ts b/packages/@aws-cdk/aws-lambda/lib/image-function.ts index a53f303c88b78..5000bae85e2c2 100644 --- a/packages/@aws-cdk/aws-lambda/lib/image-function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/image-function.ts @@ -1,5 +1,7 @@ import * as ecr from '@aws-cdk/aws-ecr'; +import { Platform } from '@aws-cdk/aws-ecr-assets'; import { Construct } from 'constructs'; +import { Architecture } from './architecture'; import { AssetImageCode, AssetImageCodeProps, EcrImageCode, EcrImageCodeProps, Code } from './code'; import { Function, FunctionOptions } from './function'; import { Handler } from './handler'; @@ -41,8 +43,12 @@ export abstract class DockerImageCode { */ public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}): DockerImageCode { return { - _bind() { - return new AssetImageCode(directory, props); + _bind(architecture?: Architecture) { + return new AssetImageCode(directory, { + // determine the platform from `architecture`. + ...architecture?.dockerPlatform ? { platform: Platform.custom(architecture.dockerPlatform) } : {}, + ...props, + }); }, }; } @@ -51,7 +57,7 @@ export abstract class DockerImageCode { * Produce a `Code` instance from this `DockerImageCode`. * @internal */ - public abstract _bind(): Code; + public abstract _bind(architecture?: Architecture): Code; } /** @@ -63,7 +69,7 @@ export class DockerImageFunction extends Function { ...props, handler: Handler.FROM_IMAGE, runtime: Runtime.FROM_IMAGE, - code: props.code._bind(), + code: props.code._bind(props.architecture), }); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/Dockerfile b/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/Dockerfile new file mode 100644 index 0000000000000..3da16ef980217 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/Dockerfile @@ -0,0 +1,6 @@ +FROM public.ecr.aws/lambda/python:latest + +ARG FUNCTION_DIR="/var/task" +COPY index.py ${FUNCTION_DIR} + +CMD [ "index.handler" ] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/index.py b/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/index.py new file mode 100644 index 0000000000000..fe4f05995f6bd --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/docker-arm64-handler/index.py @@ -0,0 +1,6 @@ +import json, platform +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps( f'Hello CDK from Lambda({platform.platform()})!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker-arm64.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker-arm64.ts new file mode 100644 index 0000000000000..b567e9b5733fc --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker-arm64.ts @@ -0,0 +1,19 @@ +import * as path from 'path'; +import { App, Stack } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import { Architecture, DockerImageCode, DockerImageFunction } from '../lib'; + +const app = new App(); + +const stack = new Stack(app, 'lambda-ecr-docker-arm64'); + +new DockerImageFunction(stack, 'MyLambda', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler')), + architecture: Architecture.ARM_64, +}); + +new integ.IntegTest(app, 'lambda-docker-arm64', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-platform.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-platform.test.ts new file mode 100644 index 0000000000000..1b38bd7ea2c8a --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda-platform.test.ts @@ -0,0 +1,44 @@ +import * as path from 'path'; +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import { Architecture, DockerImageCode, DockerImageFunction } from '../lib'; + +describe('lambda platform', () => { + test('can choose lambda architecture arm64', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + + // WHEN + new DockerImageFunction(stack, 'Lambda', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler')), + architecture: Architecture.ARM_64, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + Architectures: [ + 'arm64', + ], + }); + }); + + test('can choose lambda architecture x86_64', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + + // WHEN + new DockerImageFunction(stack, 'Lambda', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler')), + architecture: Architecture.X86_64, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + Architectures: [ + 'x86_64', + ], + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.json new file mode 100644 index 0000000000000..73b08e0ca0c6f --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.json @@ -0,0 +1,11 @@ +{ + "version": "20.0.0", + "testCases": { + "lambda-docker-arm64/DefaultTest": { + "stacks": [ + "lambda-ecr-docker-arm64" + ], + "assertionStack": "lambdadockerarm64DefaultTestDeployAssert07D408EF" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.lambda.docker-arm64.expected.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.lambda.docker-arm64.expected.json new file mode 100644 index 0000000000000..448d6b6011df9 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/integ.lambda.docker-arm64.expected.json @@ -0,0 +1,74 @@ +{ + "Resources": { + "MyLambdaServiceRole4539ECB6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyLambdaCCE802FB": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:166017f6f6f219800dee266c149e208fe18dea1788d822b1783afbc008c25db7" + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "Architectures": [ + "arm64" + ], + "PackageType": "Image" + }, + "DependsOn": [ + "MyLambdaServiceRole4539ECB6" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.assets.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.assets.json new file mode 100644 index 0000000000000..723719a1247a6 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.assets.json @@ -0,0 +1,33 @@ +{ + "version": "20.0.0", + "files": { + "e57a612d901fd500f4eae1beabe146b3d7a4f81dd10a81199b9c884d1e227a1e": { + "source": { + "path": "lambda-ecr-docker-arm64.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e57a612d901fd500f4eae1beabe146b3d7a4f81dd10a81199b9c884d1e227a1e.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": { + "027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040": { + "source": { + "directory": "asset.027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "platform": "linux/arm64" + }, + "destinations": { + "current_account-current_region": { + "repositoryName": "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}", + "imageTag": "027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.template.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.template.json new file mode 100644 index 0000000000000..20f5a2745bf18 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambda-ecr-docker-arm64.template.json @@ -0,0 +1,74 @@ +{ + "Resources": { + "MyLambdaServiceRole4539ECB6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyLambdaCCE802FB": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040" + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "Architectures": [ + "arm64" + ], + "PackageType": "Image" + }, + "DependsOn": [ + "MyLambdaServiceRole4539ECB6" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambdadockerarm64DefaultTestDeployAssert07D408EF.template.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambdadockerarm64DefaultTestDeployAssert07D408EF.template.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/lambdadockerarm64DefaultTestDeployAssert07D408EF.template.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..a910ee19ed94c --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/manifest.json @@ -0,0 +1,57 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "lambda-ecr-docker-arm64": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "lambda-ecr-docker-arm64.template.json", + "validateOnSynth": false + }, + "metadata": { + "/lambda-ecr-docker-arm64": [ + { + "type": "aws:cdk:asset", + "data": { + "repositoryName": "aws-cdk/assets", + "imageTag": "027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "id": "027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "packaging": "container-image", + "path": "asset.027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "sourceHash": "027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040", + "platform": "linux/arm64" + } + } + ], + "/lambda-ecr-docker-arm64/MyLambda/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaServiceRole4539ECB6" + } + ], + "/lambda-ecr-docker-arm64/MyLambda/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaCCE802FB" + } + ] + }, + "displayName": "lambda-ecr-docker-arm64" + }, + "lambdadockerarm64DefaultTestDeployAssert07D408EF": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "lambdadockerarm64DefaultTestDeployAssert07D408EF.template.json", + "validateOnSynth": false + }, + "displayName": "lambda-docker-arm64/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/tree.json new file mode 100644 index 0000000000000..a01ab92a26150 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/lambda.docker-arm64.integ.snapshot/tree.json @@ -0,0 +1,196 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "lambda-ecr-docker-arm64": { + "id": "lambda-ecr-docker-arm64", + "path": "lambda-ecr-docker-arm64", + "children": { + "MyLambda": { + "id": "MyLambda", + "path": "lambda-ecr-docker-arm64/MyLambda", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "lambda-ecr-docker-arm64/MyLambda/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "lambda-ecr-docker-arm64/MyLambda/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "AssetImage": { + "id": "AssetImage", + "path": "lambda-ecr-docker-arm64/MyLambda/AssetImage", + "children": { + "Staging": { + "id": "Staging", + "path": "lambda-ecr-docker-arm64/MyLambda/AssetImage/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "lambda-ecr-docker-arm64/MyLambda/AssetImage/Repository", + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr.RepositoryBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr-assets.DockerImageAsset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "lambda-ecr-docker-arm64/MyLambda/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "imageUri": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:027b9b499ce9e488d4c3cfa41abdbdc6afe203989a5bd77258f471da03f3f040" + ] + ] + } + }, + "role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "architectures": [ + "arm64" + ], + "packageType": "Image" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.DockerImageFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "lambda-docker-arm64": { + "id": "lambda-docker-arm64", + "path": "lambda-docker-arm64", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "lambda-docker-arm64/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "lambda-docker-arm64/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "lambda-docker-arm64/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file