Garage SDK
An async Rust SDK for Garage (S3-compatible) that uploads files from paths, URLs, or bytes and returns a stable public URL for CDN or proxy-fronted access. Designed for production deployments where Garage sits behind a signing proxy (e.g., Envoy) or a public CDN base URL.
Need a production-ready Garage + Envoy setup? See docs/background.md for the full deployment guide.
Features
- Upload from multiple sources: Local files, URLs, or raw bytes
- Automatic content-type detection: Uses file extensions and MIME type guessing
- Builder pattern configuration: Flexible, type-safe configuration
- Proper error handling: Custom error types with detailed messages, no panics
- Configurable limits: Set max file size and download timeouts
- Tracing integration: Debug logging via the
tracingcrate - Async/await: Built on
tokiofor async operations - MSRV: Rust 1.92 (Edition 2024)
Installation
Add to your Cargo.toml:
[]
= "0.1"
= { = "1", = ["rt-multi-thread", "macros"] }
Quick Start
use ;
async
Configuration Options
Configuration Sources
You can load configuration in three primary ways:
- Environment variables:
UploaderConfig::from_env()(recommended for K8s env injection) - Secret files directory:
UploaderConfig::from_secret_dir(...)(recommended for mounted secrets) - Env with file fallback:
UploaderConfig::from_env_or_secret_dir(...)
Using the Builder Pattern
use UploaderConfig;
use Duration;
let config = builder
.endpoint // Required: S3 endpoint
.region // Optional: defaults to "garage"
.bucket // Required: target bucket
.public_base_url // Required: public CDN URL
.key_prefix // Optional: prefix for all keys
.credentials // Required: AWS credentials
.download_timeout // Optional: defaults to 30s
.max_file_size // Optional: defaults to 100MB
.max_buffered_bytes // Optional: defaults to 8MB
.build?;
Using Environment Variables
use UploaderConfig;
// Reads from environment variables:
// - GARAGE_ENDPOINT or S3_ENDPOINT
// - GARAGE_REGION or S3_REGION (optional)
// - GARAGE_BUCKET or S3_BUCKET
// - GARAGE_PUBLIC_URL or S3_PUBLIC_URL
// - GARAGE_KEY_PREFIX or S3_KEY_PREFIX (optional)
// - AWS_ACCESS_KEY_ID
// - AWS_SECRET_ACCESS_KEY
let config = from_env?;
Kubernetes example:
env:
- name: GARAGE_ENDPOINT
value: "https://s3.example.com"
- name: GARAGE_BUCKET
valueFrom:
secretKeyRef:
name: garage-sdk
key: bucket
- name: GARAGE_PUBLIC_URL
valueFrom:
secretKeyRef:
name: garage-sdk
key: public_url
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: garage-sdk
key: access_key_id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-sdk
key: secret_access_key
Using Kubernetes Secret Files
Mount your secret as a volume (each key becomes a file), then load from the directory:
use UploaderConfig;
let config = from_secret_dir?;
Kubernetes example:
volumes:
- name: garage-secrets
secret:
secretName: garage-sdk
containers:
- name: app
volumeMounts:
- name: garage-secrets
mountPath: /var/run/secrets/garage
readOnly: true
Expected filenames:
endpointregion(optional, defaults togarage)bucketpublic_urlkey_prefix(optional)access_key_idsecret_access_key
Custom Secret Filenames
use ;
let names = SecretFileNames ;
let config = from_secret_dir_with_names?;
Or with a common prefix:
use ;
let names = with_prefix;
let config = from_secret_dir_with_names?;
Or with a common suffix:
use ;
let names = with_suffix;
let config = from_secret_dir_with_names?;
Or via the builder:
use ;
let names = new
.with_prefix
.endpoint
.bucket
.public_url
.access_key_id
.secret_access_key
.region
.key_prefix
.build?;
let config = from_secret_dir_with_names?;
Merge defaults without overriding explicit fields:
use ;
let names = new
.endpoint
.merge_defaults
.build?;
let config = from_secret_dir_with_names?;
Environment Variables with File Fallback
use UploaderConfig;
let config = from_env_or_secret_dir?;
Upload Methods
Upload from Local Path
let result = uploader.upload_from_path.await?;
println!;
println!;
println!;
println!;
Upload from URL
Downloads the content from a URL and uploads it to storage:
let result = uploader
.upload_from_url
.await?;
Small downloads are buffered in memory by default (8 MB), while larger or unknown-size responses are streamed with the size cap enforced.
Download Buffering vs Streaming
upload_from_url buffers small downloads in memory and streams larger or unknown-size
responses to avoid unbounded memory usage.
- Default buffer threshold:
8 MB(max_buffered_bytes) - Hard size limit:
100 MB(max_file_size)
If Content-Length is present and below the threshold, the response is buffered.
Otherwise, the response is streamed and the size cap is enforced during the read.
Upload Raw Bytes
let json_data = r#"{"message": "Hello!"}"#;
let result = uploader
.upload_bytes
.await?;
Upload Result
All upload methods return an UploadResult:
Extensibility
You can extend the SDK without changing core logic by plugging in your own implementations of the provided traits:
Downloader: controls how remote URLs are fetchedStorageClient: controls how objects are uploadedKeyGenerator: controls how object keys are generated
Use GarageUploader::with_components to supply custom implementations while
keeping the public API unchanged.
Module Layout
src/
config/
mod.rs
data.rs
download/
mod.rs
impls.rs
error/
mod.rs
types.rs
keygen/
mod.rs
generator.rs
storage/
mod.rs
client.rs
types/
mod.rs
model.rs
uploader/
mod.rs
client.rs
lib.rs
Error Handling
The SDK uses custom error types for proper error handling:
use Error;
match uploader.upload_from_path.await
Error Types
| Error | Description |
|---|---|
Config |
Invalid configuration |
InvalidUrl |
Failed to parse URL |
FileRead |
Cannot read local file |
Download |
Failed to download from URL |
Http |
HTTP request error |
S3Operation |
S3 API call failed |
InvalidPath |
Invalid file path |
Using in Other Applications
As a Library Dependency
# In your application's Cargo.toml
[]
= { = "../garage-sdk" }
# Or from a git repository:
# garage-sdk = { git = "https://github.com/boniface/garage-sdk" }
Example Integration
use ;
Running the Example
# Set configuration and credentials
# Optional inputs for extra examples
# Run the example
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.