diff --git a/src/garage/admin.rs b/src/garage/admin.rs index 74b24584..2eb0f187 100644 --- a/src/garage/admin.rs +++ b/src/garage/admin.rs @@ -38,9 +38,9 @@ pub enum AdminRpc { // Replies Ok(String), BucketList(Vec), - BucketInfo(Bucket), + BucketInfo(Bucket, HashMap), KeyList(Vec<(String, String)>), - KeyInfo(Key), + KeyInfo(Key, HashMap), } impl Rpc for AdminRpc { @@ -63,20 +63,7 @@ impl AdminRpcHandler { async fn handle_bucket_cmd(&self, cmd: &BucketOperation) -> Result { match cmd { BucketOperation::List => self.handle_list_buckets().await, - BucketOperation::Info(query) => { - let bucket_id = self - .garage - .bucket_helper() - .resolve_global_bucket_name(&query.name) - .await? - .ok_or_message("Bucket not found")?; - let bucket = self - .garage - .bucket_helper() - .get_existing_bucket(bucket_id) - .await?; - Ok(AdminRpc::BucketInfo(bucket)) - } + BucketOperation::Info(query) => self.handle_bucket_info(query).await, 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, @@ -96,6 +83,52 @@ impl AdminRpcHandler { Ok(AdminRpc::BucketList(bucket_aliases)) } + async fn handle_bucket_info(&self, query: &BucketOpt) -> Result { + let bucket_id = self + .garage + .bucket_helper() + .resolve_global_bucket_name(&query.name) + .await? + .ok_or_message("Bucket not found")?; + + let bucket = self + .garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let mut relevant_keys = HashMap::new(); + for (k, _) in bucket + .state + .as_option() + .unwrap() + .authorized_keys + .items() + .iter() + { + if let Some(key) = self.garage.key_table.get(&EmptyKey, k).await? { + relevant_keys.insert(k.clone(), key); + } + } + for ((k, _), _, _) in bucket + .state + .as_option() + .unwrap() + .local_aliases + .items() + .iter() + { + if relevant_keys.contains_key(k) { + continue; + } + if let Some(key) = self.garage.key_table.get(&EmptyKey, k).await? { + relevant_keys.insert(k.clone(), key); + } + } + + Ok(AdminRpc::BucketInfo(bucket, relevant_keys)) + } + #[allow(clippy::ptr_arg)] async fn handle_create_bucket(&self, name: &String) -> Result { let mut bucket = Bucket::new(); @@ -476,10 +509,7 @@ impl AdminRpcHandler { async fn handle_key_cmd(&self, cmd: &KeyOperation) -> Result { match cmd { KeyOperation::List => self.handle_list_keys().await, - KeyOperation::Info(query) => { - let key = self.get_existing_key(&query.key_pattern).await?; - Ok(AdminRpc::KeyInfo(key)) - } + KeyOperation::Info(query) => self.handle_key_info(query).await, KeyOperation::New(query) => self.handle_create_key(query).await, KeyOperation::Rename(query) => self.handle_rename_key(query).await, KeyOperation::Delete(query) => self.handle_delete_key(query).await, @@ -504,17 +534,22 @@ impl AdminRpcHandler { Ok(AdminRpc::KeyList(key_ids)) } + async fn handle_key_info(&self, query: &KeyOpt) -> Result { + let key = self.get_existing_key(&query.key_pattern).await?; + self.key_info_result(key).await + } + async fn handle_create_key(&self, query: &KeyNewOpt) -> Result { let key = Key::new(query.name.clone()); self.garage.key_table.insert(&key).await?; - Ok(AdminRpc::KeyInfo(key)) + self.key_info_result(key).await } async fn handle_rename_key(&self, query: &KeyRenameOpt) -> Result { let mut key = self.get_existing_key(&query.key_pattern).await?; key.name.update(query.new_name.clone()); self.garage.key_table.insert(&key).await?; - Ok(AdminRpc::KeyInfo(key)) + self.key_info_result(key).await } async fn handle_delete_key(&self, query: &KeyDeleteOpt) -> Result { @@ -577,7 +612,8 @@ impl AdminRpcHandler { } let imported_key = Key::import(&query.key_id, &query.secret_key, &query.name); self.garage.key_table.insert(&imported_key).await?; - Ok(AdminRpc::KeyInfo(imported_key)) + + self.key_info_result(imported_key).await } async fn get_existing_key(&self, pattern: &str) -> Result { @@ -604,6 +640,25 @@ impl AdminRpcHandler { } } + async fn key_info_result(&self, key: Key) -> Result { + let mut relevant_buckets = HashMap::new(); + + for (id, _) in key + .state + .as_option() + .unwrap() + .authorized_buckets + .items() + .iter() + { + if let Some(b) = self.garage.bucket_table.get(id, &EmptyKey).await? { + relevant_buckets.insert(*id, b); + } + } + + Ok(AdminRpc::KeyInfo(key, relevant_buckets)) + } + /// Update **key table** to inform of the new linked bucket async fn update_key_bucket( &self, diff --git a/src/garage/cli/cmd.rs b/src/garage/cli/cmd.rs index b65fea02..1c64f9b5 100644 --- a/src/garage/cli/cmd.rs +++ b/src/garage/cli/cmd.rs @@ -173,8 +173,8 @@ pub async fn cmd_admin( 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); + AdminRpc::BucketInfo(bucket, rk) => { + print_bucket_info(&bucket, &rk); } AdminRpc::KeyList(kl) => { println!("List of keys:"); @@ -182,8 +182,8 @@ pub async fn cmd_admin( println!("{}\t{}", key.0, key.1); } } - AdminRpc::KeyInfo(key) => { - print_key_info(&key); + AdminRpc::KeyInfo(key, rb) => { + print_key_info(&key, &rb); } r => { error!("Unexpected response: {:?}", r); diff --git a/src/garage/cli/util.rs b/src/garage/cli/util.rs index f586d55b..ad48c301 100644 --- a/src/garage/cli/util.rs +++ b/src/garage/cli/util.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use garage_util::crdt::*; use garage_util::data::Uuid; use garage_util::error::*; @@ -5,7 +7,24 @@ use garage_util::error::*; use garage_model::bucket_table::*; use garage_model::key_table::*; -pub fn print_key_info(key: &Key) { +pub fn print_key_info(key: &Key, relevant_buckets: &HashMap) { + let bucket_global_aliases = |b: &Uuid| { + if let Some(bucket) = relevant_buckets.get(b) { + if let Some(p) = bucket.state.as_option() { + return p + .aliases + .items() + .iter() + .filter(|(_, _, active)| *active) + .map(|(a, _, _)| a.clone()) + .collect::>() + .join(", "); + } + } + + "".to_string() + }; + println!("Key name: {}", key.name.get()); println!("Key ID: {}", key.key_id); println!("Secret key: {}", key.secret_key); @@ -16,18 +35,39 @@ pub fn print_key_info(key: &Key) { let mut table = vec![]; for (alias_name, _, alias) in p.local_aliases.items().iter() { if let Some(bucket_id) = alias.as_option() { - table.push(format!("\t{}\t{}", alias_name, hex::encode(bucket_id))); + table.push(format!( + "\t{}\t{}\t{}", + alias_name, + bucket_global_aliases(bucket_id), + hex::encode(bucket_id) + )); } } format_table(table); println!("\nAuthorized buckets:"); let mut table = vec![]; - for (b, perm) in p.authorized_buckets.items().iter() { + for (bucket_id, perm) in p.authorized_buckets.items().iter() { let rflag = if perm.allow_read { "R" } else { " " }; let wflag = if perm.allow_write { "W" } else { " " }; let oflag = if perm.allow_owner { "O" } else { " " }; - table.push(format!("\t{}{}{}\t{:?}", rflag, wflag, oflag, b)); + let local_aliases = p + .local_aliases + .items() + .iter() + .filter(|(_, _, a)| a.as_option() == Some(bucket_id)) + .map(|(a, _, _)| a.clone()) + .collect::>() + .join(", "); + table.push(format!( + "\t{}{}{}\t{}\t{}\t{:?}", + rflag, + wflag, + oflag, + bucket_global_aliases(bucket_id), + local_aliases, + bucket_id + )); } format_table(table); } @@ -37,32 +77,49 @@ pub fn print_key_info(key: &Key) { } } -pub fn print_bucket_info(bucket: &Bucket) { +pub fn print_bucket_info(bucket: &Bucket, relevant_keys: &HashMap) { println!("Bucket: {}", hex::encode(bucket.id)); match &bucket.state { Deletable::Deleted => println!("Bucket is deleted."), Deletable::Present(p) => { + println!("Website access: {}", p.website_access.get()); + println!("\nGlobal aliases:"); for (alias, _, active) in p.aliases.items().iter() { if *active { - println!("- {}", alias); + println!(" {}", alias); } } println!("\nKey-specific aliases:"); + let mut table = vec![]; for ((key_id, alias), _, active) in p.local_aliases.items().iter() { if *active { - println!("- {} {}", key_id, alias); + let key_name = relevant_keys + .get(key_id) + .map(|k| k.name.get().as_str()) + .unwrap_or(""); + table.push(format!("\t{}\t{} ({})", alias, key_id, key_name)); } } + format_table(table); println!("\nAuthorized keys:"); + let mut table = vec![]; for (k, perm) in p.authorized_keys.items().iter() { let rflag = if perm.allow_read { "R" } else { " " }; let wflag = if perm.allow_write { "W" } else { " " }; let oflag = if perm.allow_owner { "O" } else { " " }; - println!("- {}{}{} {}", rflag, wflag, oflag, k); + let key_name = relevant_keys + .get(k) + .map(|k| k.name.get().as_str()) + .unwrap_or(""); + table.push(format!( + "\t{}{}{}\t{} ({})", + rflag, wflag, oflag, k, key_name + )); } + format_table(table); } }; }