aboutsummaryrefslogtreecommitdiff
path: root/src/api/admin/bucket.rs
diff options
context:
space:
mode:
authorAlex Auvolat <lx@deuxfleurs.fr>2025-01-28 00:22:14 +0100
committerAlex Auvolat <lx@deuxfleurs.fr>2025-01-29 19:26:16 +0100
commit831f2b0207f128d67f061e6f7084337b1cbfefa4 (patch)
treec7a13614200e3fc51e1a1606bd73b161306b5920 /src/api/admin/bucket.rs
parentc1eb1610bab4d0d689dae9389f3fc10c0ab0efdc (diff)
downloadgarage-831f2b0207f128d67f061e6f7084337b1cbfefa4.tar.gz
garage-831f2b0207f128d67f061e6f7084337b1cbfefa4.zip
admin api: make all handlers impls of a single trait
Diffstat (limited to 'src/api/admin/bucket.rs')
-rw-r--r--src/api/admin/bucket.rs522
1 files changed, 284 insertions, 238 deletions
diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs
index 593848f0..d62bfa54 100644
--- a/src/api/admin/bucket.rs
+++ b/src/api/admin/bucket.rs
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::sync::Arc;
-use hyper::{body::Incoming as IncomingBody, Request, Response, StatusCode};
+use async_trait::async_trait;
use garage_util::crdt::*;
use garage_util::data::*;
@@ -18,83 +18,93 @@ use garage_model::s3::object_table::*;
use crate::admin::api::ApiBucketKeyPerm;
use crate::admin::api::{
- ApiBucketQuotas, BucketKeyPermChangeRequest, BucketLocalAlias, CreateBucketRequest,
- GetBucketInfoKey, GetBucketInfoResponse, GetBucketInfoWebsiteResponse, ListBucketsResponseItem,
- UpdateBucketRequest,
+ ApiBucketQuotas, BucketAllowKeyRequest, BucketAllowKeyResponse, BucketDenyKeyRequest,
+ BucketDenyKeyResponse, BucketKeyPermChangeRequest, BucketLocalAlias, CreateBucketRequest,
+ CreateBucketResponse, DeleteBucketRequest, DeleteBucketResponse, GetBucketInfoKey,
+ GetBucketInfoRequest, GetBucketInfoResponse, GetBucketInfoWebsiteResponse,
+ GlobalAliasBucketRequest, GlobalAliasBucketResponse, GlobalUnaliasBucketRequest,
+ GlobalUnaliasBucketResponse, ListBucketsRequest, ListBucketsResponse, ListBucketsResponseItem,
+ LocalAliasBucketRequest, LocalAliasBucketResponse, LocalUnaliasBucketRequest,
+ LocalUnaliasBucketResponse, UpdateBucketRequest, UpdateBucketResponse,
};
-use crate::admin::api_server::ResBody;
use crate::admin::error::*;
+use crate::admin::EndpointHandler;
use crate::common_error::CommonError;
-use crate::helpers::*;
-
-pub async fn handle_list_buckets(garage: &Arc<Garage>) -> Result<Response<ResBody>, Error> {
- let buckets = garage
- .bucket_table
- .get_range(
- &EmptyKey,
- None,
- Some(DeletedFilter::NotDeleted),
- 10000,
- EnumerationOrder::Forward,
- )
- .await?;
- let res = buckets
- .into_iter()
- .map(|b| {
- let state = b.state.as_option().unwrap();
- ListBucketsResponseItem {
- id: hex::encode(b.id),
- global_aliases: state
- .aliases
- .items()
- .iter()
- .filter(|(_, _, a)| *a)
- .map(|(n, _, _)| n.to_string())
- .collect::<Vec<_>>(),
- local_aliases: state
- .local_aliases
- .items()
- .iter()
- .filter(|(_, _, a)| *a)
- .map(|((k, n), _, _)| BucketLocalAlias {
- access_key_id: k.to_string(),
- alias: n.to_string(),
- })
- .collect::<Vec<_>>(),
- }
- })
- .collect::<Vec<_>>();
+#[async_trait]
+impl EndpointHandler for ListBucketsRequest {
+ type Response = ListBucketsResponse;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<ListBucketsResponse, Error> {
+ let buckets = garage
+ .bucket_table
+ .get_range(
+ &EmptyKey,
+ None,
+ Some(DeletedFilter::NotDeleted),
+ 10000,
+ EnumerationOrder::Forward,
+ )
+ .await?;
+
+ let res = buckets
+ .into_iter()
+ .map(|b| {
+ let state = b.state.as_option().unwrap();
+ ListBucketsResponseItem {
+ id: hex::encode(b.id),
+ global_aliases: state
+ .aliases
+ .items()
+ .iter()
+ .filter(|(_, _, a)| *a)
+ .map(|(n, _, _)| n.to_string())
+ .collect::<Vec<_>>(),
+ local_aliases: state
+ .local_aliases
+ .items()
+ .iter()
+ .filter(|(_, _, a)| *a)
+ .map(|((k, n), _, _)| BucketLocalAlias {
+ access_key_id: k.to_string(),
+ alias: n.to_string(),
+ })
+ .collect::<Vec<_>>(),
+ }
+ })
+ .collect::<Vec<_>>();
- Ok(json_ok_response(&res)?)
+ Ok(ListBucketsResponse(res))
+ }
}
-pub async fn handle_get_bucket_info(
- garage: &Arc<Garage>,
- id: Option<String>,
- global_alias: Option<String>,
-) -> Result<Response<ResBody>, Error> {
- let bucket_id = match (id, global_alias) {
- (Some(id), None) => parse_bucket_id(&id)?,
- (None, Some(ga)) => garage
- .bucket_helper()
- .resolve_global_bucket_name(&ga)
- .await?
- .ok_or_else(|| HelperError::NoSuchBucket(ga.to_string()))?,
- _ => {
- return Err(Error::bad_request(
- "Either id or globalAlias must be provided (but not both)",
- ));
- }
- };
+#[async_trait]
+impl EndpointHandler for GetBucketInfoRequest {
+ type Response = GetBucketInfoResponse;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<GetBucketInfoResponse, Error> {
+ let bucket_id = match (self.id, self.global_alias) {
+ (Some(id), None) => parse_bucket_id(&id)?,
+ (None, Some(ga)) => garage
+ .bucket_helper()
+ .resolve_global_bucket_name(&ga)
+ .await?
+ .ok_or_else(|| HelperError::NoSuchBucket(ga.to_string()))?,
+ _ => {
+ return Err(Error::bad_request(
+ "Either id or globalAlias must be provided (but not both)",
+ ));
+ }
+ };
- bucket_info_results(garage, bucket_id).await
+ bucket_info_results(garage, bucket_id).await
+ }
}
async fn bucket_info_results(
garage: &Arc<Garage>,
bucket_id: Uuid,
-) -> Result<Response<ResBody>, Error> {
+) -> Result<GetBucketInfoResponse, Error> {
let bucket = garage
.bucket_helper()
.get_existing_bucket(bucket_id)
@@ -211,181 +221,203 @@ async fn bucket_info_results(
},
};
- Ok(json_ok_response(&res)?)
+ Ok(res)
}
-pub async fn handle_create_bucket(
- garage: &Arc<Garage>,
- req: Request<IncomingBody>,
-) -> Result<Response<ResBody>, Error> {
- let req = parse_json_body::<CreateBucketRequest, _, Error>(req).await?;
+#[async_trait]
+impl EndpointHandler for CreateBucketRequest {
+ type Response = CreateBucketResponse;
- let helper = garage.locked_helper().await;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<CreateBucketResponse, Error> {
+ let helper = garage.locked_helper().await;
- if let Some(ga) = &req.global_alias {
- if !is_valid_bucket_name(ga) {
- return Err(Error::bad_request(format!(
- "{}: {}",
- ga, INVALID_BUCKET_NAME_MESSAGE
- )));
- }
+ if let Some(ga) = &self.global_alias {
+ if !is_valid_bucket_name(ga) {
+ return Err(Error::bad_request(format!(
+ "{}: {}",
+ ga, INVALID_BUCKET_NAME_MESSAGE
+ )));
+ }
- if let Some(alias) = garage.bucket_alias_table.get(&EmptyKey, ga).await? {
- if alias.state.get().is_some() {
- return Err(CommonError::BucketAlreadyExists.into());
+ if let Some(alias) = garage.bucket_alias_table.get(&EmptyKey, ga).await? {
+ if alias.state.get().is_some() {
+ return Err(CommonError::BucketAlreadyExists.into());
+ }
}
}
- }
- if let Some(la) = &req.local_alias {
- if !is_valid_bucket_name(&la.alias) {
- return Err(Error::bad_request(format!(
- "{}: {}",
- la.alias, INVALID_BUCKET_NAME_MESSAGE
- )));
- }
+ if let Some(la) = &self.local_alias {
+ if !is_valid_bucket_name(&la.alias) {
+ return Err(Error::bad_request(format!(
+ "{}: {}",
+ la.alias, INVALID_BUCKET_NAME_MESSAGE
+ )));
+ }
- let key = helper.key().get_existing_key(&la.access_key_id).await?;
- let state = key.state.as_option().unwrap();
- if matches!(state.local_aliases.get(&la.alias), Some(_)) {
- return Err(Error::bad_request("Local alias already exists"));
+ let key = helper.key().get_existing_key(&la.access_key_id).await?;
+ let state = key.state.as_option().unwrap();
+ if matches!(state.local_aliases.get(&la.alias), Some(_)) {
+ return Err(Error::bad_request("Local alias already exists"));
+ }
}
- }
-
- let bucket = Bucket::new();
- garage.bucket_table.insert(&bucket).await?;
- if let Some(ga) = &req.global_alias {
- helper.set_global_bucket_alias(bucket.id, ga).await?;
- }
+ let bucket = Bucket::new();
+ garage.bucket_table.insert(&bucket).await?;
- if let Some(la) = &req.local_alias {
- helper
- .set_local_bucket_alias(bucket.id, &la.access_key_id, &la.alias)
- .await?;
+ if let Some(ga) = &self.global_alias {
+ helper.set_global_bucket_alias(bucket.id, ga).await?;
+ }
- if la.allow.read || la.allow.write || la.allow.owner {
+ if let Some(la) = &self.local_alias {
helper
- .set_bucket_key_permissions(
- bucket.id,
- &la.access_key_id,
- BucketKeyPerm {
- timestamp: now_msec(),
- allow_read: la.allow.read,
- allow_write: la.allow.write,
- allow_owner: la.allow.owner,
- },
- )
+ .set_local_bucket_alias(bucket.id, &la.access_key_id, &la.alias)
.await?;
+
+ if la.allow.read || la.allow.write || la.allow.owner {
+ helper
+ .set_bucket_key_permissions(
+ bucket.id,
+ &la.access_key_id,
+ BucketKeyPerm {
+ timestamp: now_msec(),
+ allow_read: la.allow.read,
+ allow_write: la.allow.write,
+ allow_owner: la.allow.owner,
+ },
+ )
+ .await?;
+ }
}
- }
- bucket_info_results(garage, bucket.id).await
+ Ok(CreateBucketResponse(
+ bucket_info_results(garage, bucket.id).await?,
+ ))
+ }
}
-pub async fn handle_delete_bucket(
- garage: &Arc<Garage>,
- id: String,
-) -> Result<Response<ResBody>, Error> {
- let helper = garage.locked_helper().await;
+#[async_trait]
+impl EndpointHandler for DeleteBucketRequest {
+ type Response = DeleteBucketResponse;
- let bucket_id = parse_bucket_id(&id)?;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<DeleteBucketResponse, Error> {
+ let helper = garage.locked_helper().await;
- let mut bucket = helper.bucket().get_existing_bucket(bucket_id).await?;
- let state = bucket.state.as_option().unwrap();
+ let bucket_id = parse_bucket_id(&self.id)?;
- // Check bucket is empty
- if !helper.bucket().is_bucket_empty(bucket_id).await? {
- return Err(CommonError::BucketNotEmpty.into());
- }
+ let mut bucket = helper.bucket().get_existing_bucket(bucket_id).await?;
+ let state = bucket.state.as_option().unwrap();
- // --- done checking, now commit ---
- // 1. delete authorization from keys that had access
- for (key_id, perm) in bucket.authorized_keys() {
- if perm.is_any() {
- helper
- .set_bucket_key_permissions(bucket.id, key_id, BucketKeyPerm::NO_PERMISSIONS)
- .await?;
+ // Check bucket is empty
+ if !helper.bucket().is_bucket_empty(bucket_id).await? {
+ return Err(CommonError::BucketNotEmpty.into());
}
- }
- // 2. delete all local aliases
- for ((key_id, alias), _, active) in state.local_aliases.items().iter() {
- if *active {
- helper
- .unset_local_bucket_alias(bucket.id, key_id, alias)
- .await?;
+
+ // --- done checking, now commit ---
+ // 1. delete authorization from keys that had access
+ for (key_id, perm) in bucket.authorized_keys() {
+ if perm.is_any() {
+ helper
+ .set_bucket_key_permissions(bucket.id, key_id, BucketKeyPerm::NO_PERMISSIONS)
+ .await?;
+ }
}
- }
- // 3. delete all global aliases
- for (alias, _, active) in state.aliases.items().iter() {
- if *active {
- helper.purge_global_bucket_alias(bucket.id, alias).await?;
+ // 2. delete all local aliases
+ for ((key_id, alias), _, active) in state.local_aliases.items().iter() {
+ if *active {
+ helper
+ .unset_local_bucket_alias(bucket.id, key_id, alias)
+ .await?;
+ }
+ }
+ // 3. delete all global aliases
+ for (alias, _, active) in state.aliases.items().iter() {
+ if *active {
+ helper.purge_global_bucket_alias(bucket.id, alias).await?;
+ }
}
- }
- // 4. delete bucket
- bucket.state = Deletable::delete();
- garage.bucket_table.insert(&bucket).await?;
+ // 4. delete bucket
+ bucket.state = Deletable::delete();
+ garage.bucket_table.insert(&bucket).await?;
- Ok(Response::builder()
- .status(StatusCode::NO_CONTENT)
- .body(empty_body())?)
+ Ok(DeleteBucketResponse)
+ }
}
-pub async fn handle_update_bucket(
- garage: &Arc<Garage>,
- id: String,
- req: Request<IncomingBody>,
-) -> Result<Response<ResBody>, Error> {
- let req = parse_json_body::<UpdateBucketRequest, _, Error>(req).await?;
- let bucket_id = parse_bucket_id(&id)?;
+#[async_trait]
+impl EndpointHandler for UpdateBucketRequest {
+ type Response = UpdateBucketResponse;
- let mut bucket = garage
- .bucket_helper()
- .get_existing_bucket(bucket_id)
- .await?;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<UpdateBucketResponse, Error> {
+ let bucket_id = parse_bucket_id(&self.id)?;
- let state = bucket.state.as_option_mut().unwrap();
-
- if let Some(wa) = req.website_access {
- if wa.enabled {
- state.website_config.update(Some(WebsiteConfig {
- index_document: wa.index_document.ok_or_bad_request(
- "Please specify indexDocument when enabling website access.",
- )?,
- error_document: wa.error_document,
- }));
- } else {
- if wa.index_document.is_some() || wa.error_document.is_some() {
- return Err(Error::bad_request(
- "Cannot specify indexDocument or errorDocument when disabling website access.",
- ));
+ let mut bucket = garage
+ .bucket_helper()
+ .get_existing_bucket(bucket_id)
+ .await?;
+
+ let state = bucket.state.as_option_mut().unwrap();
+
+ if let Some(wa) = self.params.website_access {
+ if wa.enabled {
+ state.website_config.update(Some(WebsiteConfig {
+ index_document: wa.index_document.ok_or_bad_request(
+ "Please specify indexDocument when enabling website access.",
+ )?,
+ error_document: wa.error_document,
+ }));
+ } else {
+ if wa.index_document.is_some() || wa.error_document.is_some() {
+ return Err(Error::bad_request(
+ "Cannot specify indexDocument or errorDocument when disabling website access.",
+ ));
+ }
+ state.website_config.update(None);
}
- state.website_config.update(None);
}
- }
- if let Some(q) = req.quotas {
- state.quotas.update(BucketQuotas {
- max_size: q.max_size,
- max_objects: q.max_objects,
- });
- }
+ if let Some(q) = self.params.quotas {
+ state.quotas.update(BucketQuotas {
+ max_size: q.max_size,
+ max_objects: q.max_objects,
+ });
+ }
- garage.bucket_table.insert(&bucket).await?;
+ garage.bucket_table.insert(&bucket).await?;
- bucket_info_results(garage, bucket_id).await
+ Ok(UpdateBucketResponse(
+ bucket_info_results(garage, bucket_id).await?,
+ ))
+ }
}
// ---- BUCKET/KEY PERMISSIONS ----
+#[async_trait]
+impl EndpointHandler for BucketAllowKeyRequest {
+ type Response = BucketAllowKeyResponse;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<BucketAllowKeyResponse, Error> {
+ let res = handle_bucket_change_key_perm(garage, self.0, true).await?;
+ Ok(BucketAllowKeyResponse(res))
+ }
+}
+
+#[async_trait]
+impl EndpointHandler for BucketDenyKeyRequest {
+ type Response = BucketDenyKeyResponse;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<BucketDenyKeyResponse, Error> {
+ let res = handle_bucket_change_key_perm(garage, self.0, false).await?;
+ Ok(BucketDenyKeyResponse(res))
+ }
+}
+
pub async fn handle_bucket_change_key_perm(
garage: &Arc<Garage>,
- req: Request<IncomingBody>,
+ req: BucketKeyPermChangeRequest,
new_perm_flag: bool,
-) -> Result<Response<ResBody>, Error> {
- let req = parse_json_body::<BucketKeyPermChangeRequest, _, Error>(req).await?;
-
+) -> Result<GetBucketInfoResponse, Error> {
let helper = garage.locked_helper().await;
let bucket_id = parse_bucket_id(&req.bucket_id)?;
@@ -420,66 +452,80 @@ pub async fn handle_bucket_change_key_perm(
// ---- BUCKET ALIASES ----
-pub async fn handle_global_alias_bucket(
- garage: &Arc<Garage>,
- bucket_id: String,
- alias: String,
-) -> Result<Response<ResBody>, Error> {
- let bucket_id = parse_bucket_id(&bucket_id)?;
+#[async_trait]
+impl EndpointHandler for GlobalAliasBucketRequest {
+ type Response = GlobalAliasBucketResponse;
- let helper = garage.locked_helper().await;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<GlobalAliasBucketResponse, Error> {
+ let bucket_id = parse_bucket_id(&self.id)?;
- helper.set_global_bucket_alias(bucket_id, &alias).await?;
+ let helper = garage.locked_helper().await;
- bucket_info_results(garage, bucket_id).await
+ helper
+ .set_global_bucket_alias(bucket_id, &self.alias)
+ .await?;
+
+ Ok(GlobalAliasBucketResponse(
+ bucket_info_results(garage, bucket_id).await?,
+ ))
+ }
}
-pub async fn handle_global_unalias_bucket(
- garage: &Arc<Garage>,
- bucket_id: String,
- alias: String,
-) -> Result<Response<ResBody>, Error> {
- let bucket_id = parse_bucket_id(&bucket_id)?;
+#[async_trait]
+impl EndpointHandler for GlobalUnaliasBucketRequest {
+ type Response = GlobalUnaliasBucketResponse;
- let helper = garage.locked_helper().await;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<GlobalUnaliasBucketResponse, Error> {
+ let bucket_id = parse_bucket_id(&self.id)?;
+
+ let helper = garage.locked_helper().await;
- helper.unset_global_bucket_alias(bucket_id, &alias).await?;
+ helper
+ .unset_global_bucket_alias(bucket_id, &self.alias)
+ .await?;
- bucket_info_results(garage, bucket_id).await
+ Ok(GlobalUnaliasBucketResponse(
+ bucket_info_results(garage, bucket_id).await?,
+ ))
+ }
}
-pub async fn handle_local_alias_bucket(
- garage: &Arc<Garage>,
- bucket_id: String,
- access_key_id: String,
- alias: String,
-) -> Result<Response<ResBody>, Error> {
- let bucket_id = parse_bucket_id(&bucket_id)?;
+#[async_trait]
+impl EndpointHandler for LocalAliasBucketRequest {
+ type Response = LocalAliasBucketResponse;
- let helper = garage.locked_helper().await;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<LocalAliasBucketResponse, Error> {
+ let bucket_id = parse_bucket_id(&self.id)?;
- helper
- .set_local_bucket_alias(bucket_id, &access_key_id, &alias)
- .await?;
+ let helper = garage.locked_helper().await;
- bucket_info_results(garage, bucket_id).await
+ helper
+ .set_local_bucket_alias(bucket_id, &self.access_key_id, &self.alias)
+ .await?;
+
+ Ok(LocalAliasBucketResponse(
+ bucket_info_results(garage, bucket_id).await?,
+ ))
+ }
}
-pub async fn handle_local_unalias_bucket(
- garage: &Arc<Garage>,
- bucket_id: String,
- access_key_id: String,
- alias: String,
-) -> Result<Response<ResBody>, Error> {
- let bucket_id = parse_bucket_id(&bucket_id)?;
+#[async_trait]
+impl EndpointHandler for LocalUnaliasBucketRequest {
+ type Response = LocalUnaliasBucketResponse;
- let helper = garage.locked_helper().await;
+ async fn handle(self, garage: &Arc<Garage>) -> Result<LocalUnaliasBucketResponse, Error> {
+ let bucket_id = parse_bucket_id(&self.id)?;
- helper
- .unset_local_bucket_alias(bucket_id, &access_key_id, &alias)
- .await?;
+ let helper = garage.locked_helper().await;
- bucket_info_results(garage, bucket_id).await
+ helper
+ .unset_local_bucket_alias(bucket_id, &self.access_key_id, &self.alias)
+ .await?;
+
+ Ok(LocalUnaliasBucketResponse(
+ bucket_info_results(garage, bucket_id).await?,
+ ))
+ }
}
// ---- HELPER ----