Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
},
});

project.projectBuild.postCompileTask.exec('cp src/layer.Dockerfile lib/layer.Dockerfile');
project.addPackageIgnore('.tmp');
// required to run integ tests
project.projectBuild.testTask.exec('npm ci', { cwd: join('example', 'lambda') });
Expand Down
16 changes: 16 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ If you are already using `NodejsFunction` construct, you should be able to just
> [!WARNING]
> LLRT is currently experimental and not fully compatible with Node.js. You should expect some trial and errors to use LLRT with your existing code.

If you want to upgrade the LLRT version, remove the `.tmp` directory, which contains the cache of LLRT binary fetched from GitHub (only applicable when you set `llrtVersion` to `latest` (default)).
### LLRT version

By default the latest version of LLRT is fetched from GitHub (unless you set `llrtVersion`). This LLRT binary is cached, in a `.tmp` directory or inside a docker image if you set `useLambdaLayer`.

If you want to upgrade the LLRT version you will need to remove the `.tmp` directory or remove the docker images (the images can be identified during the cdk build e.g. `docker.io/library/cdk-926fb3006b78cf5a5efe8e6485491147c08a50b8ada8272a6761a8ddc85b2aec:latest` and can be removed using `docker image rm <image>`).

### Setting platform=browser

Expand Down Expand Up @@ -84,5 +88,17 @@ const handler = new LlrtFunction(this, 'Handler', {
});
```

### LLRT Layer

By default, CDK Lambda LLRT bundles the LLRT bootstrap binary directly in your lambda function.
If you want to package the binary in a separate layer that can be shared amongst functions then set `llrtLayer` to `true`.

```ts
const handler = new LlrtFunction(this, 'Handler', {
entry: 'lambda/index.ts',
llrtLayer: true
});
```

## Examples
See [example](./example/README.md) for examples to use `LlrtFunction` construct.
16 changes: 16 additions & 0 deletions example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ class LlrtFunctionTestStack extends Stack {
const resource = api.root.addResource('ssr');
resource.addMethod('GET', new LambdaIntegration(handler));
}

{
// multiple lambda functions using a shared layer
new Array(3).fill(0).map((_, i) => {
const handler = new LlrtFunction(this, `LayerHandler${i}`, {
entry: '../example/lambda/s3.ts',
useLambdaLayer: true,
});
handler.addToRolePolicy(
new PolicyStatement({
actions: ['s3:ListAllMyBuckets'],
resources: ['*'],
})
);
})
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/layer.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Used to build assets when useLambdaLayer is true. Downloads the LLRT runtime
# specified by the URL argument and expands it to the /assets folder
FROM alpine

ARG URL=https://github.com/awslabs/llrt/releases/latest/download/llrt-lambda-x64.zip

RUN wget -O llrt_temp.zip ${URL} && \
unzip llrt_temp.zip -d /asset
76 changes: 62 additions & 14 deletions src/llrt-function.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { posix } from 'path';
import { CfnResource } from 'aws-cdk-lib';
import { Architecture, Runtime, RuntimeFamily } from 'aws-cdk-lib/aws-lambda';
import { CfnResource, Stack } from 'aws-cdk-lib';
import { Architecture, Code, ILayerVersion, LayerVersion, Runtime, RuntimeFamily } from 'aws-cdk-lib/aws-lambda';
import { ICommandHooks, NodejsFunction, NodejsFunctionProps, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

Expand Down Expand Up @@ -46,12 +46,26 @@ export interface LlrtFunctionProps extends NodejsFunctionProps {
* @default - If this option is not provided, the LLRT binary is downloaded from GitHub and cached in the .tmp directory.
*/
readonly llrtBinaryPath?: string;

/**
* If `true` then the LLRT runtime will be built in a layer that can be shared amongst
* other `LLrtFunction`s that utilize the same `llrtBinaryType`, `llrtVersion` and `architecture`.
*
* This feature cannot be used with `llrtBinaryPath` and if both are set a ValidationError will be thrown.
*
* @default - false
*/
readonly useLambdaLayer?: boolean;
}

export class LlrtFunction extends NodejsFunction {
constructor(scope: Construct, id: string, props: LlrtFunctionProps) {
if (props.useLambdaLayer && props.llrtBinaryPath) {
throw new Error("useLambdaLayer not supported with llrtBinaryPath");
}

const version = props.llrtVersion ?? 'latest';
const arch = props.architecture == Architecture.ARM_64 ? 'arm64' : 'x64';
const arch = props.architecture?.name == Architecture.ARM_64.name ? 'arm64' : 'x64';
const binaryType = props.llrtBinaryType ?? LlrtBinaryType.STANDARD;

let binaryName: string;
Expand Down Expand Up @@ -117,18 +131,26 @@ export class LlrtFunction extends NodejsFunction {
const cacheDir = `.tmp/llrt/${version}/${arch}/${binaryType}`;

const { commandHooks: originalCommandHooks, ...otherBundlingProps } = props.bundling ?? {};
const afterBundlingCommandHook: ICommandHooks['afterBundling'] = (i, o) => !props.llrtBinaryPath ? [
const afterBundlingCommandHook: ICommandHooks['afterBundling'] = (i, o) => {
// If useLambdaLayer then we'll package the llrt binary with docker later
if (props.useLambdaLayer) return [];
// Copy llrt binary from llrtBinaryPath
if (props.llrtBinaryPath) return [
`cp ${posix.join(i, props.llrtBinaryPath)} ${posix.join(o, 'bootstrap')}`,
];
// Download llrt binary from GitHub release and cache it
`if [ ! -e ${posix.join(i, cacheDir, 'bootstrap')} ]; then
mkdir -p ${posix.join(i, cacheDir)}
cd ${posix.join(i, cacheDir)}
curl -L -o llrt_temp.zip ${binaryUrl}
unzip llrt_temp.zip
rm -rf llrt_temp.zip
cd -
fi`,
`cp ${posix.join(i, cacheDir, 'bootstrap')} ${o}`,
] : [`cp ${posix.join(i, props.llrtBinaryPath)} ${posix.join(o, 'bootstrap')}`];
return [
`if [ ! -e ${posix.join(i, cacheDir, 'bootstrap')} ]; then
mkdir -p ${posix.join(i, cacheDir)}
cd ${posix.join(i, cacheDir)}
curl -L -o llrt_temp.zip ${binaryUrl}
unzip llrt_temp.zip
rm -rf llrt_temp.zip
cd -
fi`,
`cp ${posix.join(i, cacheDir, 'bootstrap')} ${o}`,
];
}

super(scope, id, {
// set this to remove an unnecessary environment variable.
Expand All @@ -154,6 +176,32 @@ export class LlrtFunction extends NodejsFunction {
},
});

if (props.useLambdaLayer) {
this.ensureLayer(binaryUrl, binaryName, version);
}

(this.node.defaultChild as CfnResource).addPropertyOverride('Runtime', 'provided.al2023');
}

private ensureLayer(binaryUrl: string, binaryName: string, version: string) {
const id = `${binaryName.replace("lambda", "layer")}-${version}`;

let layer = Stack.of(this).node.tryFindChild(id) as ILayerVersion;

if (!layer) {
layer = new LayerVersion(Stack.of(this), id, {
code: Code.fromDockerBuild(__dirname, {
buildArgs: {
URL: binaryUrl,
},
file: "layer.Dockerfile",
}),
compatibleArchitectures: [
this.architecture,
],
});
}

this.addLayers(layer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
}
}
},
"286c6bf5a2eda8d84fa67fd1004371db925b13e33838d7fc46026ffaad295c90": {
"5ab45ed51a21459ad67b9118a974fd3fbff643528f51f4fefc0ea1d472a8ec23": {
"displayName": "LlrtFunctionIntegTestDefaultTestDeployAssertF3D2B976 Template",
"source": {
"path": "LlrtFunctionIntegTestDefaultTestDeployAssertF3D2B976.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region-734aaede": {
"current_account-current_region-d4e8dd96": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "286c6bf5a2eda8d84fa67fd1004371db925b13e33838d7fc46026ffaad295c90.json",
"objectKey": "5ab45ed51a21459ad67b9118a974fd3fbff643528f51f4fefc0ea1d472a8ec23.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading