diff --git a/scripts/run.sh b/scripts/run.sh index 71b1457..7e7ce84 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,3 +1,5 @@ export SOURCE_KEY=foobar export SOURCE_API_URL=http://localhost:3000 +export DEFAULT_ACCOUNT_ID=tttt +export DEFAULT_REPOSITORY_ID=test-2 cargo run diff --git a/src/backends/azure.rs b/src/backends/azure.rs index 752d650..3e602fe 100644 --- a/src/backends/azure.rs +++ b/src/backends/azure.rs @@ -19,7 +19,9 @@ use crate::backends::common::{ use crate::utils::core::replace_first; use crate::utils::errors::{APIError, InternalServerError, ObjectNotFoundError}; -use super::common::{MultipartPart, UploadPartResponse}; +use super::common::{ + ListAllBucketsResult, ListBucket, ListBuckets, MultipartPart, UploadPartResponse, +}; pub struct AzureRepository { pub account_id: String, @@ -326,6 +328,30 @@ impl Repository for AzureRepository { } } + Ok(result) + } + async fn list_buckets_accounts( + &self, + _prefix: String, + _continuation_token: Option, + _delimiter: Option, + _max_keys: NonZeroU32, + ) -> Result> { + let result = ListAllBucketsResult { + buckets: ListBuckets { + bucket: vec![ + ListBucket { + name: "dummy".to_string(), + creation_date: "2025-01-27T09:33:34.000Z".to_string(), + }, + ListBucket { + name: "dummy1".to_string(), + creation_date: "2025-01-27T09:33:34.000Z".to_string(), + }, + ], + }, + }; + Ok(result) } } diff --git a/src/backends/common.rs b/src/backends/common.rs index 86fcab3..94b23b1 100644 --- a/src/backends/common.rs +++ b/src/backends/common.rs @@ -82,6 +82,13 @@ pub trait Repository { delimiter: Option, max_keys: NonZeroU32, ) -> Result>; + async fn list_buckets_accounts( + &self, + prefix: String, + continuation_token: Option, + delimiter: Option, + max_keys: NonZeroU32, + ) -> Result>; } #[derive(Debug, Serialize)] @@ -124,6 +131,24 @@ pub struct CommonPrefix { pub prefix: String, } +#[derive(Debug, Serialize)] +pub struct ListAllBucketsResult { + #[serde(rename = "Buckets")] + pub buckets: ListBuckets, +} +#[derive(Debug, Serialize)] +pub struct ListBuckets { + #[serde(rename = "Bucket")] + pub bucket: Vec, +} +#[derive(Debug, Serialize)] +pub struct ListBucket { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "CreationDate")] + pub creation_date: String, +} + #[derive(Debug, Serialize)] pub struct CreateMultipartUploadResponse { #[serde(rename = "Bucket")] diff --git a/src/backends/s3.rs b/src/backends/s3.rs index 1a0da12..d7cb0e3 100644 --- a/src/backends/s3.rs +++ b/src/backends/s3.rs @@ -20,7 +20,9 @@ use rusoto_s3::{ }; use std::pin::Pin; -use super::common::{MultipartPart, UploadPartResponse}; +use super::common::{ + ListAllBucketsResult, ListBucket, ListBuckets, MultipartPart, UploadPartResponse, +}; pub struct S3Repository { pub account_id: String, @@ -617,4 +619,84 @@ impl Repository for S3Repository { } } } + async fn list_buckets_accounts( + &self, + _prefix: String, + continuation_token: Option, + delimiter: Option, + max_keys: NonZeroU32, + ) -> Result> { + let client: S3Client; + + if self.auth_method == "s3_access_key" { + let credentials = rusoto_credential::StaticProvider::new_minimal( + self.access_key_id.clone().unwrap(), + self.secret_access_key.clone().unwrap(), + ); + client = S3Client::new_with( + rusoto_core::request::HttpClient::new().unwrap(), + credentials, + self.region.clone(), + ); + } else if self.auth_method == "s3_ecs_task_role" { + let credentials = rusoto_credential::ContainerProvider::new(); + client = S3Client::new_with( + rusoto_core::request::HttpClient::new().unwrap(), + credentials, + self.region.clone(), + ); + } else if self.auth_method == "s3_local" { + let credentials = rusoto_credential::ChainProvider::new(); + client = S3Client::new_with( + rusoto_core::request::HttpClient::new().unwrap(), + credentials, + self.region.clone(), + ); + } else { + return Err(Box::new(InternalServerError { + message: format!("Internal Server Error"), + })); + } + + let mut request = ListObjectsV2Request { + bucket: self.bucket.clone(), + delimiter, + max_keys: Some(max_keys.get() as i64), + ..Default::default() + }; + + if let Some(token) = continuation_token { + request.continuation_token = Some(token); + } + + match client.list_objects_v2(request).await { + Ok(output) => { + let result = ListAllBucketsResult { + buckets: ListBuckets { + bucket: output + .common_prefixes + .unwrap_or_default() + .iter() + .map(|item| ListBucket { + name: replace_first( + item.prefix.clone().unwrap_or_else(|| "".to_string()), + "/".to_string(), + "".to_string(), + ), + // TODO: Change from default creation date + creation_date: Utc::now().to_rfc2822(), + }) + .collect(), + }, + }; + + return Ok(result); + } + Err(error) => { + return Err(Box::new(InternalServerError { + message: "Internal Server Error".to_string(), + })); + } + } + } } diff --git a/src/main.rs b/src/main.rs index 767b7f7..0a26260 100644 --- a/src/main.rs +++ b/src/main.rs @@ -573,8 +573,39 @@ async fn list_objects( } #[get("/")] -async fn index() -> impl Responder { - HttpResponse::Ok().body(format!("Source Cooperative Data Proxy v{}", VERSION)) +async fn index(api_client: web::Data) -> impl Responder { + // HttpResponse::Ok().body(format!("Source Cooperative Data Proxy v{}", VERSION)) + + // TODO: Change to some existing default accId & repoId + let account_id = env::var("DEFAULT_ACCOUNT_ID").unwrap(); + let repository_id = env::var("DEFAULT_REPOSITORY_ID").unwrap(); + + if let Ok(client) = api_client + .get_backend_client(&account_id, &repository_id.to_string()) + .await + { + match client + // Pass default static values + .list_buckets_accounts( + "".to_string(), + None, + Some("/".to_string()), + NonZeroU32::new(1000).unwrap(), + ) + .await + { + Ok(res) => match to_string_with_root("ListAllBucketsResult", &res) { + Ok(serialized) => HttpResponse::Ok() + .content_type("application/xml") + .body(serialized), + Err(e) => HttpResponse::InternalServerError().finish(), + }, + Err(_) => HttpResponse::NotFound().finish(), + } + } else { + // Could not find the repository + return HttpResponse::NotFound().finish(); + } } // Main function to set up and run the HTTP server