wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

163
storage/src/backend.rs Normal file
View File

@@ -0,0 +1,163 @@
use aws_sdk_s3::{primitives::ByteStream, Client as AwsClient};
use aws_config::BehaviorVersion;
use aws_sdk_s3::config::Credentials;
use aws_sdk_s3::config::Region;
use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use std::env;
/// Storage backend trait for supporting multiple S3-compatible services
#[async_trait]
pub trait StorageBackend: Send + Sync {
async fn put_object(&self, bucket: &str, key: &str, data: Bytes) -> Result<()>;
async fn get_object(&self, bucket: &str, key: &str) -> Result<Bytes>;
async fn delete_object(&self, bucket: &str, key: &str) -> Result<()>;
async fn create_bucket(&self, bucket: &str) -> Result<()>;
}
/// AWS SDK S3 implementation (for Hetzner Bucket Storage and AWS S3)
pub struct AwsS3Backend {
client: AwsClient,
bucket_name: String,
}
impl AwsS3Backend {
pub async fn new() -> Result<Self> {
let endpoint = env::var("S3_ENDPOINT")
.unwrap_or_else(|_| "https://fsn1.your-objectstorage.com".to_string()); // Hetzner default
let access_key = env::var("S3_ACCESS_KEY")
.or_else(|_| env::var("MINIO_ROOT_USER"))
.expect("S3_ACCESS_KEY or MINIO_ROOT_USER must be set");
let secret_key = env::var("S3_SECRET_KEY")
.or_else(|_| env::var("MINIO_ROOT_PASSWORD"))
.expect("S3_SECRET_KEY or MINIO_ROOT_PASSWORD must be set");
let bucket_name = env::var("S3_BUCKET")
.unwrap_or_else(|_| "madbase".to_string());
let region = env::var("S3_REGION")
.unwrap_or_else(|_| "us-east-1".to_string());
tracing::info!("Initializing AWS S3 Backend");
tracing::info!(" Endpoint: {}", endpoint);
tracing::info!(" Bucket: {}", bucket_name);
tracing::info!(" Region: {}", region);
// Build AWS config with custom endpoint
let aws_config = aws_config::defaults(BehaviorVersion::latest())
.region(Region::new(region.clone()))
.endpoint_url(&endpoint)
.credentials_provider(Credentials::new(
access_key.clone(),
secret_key.clone(),
None,
None,
"static",
))
.load()
.await;
let s3_config = aws_sdk_s3::config::Builder::from(&aws_config)
.endpoint_url(&endpoint)
.force_path_style(true) // Required for MinIO and custom S3 endpoints
.build();
let client = AwsClient::from_conf(s3_config);
Ok(Self {
client,
bucket_name,
})
}
pub fn bucket_name(&self) -> &str {
&self.bucket_name
}
pub fn client(&self) -> &AwsClient {
&self.client
}
}
#[async_trait]
impl StorageBackend for AwsS3Backend {
async fn put_object(&self, _bucket: &str, key: &str, data: Bytes) -> Result<()> {
self.client
.put_object()
.bucket(&self.bucket_name)
.key(key)
.body(ByteStream::from(data))
.send()
.await?;
Ok(())
}
async fn get_object(&self, _bucket: &str, key: &str) -> Result<Bytes> {
let resp = self.client
.get_object()
.bucket(&self.bucket_name)
.key(key)
.send()
.await?;
Ok(resp.body.collect().await?.into_bytes())
}
async fn delete_object(&self, _bucket: &str, key: &str) -> Result<()> {
self.client
.delete_object()
.bucket(&self.bucket_name)
.key(key)
.send()
.await?;
Ok(())
}
async fn create_bucket(&self, _bucket: &str) -> Result<()> {
// Try to create bucket, ignore if it already exists
let _ = self.client.create_bucket()
.bucket(&self.bucket_name)
.send()
.await;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
/// Helper to create a test backend
async fn create_test_backend() -> AwsS3Backend {
// Set test environment variables
env::set_var("S3_ENDPOINT", "http://localhost:9000");
env::set_var("S3_ACCESS_KEY", "test_access_key");
env::set_var("S3_SECRET_KEY", "test_secret_key");
env::set_var("S3_BUCKET", "test-bucket");
env::set_var("S3_REGION", "us-east-1");
AwsS3Backend::new().await.expect("Failed to create test backend")
}
#[tokio::test]
#[ignore]
async fn test_backend_initialization() {
let backend = create_test_backend().await;
assert_eq!(backend.bucket_name(), "test-bucket");
}
#[tokio::test]
#[ignore]
async fn test_put_and_get_object() {
let backend = create_test_backend().await;
let test_data = Bytes::from("Hello, World!");
let test_key = "test/file.txt";
let put_result = backend.put_object("test-bucket", test_key, test_data.clone()).await;
assert!(put_result.is_ok());
let get_result = backend.get_object("test-bucket", test_key).await;
assert!(get_result.is_ok());
assert_eq!(get_result.unwrap(), test_data);
}
}

File diff suppressed because it is too large Load Diff