Skip to content

Commit 5092de1

Browse files
authored
Lambda Extensions (#376)
* Create new Runtime Client crate Extract the HTTP client logic into a new crate. This allows other crates to share the same logic to interact with the Runtime API. Signed-off-by: David Calavera <[email protected]> * Add Lambda Extension crate This new crate encapsulates the logic to create Lambda Extensions. It includes reference examples. Signed-off-by: David Calavera <[email protected]> * Rename client crate to avoid confusion. Once upon a time, there was a crate called `lambda-runtime-client`. We don't want people to mistake this new crate with the old one. Signed-off-by: David Calavera <[email protected]> * Modify user API. - Remove async_trait dependency. - Use a similar handler api than the runtime. - Allow to register the extension for only certain events. Signed-off-by: David Calavera <[email protected]> * Cleanup user API. - Remove extension ID from call signature. - Make extension mutable. - Add example showing how to implement an extension with a struct. Signed-off-by: David Calavera <[email protected]> * Add documentation and cleanup code. Signed-off-by: David Calavera <[email protected]> * Make custom trait example more useful. Signed-off-by: David Calavera <[email protected]> * Fix formatting. Signed-off-by: David Calavera <[email protected]> * Remove unused dependencies. Signed-off-by: David Calavera <[email protected]> * Add README files for the new crates. Signed-off-by: David Calavera <[email protected]> * Update readme files. Signed-off-by: David Calavera <[email protected]> * Fix extension name Cleanup the path from the executable when it takes the name from arg[0]. Signed-off-by: David Calavera <[email protected]>
1 parent 6b1b3f0 commit 5092de1

File tree

16 files changed

+986
-405
lines changed

16 files changed

+986
-405
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[workspace]
22
members = [
33
"lambda-http",
4-
"lambda-runtime"
4+
"lambda-runtime-api-client",
5+
"lambda-runtime",
6+
"lambda-extension"
57
]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor
66

77
- [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust.
88
- [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust.
9+
- [![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust.
10+
- [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API.
11+
912

1013
## Example function
1114

lambda-extension/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "lambda_extension"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["David Calavera <[email protected]>"]
6+
description = "AWS Lambda Extension API"
7+
license = "Apache-2.0"
8+
repository = "https://github.com/awslabs/aws-lambda-rust-runtime"
9+
categories = ["web-programming::http-server"]
10+
keywords = ["AWS", "Lambda", "API"]
11+
readme = "README.md"
12+
13+
[dependencies]
14+
tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] }
15+
hyper = { version = "0.14", features = ["http1", "client", "server", "stream", "runtime"] }
16+
serde = { version = "1", features = ["derive"] }
17+
serde_json = "^1"
18+
bytes = "1.0"
19+
http = "0.2"
20+
async-stream = "0.3"
21+
tracing = { version = "0.1", features = ["log"] }
22+
tower-service = "0.3"
23+
tokio-stream = "0.1.2"
24+
lambda_runtime_api_client = { version = "0.4", path = "../lambda-runtime-api-client" }
25+
26+
[dev-dependencies]
27+
tracing-subscriber = "0.3"
28+
once_cell = "1.4.0"
29+
simple_logger = "1.6.0"
30+
log = "^0.4"
31+
simple-error = "0.2"

lambda-extension/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Runtime Extensions for AWS Lambda in Rust
2+
3+
[![Docs](https://docs.rs/lambda_extension/badge.svg)](https://docs.rs/lambda_extension)
4+
5+
**`lambda-extension`** is a library that makes it easy to write [AWS Lambda Runtime Extensions](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html) in Rust.
6+
7+
## Example extension
8+
9+
The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch.
10+
11+
```rust,no_run
12+
use lambda_extension::{extension_fn, Error, NextEvent};
13+
use log::LevelFilter;
14+
use simple_logger::SimpleLogger;
15+
use tracing::info;
16+
17+
async fn log_extension(event: NextEvent) -> Result<(), Error> {
18+
match event {
19+
NextEvent::Shutdown(event) => {
20+
info!("{}", event);
21+
}
22+
NextEvent::Invoke(event) => {
23+
info!("{}", event);
24+
}
25+
}
26+
Ok(())
27+
}
28+
29+
#[tokio::main]
30+
async fn main() -> Result<(), Error> {
31+
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();
32+
33+
let func = extension_fn(log_extension);
34+
lambda_extension::run(func).await
35+
}
36+
```
37+
38+
## Deployment
39+
40+
Lambda extensions can be added to your functions either using [Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#using-extensions-config), or adding them to [containers images](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#invocation-extensions-images).
41+
42+
Regardless of how you deploy them, the extensions MUST be compiled against the same architecture that your lambda functions runs on.
43+
44+
### Building extensions
45+
46+
Once you've decided which target you'll use, you can install it by running the next `rustup` command:
47+
48+
```bash
49+
$ rustup target add x86_64-unknown-linux-musl
50+
```
51+
52+
Then, you can compile the extension against that target:
53+
54+
```bash
55+
$ cargo build -p lambda_extension --example basic --release --target x86_64-unknown-linux-musl
56+
```
57+
58+
This previous command will generate a binary file in `target/x86_64-unknown-linux-musl/release/examples` called `basic`. When the extension is registered with the [Runtime Extensions API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-extensions-api-reg), that's the name that the extension will be registered with. If you want to register the extension with a different name, you only have to rename this binary file and deploy it with the new name.

lambda-extension/examples/basic.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use lambda_extension::{extension_fn, Error, NextEvent};
2+
use log::LevelFilter;
3+
use simple_logger::SimpleLogger;
4+
5+
async fn my_extension(event: NextEvent) -> Result<(), Error> {
6+
match event {
7+
NextEvent::Shutdown(_e) => {
8+
// do something with the shutdown event
9+
}
10+
NextEvent::Invoke(_e) => {
11+
// do something with the invoke event
12+
}
13+
}
14+
Ok(())
15+
}
16+
17+
#[tokio::main]
18+
async fn main() -> Result<(), Error> {
19+
// required to enable CloudWatch error logging by the runtime
20+
// can be replaced with any other method of initializing `log`
21+
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();
22+
23+
let func = extension_fn(my_extension);
24+
lambda_extension::run(func).await
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use lambda_extension::{extension_fn, Error, NextEvent, Runtime};
2+
use log::LevelFilter;
3+
use simple_logger::SimpleLogger;
4+
5+
async fn my_extension(event: NextEvent) -> Result<(), Error> {
6+
match event {
7+
NextEvent::Shutdown(_e) => {
8+
// do something with the shutdown event
9+
}
10+
_ => {
11+
// ignore any other event
12+
// because we've registered the extension
13+
// only to receive SHUTDOWN events
14+
}
15+
}
16+
Ok(())
17+
}
18+
19+
#[tokio::main]
20+
async fn main() -> Result<(), Error> {
21+
// required to enable CloudWatch error logging by the runtime
22+
// can be replaced with any other method of initializing `log`
23+
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();
24+
25+
let func = extension_fn(my_extension);
26+
27+
let runtime = Runtime::builder().with_events(&["SHUTDOWN"]).register().await?;
28+
29+
runtime.run(func).await
30+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use lambda_extension::{run, Error, Extension, InvokeEvent, NextEvent};
2+
use log::LevelFilter;
3+
use simple_logger::SimpleLogger;
4+
use std::{
5+
future::{ready, Future},
6+
pin::Pin,
7+
};
8+
9+
#[derive(Default)]
10+
struct MyExtension {
11+
data: Vec<InvokeEvent>,
12+
}
13+
14+
impl Extension for MyExtension {
15+
type Fut = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
16+
fn call(&mut self, event: NextEvent) -> Self::Fut {
17+
match event {
18+
NextEvent::Shutdown(_e) => {
19+
self.data.clear();
20+
}
21+
NextEvent::Invoke(e) => {
22+
self.data.push(e);
23+
}
24+
}
25+
Box::pin(ready(Ok(())))
26+
}
27+
}
28+
29+
#[tokio::main]
30+
async fn main() -> Result<(), Error> {
31+
// required to enable CloudWatch error logging by the runtime
32+
// can be replaced with any other method of initializing `log`
33+
SimpleLogger::new().with_level(LevelFilter::Info).init().unwrap();
34+
35+
run(MyExtension::default()).await
36+
}

0 commit comments

Comments
 (0)