MuMu × AWS (S3) Plugin
A lightweight extension that adds Amazon S3 capabilities to the MuMu runtime: credentials sessions, bucket creation, and object fetching (to file or as a streaming iterator). Built-in SigV4 signing, friendly error surfaces, and examples for both real S3 and S3‑compatible endpoints (e.g., LocalStack).
Language:RustRuntime:MuMuAuth:SigV4Transport:reqwest (blocking)
Table of contents
- Highlights
- Quick start
- Usage
- API reference
- How it works
- Repository layout
- LocalStack tips
- Troubleshooting
- Security
- License
- Acknowledgements
Highlights
- 🔐 Session‑based credentials via aws:credentials(…) (no global state; returns a short session id)
- ✍️ SigV4 signing in pure Rust (src/functions/sigv4.rs)
- 🪣 Create bucket: aws:s3:create_bucket(…)
- ⬇️ Fetch object: save to file (path/folder) or stream as a MuMu InkIterator
- 🧯 Robust errors: dynamic calls catch panics and return [ok: false, error: "…"]
Quick start
1) Build the plugin
The MuMu loader looks for a dynamic library named libmumu.{so|dylib} (Linux/macOS) or .dll (Windows). For this plugin, base is aws, so you’ll get libmumuaws.so, libmumuaws.dylib, or mumuaws.dll.
# from the repository root
# point MuMu to the build directory (adjust for your OS/shell)
2) Run MuMu (REPL or script)
# REPL (verbose is handy while developing)
# or (depending on install)
# Run a .mu script
3) Load the plugin and configure credentials
extend("aws")
extend("process") // to read environment variables
credentials = aws:credentials([
accessKeyId: process:env("AWS_ACCESS_KEY_ID"),
secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
region: process:env("AWS_REGION"),
// Optional: S3-compatible endpoint (e.g., LocalStack)
// endpoint: "http://localhost:4566"
])
Return value: a short session id string — pass this to other AWS calls.
Usage
Create a bucket
extend("aws")
extend("process")
extend("test") // just for test:diceware()
credentials = aws:credentials([
accessKeyId: process:env("AWS_ACCESS_KEY_ID"),
secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
region: process:env("AWS_REGION"),
endpoint: "http://localhost:4566" // optional
])
result = aws:s3:create_bucket([
credentials: credentials,
bucket: test:diceware()
])
slog(result) // → [ok: true, bucket: "..."] or [ok: false, error: "..."]
Fetch an object — save to disk
extend("aws")
extend("process")
credentials = aws:credentials([
accessKeyId: process:env("AWS_ACCESS_KEY_ID"),
secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
region: process:env("AWS_REGION")
])
result = aws:s3:fetch([
credentials: credentials,
bucket: "my-bucket",
key: "path/to/object.png",
// use either:
// path: "/tmp/object.png",
folder: "/tmp/s3-downloads" // filename inferred from key
])
slog(result) // → [ok: true, path: "/tmp/s3-downloads/object.png"]
Fetch an object — stream chunks
Omit path/folder to get an InkIterator yielding chunks: { chunk: IntArray }.
extend("aws")
extend("file")
extend("process")
credentials = aws:credentials([
accessKeyId: process:env("AWS_ACCESS_KEY_ID"),
secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
region: process:env("AWS_REGION")
])
stream = aws:s3:fetch([
credentials: credentials,
bucket: "my-bucket",
key: "large-object.bin"
])
file:write("/tmp/large-object.bin", stream)
Chunks are read in up to 8192‑byte blocks and delivered as signed ints (0–255) in an IntArray. The iterator finishes naturally when the stream ends.
More streaming examples: examples/s3/fetch-stream.mu, examples/s3/fetch-stream-map.mu, examples/s3/fetch-multi.mu.
API reference
aws:credentials(opts) → session_id:string
| Key | Type | Required | Notes |
|---|---|---|---|
accessKeyId |
string | Yes | AWS access key id |
secretAccessKey |
string | Yes | AWS secret |
region |
string | Yes | e.g., us-east-1 |
endpoint |
string | No | S3‑compatible endpoint (LocalStack, MinIO w/ S3 API, etc.) |
Validates inputs and stores an in‑memory session. Returns a short session id. On failure, the dynamic wrapper surfaces an error object:
[ok: false, error: "aws:credentials: ..."]
aws:s3:create_bucket(opts) → { ok, bucket? , error? }
| Key | Type | Required | Notes |
|---|---|---|---|
credentials |
string | Yes | Session id from aws:credentials |
bucket |
string | Yes | Bucket name |
On success returns [ok: true, bucket: "…"]; on error returns [ok: false, error: "…"].
aws:s3:fetch(opts) → InkIterator or { ok, path? , error? }
| Key | Type | Required | Notes |
|---|---|---|---|
credentials |
string | Yes | Session id |
bucket |
string | Yes | Bucket name |
key |
string | Yes | Object key |
path |
string | No | Full destination path (direct download) |
folder |
string | No | Destination directory (filename inferred) |
- If
pathorfolderis provided → downloads the object and returns [ok: true, path: "..."]. - If neither is provided → returns a streaming InkIterator of chunks
{ chunk: IntArray }.
How it works
- Session store: SESSIONS (lazy static HashMap<String, AwsSession>) keyed by session id. (src/register/credentials.rs)
- SigV4: canonical request + hex SHA‑256 + HMAC chain; returns ready‑to‑use headers (including Authorization). (src/functions/sigv4.rs)
- Networking: reqwest::blocking client. Direct download writes to a file; streaming spawns a reader thread that sends 8 KiB blocks over an MPSC channel to a MuMu plugin iterator. (src/register/s3_fetch.rs)
- Error model: all dynamic functions are guarded with catch_unwind; failures are returned as keyed arrays, never raw panics.
Repository layout
LOCAL/
src/
functions/
sigv4.rs
register/
credentials.rs
s3_create_bucket.rs
s3_fetch.rs
hello.rs
examples/
s3/
create-bucket.mu
fetch.mu
fetch-multi.mu
fetch-stream.mu
fetch-stream-map.mu
REMOTE/ (MuMu engine; vendored)
src/
parser/... # lexer, parser, interpreter, eval helpers, etc.
modules/... # extend, compose/pipe, slog/sput, ink, include, type
The vendored MuMu engine includes improvements for λ by‑reference semantics, composition, numeric coercions, REPL input, autocompletion, and statement write‑through to reference cells.
LocalStack tips
-
Run LocalStack with S3 enabled (e.g., via Docker).
-
Use test credentials and set
endpointin
aws:credentials:
credentials = aws:credentials([ accessKeyId: "test", secretAccessKey: "test", region: "us-east-1", endpoint: "http://localhost:4566" ]) -
Use the same APIs as you would for AWS.
Troubleshooting
-
“no session with id …” — The session id is missing or stale. Call aws:credentials again and reuse its return value.
-
“AWS error status …” — Check bucket permissions, region, and the exact bucket/key. For S3‑compatible endpoints ensure the endpoint is set in aws:credentials.
-
Plugin not found
— Ensure
MUMU_PLUGIN_PATH
includes the directory with the compiled library. The loader also searches near the MuMu binary and common system locations:
- Current exe dir and ../lib
- CWD: ./libmumuaws.* or ./aws/libmumuaws.*
- Linux: /usr/local/lib, /usr/lib64, /usr/lib
- macOS: /usr/local/lib, /opt/homebrew/lib
- Windows: %ProgramFiles%\lava-mumu\lib
Security
- Sessions are in‑memory only; no persistence.
- Prefer process:env("…") for secrets; avoid hard‑coding.
- Use S3‑compatible stacks (e.g., LocalStack) for development and tests.
License
MuMu engine files reference the MIT license in headers. If this plugin has a separate license, consult the repository’s LICENSE. Example files are provided for educational use.
Acknowledgements
Thanks to the MuMu contributors for the runtime, REPL, and libraries. This plugin follows MuMu’s dynamic function conventions (extend("aws")) and uses InkIterator for streaming downloads.
© 2025 MuMu × AWS Plugin