Musl

Die für AWS Lambda verwendeten Micro-VM unterstützen kein Standard glibc sondern nur musl. Die grundlegenden Funktionen sind zwar mit glibc deckungsgleich, aber es gibt Anwendungsfälle, die schlechter unterstützt werden. Daher muss Cargo wissen, dass die Lambdas auch nach Musl kompiliert werden müssen. Um das lokale Build-Target mit Cargo, den Rust Package Manager, erstellen zu können, müssen einige Vorbereitungen getroffen werden.

Target hinzufuegen

rustup target add x86_64-unknown-linux-musl

Kompilieren

cargo build --release --target x86_64-unknown-linux-musl

Paketieren

zip -j rust.zip ./target/x86_64-unknown-linux-musl/release/bootstrap

Docker

Auf einigen Systemen kann es zu Probleme bei dem lokalen bauen von musl paketen kommen, glücklicherweise gibt es ein Docker Image das bei dieser Aufgabe unterstuetzt: https://github.com/emk/rust-musl-builder

Da beim entwickeln der Lambda, der Compiler oefter mal aufgerufen wird, hilft ein kleines Alias: alias rust-musl-builder=‘docker run –rm -it -v “$(pwd)”:/home/rust/src ekidd/rust-musl-builder’ rust-musl-builder cargo build –release

Lambdas Funktion

Die AWS-Labs stellen eine Rust Runtime für die Lambda bereit, die bereits eine handler_fn, Context und Error handling mitbringt. https://github.com/awslabs/aws-lambda-rust-runtime

Die Beispiel Lambda reagiert auf Nachrichten in AWS SNS (Simple Notification Service), die beispielsweise von einer CodePipeline im Fehlerfall erstellt werden. Dieses SNS Event kann dann an einen Slack Webhook weitergeleitet werden.

use lambda_runtime::{handler_fn, Context, Error};
use serde_json::{json, Value};
use aws_lambda_events::event::sns::{SnsEvent};
use slack_hook3::{Slack, PayloadBuilder};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Error> {
    let func = handler_fn(func);
    lambda_runtime::run(func).await?;
    Ok(())
}
async fn call_slack(message: &str) {
    let slack_channel =env::var("SLACK_CHANNEL").unwrap();
    let slack_username =env::var("SLACK_USER").unwrap();
    let slack_webhook_url = env::var("SLACK_WEBHOOK_URL").unwrap();
    let slack = Slack::new(slack_webhook_url).unwrap();
    let p = PayloadBuilder::new()
      .text(message)
      .channel(slack_channel)
      .username(slack_username)
      .build()
      .unwrap();
    
    let res = slack.send(&p);
    print!("{:?}",res.await)
}
async fn func(input: Value, _: Context) -> Result<Value, Error> {
    let event: SnsEvent = serde_json::from_value(input)?;
    let tmp_message: &Option<String> = &event.records[0].sns.message.to_owned();
    let message = tmp_message.as_deref().unwrap_or("no message");
    call_slack(message).await;
    Ok(json!({ "message": format!("{:?}", event.records) }))
}

Die passende Cargo Einstellungen sehen in etwa so aus:

[package]
name = "my_lambda_function"
version = "0.1.0"
authors = ["Jon Doe <your-email-id>"]
edition = "2018"
autobins = false

[dependencies]
lambda_runtime = "0.3.0"
serde = "^1"
serde_json = "^1"
serde_derive = "^1"
log = "^0.4"
simple_logger = "^1"
tokio = "1.9.0"
aws_lambda_events = "0.4.0"
slack-hook3 = "0.11"

[[bin]]
name = "bootstrap"
path = "src/main.rs"

Fazit

Nachdem klar ist, wie das Musl Target erstellt werden muss, geht das Schreiben der Lambda an sich recht fliesen von der Hand.