mirror of
https://git.deuxfleurs.fr/Deuxfleurs/garage.git
synced 2025-04-13 04:44:07 +00:00
cli: add functions to manage admin api tokens
This commit is contained in:
parent
ec0da3b644
commit
1bd7689301
4 changed files with 356 additions and 0 deletions
|
@ -38,6 +38,7 @@ garage_web.workspace = true
|
|||
backtrace.workspace = true
|
||||
bytes.workspace = true
|
||||
bytesize.workspace = true
|
||||
chrono.workspace = true
|
||||
timeago.workspace = true
|
||||
parse_duration.workspace = true
|
||||
hex.workspace = true
|
||||
|
|
227
src/garage/cli/remote/admin_token.rs
Normal file
227
src/garage/cli/remote/admin_token.rs
Normal file
|
@ -0,0 +1,227 @@
|
|||
use format_table::format_table;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use garage_util::error::*;
|
||||
|
||||
use garage_api_admin::api::*;
|
||||
|
||||
use crate::cli::remote::*;
|
||||
use crate::cli::structs::*;
|
||||
|
||||
impl Cli {
|
||||
pub async fn cmd_admin_token(&self, cmd: AdminTokenOperation) -> Result<(), Error> {
|
||||
match cmd {
|
||||
AdminTokenOperation::List => self.cmd_list_admin_tokens().await,
|
||||
AdminTokenOperation::Info { api_token } => self.cmd_admin_token_info(api_token).await,
|
||||
AdminTokenOperation::Create(opt) => self.cmd_create_admin_token(opt).await,
|
||||
AdminTokenOperation::Rename {
|
||||
api_token,
|
||||
new_name,
|
||||
} => self.cmd_rename_admin_token(api_token, new_name).await,
|
||||
AdminTokenOperation::Set(opt) => self.cmd_update_admin_token(opt).await,
|
||||
AdminTokenOperation::Delete { api_token, yes } => {
|
||||
self.cmd_delete_admin_token(api_token, yes).await
|
||||
}
|
||||
AdminTokenOperation::DeleteExpired { yes } => {
|
||||
self.cmd_delete_expired_admin_tokens(yes).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cmd_list_admin_tokens(&self) -> Result<(), Error> {
|
||||
let list = self.api_request(ListAdminTokensRequest).await?;
|
||||
|
||||
let mut table = vec!["ID\tNAME\tEXPIRATION\tSCOPE".to_string()];
|
||||
for tok in list.0.iter() {
|
||||
let scope = if tok.scope.len() > 1 {
|
||||
format!("[{}]", tok.scope.len())
|
||||
} else {
|
||||
tok.scope.get(0).cloned().unwrap_or_default()
|
||||
};
|
||||
let exp = if tok.expired {
|
||||
"expired".to_string()
|
||||
} else {
|
||||
tok.expiration
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or("never".into())
|
||||
};
|
||||
table.push(format!(
|
||||
"{}\t{}\t{}\t{}\t",
|
||||
tok.id.as_deref().unwrap_or("-"),
|
||||
tok.name,
|
||||
exp,
|
||||
scope,
|
||||
));
|
||||
}
|
||||
format_table(table);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_admin_token_info(&self, search: String) -> Result<(), Error> {
|
||||
let info = self
|
||||
.api_request(GetAdminTokenInfoRequest {
|
||||
id: None,
|
||||
search: Some(search),
|
||||
})
|
||||
.await?;
|
||||
|
||||
print_token_info(&info);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_create_admin_token(&self, opt: AdminTokenCreateOp) -> Result<(), Error> {
|
||||
// TODO
|
||||
let res = self
|
||||
.api_request(CreateAdminTokenRequest(UpdateAdminTokenRequestBody {
|
||||
name: opt.name,
|
||||
expiration: opt
|
||||
.expires_in
|
||||
.map(|x| parse_duration::parse::parse(&x))
|
||||
.transpose()
|
||||
.ok_or_message("Invalid duration passed for --expires-in parameter")?
|
||||
.map(|dur| Utc::now() + dur),
|
||||
scope: opt.scope.map(|s| {
|
||||
s.split(",")
|
||||
.map(|x| x.trim().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
if opt.quiet {
|
||||
println!("{}", res.secret_token);
|
||||
} else {
|
||||
println!("This is your secret bearer token, it will not be shown again by Garage:");
|
||||
println!("\n {}\n", res.secret_token);
|
||||
print_token_info(&res.info);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_rename_admin_token(&self, old: String, new: String) -> Result<(), Error> {
|
||||
let token = self
|
||||
.api_request(GetAdminTokenInfoRequest {
|
||||
id: None,
|
||||
search: Some(old),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let info = self
|
||||
.api_request(UpdateAdminTokenRequest {
|
||||
id: token.id.unwrap(),
|
||||
body: UpdateAdminTokenRequestBody {
|
||||
name: Some(new),
|
||||
expiration: None,
|
||||
scope: None,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
print_token_info(&info.0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_update_admin_token(&self, opt: AdminTokenSetOp) -> Result<(), Error> {
|
||||
let token = self
|
||||
.api_request(GetAdminTokenInfoRequest {
|
||||
id: None,
|
||||
search: Some(opt.api_token),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let info = self
|
||||
.api_request(UpdateAdminTokenRequest {
|
||||
id: token.id.unwrap(),
|
||||
body: UpdateAdminTokenRequestBody {
|
||||
name: None,
|
||||
expiration: opt
|
||||
.expires_in
|
||||
.map(|x| parse_duration::parse::parse(&x))
|
||||
.transpose()
|
||||
.ok_or_message("Invalid duration passed for --expires-in parameter")?
|
||||
.map(|dur| Utc::now() + dur),
|
||||
scope: opt.scope.map(|s| {
|
||||
s.split(",")
|
||||
.map(|x| x.trim().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
print_token_info(&info.0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_delete_admin_token(&self, token: String, yes: bool) -> Result<(), Error> {
|
||||
let token = self
|
||||
.api_request(GetAdminTokenInfoRequest {
|
||||
id: None,
|
||||
search: Some(token),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let id = token.id.unwrap();
|
||||
|
||||
if !yes {
|
||||
return Err(Error::Message(format!(
|
||||
"Add the --yes flag to delete API token `{}` ({})",
|
||||
token.name, id
|
||||
)));
|
||||
}
|
||||
|
||||
self.api_request(DeleteAdminTokenRequest { id }).await?;
|
||||
|
||||
println!("Admin API token has been deleted.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cmd_delete_expired_admin_tokens(&self, yes: bool) -> Result<(), Error> {
|
||||
let mut list = self.api_request(ListAdminTokensRequest).await?.0;
|
||||
|
||||
list.retain(|tok| tok.expired);
|
||||
|
||||
if !yes {
|
||||
return Err(Error::Message(format!(
|
||||
"This would delete {} admin API tokens, add the --yes flag to proceed.",
|
||||
list.len(),
|
||||
)));
|
||||
}
|
||||
|
||||
for token in list.iter() {
|
||||
let id = token.id.clone().unwrap();
|
||||
println!("Deleting token `{}` ({})", token.name, id);
|
||||
self.api_request(DeleteAdminTokenRequest { id }).await?;
|
||||
}
|
||||
|
||||
println!("{} admin API tokens have been deleted.", list.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_token_info(token: &GetAdminTokenInfoResponse) {
|
||||
format_table(vec![
|
||||
format!("ID:\t{}", token.id.as_deref().unwrap_or("-")),
|
||||
format!("Name:\t{}", token.name),
|
||||
format!(
|
||||
"Validity:\t{}",
|
||||
token.expired.then_some("EXPIRED").unwrap_or("valid")
|
||||
),
|
||||
format!(
|
||||
"Expiration:\t{}",
|
||||
token
|
||||
.expiration
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or("never".into())
|
||||
),
|
||||
format!("Scope:\t{}", token.scope.to_vec().join(", ")),
|
||||
]);
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod admin_token;
|
||||
pub mod bucket;
|
||||
pub mod cluster;
|
||||
pub mod key;
|
||||
|
@ -35,6 +36,7 @@ impl Cli {
|
|||
}
|
||||
Command::Layout(layout_opt) => self.layout_command_dispatch(layout_opt).await,
|
||||
Command::Bucket(bo) => self.cmd_bucket(bo).await,
|
||||
Command::AdminToken(to) => self.cmd_admin_token(to).await,
|
||||
Command::Key(ko) => self.cmd_key(ko).await,
|
||||
Command::Worker(wo) => self.cmd_worker(wo).await,
|
||||
Command::Block(bo) => self.cmd_block(bo).await,
|
||||
|
|
|
@ -30,6 +30,10 @@ pub enum Command {
|
|||
#[structopt(name = "key", version = garage_version())]
|
||||
Key(KeyOperation),
|
||||
|
||||
/// Operations on admin API tokens
|
||||
#[structopt(name = "admin-token", version = garage_version())]
|
||||
AdminToken(AdminTokenOperation),
|
||||
|
||||
/// Start repair of node data on remote node
|
||||
#[structopt(name = "repair", version = garage_version())]
|
||||
Repair(RepairOpt),
|
||||
|
@ -64,6 +68,10 @@ pub enum Command {
|
|||
AdminApiSchema,
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// ---- garage node ... ----
|
||||
// -------------------------
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum NodeOperation {
|
||||
/// Print the full node ID (public key) of this Garage node, and its publicly reachable IP
|
||||
|
@ -91,6 +99,10 @@ pub struct ConnectNodeOpt {
|
|||
pub(crate) node: String,
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// ---- garage layout ... ----
|
||||
// ---------------------------
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum LayoutOperation {
|
||||
/// Assign role to Garage node
|
||||
|
@ -193,6 +205,10 @@ pub struct SkipDeadNodesOpt {
|
|||
pub(crate) allow_missing_data: bool,
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// ---- garage bucket ... ----
|
||||
// ---------------------------
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum BucketOperation {
|
||||
/// List buckets
|
||||
|
@ -350,6 +366,10 @@ pub struct CleanupIncompleteUploadsOpt {
|
|||
pub buckets: Vec<String>,
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// ---- garage key ... ----
|
||||
// ------------------------
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum KeyOperation {
|
||||
/// List keys
|
||||
|
@ -447,6 +467,92 @@ pub struct KeyImportOpt {
|
|||
pub yes: bool,
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
// ---- garage admin-token ... ----
|
||||
// --------------------------------
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum AdminTokenOperation {
|
||||
/// List all admin API tokens
|
||||
#[structopt(name = "list", version = garage_version())]
|
||||
List,
|
||||
|
||||
/// Fetch info about a specific admin API token
|
||||
#[structopt(name = "info", version = garage_version())]
|
||||
Info {
|
||||
/// Name or prefix of the ID of the token to look up
|
||||
api_token: String,
|
||||
},
|
||||
|
||||
/// Create new admin API token
|
||||
#[structopt(name = "create", version = garage_version())]
|
||||
Create(AdminTokenCreateOp),
|
||||
|
||||
/// Rename an admin API token
|
||||
#[structopt(name = "rename", version = garage_version())]
|
||||
Rename {
|
||||
/// Name or prefix of the ID of the token to rename
|
||||
api_token: String,
|
||||
/// New name of the admintoken
|
||||
new_name: String,
|
||||
},
|
||||
|
||||
/// Set parameters for an admin API token
|
||||
#[structopt(name = "set", version = garage_version())]
|
||||
Set(AdminTokenSetOp),
|
||||
|
||||
/// Delete an admin API token
|
||||
#[structopt(name = "delete", version = garage_version())]
|
||||
Delete {
|
||||
/// Name or prefix of the ID of the token to delete
|
||||
api_token: String,
|
||||
/// Confirm deletion
|
||||
#[structopt(long = "yes")]
|
||||
yes: bool,
|
||||
},
|
||||
|
||||
/// Delete all expired admin API tokens
|
||||
#[structopt(name = "delete-expired", version = garage_version())]
|
||||
DeleteExpired {
|
||||
/// Confirm deletion
|
||||
#[structopt(long = "yes")]
|
||||
yes: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
pub struct AdminTokenCreateOp {
|
||||
/// Set a name for the token
|
||||
pub name: Option<String>,
|
||||
/// Set an expiration time for the token (see docs.rs/parse_duration for date
|
||||
/// format)
|
||||
#[structopt(long = "expires-in")]
|
||||
pub expires_in: Option<String>,
|
||||
/// Set a limited scope for the token (by default, `*`)
|
||||
#[structopt(long = "scope")]
|
||||
pub scope: Option<String>,
|
||||
/// Print only the newly generated API token to stdout
|
||||
#[structopt(short = "q", long = "quiet")]
|
||||
pub quiet: bool,
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
pub struct AdminTokenSetOp {
|
||||
/// Name or prefix of the ID of the token to modify
|
||||
pub api_token: String,
|
||||
/// Set an expiration time for the token (see docs.rs/parse_duration for date
|
||||
/// format)
|
||||
#[structopt(long = "expires-in")]
|
||||
pub expires_in: Option<String>,
|
||||
/// Set a limited scope for the token
|
||||
#[structopt(long = "scope")]
|
||||
pub scope: Option<String>,
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// ---- garage repair ... ----
|
||||
// ---------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
pub struct RepairOpt {
|
||||
/// Launch repair operation on all nodes
|
||||
|
@ -508,6 +614,10 @@ pub enum ScrubCmd {
|
|||
Cancel,
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// ---- garage offline-repair ... ----
|
||||
// -----------------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
pub struct OfflineRepairOpt {
|
||||
/// Confirm the launch of the repair operation
|
||||
|
@ -529,6 +639,10 @@ pub enum OfflineRepairWhat {
|
|||
ObjectCounters,
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// ---- garage stats ... ----
|
||||
// --------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
pub struct StatsOpt {
|
||||
/// Gather statistics from all nodes
|
||||
|
@ -536,6 +650,10 @@ pub struct StatsOpt {
|
|||
pub all_nodes: bool,
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// ---- garage worker ... ----
|
||||
// ---------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||
pub enum WorkerOperation {
|
||||
/// List all workers on Garage node
|
||||
|
@ -579,6 +697,10 @@ pub struct WorkerListOpt {
|
|||
pub errors: bool,
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// ---- garage block ... ----
|
||||
// --------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||
pub enum BlockOperation {
|
||||
/// List all blocks that currently have a resync error
|
||||
|
@ -611,6 +733,10 @@ pub enum BlockOperation {
|
|||
},
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// ---- garage meta ... ----
|
||||
// -------------------------
|
||||
|
||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum MetaOperation {
|
||||
/// Save a snapshot of the metadata db file
|
||||
|
|
Loading…
Reference in a new issue