Skip to content

Commit c4a9ee6

Browse files
authored
Support building extensions. (#3)
Upload them as layers, and assign them to functions. Signed-off-by: David Calavera <david.calavera@gmail.com> Signed-off-by: David Calavera <david.calavera@gmail.com>
1 parent b6d4207 commit c4a9ee6

10 files changed

Lines changed: 585 additions & 38 deletions

File tree

API.md

Lines changed: 411 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ This library provides constructs for Rust Lambda functions built with Cargo Lamb
55
To use this module you will either need to have [Cargo Lambda installed](https://www.cargo-lambda.info/guide/installation.html) (`0.12.0` or later), or `Docker` installed.
66
See [Local Bundling](#local-bundling)/[Docker Bundling](#docker-bundling) for more information.
77

8-
98
## Rust Function
109

1110
Define a `RustFunction`:
@@ -27,10 +26,29 @@ lamda-app
2726
└── main.rs
2827
```
2928

30-
## Runtime
29+
### Runtime
3130

3231
The `RustFunction` uses the `PROVIDED_AL2` runtime.
3332

33+
## Rust Extension
34+
35+
Define a `RustExtension` that get's deployed as a layer to use it with any other function later.
36+
37+
```ts
38+
import { RustExtension, RustFunction } from 'cargo-lambda-cdk';
39+
40+
const extensionLayer = new RustExtension(this, 'extension-package-name', {
41+
packageDir: 'path/to/package/directory/with/Cargo.toml',
42+
});
43+
44+
new RustFunction(this, 'function-package-name', {
45+
packageDir: 'path/to/package/directory/with/Cargo.toml',
46+
layers: [
47+
extensionLayer
48+
],
49+
});
50+
```
51+
3452
## Environment
3553

3654
Use the `environment` prop to define additional environment variables when Cargo Lambda runs:
@@ -61,7 +79,7 @@ Use the `bundling.dockerImage` prop to use a custom bundling image:
6179
```ts
6280
import { RustFunction } from 'cargo-lambda-cdk';
6381

64-
new lambda.RustFunction(this, 'package-name', {
82+
new RustFunction(this, 'package-name', {
6583
packageDir: 'path/to/package/directory/with/Cargo.toml',
6684
bundling: {
6785
dockerImage: DockerImage.fromBuild('/path/to/Dockerfile'),

src/bundling.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export interface BundlingProps extends BundlingOptions {
3939
* The name of the binary to build, in case that's different than the package's name.
4040
*/
4141
readonly binaryName?: string;
42+
43+
/**
44+
* Whether the code to compile is a Lambda Extension or not.
45+
*/
46+
readonly lambdaExtension?: boolean;
4247
}
4348

4449
interface CommandOptions {
@@ -48,6 +53,7 @@ interface CommandOptions {
4853
readonly binaryName?: string;
4954
readonly osPlatform: NodeJS.Platform;
5055
readonly architecture?: Architecture;
56+
readonly lambdaExtension?: boolean;
5157
}
5258

5359
/**
@@ -103,6 +109,7 @@ export class Bundling implements cdk.BundlingOptions {
103109
packageName: props.packageName,
104110
binaryName: props.binaryName,
105111
architecture: props.architecture,
112+
lambdaExtension: props.lambdaExtension,
106113
});
107114

108115
this.command = ['bash', '-c', bundlingCommand];
@@ -118,6 +125,7 @@ export class Bundling implements cdk.BundlingOptions {
118125
packageName: props.packageName,
119126
binaryName: props.binaryName,
120127
architecture: props.architecture,
128+
lambdaExtension: props.lambdaExtension,
121129
});
122130
};
123131

@@ -162,19 +170,26 @@ export class Bundling implements cdk.BundlingOptions {
162170
props.outputDir,
163171
];
164172

173+
if (props.lambdaExtension) {
174+
buildBinary.push('--extension');
175+
}
176+
165177
if (props.architecture) {
166178
const targetFlag = props.architecture.name == Architecture.ARM_64.name ? '--arm64' : '--x86-64';
167179
buildBinary.push(targetFlag);
168180
}
169181

182+
let flattenPackage = props.packageName;
183+
170184
if (props.binaryName) {
171-
buildBinary.push('--flatten');
172-
buildBinary.push(props.binaryName);
173185
buildBinary.push('--bin');
174186
buildBinary.push(props.binaryName);
175-
} else if (props.packageName) {
187+
flattenPackage = props.binaryName;
188+
}
189+
190+
if (!props.lambdaExtension && flattenPackage) {
176191
buildBinary.push('--flatten');
177-
buildBinary.push(props.packageName);
192+
buildBinary.push(flattenPackage);
178193
}
179194

180195
return chain([

src/cargo.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { existsSync } from 'fs';
2+
import { join, parse } from 'path';
3+
4+
export function getCargoManifestPath(manifestPath: string): string {
5+
// const manifestPathProp = props.manifestPath ?? 'Cargo.toml';
6+
const parsedManifestPath = parse(manifestPath);
7+
let manifestPathResult: string;
8+
9+
if (parsedManifestPath.base && parsedManifestPath.ext && parsedManifestPath.base === 'Cargo.toml') {
10+
if (!existsSync(manifestPath)) {
11+
throw new Error('Cargo.toml doesn\'t exist');
12+
}
13+
manifestPathResult = manifestPath;
14+
} else if (parsedManifestPath.base && parsedManifestPath.ext && parsedManifestPath.base != 'Cargo.toml') {
15+
throw new Error('manifestPath is specifying a file that is not Cargo.toml');
16+
} else if (!existsSync(join(manifestPath, 'Cargo.toml'))) {
17+
throw new Error(`Cargo.toml file at ${manifestPath} doesn't exist`);
18+
} else {
19+
manifestPathResult = join(manifestPath, 'Cargo.toml');
20+
}
21+
22+
return manifestPathResult;
23+
}

src/extension.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as lambda from 'aws-cdk-lib/aws-lambda';
2+
import { Construct } from 'constructs';
3+
import { Bundling } from './bundling';
4+
import { getCargoManifestPath } from './cargo';
5+
import { BundlingOptions } from './types';
6+
7+
8+
export interface RustExtensionProps extends lambda.LayerVersionOptions {
9+
/**
10+
* The name of the binary to build, in case that's different than the package's name.
11+
*/
12+
readonly binaryName?: string;
13+
14+
/**
15+
* Path to a directory containing your Cargo.toml file, or to your Cargo.toml directly.
16+
*
17+
* This will accept either a directory path containing a `Cargo.toml` file
18+
* or a filepath to your `Cargo.toml` file (i.e. `path/to/Cargo.toml`).
19+
*
20+
* @default - check the current directory for a `Cargo.toml` file, and throws
21+
* an error if the file doesn't exist.
22+
*/
23+
readonly manifestPath?: string;
24+
25+
/**
26+
* Bundling options
27+
*
28+
* @default - use default bundling options
29+
*/
30+
readonly bundling?: BundlingOptions;
31+
}
32+
33+
/**
34+
* A Lambda extension written in Rust
35+
*/
36+
export class RustExtension extends lambda.LayerVersion {
37+
constructor(scope: Construct, packageName: string, props?: RustExtensionProps) {
38+
const manifestPath = getCargoManifestPath(props?.manifestPath ?? 'Cargo.toml');
39+
const bundling = props?.bundling ?? {};
40+
41+
super(scope, packageName, {
42+
...props,
43+
code: Bundling.bundle({
44+
...bundling,
45+
packageName,
46+
manifestPath,
47+
binaryName: props?.binaryName,
48+
lambdaExtension: true,
49+
}),
50+
});
51+
}
52+
}

src/function.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import * as fs from 'fs';
2-
import * as path from 'path';
31
import * as lambda from 'aws-cdk-lib/aws-lambda';
42
import { Construct } from 'constructs';
53
import { Bundling } from './bundling';
4+
import { getCargoManifestPath } from './cargo';
65
import { BundlingOptions } from './types';
76

87
export { cargoLambdaVersion } from './bundling';
@@ -40,27 +39,10 @@ export interface RustFunctionProps extends lambda.FunctionOptions {
4039
*/
4140
export class RustFunction extends lambda.Function {
4241
constructor(scope: Construct, packageName: string, props?: RustFunctionProps) {
43-
// Find the package root
44-
props = props ?? {};
45-
const manifestPathProp = props.manifestPath ?? 'Cargo.toml';
46-
const parsedManifestPath = path.parse(manifestPathProp);
47-
let manifestPath: string;
48-
49-
if (parsedManifestPath.base && parsedManifestPath.ext && parsedManifestPath.base === 'Cargo.toml') {
50-
if (!fs.existsSync(manifestPathProp)) {
51-
throw new Error('Cargo.toml doesn\'t exist');
52-
}
53-
manifestPath = manifestPathProp;
54-
} else if (parsedManifestPath.base && parsedManifestPath.ext && parsedManifestPath.base != 'Cargo.toml') {
55-
throw new Error('manifestPath is specifying a file that is not Cargo.toml');
56-
} else if (!fs.existsSync(path.join(manifestPathProp, 'Cargo.toml'))) {
57-
throw new Error(`Cargo.toml file at ${manifestPathProp} doesn't exist`);
58-
} else {
59-
manifestPath = path.join(manifestPathProp, 'Cargo.toml');
60-
}
42+
const manifestPath = getCargoManifestPath(props?.manifestPath ?? 'Cargo.toml');
6143

6244
const runtime = lambda.Runtime.PROVIDED_AL2;
63-
const bundling = props.bundling ?? {};
45+
const bundling = props?.bundling ?? {};
6446

6547
super(scope, packageName, {
6648
...props,
@@ -69,7 +51,7 @@ export class RustFunction extends lambda.Function {
6951
...bundling,
7052
packageName,
7153
manifestPath,
72-
binaryName: props.binaryName,
54+
binaryName: props?.binaryName,
7355
}),
7456
handler: 'bootstrap',
7557
});

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './extension';
12
export * from './function';
23
export * from './types';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
2-
println!("Hello, world!");
2+
println!("Hello, binary1!");
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
2-
println!("Hello, world!");
2+
println!("Hello, binary2!");
33
}

test/integration.test.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import * as path from 'path';
22
import { env } from 'process';
33
import * as cdk from 'aws-cdk-lib/core';
4-
import { RustFunction, cargoLambdaVersion } from '../lib/index';
4+
import { RustExtension, RustFunction, cargoLambdaVersion } from '../lib/index';
55

6-
describe('CargoLambda.RustFunction', () => {
7-
8-
const app = new cdk.App();
9-
const stack = new cdk.Stack(app);
10-
const forcedDockerBundling = !!env.FORCE_DOCKER_RUN || !cargoLambdaVersion();
6+
const forcedDockerBundling = !!env.FORCE_DOCKER_RUN || !cargoLambdaVersion();
117

8+
describe('CargoLambda.RustFunction', () => {
129
describe('With single package Cargo project', () => {
10+
const app = new cdk.App();
11+
const stack = new cdk.Stack(app);
1312
const testSource = path.join(__dirname, 'fixtures/single-package');
1413

1514
new RustFunction(stack, 'single-package', {
@@ -25,6 +24,8 @@ describe('CargoLambda.RustFunction', () => {
2524
});
2625

2726
describe('With a Cargo workspace', () => {
27+
const app = new cdk.App();
28+
const stack = new cdk.Stack(app);
2829
const testSource = path.join(__dirname, 'fixtures/cargo-workspace');
2930

3031
new RustFunction(stack, 'binary1', {
@@ -35,7 +36,6 @@ describe('CargoLambda.RustFunction', () => {
3536
},
3637
});
3738

38-
3939
new RustFunction(stack, 'binary2', {
4040
manifestPath: path.join(testSource, 'binary2'),
4141
binaryName: 'binary2',
@@ -48,4 +48,49 @@ describe('CargoLambda.RustFunction', () => {
4848
app.synth();
4949
});
5050
});
51+
});
52+
53+
describe('CargoLambda.RustExtension', () => {
54+
describe('With single package Cargo project', () => {
55+
const app = new cdk.App();
56+
const stack = new cdk.Stack(app);
57+
const testSource = path.join(__dirname, 'fixtures/single-package');
58+
59+
new RustExtension(stack, 'single-package', {
60+
manifestPath: testSource,
61+
bundling: {
62+
forcedDockerBundling,
63+
},
64+
});
65+
66+
test('bundle extension', () => {
67+
app.synth();
68+
});
69+
});
70+
71+
describe('With a Cargo workspace', () => {
72+
const app = new cdk.App();
73+
const stack = new cdk.Stack(app);
74+
const testSource = path.join(__dirname, 'fixtures/cargo-workspace');
75+
76+
new RustExtension(stack, 'binary1', {
77+
manifestPath: path.join(testSource, 'binary1'),
78+
binaryName: 'binary1',
79+
bundling: {
80+
forcedDockerBundling,
81+
},
82+
});
83+
84+
new RustExtension(stack, 'binary2', {
85+
manifestPath: path.join(testSource, 'binary2'),
86+
binaryName: 'binary2',
87+
bundling: {
88+
forcedDockerBundling,
89+
},
90+
});
91+
92+
test('bundle extension', () => {
93+
app.synth();
94+
});
95+
});
5196
});

0 commit comments

Comments
 (0)