New model for buckets #172
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::Create(query) => self.handle_create_bucket(&query.name).await,
|
||||||
BucketOperation::Delete(query) => self.handle_delete_bucket(query).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::Allow(query) => self.handle_bucket_allow(query).await,
|
||||||
BucketOperation::Deny(query) => self.handle_bucket_deny(query).await,
|
BucketOperation::Deny(query) => self.handle_bucket_deny(query).await,
|
||||||
BucketOperation::Website(query) => self.handle_bucket_website(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)))
|
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> {
|
async fn handle_bucket_allow(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
||||||
let bucket_id = self
|
let bucket_id = self
|
||||||
.garage
|
.garage
|
||||||
|
|
|
@ -161,12 +161,15 @@ pub async fn cmd_admin(
|
||||||
}
|
}
|
||||||
AdminRpc::BucketList(bl) => {
|
AdminRpc::BucketList(bl) => {
|
||||||
println!("List of buckets:");
|
println!("List of buckets:");
|
||||||
|
let mut table = vec![];
|
||||||
for alias in bl {
|
for alias in bl {
|
||||||
if let Some(p) = alias.state.get().as_option() {
|
if let Some(p) = alias.state.get().as_option() {
|
||||||
let wflag = if p.website_access { "W" } else { " " };
|
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) => {
|
AdminRpc::BucketInfo(bucket) => {
|
||||||
print_bucket_info(&bucket);
|
print_bucket_info(&bucket);
|
||||||
|
|
|
@ -150,6 +150,14 @@ pub enum BucketOperation {
|
||||||
#[structopt(name = "delete")]
|
#[structopt(name = "delete")]
|
||||||
Delete(DeleteBucketOpt),
|
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
|
/// Allow key to read or write to bucket
|
||||||
#[structopt(name = "allow")]
|
#[structopt(name = "allow")]
|
||||||
Allow(PermBucketOpt),
|
Allow(PermBucketOpt),
|
||||||
|
@ -193,6 +201,29 @@ pub struct DeleteBucketOpt {
|
||||||
pub yes: bool,
|
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)]
|
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||||
pub struct PermBucketOpt {
|
pub struct PermBucketOpt {
|
||||||
/// Access key name or ID
|
/// Access key name or ID
|
||||||
|
|
|
@ -12,17 +12,22 @@ pub fn print_key_info(key: &Key) {
|
||||||
match &key.state {
|
match &key.state {
|
||||||
Deletable::Present(p) => {
|
Deletable::Present(p) => {
|
||||||
println!("\nKey-specific bucket aliases:");
|
println!("\nKey-specific bucket aliases:");
|
||||||
|
let mut table = vec![];
|
||||||
for (alias_name, _, alias) in p.local_aliases.items().iter() {
|
for (alias_name, _, alias) in p.local_aliases.items().iter() {
|
||||||
if let Some(bucket_id) = alias.as_option() {
|
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:");
|
println!("\nAuthorized buckets:");
|
||||||
|
let mut table = vec![];
|
||||||
for (b, perm) in p.authorized_buckets.items().iter() {
|
for (b, perm) in p.authorized_buckets.items().iter() {
|
||||||
let rflag = if perm.allow_read { "R" } else { " " };
|
let rflag = if perm.allow_read { "R" } else { " " };
|
||||||
let wflag = if perm.allow_write { "W" } 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 => {
|
Deletable::Deleted => {
|
||||||
println!("\nKey is deleted.");
|
println!("\nKey is deleted.");
|
||||||
|
@ -41,12 +46,14 @@ pub fn print_bucket_info(bucket: &Bucket) {
|
||||||
println!("- {}", alias);
|
println!("- {}", alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nKey-specific aliases:");
|
println!("\nKey-specific aliases:");
|
||||||
for ((key_id, alias), _, active) in p.local_aliases.items().iter() {
|
for ((key_id, alias), _, active) in p.local_aliases.items().iter() {
|
||||||
if *active {
|
if *active {
|
||||||
println!("- {} {}", key_id, alias);
|
println!("- {} {}", key_id, alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nAuthorized keys:");
|
println!("\nAuthorized keys:");
|
||||||
for (k, perm) in p.authorized_keys.items().iter() {
|
for (k, perm) in p.authorized_keys.items().iter() {
|
||||||
let rflag = if perm.allow_read { "R" } else { " " };
|
let rflag = if perm.allow_read { "R" } else { " " };
|
||||||
|
|
|
@ -14,13 +14,27 @@ impl<'a> BucketHelper<'a> {
|
||||||
&self,
|
&self,
|
||||||
bucket_name: &String,
|
bucket_name: &String,
|
||||||
) -> Result<Option<Uuid>, Error> {
|
) -> Result<Option<Uuid>, Error> {
|
||||||
Ok(self
|
let hexbucket = hex::decode(bucket_name.as_str())
|
||||||
.0
|
.ok()
|
||||||
.bucket_alias_table
|
.map(|by| Uuid::try_from(&by))
|
||||||
.get(&EmptyKey, bucket_name)
|
.flatten();
|
||||||
.await?
|
if let Some(bucket_id) = hexbucket {
|
||||||
.map(|x| x.state.get().as_option().map(|x| x.bucket_id))
|
Ok(self
|
||||||
.flatten())
|
.0
|
||||||
|
.bucket_table
|
||||||
|
.get(&bucket_id, &EmptyKey)
|
||||||
|
.await?
|
||||||
|
.filter(|x| !x.state.is_deleted())
|
||||||
|
.map(|_| bucket_id))
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.bucket_alias_table
|
||||||
|
.get(&EmptyKey, bucket_name)
|
||||||
|
.await?
|
||||||
|
.map(|x| x.state.get().as_option().map(|x| x.bucket_id))
|
||||||
|
.flatten())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::ptr_arg)]
|
#[allow(clippy::ptr_arg)]
|
||||||
|
|
Loading…
Reference in a new issue