forked from Deuxfleurs/garage
Implement bucket alias and bucket unalias
This commit is contained in:
parent
5b1117e582
commit
53f71b3a57
5 changed files with 252 additions and 10 deletions
|
@ -77,6 +77,8 @@ impl AdminRpcHandler {
|
|||
}
|
||||
BucketOperation::Create(query) => self.handle_create_bucket(&query.name).await,
|
||||
BucketOperation::Delete(query) => self.handle_delete_bucket(query).await,
|
||||
BucketOperation::Alias(query) => self.handle_alias_bucket(query).await,
|
||||
BucketOperation::Unalias(query) => self.handle_unalias_bucket(query).await,
|
||||
BucketOperation::Allow(query) => self.handle_bucket_allow(query).await,
|
||||
BucketOperation::Deny(query) => self.handle_bucket_deny(query).await,
|
||||
BucketOperation::Website(query) => self.handle_bucket_website(query).await,
|
||||
|
@ -193,6 +195,191 @@ impl AdminRpcHandler {
|
|||
Ok(AdminRpc::Ok(format!("Bucket {} was deleted.", query.name)))
|
||||
}
|
||||
|
||||
async fn handle_alias_bucket(&self, query: &AliasBucketOpt) -> Result<AdminRpc, Error> {
|
||||
let bucket_id = self
|
||||
.garage
|
||||
.bucket_helper()
|
||||
.resolve_global_bucket_name(&query.existing_bucket)
|
||||
.await?
|
||||
.ok_or_message("Bucket not found")?;
|
||||
let mut bucket = self
|
||||
.garage
|
||||
.bucket_helper()
|
||||
.get_existing_bucket(bucket_id)
|
||||
.await?;
|
||||
|
||||
if let Some(key_local) = &query.local {
|
||||
let mut key = self.get_existing_key(key_local).await?;
|
||||
let mut key_param = key.state.as_option_mut().unwrap();
|
||||
|
||||
if let Some(Deletable::Present(existing_alias)) =
|
||||
key_param.local_aliases.get(&query.new_name)
|
||||
{
|
||||
if *existing_alias == bucket_id {
|
||||
return Ok(AdminRpc::Ok(format!(
|
||||
"Alias {} already points to bucket {:?} in namespace of key {}",
|
||||
query.new_name, bucket_id, key.key_id
|
||||
)));
|
||||
} else {
|
||||
return Err(Error::Message(format!("Alias {} already exists and points to different bucket: {:?} in namespace of key {}", query.new_name, existing_alias, key.key_id)));
|
||||
}
|
||||
}
|
||||
|
||||
key_param.local_aliases = key_param
|
||||
.local_aliases
|
||||
.update_mutator(query.new_name.clone(), Deletable::present(bucket_id));
|
||||
self.garage.key_table.insert(&key).await?;
|
||||
|
||||
let mut bucket_p = bucket.state.as_option_mut().unwrap();
|
||||
bucket_p.local_aliases = bucket_p
|
||||
.local_aliases
|
||||
.update_mutator((key.key_id.clone(), query.new_name.clone()), true);
|
||||
self.garage.bucket_table.insert(&bucket).await?;
|
||||
|
||||
Ok(AdminRpc::Ok(format!(
|
||||
"Alias {} created to bucket {:?} in namespace of key {}",
|
||||
query.new_name, bucket_id, key.key_id
|
||||
)))
|
||||
} else {
|
||||
let mut alias = self
|
||||
.garage
|
||||
.bucket_alias_table
|
||||
.get(&EmptyKey, &query.new_name)
|
||||
.await?
|
||||
.unwrap_or(BucketAlias {
|
||||
name: query.new_name.clone(),
|
||||
state: Lww::new(Deletable::delete()),
|
||||
});
|
||||
|
||||
if let Some(existing_alias) = alias.state.get().as_option() {
|
||||
if existing_alias.bucket_id == bucket_id {
|
||||
return Ok(AdminRpc::Ok(format!(
|
||||
"Alias {} already points to bucket {:?}",
|
||||
query.new_name, bucket_id
|
||||
)));
|
||||
} else {
|
||||
return Err(Error::Message(format!(
|
||||
"Alias {} already exists and points to different bucket: {:?}",
|
||||
query.new_name, existing_alias.bucket_id
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Checks ok, add alias
|
||||
alias.state.update(Deletable::present(AliasParams {
|
||||
bucket_id,
|
||||
website_access: false,
|
||||
}));
|
||||
self.garage.bucket_alias_table.insert(&alias).await?;
|
||||
|
||||
let mut bucket_p = bucket.state.as_option_mut().unwrap();
|
||||
bucket_p.aliases = bucket_p
|
||||
.aliases
|
||||
.update_mutator(query.new_name.clone(), true);
|
||||
self.garage.bucket_table.insert(&bucket).await?;
|
||||
|
||||
Ok(AdminRpc::Ok(format!(
|
||||
"Alias {} created to bucket {:?}",
|
||||
query.new_name, bucket_id
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_unalias_bucket(&self, query: &UnaliasBucketOpt) -> Result<AdminRpc, Error> {
|
||||
if let Some(key_local) = &query.local {
|
||||
let mut key = self.get_existing_key(key_local).await?;
|
||||
|
||||
let bucket_id = key
|
||||
.state
|
||||
.as_option()
|
||||
.unwrap()
|
||||
.local_aliases
|
||||
.get(&query.name)
|
||||
.map(|a| a.into_option())
|
||||
.flatten()
|
||||
.ok_or_message("Bucket not found")?;
|
||||
let mut bucket = self
|
||||
.garage
|
||||
.bucket_helper()
|
||||
.get_existing_bucket(bucket_id)
|
||||
.await?;
|
||||
let mut bucket_state = bucket.state.as_option_mut().unwrap();
|
||||
|
||||
let has_other_aliases = bucket_state
|
||||
.aliases
|
||||
.items()
|
||||
.iter()
|
||||
.any(|(_, _, active)| *active)
|
||||
|| bucket_state
|
||||
.local_aliases
|
||||
.items()
|
||||
.iter()
|
||||
.any(|((k, n), _, active)| *k == key.key_id && *n == query.name && *active);
|
||||
if !has_other_aliases {
|
||||
return Err(Error::Message(format!("Bucket {} doesn't have other aliases, please delete it instead of just unaliasing.", query.name)));
|
||||
}
|
||||
|
||||
let mut key_param = key.state.as_option_mut().unwrap();
|
||||
key_param.local_aliases = key_param
|
||||
.local_aliases
|
||||
.update_mutator(query.name.clone(), Deletable::delete());
|
||||
self.garage.key_table.insert(&key).await?;
|
||||
|
||||
bucket_state.local_aliases = bucket_state
|
||||
.local_aliases
|
||||
.update_mutator((key.key_id.clone(), query.name.clone()), false);
|
||||
self.garage.bucket_table.insert(&bucket).await?;
|
||||
|
||||
Ok(AdminRpc::Ok(format!(
|
||||
"Bucket alias {} deleted from namespace of key {}",
|
||||
query.name, key.key_id
|
||||
)))
|
||||
} else {
|
||||
let bucket_id = self
|
||||
.garage
|
||||
.bucket_helper()
|
||||
.resolve_global_bucket_name(&query.name)
|
||||
.await?
|
||||
.ok_or_message("Bucket not found")?;
|
||||
let mut bucket = self
|
||||
.garage
|
||||
.bucket_helper()
|
||||
.get_existing_bucket(bucket_id)
|
||||
.await?;
|
||||
let mut bucket_state = bucket.state.as_option_mut().unwrap();
|
||||
|
||||
let has_other_aliases = bucket_state
|
||||
.aliases
|
||||
.items()
|
||||
.iter()
|
||||
.any(|(name, _, active)| *name != query.name && *active)
|
||||
|| bucket_state
|
||||
.local_aliases
|
||||
.items()
|
||||
.iter()
|
||||
.any(|(_, _, active)| *active);
|
||||
if !has_other_aliases {
|
||||
return Err(Error::Message(format!("Bucket {} doesn't have other aliases, please delete it instead of just unaliasing.", query.name)));
|
||||
}
|
||||
|
||||
let mut alias = self
|
||||
.garage
|
||||
.bucket_alias_table
|
||||
.get(&EmptyKey, &query.name)
|
||||
.await?
|
||||
.ok_or_message("Internal error: alias not found")?;
|
||||
alias.state.update(Deletable::delete());
|
||||
self.garage.bucket_alias_table.insert(&alias).await?;
|
||||
|
||||
bucket_state.aliases = bucket_state
|
||||
.aliases
|
||||
.update_mutator(query.name.clone(), false);
|
||||
self.garage.bucket_table.insert(&bucket).await?;
|
||||
|
||||
Ok(AdminRpc::Ok(format!("Bucket alias {} deleted", query.name)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_bucket_allow(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
||||
let bucket_id = self
|
||||
.garage
|
||||
|
|
|
@ -161,12 +161,15 @@ pub async fn cmd_admin(
|
|||
}
|
||||
AdminRpc::BucketList(bl) => {
|
||||
println!("List of buckets:");
|
||||
let mut table = vec![];
|
||||
for alias in bl {
|
||||
if let Some(p) = alias.state.get().as_option() {
|
||||
let wflag = if p.website_access { "W" } else { " " };
|
||||
println!("- {} {} {:?}", wflag, alias.name, p.bucket_id);
|
||||
table.push(format!("{}\t{}\t{:?}", wflag, alias.name, p.bucket_id));
|
||||
}
|
||||
}
|
||||
format_table(table);
|
||||
println!("Buckets that don't have a global alias (i.e. that only exist in the namespace of an access key) are not shown.");
|
||||
}
|
||||
AdminRpc::BucketInfo(bucket) => {
|
||||
print_bucket_info(&bucket);
|
||||
|
|
|
@ -150,6 +150,14 @@ pub enum BucketOperation {
|
|||
#[structopt(name = "delete")]
|
||||
Delete(DeleteBucketOpt),
|
||||
|
||||
/// Alias bucket under new name
|
||||
#[structopt(name = "alias")]
|
||||
Alias(AliasBucketOpt),
|
||||
|
||||
/// Remove bucket alias
|
||||
#[structopt(name = "unalias")]
|
||||
Unalias(UnaliasBucketOpt),
|
||||
|
||||
/// Allow key to read or write to bucket
|
||||
#[structopt(name = "allow")]
|
||||
Allow(PermBucketOpt),
|
||||
|
@ -193,6 +201,29 @@ pub struct DeleteBucketOpt {
|
|||
pub yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||
pub struct AliasBucketOpt {
|
||||
/// Existing bucket name (its alias in global namespace or its full hex uuid)
|
||||
pub existing_bucket: String,
|
||||
|
||||
/// New bucket name
|
||||
pub new_name: String,
|
||||
|
||||
/// Make this alias local to the specified access key
|
||||
#[structopt(long = "local")]
|
||||
pub local: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||
pub struct UnaliasBucketOpt {
|
||||
/// Bucket name
|
||||
pub name: String,
|
||||
|
||||
/// Unalias in bucket namespace local to this access key
|
||||
#[structopt(long = "local")]
|
||||
pub local: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||
pub struct PermBucketOpt {
|
||||
/// Access key name or ID
|
||||
|
|
|
@ -12,17 +12,22 @@ pub fn print_key_info(key: &Key) {
|
|||
match &key.state {
|
||||
Deletable::Present(p) => {
|
||||
println!("\nKey-specific bucket aliases:");
|
||||
let mut table = vec![];
|
||||
for (alias_name, _, alias) in p.local_aliases.items().iter() {
|
||||
if let Some(bucket_id) = alias.as_option() {
|
||||
println!("- {} {:?}", alias_name, bucket_id);
|
||||
table.push(format!("\t{}\t{}", alias_name, hex::encode(bucket_id)));
|
||||
}
|
||||
}
|
||||
format_table(table);
|
||||
|
||||
println!("\nAuthorized buckets:");
|
||||
let mut table = vec![];
|
||||
for (b, perm) in p.authorized_buckets.items().iter() {
|
||||
let rflag = if perm.allow_read { "R" } else { " " };
|
||||
let wflag = if perm.allow_write { "W" } else { " " };
|
||||
println!("- {}{} {:?}", rflag, wflag, b);
|
||||
table.push(format!("\t{}{}\t{:?}", rflag, wflag, b));
|
||||
}
|
||||
format_table(table);
|
||||
}
|
||||
Deletable::Deleted => {
|
||||
println!("\nKey is deleted.");
|
||||
|
@ -41,12 +46,14 @@ pub fn print_bucket_info(bucket: &Bucket) {
|
|||
println!("- {}", alias);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nKey-specific aliases:");
|
||||
for ((key_id, alias), _, active) in p.local_aliases.items().iter() {
|
||||
if *active {
|
||||
println!("- {} {}", key_id, alias);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nAuthorized keys:");
|
||||
for (k, perm) in p.authorized_keys.items().iter() {
|
||||
let rflag = if perm.allow_read { "R" } else { " " };
|
||||
|
|
|
@ -14,6 +14,19 @@ impl<'a> BucketHelper<'a> {
|
|||
&self,
|
||||
bucket_name: &String,
|
||||
) -> Result<Option<Uuid>, Error> {
|
||||
let hexbucket = hex::decode(bucket_name.as_str())
|
||||
.ok()
|
||||
.map(|by| Uuid::try_from(&by))
|
||||
.flatten();
|
||||
if let Some(bucket_id) = hexbucket {
|
||||
Ok(self
|
||||
.0
|
||||
.bucket_table
|
||||
.get(&bucket_id, &EmptyKey)
|
||||
.await?
|
||||
.filter(|x| !x.state.is_deleted())
|
||||
.map(|_| bucket_id))
|
||||
} else {
|
||||
Ok(self
|
||||
.0
|
||||
.bucket_alias_table
|
||||
|
@ -22,6 +35,7 @@ impl<'a> BucketHelper<'a> {
|
|||
.map(|x| x.state.get().as_option().map(|x| x.bucket_id))
|
||||
.flatten())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub async fn get_existing_bucket(&self, bucket_id: Uuid) -> Result<Bucket, Error> {
|
||||
|
|
Loading…
Reference in a new issue