use std::io::IsTerminal;
use clap::{CommandFactory, Parser, Subcommand};
use microsandbox_cli::{
commands::{
create, exec, image, inspect, install, list, logs, metrics, ps, pull, registry, remove,
run, self_cmd, snapshot, start, stop, uninstall, volume,
},
log_args::{self, LogArgs},
sandbox_cmd::{self, SandboxArgs},
};
#[derive(Parser)]
#[command(
name = "msb",
version,
about = format!("Microsandbox CLI v{}", env!("CARGO_PKG_VERSION")),
styles = microsandbox_cli::styles::styles()
)]
struct Cli {
#[arg(long, global = true)]
tree: bool,
#[command(flatten)]
logs: LogArgs,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
#[command(hide = true)]
Sandbox(Box<SandboxArgs>),
Run(run::RunArgs),
Create(create::CreateArgs),
Start(start::StartArgs),
Stop(stop::StopArgs),
#[command(visible_alias = "ls")]
List(list::ListArgs),
#[command(name = "status", visible_alias = "ps")]
Status(ps::PsArgs),
Metrics(metrics::MetricsArgs),
#[command(visible_alias = "rm")]
Remove(remove::RemoveArgs),
Exec(exec::ExecArgs),
Logs(logs::LogsArgs),
Image(image::ImageArgs),
Pull(pull::PullArgs),
Registry(registry::RegistryArgs),
#[command(hide = true)]
Images(image::ImageListArgs),
#[command(hide = true)]
Rmi(image::ImageRemoveArgs),
Inspect(inspect::InspectArgs),
#[command(visible_alias = "vol")]
Volume(volume::VolumeArgs),
#[command(visible_alias = "snap")]
Snapshot(snapshot::SnapshotArgs),
Install(install::InstallArgs),
Uninstall(uninstall::UninstallArgs),
#[command(name = "self")]
Self_(self_cmd::SelfArgs),
}
fn main() {
microsandbox_cli::ui::install_panic_hook();
if std::env::var("MSB_PATH").is_err()
&& let Ok(exe) = std::env::current_exe()
{
unsafe { std::env::set_var("MSB_PATH", &exe) };
}
if let Some(tree) = microsandbox_cli::tree::try_show_tree(&Cli::command()) {
println!("{tree}");
return;
}
let cli = Cli::parse();
let log_level = cli.logs.selected_level();
let exit_code = match cli.command {
Commands::Sandbox(args) => {
let sandbox_level = log_level.or(Some(microsandbox_runtime::logging::LogLevel::Info));
log_args::init_tracing(sandbox_level, false);
sandbox_cmd::run(*args, log_level); }
command => {
let ansi = std::io::stderr().is_terminal() && std::env::var_os("NO_COLOR").is_none();
log_args::init_tracing(log_level, ansi);
match run_async_command_anyhow(command, log_level) {
Ok(()) => 0,
Err(e) => render_anyhow_error(&e),
}
}
};
if exit_code != 0 {
std::process::exit(exit_code);
}
}
fn render_anyhow_error(err: &anyhow::Error) -> i32 {
if let Some((name, boot_err)) = find_boot_start_in_chain(err) {
microsandbox_cli::boot_error_render::render(&name, &boot_err);
return 1;
}
if let Some(failed) = find_exec_failed_in_chain(err) {
let cmd = extract_quoted_token_str(&err.to_string())
.or_else(|| extract_quoted_token_str(&failed.message))
.unwrap_or_else(|| "<unknown>".into());
microsandbox_cli::exec_error_render::render(&cmd, &failed);
return microsandbox_cli::exec_error_render::exit_code_for(failed.kind);
}
microsandbox_cli::ui::error(&err.to_string());
1
}
fn find_boot_start_in_chain(
err: &anyhow::Error,
) -> Option<(String, microsandbox_runtime::boot_error::BootError)> {
for cause in err.chain() {
if let Some(microsandbox::MicrosandboxError::BootStart { name, err: b }) =
cause.downcast_ref::<microsandbox::MicrosandboxError>()
{
return Some((name.clone(), b.clone()));
}
}
None
}
fn find_exec_failed_in_chain(
err: &anyhow::Error,
) -> Option<microsandbox_protocol::exec::ExecFailed> {
for cause in err.chain() {
if let Some(microsandbox::MicrosandboxError::ExecFailed(payload)) =
cause.downcast_ref::<microsandbox::MicrosandboxError>()
{
return Some(payload.clone());
}
}
None
}
fn extract_quoted_token_str(s: &str) -> Option<String> {
let start = s.find('"')? + 1;
let rest = &s[start..];
let end = rest.find('"')?;
let name = &rest[..end];
if name.is_empty() {
return None;
}
Some(name.to_string())
}
fn run_async_command_anyhow(
command: Commands,
_log_level: Option<microsandbox::LogLevel>,
) -> anyhow::Result<()> {
let worker_threads = std::thread::available_parallelism()
.map(|count| count.get().clamp(4, 8))
.unwrap_or(4);
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(worker_threads)
.enable_all()
.build()?;
runtime.block_on(async move {
microsandbox::sandbox::spawn_reaper();
match command {
Commands::Sandbox(_) => unreachable!("handled before Tokio starts"),
Commands::Run(args) => run::run(args).await,
Commands::Create(args) => create::run(args).await,
Commands::Start(args) => start::run(args).await,
Commands::Stop(args) => stop::run(args).await,
Commands::List(args) => list::run(args).await,
Commands::Status(args) => ps::run(args).await,
Commands::Metrics(args) => metrics::run(args).await,
Commands::Remove(args) => remove::run(args).await,
Commands::Exec(args) => exec::run(args).await,
Commands::Logs(args) => logs::run(args).await,
Commands::Image(args) => image::run(args).await,
Commands::Pull(args) => image::run_pull(args).await,
Commands::Registry(args) => registry::run(args).await,
Commands::Images(args) => image::run_list(args).await,
Commands::Rmi(args) => image::run_remove(args).await,
Commands::Inspect(args) => inspect::run(args).await,
Commands::Volume(args) => volume::run(args).await,
Commands::Snapshot(args) => snapshot::run(args).await,
Commands::Install(args) => install::run(args).await,
Commands::Uninstall(args) => uninstall::run(args).await,
Commands::Self_(args) => self_cmd::run(args).await,
}
})
}