forked from Deuxfleurs/garage
Implement DeleteBucket
This commit is contained in:
parent
8395030e48
commit
135858d067
3 changed files with 116 additions and 6 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue