#[macro_use]
extern crate prettytable;
use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::{generate, Shell};
use clap_verbosity_flag::Verbosity;
use std::path::PathBuf;
use std::os::unix::net::UnixStream as StdUnixStream;
use tokio::net::UnixStream as TokioUnixStream;
use futures::StreamExt;
use crate::{
core::{
bootstrap::bootstrap_server_connection_and_drop_privileges,
protocol::{create_client_to_server_message_stream, Response},
},
server::command::ServerArgs,
};
#[cfg(feature = "mysql-admutils-compatibility")]
use crate::cli::mysql_admutils_compatibility::{mysql_dbadm, mysql_useradm};
mod server;
mod cli;
mod core;
#[cfg(feature = "tui")]
mod tui;
#[derive(Parser, Debug)]
#[command(bin_name = "mysqladm", version, about, disable_help_subcommand = true)]
struct Args {
#[command(subcommand)]
command: Command,
#[arg(
short,
long,
value_name = "PATH",
global = true,
hide_short_help = true
)]
server_socket_path: Option<PathBuf>,
#[arg(
short,
long,
value_name = "PATH",
global = true,
hide_short_help = true
)]
config: Option<PathBuf>,
#[command(flatten)]
verbose: Verbosity,
#[cfg(feature = "tui")]
#[arg(short, long, alias = "tui", global = true)]
interactive: bool,
}
#[derive(Parser, Debug, Clone)]
enum Command {
#[command(flatten)]
Db(cli::database_command::DatabaseCommand),
#[command(flatten)]
User(cli::user_command::UserCommand),
#[command(hide = true)]
Server(server::command::ServerArgs),
#[command(hide = true)]
GenerateCompletions(GenerateCompletionArgs),
}
#[derive(Parser, Debug, Clone)]
struct GenerateCompletionArgs {
#[arg(long, default_value = "bash")]
shell: Shell,
#[arg(long, default_value = "mysqladm")]
command: ToplevelCommands,
}
#[cfg(feature = "mysql-admutils-compatibility")]
#[derive(ValueEnum, Debug, Clone)]
enum ToplevelCommands {
Mysqladm,
MysqlDbadm,
MysqlUseradm,
}
fn main() -> anyhow::Result<()> {
#[cfg(feature = "mysql-admutils-compatibility")]
if handle_mysql_admutils_command()?.is_some() {
return Ok(());
}
let args: Args = Args::parse();
if handle_server_command(&args)?.is_some() {
return Ok(());
}
if handle_generate_completions_command(&args)?.is_some() {
return Ok(());
}
let server_connection =
bootstrap_server_connection_and_drop_privileges(args.server_socket_path, args.config)?;
env_logger::Builder::new()
.filter_level(args.verbose.log_level_filter())
.init();
tokio_run_command(args.command, server_connection)?;
Ok(())
}
fn handle_mysql_admutils_command() -> anyhow::Result<Option<()>> {
let argv0 = std::env::args().next().and_then(|s| {
PathBuf::from(s)
.file_name()
.map(|s| s.to_string_lossy().to_string())
});
match argv0.as_deref() {
Some("mysql-dbadm") => mysql_dbadm::main().map(Some),
Some("mysql-useradm") => mysql_useradm::main().map(Some),
_ => Ok(None),
}
}
fn handle_server_command(args: &Args) -> anyhow::Result<Option<()>> {
match args.command {
Command::Server(ref command) => {
tokio_start_server(
args.server_socket_path.to_owned(),
args.config.to_owned(),
args.verbose.to_owned(),
command.to_owned(),
)?;
Ok(Some(()))
}
_ => Ok(None),
}
}
fn handle_generate_completions_command(args: &Args) -> anyhow::Result<Option<()>> {
match args.command {
Command::GenerateCompletions(ref completion_args) => {
let mut cmd = match completion_args.command {
ToplevelCommands::Mysqladm => Args::command(),
#[cfg(feature = "mysql-admutils-compatibility")]
ToplevelCommands::MysqlDbadm => mysql_dbadm::Args::command(),
#[cfg(feature = "mysql-admutils-compatibility")]
ToplevelCommands::MysqlUseradm => mysql_useradm::Args::command(),
};
let binary_name = cmd.get_bin_name().unwrap().to_owned();
generate(
completion_args.shell,
&mut cmd,
binary_name,
&mut std::io::stdout(),
);
Ok(Some(()))
}
_ => Ok(None),
}
}
fn tokio_start_server(
server_socket_path: Option<PathBuf>,
config_path: Option<PathBuf>,
verbosity: Verbosity,
args: ServerArgs,
) -> anyhow::Result<()> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
server::command::handle_command(server_socket_path, config_path, verbosity, args).await
})
}
fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyhow::Result<()> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let tokio_socket = TokioUnixStream::from_std(server_connection)?;
let mut message_stream = create_client_to_server_message_stream(tokio_socket);
while let Some(Ok(message)) = message_stream.next().await {
match message {
Response::Error(err) => {
anyhow::bail!("{}", err);
}
Response::Ready => break,
message => {
eprintln!("Unexpected message from server: {:?}", message);
}
}
}
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::Server(_) => unreachable!(),
Command::GenerateCompletions(_) => unreachable!(),
}
})
}