Implement DeleteBucket

This commit is contained in:
Alex Auvolat 2022-01-05 16:23:09 +01:00
parent 8395030e48
commit 135858d067
No known key found for this signature in database
GPG key ID: EDABF9711E244EB1
3 changed files with 116 additions and 6 deletions

View file

@ -123,6 +123,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let allowed = match endpoint.authorization_type() { let allowed = match endpoint.authorization_type() {
Authorization::Read(_) => api_key.allow_read(&bucket_id), Authorization::Read(_) => api_key.allow_read(&bucket_id),
Authorization::Write(_) => api_key.allow_write(&bucket_id), Authorization::Write(_) => api_key.allow_write(&bucket_id),
Authorization::Owner(_) => api_key.allow_owner(&bucket_id),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -199,9 +200,9 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let response = Response::builder().body(empty_body).unwrap(); let response = Response::builder().body(empty_body).unwrap();
Ok(response) Ok(response)
} }
Endpoint::DeleteBucket { .. } => Err(Error::Forbidden( Endpoint::DeleteBucket { .. } => {
"Cannot delete buckets using S3 api, please talk to Garage directly".into(), handle_delete_bucket(&garage, bucket_id, bucket_name, api_key).await
)), }
Endpoint::GetBucketLocation { .. } => handle_get_bucket_location(garage), Endpoint::GetBucketLocation { .. } => handle_get_bucket_location(garage),
Endpoint::GetBucketVersioning { .. } => handle_get_bucket_versioning(), Endpoint::GetBucketVersioning { .. } => handle_get_bucket_versioning(),
Endpoint::ListObjects { Endpoint::ListObjects {

View file

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use hyper::{Body, Request, Response}; use hyper::{Body, Request, Response, StatusCode};
use garage_model::bucket_alias_table::*; use garage_model::bucket_alias_table::*;
use garage_model::bucket_table::Bucket; use garage_model::bucket_table::Bucket;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use garage_model::key_table::Key; use garage_model::key_table::Key;
use garage_model::permission::BucketKeyPerm; use garage_model::permission::BucketKeyPerm;
use garage_table::util::EmptyKey; use garage_table::util::*;
use garage_util::crdt::*; use garage_util::crdt::*;
use garage_util::data::*; use garage_util::data::*;
use garage_util::time::*; use garage_util::time::*;
@ -187,6 +187,100 @@ pub async fn handle_create_bucket(
.unwrap()) .unwrap())
} }
pub async fn handle_delete_bucket(
garage: &Garage,
bucket_id: Uuid,
bucket_name: String,
api_key: Key,
) -> Result<Response<Body>, Error> {
let key_params = api_key
.params()
.ok_or_internal_error("Key should not be deleted at this point")?;
let is_local_alias = matches!(key_params.local_aliases.get(&bucket_name), Some(Some(_)));
let mut bucket = garage
.bucket_helper()
.get_existing_bucket(bucket_id)
.await?;
let bucket_state = bucket.state.as_option().unwrap();
// If the bucket has no other aliases, this is a true deletion.
// Otherwise, it is just an alias removal.
let has_other_global_aliases = bucket_state
.aliases
.items()
.iter()
.filter(|(_, _, active)| *active)
.any(|(n, _, _)| is_local_alias || (*n != bucket_name));
let has_other_local_aliases = bucket_state
.local_aliases
.items()
.iter()
.filter(|(_, _, active)| *active)
.any(|((k, n), _, _)| !is_local_alias || *n != bucket_name || *k != api_key.key_id);
if !has_other_global_aliases && !has_other_local_aliases {
// Delete bucket
// Check bucket is empty
let objects = garage
.object_table
.get_range(&bucket_id, None, Some(DeletedFilter::NotDeleted), 10)
.await?;
if !objects.is_empty() {
return Err(Error::BadRequest(format!(
"Bucket {} is not empty",
bucket_name
)));
}
// --- done checking, now commit ---
// 1. delete bucket alias
if is_local_alias {
garage
.bucket_helper()
.unset_local_bucket_alias(bucket_id, &api_key.key_id, &bucket_name)
.await?;
} else {
garage
.bucket_helper()
.unset_global_bucket_alias(bucket_id, &bucket_name)
.await?;
}
// 2. delete authorization from keys that had access
for (key_id, _) in bucket.authorized_keys() {
garage
.bucket_helper()
.set_bucket_key_permissions(bucket.id, key_id, BucketKeyPerm::NO_PERMISSIONS)
.await?;
}
// 3. delete bucket
bucket.state = Deletable::delete();
garage.bucket_table.insert(&bucket).await?;
} else if is_local_alias {
// Just unalias
garage
.bucket_helper()
.unset_local_bucket_alias(bucket_id, &api_key.key_id, &bucket_name)
.await?;
} else {
// Just unalias (but from global namespace)
garage
.bucket_helper()
.unset_global_bucket_alias(bucket_id, &bucket_name)
.await?;
}
Ok(Response::builder()
.status(StatusCode::NO_CONTENT)
.body(Body::empty())?)
}
fn parse_create_bucket_xml(xml_bytes: &[u8]) -> Option<Option<String>> { fn parse_create_bucket_xml(xml_bytes: &[u8]) -> Option<Option<String>> {
// Returns None if invalid data // Returns None if invalid data
// Returns Some(None) if no location constraint is given // Returns Some(None) if no location constraint is given

View file

@ -789,7 +789,6 @@ impl Endpoint {
GetBucketRequestPayment, GetBucketRequestPayment,
GetBucketTagging, GetBucketTagging,
GetBucketVersioning, GetBucketVersioning,
GetBucketWebsite,
GetObject, GetObject,
GetObjectAcl, GetObjectAcl,
GetObjectLegalHold, GetObjectLegalHold,
@ -813,8 +812,22 @@ impl Endpoint {
] ]
} }
.is_some(); .is_some();
let owner = s3_match! {
@extract
self,
bucket,
[
DeleteBucket,
GetBucketWebsite,
PutBucketWebsite,
DeleteBucketWebsite,
]
}
.is_some();
if readonly { if readonly {
Authorization::Read(bucket) Authorization::Read(bucket)
} else if owner {
Authorization::Owner(bucket)
} else { } else {
Authorization::Write(bucket) Authorization::Write(bucket)
} }
@ -830,6 +843,8 @@ pub enum Authorization<'a> {
Read(&'a str), Read(&'a str),
/// Having Write permission on bucket .0 is required /// Having Write permission on bucket .0 is required
Write(&'a str), Write(&'a str),
/// Having Owner permission on bucket .0 is required
Owner(&'a str),
} }
/// This macro is used to generate part of the code in this module. It must be called only one, and /// This macro is used to generate part of the code in this module. It must be called only one, and