Objective: Implement persistent storage, achieve Terraform compatibility, and optimize S3, DynamoDB, SQS, and SNS implementations.
Timeline: Months 1-3
Implement the persistence layer so data survives restarts. Currently uses in-memory (ephemeral) storage. Need to add SQLite/File I/O persistence.
- S3: Uses
EphemeralStorageinruststack-s3/src/storage/ephemeral.rs - DynamoDB: Uses in-memory
DynamoDBStorageinruststack-dynamodb/src/storage.rs - Other services: Various in-memory implementations
-
Explore current storage implementations:
ls -la ruststack-s3/src/storage/ ls -la ruststack-dynamodb/src/
-
Add SQLite dependency to workspace: Edit
Cargo.toml:# Add to workspace.dependencies rusqlite = { version = "0.31", features = ["bundled"] }
-
Create persistent storage for S3:
Create
ruststack-s3/src/storage/persistent.rs:use rusqlite::{Connection, params}; use std::path::Path; pub struct PersistentStorage { conn: Connection, } impl PersistentStorage { pub fn new(data_dir: &Path) -> anyhow::Result<Self> { std::fs::create_dir_all(data_dir)?; let conn = Connection::open(data_dir / "s3.db")?; conn.execute( "CREATE TABLE IF NOT EXISTS buckets ( name TEXT PRIMARY KEY, created_at TEXT NOT NULL )", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS objects ( bucket TEXT NOT NULL, key TEXT NOT NULL, value BLOB NOT NULL, content_type TEXT, created_at TEXT NOT NULL, PRIMARY KEY (bucket, key) )", [], )?; Ok(Self { conn }) } // Implement: create_bucket, delete_bucket, put_object, get_object, list_objects, delete_object }
-
Create storage trait for abstraction:
Read
ruststack-s3/src/storage/traits.rsand extend:pub trait ObjectStorage: Send + Sync { fn create_bucket(&self, name: &str) -> Result<(), S3Error>; fn delete_bucket(&self, name: &str) -> Result<(), S3Error>; fn list_buckets(&self) -> Result<Vec<Bucket>, S3Error>; fn put_object(&self, bucket: &str, key: &str, value: Vec<u8>, content_type: Option<&str>) -> Result<(), S3Error>; fn get_object(&self, bucket: &str, key: &str) -> Result<Vec<u8>, S3Error>; fn delete_object(&self, bucket: &str, key: &str) -> Result<(), S3Error>; fn list_objects(&self, bucket: &str, prefix: &str) -> Result<Vec<ObjectInfo>, S3Error>; }
-
Wire up persistence based on config:
In
ruststack/src/main.rs:let storage: Arc<dyn ObjectStorage> = if args.persistence { Arc::new(PersistentStorage::new(&data_dir)?) } else { Arc::new(EphemeralStorage::new()) };
-
Apply same pattern to DynamoDB:
- Create
rusqlitetables for tables, items, and metadata - Implement
PersistentStorageinruststack-dynamodb/src/storage.rs
- Create
-
Test persistence:
# Start with persistence RUSTSTACK_DATA_DIR=/tmp/ruststack cargo run & # Create bucket and object aws s3 mb s3://test-bucket --endpoint-url http://localhost:4566 echo "hello" | aws s3 cp - s3://test-bucket/hello.txt --endpoint-url http://localhost:4566 # Kill and restart pkill ruststack RUSTSTACK_DATA_DIR=/tmp/ruststack cargo run & # Verify data persists aws s3 ls s3://test-bucket --endpoint-url http://localhost:4566
S3 is already implemented but needs enhancement for Terraform compatibility and improved path-style routing.
- Bucket operations: Create, List, Delete ✓
- Object operations: Put, Get, List, Delete ✓
- Path-style routing ✓
- Virtual-hosted style - needs improvement
-
Review current S3 implementation:
cat ruststack-s3/src/handlers.rs cat ruststack-s3/src/service.rs
-
Enhance virtual-hosted style routing:
The router should handle
bucket.localhost:4566style URLs:In
ruststack/src/router.rs, enhancehandle_bucket:async fn handle_bucket( State(state): State<Arc<AppState>>, Path(bucket): Path<String>, method: Method, Query(query): Query<ListObjectsQuery>, headers: HeaderMap, body: Bytes, ) -> Response { // Check if this is a virtual-hosted request // Host header format: bucket.localhost:4566 or bucket.host:4566 if let Some(host) = headers.get("host") { if let Ok(host_str) = host.to_str() { // Extract bucket from host if present // e.g., "mybucket.localhost:4566" -> bucket = "mybucket" } } // ... existing logic }
-
Add S3 multipart upload support:
Required for large file uploads via SDK:
// In handlers.rs pub async fn create_multipart_upload( State(state): State<Arc<S3State>>, Path((bucket, key)): Path<(String, String)>, ) -> Response; pub async fn upload_part( State(state): State<Arc<S3State>>, Path((bucket, key, upload_id)): Path<(String, String, String)>, body: Bytes, ) -> Response; pub async fn complete_multipart_upload( State(state): State<Arc<S3State>>, Path((bucket, key, upload_id)): Path<(String, String, String)>, body: Bytes, ) -> Response;
-
Add S3 CORS support:
pub async fn put_bucket_cors(...) -> Response; pub async fn get_bucket_cors(...) -> Response;
-
Add S3 versioning (basic):
// Enable versioning per bucket pub async fn put_bucket_versioning(...) -> Response; pub async fn get_bucket_versioning(...) -> Response;
-
Test with Terraform:
provider "aws" { endpoint = "http://localhost:4566" region = "us-east-1" access_key = "test" secret_key = "test" s3_use_path_style = true } resource "aws_s3_bucket" "test" { bucket = "terraform-test-bucket" }
Run:
terraform init && terraform apply -
Acceptance Test:
- Terraform S3 bucket creation works
- Path-style:
http://localhost:4566/bucket/key - Virtual-hosted:
http://bucket.localhost:4566/key - Large file uploads work via multipart
DynamoDB is partially implemented. Need to add more operations and improve query expression support.
- CreateTable, DeleteTable ✓
- PutItem, GetItem, DeleteItem ✓
- Basic Query support - needs enhancement
- Scan - needs implementation
- UpdateItem - needs implementation
-
Review current DynamoDB implementation:
cat ruststack-dynamodb/src/handlers.rs cat ruststack-dynamodb/src/expression.rs
-
Add missing DynamoDB operations:
a. UpdateItem:
pub async fn update_item( State(state): State<Arc<DynamoDBState>>, headers: HeaderMap, body: Bytes, ) -> Response { // Parse UpdateItem request // Handle: SET, REMOVE, ADD, DELETE clauses // Return updated item }
b. Scan:
pub async fn scan( State(state): State<Arc<DynamoDBState>>, headers: HeaderMap, body: Bytes, ) -> Response { // Scan all items in table // Support: FilterExpression, ProjectionExpression, Limit }
c. Batch operations:
pub async fn batch_write_item(...) -> Response; pub async fn batch_get_item(...) -> Response;
-
Enhance Query support:
Improve
ruststack-dynamodb/src/expression.rs:- Add support for more comparison operators:
BETWEEN,begins_with,attribute_exists,attribute_not_exists - Add support for
KeyConditionExpression - Add support for
FilterExpression
- Add support for more comparison operators:
-
Add DynamoDB Local secondary indexes (optional):
// If defined in CreateTable request pub struct LocalSecondaryIndex { index_name: String, key_schema: Vec<KeySchemaElement>, projection: Projection, }
-
Test with Terraform:
resource "aws_dynamodb_table" "test" { name = "terraform-test-table" billing_mode = "PAY_PER_REQUEST" hash_key = "id" attribute { name = "id" type = "S" } }
-
Acceptance Test:
- Terraform DynamoDB table creation works
aws dynamodb put-itemworksaws dynamodb get-itemworksaws dynamodb queryworks with key conditionsaws dynamodb scanworks
SQS and SNS are partially implemented. Need to add persistence and proper fan-out support.
- SQS: Basic queue operations implemented
- SNS: Basic topic operations implemented
- SNS → SQS fan-out: Not implemented
- Persistence: Not implemented
-
Review current implementations:
cat ruststack-sqs/src/handlers.rs cat ruststack-sns/src/handlers.rs cat ruststack-sqs/src/storage.rs cat ruststack-sns/src/storage.rs
-
Add SQS persistence:
In
ruststack-sqs/src/storage.rs:use rusqlite::Connection; use std::path::Path; pub struct PersistentQueueStorage { conn: Connection, } impl PersistentQueueStorage { pub fn new(data_dir: &Path) -> anyhow::Result<Self> { std::fs::create_dir_all(data_dir)?; let conn = Connection::open(data_dir / "sqs.db")?; conn.execute( "CREATE TABLE IF NOT EXISTS queues ( url TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL, attributes TEXT )", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS messages ( queue_url TEXT NOT NULL, receipt_handle TEXT PRIMARY KEY, body TEXT NOT NULL, visible_after TEXT NOT NULL, created_at TEXT NOT NULL )", [], )?; Ok(Self { conn }) } }
-
Add SNS persistence:
Similar to SQS in
ruststack-sns/src/storage.rs -
Implement SNS → SQS fan-out:
When publishing to SNS topic, check for subscribed SQS queues and deliver:
In
ruststack-sns/src/handlers.rs:pub async fn publish( State(state): State<Arc<SnsState>>, headers: HeaderMap, body: Bytes, ) -> Response { let request: PublishInput = serde_json::from_slice(&body).unwrap(); // Get topic let topic = state.get_topic(&request.topic_arn); // Publish to topic let message_id = topic.publish(&request.message).await; // Fan-out to SQS subscriptions for subscription in &topic.subscriptions { if subscription.protocol == "sqs" { // Send to SQS queue send_to_sqs(&subscription.endpoint, &request.message).await; } } // Return response }
-
Add SNS subscription confirmation:
SNS requires subscription confirmation via URL:
pub async fn subscribe( State(state): State<Arc<SnsState>>, headers: HeaderMap, body: Bytes, ) -> Response { // Handle SubscriptionConfirmation for HTTP endpoints // Return proper response based on protocol }
-
Test SNS → SQS:
# Create SQS queue aws sqs create-queue --queue-name test-queue --endpoint-url http://localhost:4566 # Create SNS topic aws sns create-topic --name test-topic --endpoint-url http://localhost:4566 # Subscribe SQS to SNS aws sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:test-topic \ --protocol sqs \ --notification-endpoint http://localhost:4566/000000000000/test-queue \ --endpoint-url http://localhost:4566 # Publish to topic aws sns publish --topic-arn arn:aws:sns:us-east-1:000000000000:test-topic \ --message "test message" \ --endpoint-url http://localhost:4566 # Receive from SQS aws sqs receive-message --queue-url http://localhost:4566/000000000000/test-queue
-
Acceptance Test:
- SQS queues persist across restarts
- SNS topics persist across restarts
- SNS → SQS fan-out works
- Terraform can create SQS queues and SNS topics
| Criterion | Test Command | Expected Result |
|---|---|---|
| S3 persistence | Create bucket, restart, list buckets | Bucket still exists |
| S3 Terraform | terraform apply with S3 resource |
Bucket created |
| S3 path-style | curl localhost:4566/bucket/key |
Works |
| DynamoDB UpdateItem | AWS CLI update-item | Item updated |
| DynamoDB Scan | AWS CLI scan | All items returned |
| DynamoDB Terraform | terraform apply with DynamoDB |
Table created |
| SQS persistence | Create queue, restart, list queues | Queue exists |
| SNS → SQS fan-out | Publish to SNS | Message delivered to SQS |
| Memory usage | Run with data | Under 50MB for basic operations |
-
Key files:
- S3 handlers:
ruststack-s3/src/handlers.rs - S3 storage:
ruststack-s3/src/storage/ - DynamoDB handlers:
ruststack-dynamodb/src/handlers.rs - SQS:
ruststack-sqs/src/ - SNS:
ruststack-sns/src/
- S3 handlers:
-
Database location:
- Default:
./data/s3.db,./data/dynamodb.db, etc. - Configurable via
RUSTSTACK_DATA_DIR
- Default:
-
Testing:
- Use AWS CLI for manual testing
- Use Terraform for integration testing
- Add unit tests in respective
src/directories