├── .github └── workflows │ ├── demo.yml │ ├── release.yml │ └── shellcheck.yml ├── LICENSE ├── README.md ├── bin └── wasmtime ├── bootstrap └── demo ├── .gitignore ├── Cargo.toml ├── demo.sh ├── demo.zip ├── luigi.png └── src └── lib.rs /.github/workflows/demo.yml: -------------------------------------------------------------------------------- 1 | name: demo 2 | 3 | on: [push, pull_request, release] 4 | 5 | jobs: 6 | check: 7 | name: showcase a demo 8 | runs-on: macos-latest 9 | steps: 10 | - name: checkout 11 | uses: actions/checkout@v1.0.0 12 | with: 13 | ref: ${{ github.ref }} 14 | - name: install rust, cargo, rustup n more 15 | uses: hecrj/setup-rust-action@v1 16 | with: 17 | rust-version: stable 18 | - name: install cargo wasi 19 | run: cargo install cargo-wasi 20 | - name: install node n npm 21 | uses: actions/setup-node@v1.1.2 22 | with: 23 | node-version: 12.x 24 | - name: install wasmtime 25 | run: curl https://wasmtime.dev/install.sh -sSf | bash 26 | - name: run a demo 27 | run: | 28 | source "$HOME"/.bash_profile # make wasmtime available 29 | bash ./demo/demo.sh "$(pwd)/demo" 30 | if [[ "$(wc -c ./demo/thumbnail.png | grep -oE '[0-9]+')" -eq "0" ]]; then exit 1; fi -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | name: publish a release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: get version 14 | id: get_version 15 | run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} 16 | - name: checkout@${{ steps.get_version.outputs.version }} 17 | uses: actions/checkout@v1.0.0 18 | with: 19 | ref: ${{ github.ref }} 20 | - name: shellcheck bootstrap script 21 | run: shellcheck -e SC2153 ./bootstrap # ignore false error 22 | - name: bundle lambda runtime layer 23 | run: | 24 | chmod 755 ./bootstrap ./bin/wasmtime 25 | zip -r ./lambda_wasmtime.zip ./bootstrap ./bin 26 | - name: create release 27 | id: create_release 28 | uses: actions/create-release@v1.0.0 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.ref }} 33 | release_name: ${{ github.ref }} 34 | draft: false 35 | prerelease: false 36 | - name: upload release asset 37 | uses: actions/upload-release-asset@v1.0.1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | upload_url: ${{ steps.create_release.outputs.upload_url }} 42 | asset_path: ./lambda_wasmtime.zip 43 | asset_name: lambda_wasmtime_${{ steps.get_version.outputs.version }}.zip 44 | asset_content_type: application/zip -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: shellcheck 2 | 3 | on: push 4 | 5 | jobs: 6 | check: 7 | name: shellcheck the bootstrap script 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: checkout 11 | uses: actions/checkout@v1.0.0 12 | with: 13 | ref: ${{ github.ref }} 14 | - name: shellcheck bootstrap script 15 | run: shellcheck -e SC2153 ./bootstrap # ignore false error -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Noah Anabiik Schwarz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-wasmtime 2 | 3 | [![release](https://img.shields.io/github/release/chiefbiiko/lambda-wasmtime/all.svg)](https://github.com/chiefbiiko/lambda-wasmtime/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/chiefbiiko/lambda-wasmtime/total.svg)](https://github.com/chiefbiiko/lambda-wasmtime/releases/latest) [![GitHub license](https://img.shields.io/github/license/chiefbiiko/lambda-wasmtime.svg)](https://github.com/chiefbiiko/lambda-wasmtime/blob/master/LICENSE) [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/chiefbiiko/lambda-wasmtime) [![shellcheck](https://github.com/chiefbiiko/lambda-wasmtime/workflows/shellcheck/badge.svg)](./bootstrap) [![demo](https://github.com/chiefbiiko/lambda-wasmtime/workflows/demo/badge.svg)](./demo) [![mvp](https://img.shields.io/badge/mvp-bash-lightgreen.svg)](https://shields.io/) [![bash](https://badges.frapsoft.com/bash/v1/bash.png?v=103)](./bootstrap) 4 | 5 | **wat??** `lambda-wasmtime` is a custom AWS Lambda runtime built with [`wasmtime`](https://wasmtime.dev/). Runs WebAssembly on AWS Lambda. 6 | 7 | ### Getting the Runtime 8 | 9 | Currently only available from [Github Releases](https://github.com/chiefbiiko/lambda-wasmtime/releases/latest). Make sure to check for new releases and update your runtime layer from time to time. 10 | 11 | ### Building a WebAssembly Lambda 12 | 13 | Check out this [2-minute article](https://dev.to/chiefbiiko/lambda-wasmtime-running-webassembly-on-aws-lambda-51gi) for a walkthrough of building a wasm lambda. More background info can be found in the [`wasmtime` guide](https://bytecodealliance.github.io/wasmtime/wasm-rust.html#webassembly-interface-types). 14 | 15 | ## License 16 | 17 | [MIT](./LICENSE) -------------------------------------------------------------------------------- /bin/wasmtime: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chiefbiiko/lambda-wasmtime/1f47d8c9f501b18263130376969d6f92adf4fc8b/bin/wasmtime -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | post_init_error() { 6 | curl -fsSLX POST "$API/init/error" -d '{\"error\":\"bootstrap fail\"}' 7 | } 8 | 9 | trap post_init_error EXIT 10 | 11 | API="http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime" 12 | FILE="$LAMBDA_TASK_ROOT/${_HANDLER%%.*}.wasm" 13 | HANDLER="${_HANDLER##*.}" 14 | HEADERS=/tmp/.headers.txt 15 | WARNING="warning: .* is experimental and may break in the future" 16 | PREP_ENV_VARS="$(paste -sd "," <<< "$(env)")" 17 | 18 | printf "env vars\n%s\nwasmtime version %s\n" "$(env)" "$(/opt/bin/wasmtime --version)" 19 | 20 | set +Eeu 21 | trap - EXIT 22 | trap 'rm -f "$HEADERS"' EXIT 23 | 24 | invoke_wasm() { # request_id trace_id event context 25 | if result="$(/opt/bin/wasmtime --disable-cache --enable-simd --env="$PREP_ENV_VARS,_X_AMZN_TRACE_ID=$2" --invoke="$HANDLER" "$FILE" "$3" "$4" 2>&1 | grep -v "$WARNING")"; then 26 | curl -fsSLX POST "$API/invocation/$1/response" -d "$result" 27 | else 28 | curl -fsSLX POST "$API/invocation/$1/error" -d "$result" 29 | fi 30 | } 31 | 32 | while :; do 33 | event="$(curl -fsSLD "$HEADERS" "$API/invocation/next")" 34 | function_arn="$(grep -ioP "(?<=Lambda-Runtime-Invoked-Function-Arn: )([^[:space:]]*)" "$HEADERS")" 35 | deadline_ms="$(grep -ioP "(?<=Lambda-Runtime-Deadline-Ms: )([^[:space:]]*)" "$HEADERS")" 36 | trace_id="$(grep -ioP "(?<=Lambda-Runtime-Trace-Id: )([^[:space:]]*)" "$HEADERS")" 37 | request_id="$(grep -ioP "(?<=Lambda-Runtime-Aws-Request-Id: )([^[:space:]]*)" "$HEADERS")" 38 | context="{\"function_arn\":\"$function_arn\",\"deadline_ms\":\"$deadline_ms\",\"request_id\":\"$request_id\",\"trace_id\":\"$trace_id\"}" 39 | 40 | # TODO: find a way to not block # & has failed for me 41 | invoke_wasm "$request_id" "$trace_id" "$event" "$context" 42 | done -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | thumbnail.png -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo" 3 | version = "0.1.0" 4 | authors = ["Noah Anabiik Schwarz "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ['cdylib'] 9 | 10 | [dependencies] 11 | anyhow = "1.0.19" 12 | base64 = "0.3.1" 13 | image = "0.22.3" 14 | serde_json = "1.0.41" 15 | wasm-bindgen = "0.2.54" -------------------------------------------------------------------------------- /demo/demo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # usage: bash $0 [demo_dir] 4 | 5 | cat << EOL 6 | simple demo script showcasing 7 | + how to compile Rust to a wasm module that uses interface types (strings!) 8 | + that such wasm can do real-world tasks like image processing 9 | + the wasm module exports a function "handler" that when invoked with two 10 | JSON strings, the event and context objects, processes a base64 encoded 11 | image, creates a thumbnail of it, and returns the base64 representation 12 | of that thumbnail as a JSON response 13 | EOL 14 | 15 | cd "${1:-"$(pwd)"}" 16 | 17 | wasm=./target/wasm32-wasi/release/demo.wasm 18 | event="{\"data\":\"$(base64 ./luigi.png)\"}" 19 | context="{}" 20 | 21 | # build the interface-types-enabled wasm module 22 | cargo wasi build --release 23 | 24 | # run an export from our wasm module - passing in strings!!! 25 | result="$(wasmtime --disable-cache --enable-simd --invoke=handler "$wasm" "$event" "$context" 2>&1 | grep -v warning)" 26 | 27 | # massage the image from JSON to a PNG 28 | node -e "fs.writeFileSync('./thumbnail.png',Buffer.from(JSON.parse('$result').data,'base64'))" 29 | 30 | # inspect images 31 | if ! viu -ntv ./luigi.png ./thumbnail.png; then 32 | cargo install viu 33 | viu -ntv ./luigi.png ./thumbnail.png 34 | fi 35 | 36 | # zipup a demo lambda bundle 37 | # can be deployed on aws with the runtime layer - get its latest release from: 38 | # https://github.com/chiefbiiko/lambda-wasmtime/releases/latest 39 | # make sure to provision the lambda with approx. 2048 MB memory and a 5s timeout 40 | # note: all of this is mvp and experimental at the moment 41 | zip -j ./demo.zip "$wasm" > /dev/null -------------------------------------------------------------------------------- /demo/demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chiefbiiko/lambda-wasmtime/1f47d8c9f501b18263130376969d6f92adf4fc8b/demo/demo.zip -------------------------------------------------------------------------------- /demo/luigi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chiefbiiko/lambda-wasmtime/1f47d8c9f501b18263130376969d6f92adf4fc8b/demo/luigi.png -------------------------------------------------------------------------------- /demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result as AnyhowResult}; 2 | use base64::{decode as from_base64, encode as to_base64}; 3 | use image::{load_from_memory as load_image_from_memory, ImageOutputFormat::PNG}; 4 | use serde_json::{from_str as from_json, Value}; 5 | use wasm_bindgen::prelude::wasm_bindgen; 6 | 7 | fn process(event: &str, mut thumbnail_buf: Vec) -> AnyhowResult> { 8 | load_image_from_memory( 9 | &from_base64( 10 | from_json::(event)? 11 | .get("data").ok_or(anyhow!("missing property \"data\""))? 12 | .as_str().ok_or(anyhow!("invalid string"))?, 13 | )?, 14 | )? 15 | .thumbnail(128, 128) 16 | .write_to(&mut thumbnail_buf, PNG)?; 17 | 18 | Ok(thumbnail_buf) 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub fn handler(event: &str, _context: &str) -> String { 23 | match process(event, Vec::new()) { 24 | Ok(thumbnail_buf) => format!("{{\"data\":\"{}\"}}", to_base64(&thumbnail_buf)), 25 | _ => "{\"error\":\"processing failed\"}".to_string(), 26 | } 27 | } --------------------------------------------------------------------------------