Skip to content

Commit 0b79b80

Browse files
feat(s3-deployment): add architecture property to BucketDeployment
Add an `architecture` property to `BucketDeploymentProps` so users can configure the Lambda function architecture (e.g. ARM_64/Graviton) for cost savings and sustainability improvements. - Pass architecture through to BucketDeploymentSingletonFunction - Include architecture in singleton UUID to isolate different configs - Declare compatibleArchitectures on AwsCliLayer for both x86_64/arm64 - Add unit tests for architecture passthrough, singleton isolation, default behavior, and combined memoryLimit scenarios - Update README with Lambda Architecture section Closes #29996 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c0b1d4e commit 0b79b80

File tree

5 files changed

+159
-5
lines changed

5 files changed

+159
-5
lines changed

packages/aws-cdk-lib/aws-s3-deployment/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,25 @@ resource handler.
373373
> NOTE: a new AWS Lambda handler will be created in your stack for each combination
374374
> of memory and storage size.
375375
376+
## Lambda Architecture
377+
378+
By default, the deployment handler Lambda function uses the `X86_64` architecture.
379+
You can use the `architecture` property to deploy using the `ARM_64` (Graviton)
380+
architecture for cost savings and sustainability improvements.
381+
382+
```ts
383+
declare const destinationBucket: s3.Bucket;
384+
385+
new s3deploy.BucketDeployment(this, 'DeployWithGraviton', {
386+
sources: [s3deploy.Source.asset('./website')],
387+
destinationBucket,
388+
architecture: lambda.Architecture.ARM_64,
389+
});
390+
```
391+
392+
> NOTE: a new AWS Lambda handler will be created in your stack for each unique
393+
> combination of memory, storage size, VPC, security groups, and architecture.
394+
376395
## JSON-Aware Source Processing
377396

378397
When using `Source.jsonData` with CDK Tokens (references to construct properties), you may need to enable the escaping option. This is particularly important when the referenced properties might contain special characters that require proper JSON escaping (like double quotes, line breaks, etc.).

packages/aws-cdk-lib/aws-s3-deployment/lib/bucket-deployment.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,13 @@ export interface BucketDeploymentProps {
305305
* not specified a dedicated security group will be created for this function.
306306
*/
307307
readonly securityGroups?: ec2.ISecurityGroup[];
308+
309+
/**
310+
* The system architectures compatible with the lambda function for this deployment.
311+
*
312+
* @default - the default Lambda architecture (X86_64)
313+
*/
314+
readonly architecture?: lambda.Architecture;
308315
}
309316

310317
/**
@@ -380,7 +387,8 @@ export class BucketDeployment extends Construct {
380387

381388
const mountPath = `/mnt${accessPointPath}`;
382389
const handler = new BucketDeploymentSingletonFunction(this, 'CustomResourceHandler', {
383-
uuid: this.renderSingletonUuid(props.memoryLimit, props.ephemeralStorageSize, props.vpc, props.securityGroups),
390+
uuid: this.renderSingletonUuid(props.memoryLimit, props.ephemeralStorageSize, props.vpc, props.securityGroups, props.architecture),
391+
architecture: props.architecture,
384392
layers: [new AwsCliLayer(this, 'AwsCliLayer')],
385393
environment: {
386394
...props.useEfs ? { MOUNT_PATH: mountPath } : undefined,
@@ -436,7 +444,7 @@ export class BucketDeployment extends Construct {
436444
},
437445
});
438446

439-
const crUniqueId = `CustomResource${this.renderUniqueId(props.memoryLimit, props.ephemeralStorageSize, props.vpc)}`;
447+
const crUniqueId = `CustomResource${this.renderUniqueId(props.memoryLimit, props.ephemeralStorageSize, props.vpc, undefined, props.architecture)}`;
440448
this.cr = new cdk.CustomResource(this, crUniqueId, {
441449
serviceToken: handler.functionArn,
442450
resourceType: 'Custom::CDKBucketDeployment',
@@ -602,7 +610,13 @@ export class BucketDeployment extends Construct {
602610
}
603611
}
604612

605-
private renderUniqueId(memoryLimit?: number, ephemeralStorageSize?: cdk.Size, vpc?: ec2.IVpc, securityGroups?: ec2.ISecurityGroup[]) {
613+
private renderUniqueId(
614+
memoryLimit?: number,
615+
ephemeralStorageSize?: cdk.Size,
616+
vpc?: ec2.IVpc,
617+
securityGroups?: ec2.ISecurityGroup[],
618+
architecture?: lambda.Architecture,
619+
) {
606620
let uuid = '';
607621

608622
// if the user specifes a custom memory limit, we define another singleton handler
@@ -646,13 +660,26 @@ export class BucketDeployment extends Construct {
646660
uuid += `-${sortedSecurityGroupIds}`;
647661
}
648662

663+
// if the user specifies an architecture, we define another singleton handler
664+
// with this configuration. otherwise, it won't be possible to use multiple
665+
// configurations since we have a singleton.
666+
if (architecture) {
667+
uuid += `-${architecture.name}`;
668+
}
669+
649670
return uuid;
650671
}
651672

652-
private renderSingletonUuid(memoryLimit?: number, ephemeralStorageSize?: cdk.Size, vpc?: ec2.IVpc, securityGroups?: ec2.ISecurityGroup[]) {
673+
private renderSingletonUuid(
674+
memoryLimit?: number,
675+
ephemeralStorageSize?: cdk.Size,
676+
vpc?: ec2.IVpc,
677+
securityGroups?: ec2.ISecurityGroup[],
678+
architecture?: lambda.Architecture,
679+
) {
653680
let uuid = '8693BB64-9689-44B6-9AAF-B0CC9EB8756C';
654681

655-
uuid += this.renderUniqueId(memoryLimit, ephemeralStorageSize, vpc, securityGroups);
682+
uuid += this.renderUniqueId(memoryLimit, ephemeralStorageSize, vpc, securityGroups, architecture);
656683

657684
return uuid;
658685
}

packages/aws-cdk-lib/aws-s3-deployment/test/bucket-deployment.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Match, Template } from '../../assertions';
55
import * as cloudfront from '../../aws-cloudfront';
66
import * as ec2 from '../../aws-ec2';
77
import * as iam from '../../aws-iam';
8+
import * as lambda from '../../aws-lambda';
89
import * as logs from '../../aws-logs';
910
import * as s3 from '../../aws-s3';
1011
import * as sns from '../../aws-sns';
@@ -1802,3 +1803,96 @@ test('outputObjectKeys default value is true', () => {
18021803
OutputObjectKeys: true,
18031804
});
18041805
});
1806+
1807+
test('architecture can be used to specify the Lambda architecture', () => {
1808+
// GIVEN
1809+
const stack = new cdk.Stack();
1810+
const bucket = new s3.Bucket(stack, 'Dest');
1811+
1812+
// WHEN
1813+
new s3deploy.BucketDeployment(stack, 'Deploy', {
1814+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1815+
destinationBucket: bucket,
1816+
architecture: lambda.Architecture.ARM_64,
1817+
});
1818+
1819+
// THEN
1820+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
1821+
Architectures: ['arm64'],
1822+
});
1823+
});
1824+
1825+
test('architecture creates separate singleton handlers for different architectures', () => {
1826+
// GIVEN
1827+
const stack = new cdk.Stack();
1828+
const bucket = new s3.Bucket(stack, 'Dest');
1829+
1830+
// WHEN
1831+
new s3deploy.BucketDeployment(stack, 'DeployX86-1', {
1832+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1833+
destinationBucket: bucket,
1834+
architecture: lambda.Architecture.X86_64,
1835+
});
1836+
1837+
new s3deploy.BucketDeployment(stack, 'DeployX86-2', {
1838+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1839+
destinationBucket: bucket,
1840+
architecture: lambda.Architecture.X86_64,
1841+
});
1842+
1843+
new s3deploy.BucketDeployment(stack, 'DeployARM', {
1844+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1845+
destinationBucket: bucket,
1846+
architecture: lambda.Architecture.ARM_64,
1847+
});
1848+
1849+
// THEN - two handlers (one for x86_64, one for arm64)
1850+
Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 2);
1851+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
1852+
Architectures: ['x86_64'],
1853+
});
1854+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
1855+
Architectures: ['arm64'],
1856+
});
1857+
});
1858+
1859+
test('default architecture does not set Architectures property', () => {
1860+
// GIVEN
1861+
const stack = new cdk.Stack();
1862+
const bucket = new s3.Bucket(stack, 'Dest');
1863+
1864+
// WHEN
1865+
new s3deploy.BucketDeployment(stack, 'Deploy', {
1866+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1867+
destinationBucket: bucket,
1868+
});
1869+
1870+
// THEN - Architectures is not set (Lambda defaults to x86_64)
1871+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
1872+
Architectures: Match.absent(),
1873+
});
1874+
});
1875+
1876+
test('architecture combined with memoryLimit creates separate singleton handlers', () => {
1877+
// GIVEN
1878+
const stack = new cdk.Stack();
1879+
const bucket = new s3.Bucket(stack, 'Dest');
1880+
1881+
// WHEN
1882+
new s3deploy.BucketDeployment(stack, 'DeployX86-256', {
1883+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1884+
destinationBucket: bucket,
1885+
architecture: lambda.Architecture.X86_64,
1886+
memoryLimit: 256,
1887+
});
1888+
1889+
new s3deploy.BucketDeployment(stack, 'DeployARM-256', {
1890+
sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))],
1891+
destinationBucket: bucket,
1892+
architecture: lambda.Architecture.ARM_64,
1893+
memoryLimit: 256,
1894+
});
1895+
1896+
// THEN - same memory but different architecture = different singletons
1897+
Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 2);
1898+
});

packages/aws-cdk-lib/lambda-layer-awscli/lib/awscli-layer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class AwsCliLayer extends lambda.LayerVersion {
1919
assetHash: FileSystem.fingerprint(LAYER_SOURCE_DIR),
2020
}),
2121
description: '/opt/awscli/aws',
22+
compatibleArchitectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64],
2223
});
2324
}
2425
}

packages/aws-cdk-lib/lambda-layer-awscli/test/awscli-layer.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,16 @@ test('synthesized to a layer version', () => {
1414
Description: '/opt/awscli/aws',
1515
});
1616
});
17+
18+
test('layer declares compatible architectures', () => {
19+
// GIVEN
20+
const stack = new Stack();
21+
22+
// WHEN
23+
new AwsCliLayer(stack, 'MyLayer');
24+
25+
// THEN
26+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::LayerVersion', {
27+
CompatibleArchitectures: ['x86_64', 'arm64'],
28+
});
29+
});

0 commit comments

Comments
 (0)