client: rename and merge user/db command modules

This commit is contained in:
2025-11-26 01:25:47 +09:00
parent 39fa228d1c
commit 4fb60f8563
10 changed files with 439 additions and 486 deletions

View File

@@ -1,20 +0,0 @@
use crate::core::protocol::Response;
pub fn erroneous_server_response(
response: Option<Result<Response, std::io::Error>>,
) -> anyhow::Result<()> {
match response {
Some(Ok(Response::Error(e))) => {
anyhow::bail!("Server returned error: {}", e);
}
Some(Err(e)) => {
anyhow::bail!(e);
}
Some(response) => {
anyhow::bail!("Unexpected response from server: {:?}", response);
}
None => {
anyhow::bail!("No response from server");
}
}
}

View File

@@ -1,425 +0,0 @@
use anyhow::Context;
use clap::Parser;
use dialoguer::{Confirm, Password};
use futures_util::{SinkExt, StreamExt};
use crate::core::protocol::{
ClientToServerMessageStream, ListUsersError, MySQLUser, Request, Response,
print_create_users_output_status, print_create_users_output_status_json,
print_drop_users_output_status, print_drop_users_output_status_json,
print_lock_users_output_status, print_lock_users_output_status_json,
print_set_password_output_status, print_unlock_users_output_status,
print_unlock_users_output_status_json,
};
use super::common::erroneous_server_response;
#[derive(Parser, Debug, Clone)]
pub struct UserArgs {
#[clap(subcommand)]
subcmd: UserCommand,
}
#[allow(clippy::enum_variant_names)]
#[derive(Parser, Debug, Clone)]
pub enum UserCommand {
/// Create one or more users
#[command()]
CreateUser(UserCreateArgs),
/// Delete one or more users
#[command()]
DropUser(UserDeleteArgs),
/// Change the MySQL password for a user
#[command()]
PasswdUser(UserPasswdArgs),
/// Print information about one or more users
///
/// If no username is provided, all users you have access will be shown.
#[command()]
ShowUser(UserShowArgs),
/// Lock account for one or more users
#[command()]
LockUser(UserLockArgs),
/// Unlock account for one or more users
#[command()]
UnlockUser(UserUnlockArgs),
}
#[derive(Parser, Debug, Clone)]
pub struct UserCreateArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Do not ask for a password, leave it unset
#[clap(long)]
no_password: bool,
/// Print the information as JSON
///
/// Note that this implies `--no-password`, since the command will become non-interactive.
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserDeleteArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserPasswdArgs {
username: MySQLUser,
#[clap(short, long)]
password_file: Option<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserShowArgs {
#[arg(num_args = 0..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserLockArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserUnlockArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
pub async fn handle_command(
command: UserCommand,
server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
match command {
UserCommand::CreateUser(args) => create_users(args, server_connection).await,
UserCommand::DropUser(args) => drop_users(args, server_connection).await,
UserCommand::PasswdUser(args) => passwd_user(args, server_connection).await,
UserCommand::ShowUser(args) => show_users(args, server_connection).await,
UserCommand::LockUser(args) => lock_users(args, server_connection).await,
UserCommand::UnlockUser(args) => unlock_users(args, server_connection).await,
}
}
async fn create_users(
args: UserCreateArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::CreateUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(anyhow::Error::from(err).context("Failed to communicate with server"));
}
let result = match server_connection.next().await {
Some(Ok(Response::CreateUsers(result))) => result,
response => return erroneous_server_response(response),
};
if args.json {
print_create_users_output_status_json(&result);
} else {
print_create_users_output_status(&result);
let successfully_created_users = result
.iter()
.filter_map(|(username, result)| result.as_ref().ok().map(|_| username))
.collect::<Vec<_>>();
for username in successfully_created_users {
if !args.no_password
&& Confirm::new()
.with_prompt(format!(
"Do you want to set a password for user '{}'?",
username
))
.default(false)
.interact()?
{
let password = read_password_from_stdin_with_double_check(username)?;
let message = Request::PasswdUser(username.to_owned(), password);
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
match server_connection.next().await {
Some(Ok(Response::PasswdUser(result))) => {
print_set_password_output_status(&result, username)
}
response => return erroneous_server_response(response),
}
println!();
}
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}
async fn drop_users(
args: UserDeleteArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::DropUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::DropUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_drop_users_output_status_json(&result);
} else {
print_drop_users_output_status(&result);
}
Ok(())
}
pub fn read_password_from_stdin_with_double_check(username: &MySQLUser) -> anyhow::Result<String> {
Password::new()
.with_prompt(format!("New MySQL password for user '{}'", username))
.with_confirmation(
format!("Retype new MySQL password for user '{}'", username),
"Passwords do not match",
)
.interact()
.map_err(Into::into)
}
async fn passwd_user(
args: UserPasswdArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
// TODO: create a "user" exists check" command
let message = Request::ListUsers(Some(vec![args.username.to_owned()]));
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let response = match server_connection.next().await {
Some(Ok(Response::ListUsers(users))) => users,
response => return erroneous_server_response(response),
};
match response
.get(&args.username)
.unwrap_or(&Err(ListUsersError::UserDoesNotExist))
{
Ok(_) => {}
Err(err) => {
server_connection.send(Request::Exit).await?;
server_connection.close().await.ok();
anyhow::bail!("{}", err.to_error_message(&args.username));
}
}
let password = if let Some(password_file) = args.password_file {
std::fs::read_to_string(password_file)
.context("Failed to read password file")?
.trim()
.to_string()
} else {
read_password_from_stdin_with_double_check(&args.username)?
};
let message = Request::PasswdUser(args.username.to_owned(), password);
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::PasswdUser(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
print_set_password_output_status(&result, &args.username);
Ok(())
}
async fn show_users(
args: UserShowArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
let message = if args.username.is_empty() {
Request::ListUsers(None)
} else {
Request::ListUsers(Some(args.username.to_owned()))
};
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let users = match server_connection.next().await {
Some(Ok(Response::ListUsers(users))) => users
.into_iter()
.filter_map(|(username, result)| match result {
Ok(user) => Some(user),
Err(err) => {
eprintln!("{}", err.to_error_message(&username));
eprintln!("Skipping...");
None
}
})
.collect::<Vec<_>>(),
Some(Ok(Response::ListAllUsers(users))) => match users {
Ok(users) => users,
Err(err) => {
server_connection.send(Request::Exit).await?;
return Err(
anyhow::anyhow!(err.to_error_message()).context("Failed to list all users")
);
}
},
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
println!(
"{}",
serde_json::to_string_pretty(&users).context("Failed to serialize users to JSON")?
);
} else if users.is_empty() {
println!("No users to show.");
} else {
let mut table = prettytable::Table::new();
table.add_row(row![
"User",
"Password is set",
"Locked",
"Databases where user has privileges"
]);
for user in users {
table.add_row(row![
user.user,
user.has_password,
user.is_locked,
user.databases.join("\n")
]);
}
table.printstd();
}
Ok(())
}
async fn lock_users(
args: UserLockArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::LockUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::LockUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_lock_users_output_status_json(&result);
} else {
print_lock_users_output_status(&result);
}
Ok(())
}
async fn unlock_users(
args: UserUnlockArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::UnlockUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::UnlockUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_unlock_users_output_status_json(&result);
} else {
print_unlock_users_output_status(&result);
}
Ok(())
}

View File

@@ -1,6 +1,4 @@
mod common;
pub mod database_command;
pub mod user_command;
pub mod command;
#[cfg(feature = "mysql-admutils-compatibility")]
pub mod mysql_admutils_compatibility;

View File

@@ -2,13 +2,12 @@ use std::collections::BTreeSet;
use anyhow::Context;
use clap::Parser;
use dialoguer::{Confirm, Editor};
use dialoguer::{Confirm, Editor, Password};
use futures_util::{SinkExt, StreamExt};
use nix::unistd::{User, getuid};
use prettytable::{Cell, Row, Table};
use crate::{
cli::common::erroneous_server_response,
core::{
common::yn,
database_privileges::{
@@ -19,17 +18,21 @@ use crate::{
reduce_privilege_diffs,
},
protocol::{
ClientToServerMessageStream, MySQLDatabase, Request, Response,
print_create_databases_output_status, print_create_databases_output_status_json,
print_drop_databases_output_status, print_drop_databases_output_status_json,
print_modify_database_privileges_output_status,
ClientToServerMessageStream, ListUsersError, MySQLDatabase, MySQLUser, Request,
Response, print_create_databases_output_status,
print_create_databases_output_status_json, print_create_users_output_status,
print_create_users_output_status_json, print_drop_databases_output_status,
print_drop_databases_output_status_json, print_drop_users_output_status,
print_drop_users_output_status_json, print_lock_users_output_status,
print_lock_users_output_status_json, print_modify_database_privileges_output_status,
print_set_password_output_status, print_unlock_users_output_status,
print_unlock_users_output_status_json,
},
},
};
#[derive(Parser, Debug, Clone)]
// #[command(next_help_heading = Some(DATABASE_COMMAND_HEADER))]
pub enum DatabaseCommand {
pub enum ClientCommand {
/// Create one or more databases
#[command()]
CreateDb(DatabaseCreateArgs),
@@ -102,6 +105,32 @@ pub enum DatabaseCommand {
///
#[command(verbatim_doc_comment)]
EditDbPrivs(DatabaseEditPrivsArgs),
/// Create one or more users
#[command()]
CreateUser(UserCreateArgs),
/// Delete one or more users
#[command()]
DropUser(UserDeleteArgs),
/// Change the MySQL password for a user
#[command()]
PasswdUser(UserPasswdArgs),
/// Print information about one or more users
///
/// If no username is provided, all users you have access will be shown.
#[command()]
ShowUser(UserShowArgs),
/// Lock account for one or more users
#[command()]
LockUser(UserLockArgs),
/// Unlock account for one or more users
#[command()]
UnlockUser(UserUnlockArgs),
}
#[derive(Parser, Debug, Clone)]
@@ -169,19 +198,108 @@ pub struct DatabaseEditPrivsArgs {
pub yes: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserCreateArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Do not ask for a password, leave it unset
#[clap(long)]
no_password: bool,
/// Print the information as JSON
///
/// Note that this implies `--no-password`, since the command will become non-interactive.
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserDeleteArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserPasswdArgs {
username: MySQLUser,
#[clap(short, long)]
password_file: Option<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserShowArgs {
#[arg(num_args = 0..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserLockArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserUnlockArgs {
#[arg(num_args = 1..)]
username: Vec<MySQLUser>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
pub async fn handle_command(
command: DatabaseCommand,
command: ClientCommand,
server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
match command {
DatabaseCommand::CreateDb(args) => create_databases(args, server_connection).await,
DatabaseCommand::DropDb(args) => drop_databases(args, server_connection).await,
DatabaseCommand::ShowDb(args) => show_databases(args, server_connection).await,
DatabaseCommand::ShowDbPrivs(args) => {
show_database_privileges(args, server_connection).await
ClientCommand::CreateDb(args) => create_databases(args, server_connection).await,
ClientCommand::DropDb(args) => drop_databases(args, server_connection).await,
ClientCommand::ShowDb(args) => show_databases(args, server_connection).await,
ClientCommand::ShowDbPrivs(args) => show_database_privileges(args, server_connection).await,
ClientCommand::EditDbPrivs(args) => edit_database_privileges(args, server_connection).await,
ClientCommand::CreateUser(args) => create_users(args, server_connection).await,
ClientCommand::DropUser(args) => drop_users(args, server_connection).await,
ClientCommand::PasswdUser(args) => passwd_user(args, server_connection).await,
ClientCommand::ShowUser(args) => show_users(args, server_connection).await,
ClientCommand::LockUser(args) => lock_users(args, server_connection).await,
ClientCommand::UnlockUser(args) => unlock_users(args, server_connection).await,
}
DatabaseCommand::EditDbPrivs(args) => {
edit_database_privileges(args, server_connection).await
}
pub fn erroneous_server_response(
response: Option<Result<Response, std::io::Error>>,
) -> anyhow::Result<()> {
match response {
Some(Ok(Response::Error(e))) => {
anyhow::bail!("Server returned error: {}", e);
}
Some(Err(e)) => {
anyhow::bail!(e);
}
Some(response) => {
anyhow::bail!("Unexpected response from server: {:?}", response);
}
None => {
anyhow::bail!("No response from server");
}
}
}
@@ -487,3 +605,295 @@ fn edit_privileges_with_editor(
.context("Could not parse privilege data from editor"),
}
}
async fn create_users(
args: UserCreateArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::CreateUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(anyhow::Error::from(err).context("Failed to communicate with server"));
}
let result = match server_connection.next().await {
Some(Ok(Response::CreateUsers(result))) => result,
response => return erroneous_server_response(response),
};
if args.json {
print_create_users_output_status_json(&result);
} else {
print_create_users_output_status(&result);
let successfully_created_users = result
.iter()
.filter_map(|(username, result)| result.as_ref().ok().map(|_| username))
.collect::<Vec<_>>();
for username in successfully_created_users {
if !args.no_password
&& Confirm::new()
.with_prompt(format!(
"Do you want to set a password for user '{}'?",
username
))
.default(false)
.interact()?
{
let password = read_password_from_stdin_with_double_check(username)?;
let message = Request::PasswdUser(username.to_owned(), password);
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
match server_connection.next().await {
Some(Ok(Response::PasswdUser(result))) => {
print_set_password_output_status(&result, username)
}
response => return erroneous_server_response(response),
}
println!();
}
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}
async fn drop_users(
args: UserDeleteArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::DropUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::DropUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_drop_users_output_status_json(&result);
} else {
print_drop_users_output_status(&result);
}
Ok(())
}
pub fn read_password_from_stdin_with_double_check(username: &MySQLUser) -> anyhow::Result<String> {
Password::new()
.with_prompt(format!("New MySQL password for user '{}'", username))
.with_confirmation(
format!("Retype new MySQL password for user '{}'", username),
"Passwords do not match",
)
.interact()
.map_err(Into::into)
}
async fn passwd_user(
args: UserPasswdArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
// TODO: create a "user" exists check" command
let message = Request::ListUsers(Some(vec![args.username.to_owned()]));
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let response = match server_connection.next().await {
Some(Ok(Response::ListUsers(users))) => users,
response => return erroneous_server_response(response),
};
match response
.get(&args.username)
.unwrap_or(&Err(ListUsersError::UserDoesNotExist))
{
Ok(_) => {}
Err(err) => {
server_connection.send(Request::Exit).await?;
server_connection.close().await.ok();
anyhow::bail!("{}", err.to_error_message(&args.username));
}
}
let password = if let Some(password_file) = args.password_file {
std::fs::read_to_string(password_file)
.context("Failed to read password file")?
.trim()
.to_string()
} else {
read_password_from_stdin_with_double_check(&args.username)?
};
let message = Request::PasswdUser(args.username.to_owned(), password);
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::PasswdUser(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
print_set_password_output_status(&result, &args.username);
Ok(())
}
async fn show_users(
args: UserShowArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
let message = if args.username.is_empty() {
Request::ListUsers(None)
} else {
Request::ListUsers(Some(args.username.to_owned()))
};
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let users = match server_connection.next().await {
Some(Ok(Response::ListUsers(users))) => users
.into_iter()
.filter_map(|(username, result)| match result {
Ok(user) => Some(user),
Err(err) => {
eprintln!("{}", err.to_error_message(&username));
eprintln!("Skipping...");
None
}
})
.collect::<Vec<_>>(),
Some(Ok(Response::ListAllUsers(users))) => match users {
Ok(users) => users,
Err(err) => {
server_connection.send(Request::Exit).await?;
return Err(
anyhow::anyhow!(err.to_error_message()).context("Failed to list all users")
);
}
},
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
println!(
"{}",
serde_json::to_string_pretty(&users).context("Failed to serialize users to JSON")?
);
} else if users.is_empty() {
println!("No users to show.");
} else {
let mut table = prettytable::Table::new();
table.add_row(row![
"User",
"Password is set",
"Locked",
"Databases where user has privileges"
]);
for user in users {
table.add_row(row![
user.user,
user.has_password,
user.is_locked,
user.databases.join("\n")
]);
}
table.printstd();
}
Ok(())
}
async fn lock_users(
args: UserLockArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::LockUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::LockUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_lock_users_output_status_json(&result);
} else {
print_lock_users_output_status(&result);
}
Ok(())
}
async fn unlock_users(
args: UserUnlockArgs,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
if args.username.is_empty() {
anyhow::bail!("No usernames provided");
}
let message = Request::UnlockUsers(args.username.to_owned());
if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok();
anyhow::bail!(err);
}
let result = match server_connection.next().await {
Some(Ok(Response::UnlockUsers(result))) => result,
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_unlock_users_output_status_json(&result);
} else {
print_unlock_users_output_status(&result);
}
Ok(())
}

View File

@@ -5,9 +5,8 @@ use std::path::PathBuf;
use tokio::net::UnixStream as TokioUnixStream;
use crate::{
cli::{
common::erroneous_server_response,
database_command,
client::{
command::{DatabaseEditPrivsArgs, edit_database_privileges, erroneous_server_response},
mysql_admutils_compatibility::{
common::trim_db_name_to_32_chars,
error_messages::{
@@ -187,7 +186,7 @@ fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyh
Command::Drop(args) => drop_databases(args, message_stream).await,
Command::Show(args) => show_databases(args, message_stream).await,
Command::Editperm(args) => {
let edit_privileges_args = database_command::DatabaseEditPrivsArgs {
let edit_privileges_args = DatabaseEditPrivsArgs {
name: Some(args.database),
privs: vec![],
json: false,
@@ -195,8 +194,7 @@ fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyh
yes: false,
};
database_command::edit_database_privileges(edit_privileges_args, message_stream)
.await
edit_database_privileges(edit_privileges_args, message_stream).await
}
}
})

View File

@@ -6,15 +6,13 @@ use std::os::unix::net::UnixStream as StdUnixStream;
use tokio::net::UnixStream as TokioUnixStream;
use crate::{
cli::{
common::erroneous_server_response,
mysql_admutils_compatibility::{
client::{
command::{erroneous_server_response, read_password_from_stdin_with_double_check}, mysql_admutils_compatibility::{
common::trim_user_name_to_32_chars,
error_messages::{
handle_create_user_error, handle_drop_user_error, handle_list_users_error,
},
},
user_command::read_password_from_stdin_with_double_check,
}
},
core::{
bootstrap::bootstrap_server_connection_and_drop_privileges,

View File

@@ -23,11 +23,11 @@ use crate::{
};
#[cfg(feature = "mysql-admutils-compatibility")]
use crate::cli::mysql_admutils_compatibility::{mysql_dbadm, mysql_useradm};
use crate::client::mysql_admutils_compatibility::{mysql_dbadm, mysql_useradm};
mod server;
mod cli;
mod client;
mod core;
#[cfg(feature = "tui")]
@@ -77,10 +77,7 @@ struct Args {
#[derive(Parser, Debug, Clone)]
enum Command {
#[command(flatten)]
Db(cli::database_command::DatabaseCommand),
#[command(flatten)]
User(cli::user_command::UserCommand),
Client(client::command::ClientCommand),
#[command(hide = true)]
Server(server::command::ServerArgs),
@@ -241,11 +238,8 @@ fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyh
}
match command {
Command::User(user_args) => {
cli::user_command::handle_command(user_args, message_stream).await
}
Command::Db(db_args) => {
cli::database_command::handle_command(db_args, message_stream).await
Command::Client(client_args) => {
client::command::handle_command(client_args, message_stream).await
}
Command::Server(_) => unreachable!(),
Command::GenerateCompletions(_) => unreachable!(),