2 Commits

Author SHA1 Message Date
9f45c2e5da client: error out on non-tty interactivity
All checks were successful
Build and test / build (push) Successful in 3m25s
Build and test / test (push) Successful in 3m41s
Build and test / docs (push) Successful in 5m34s
Build and test / check-license (push) Successful in 1m0s
Build and test / check (push) Successful in 1m53s
2025-12-23 17:40:07 +09:00
107333208c client: add subcommand aliases
All checks were successful
Build and test / check-license (push) Successful in 1m37s
Build and test / check (push) Successful in 1m56s
Build and test / build (push) Successful in 3m12s
Build and test / test (push) Successful in 4m39s
Build and test / docs (push) Successful in 5m45s
2025-12-23 15:45:53 +09:00
6 changed files with 58 additions and 4 deletions

View File

@@ -1,3 +1,5 @@
use std::io::IsTerminal;
use clap::Parser;
use clap_complete::ArgValueCompleter;
use dialoguer::Confirm;
@@ -78,6 +80,15 @@ pub async fn create_users(
.filter_map(|(username, result)| result.as_ref().ok().map(|()| username))
.collect::<Vec<_>>();
if !std::io::stdin().is_terminal()
&& !args.no_password
&& !successfully_created_users.is_empty()
{
anyhow::bail!(
"Cannot prompt for passwords in non-interactive mode. Use --no-password to skip setting passwords."
);
}
for username in successfully_created_users {
if !args.no_password
&& Confirm::new()

View File

@@ -1,3 +1,5 @@
use std::io::IsTerminal;
use clap::Parser;
use clap_complete::ArgValueCompleter;
use dialoguer::Confirm;
@@ -41,6 +43,12 @@ pub async fn drop_databases(
anyhow::bail!("No database names provided");
}
if !std::io::stdin().is_terminal() && !args.yes {
anyhow::bail!(
"Cannot prompt for confirmation in non-interactive mode. Use --yes to automatically confirm."
);
}
if !args.yes {
let confirmation = Confirm::new()
.with_prompt(format!(
@@ -53,7 +61,6 @@ pub async fn drop_databases(
))
.interact()?;
//
if !confirmation {
// TODO: should we return with an error code here?
println!("Aborting drop operation.");

View File

@@ -1,3 +1,5 @@
use std::io::IsTerminal;
use clap::Parser;
use clap_complete::ArgValueCompleter;
use dialoguer::Confirm;
@@ -41,6 +43,12 @@ pub async fn drop_users(
anyhow::bail!("No usernames provided");
}
if !std::io::stdin().is_terminal() && !args.yes {
anyhow::bail!(
"Cannot prompt for confirmation in non-interactive mode. Use --yes to automatically confirm."
);
}
if !args.yes {
let confirmation = Confirm::new()
.with_prompt(format!(

View File

@@ -1,4 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
use std::{
collections::{BTreeMap, BTreeSet},
io::IsTerminal,
};
use anyhow::Context;
use clap::{Args, Parser};
@@ -213,6 +216,11 @@ pub async fn edit_database_privileges(
};
let diffs: BTreeSet<DatabasePrivilegesDiff> = if privs.is_empty() {
if !std::io::stdin().is_terminal() {
anyhow::bail!(
"Cannot launch editor in non-interactive mode. Please provide privileges via command line arguments."
);
}
let privileges_to_change =
edit_privileges_with_editor(&existing_privilege_rows, use_database.as_ref())?;
diff_privileges(&existing_privilege_rows, &privileges_to_change)
@@ -275,7 +283,8 @@ pub async fn edit_database_privileges(
println!("The following changes will be made:\n");
println!("{}", display_privilege_diffs(&diffs));
if !args.yes
if std::io::stdin().is_terminal()
&& !args.yes
&& !Confirm::new()
.with_prompt("Do you want to apply these changes?")
.default(false)

View File

@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{io::IsTerminal, path::PathBuf};
use anyhow::Context;
use clap::Parser;
@@ -88,6 +88,11 @@ pub async fn passwd_user(
.context("Failed to read password from stdin")?;
buffer.trim().to_string()
} else {
if !std::io::stdin().is_terminal() {
anyhow::bail!(
"Cannot prompt for password in non-interactive mode. Use --stdin or --password-file to provide the password."
);
}
read_password_from_stdin_with_double_check(&args.username)?
};

View File

@@ -82,9 +82,11 @@ const EXAMPLES: &str = const_format::concatcp!(
# Show all databases
muscl show-db
muscl sd
# Show which users have privileges on which databases
muscl show-privs
muscl sp
"#,
);
@@ -169,22 +171,27 @@ const EDIT_PRIVS_EXAMPLES: &str = color_print::cstr!(
#[command(subcommand_required = true)]
pub enum ClientCommand {
/// Check whether you are authorized to manage the specified databases or users.
#[command(alias = "ca")]
CheckAuth(CheckAuthArgs),
/// Create one or more databases
#[command(alias = "cd")]
CreateDb(CreateDbArgs),
/// Delete one or more databases
#[command(alias = "dd")]
DropDb(DropDbArgs),
/// Print information about one or more databases
///
/// If no database name is provided, all databases you have access will be shown.
#[command(alias = "sd")]
ShowDb(ShowDbArgs),
/// Print user privileges for one or more databases
///
/// If no database names are provided, all databases you have access to will be shown.
#[command(alias = "sp")]
ShowPrivs(ShowPrivsArgs),
/// Change user privileges for one or more databases. See `edit-privs --help` for details.
@@ -239,27 +246,34 @@ pub enum ClientCommand {
verbatim_doc_comment,
override_usage = "muscl edit-privs [OPTIONS] [ -p <DB_NAME:USER_NAME:[+-]PRIVILEGES>... | <DB_NAME> <USER_NAME> <[+-]PRIVILEGES> ]",
after_long_help = EDIT_PRIVS_EXAMPLES,
alias = "ep",
)]
EditPrivs(EditPrivsArgs),
/// Create one or more users
#[command(alias = "cu")]
CreateUser(CreateUserArgs),
/// Delete one or more users
#[command(alias = "du")]
DropUser(DropUserArgs),
/// Change the MySQL password for a user
#[command(alias = "pu")]
PasswdUser(PasswdUserArgs),
/// Print information about one or more users
///
/// If no username is provided, all users you have access will be shown.
#[command(alias = "su")]
ShowUser(ShowUserArgs),
/// Lock account for one or more users
#[command(alias = "lu")]
LockUser(LockUserArgs),
/// Unlock account for one or more users
#[command(alias = "uu")]
UnlockUser(UnlockUserArgs),
}