#![deny(unsafe_code)]
use std::process;
#[cfg(feature = "completions")]
use clap::CommandFactory;
mod cli;
mod dispatch;
mod generate;
mod resolve;
mod startup;
use cli::*;
use generate::write_quadlet;
use resolve::*;
use startup::{init_tracing, internal_error_notice, is_mutating, parse_cli};
fn main() {
std::panic::set_hook(Box::new(|info| {
eprintln!("podup: internal error: {info}");
eprintln!("{}", internal_error_notice());
}));
std::thread::Builder::new()
.stack_size(8 * 1024 * 1024)
.name("podup".into())
.spawn(run_to_exit)
.expect("spawn podup worker thread")
.join()
.expect("podup worker thread panicked");
}
fn run_to_exit() {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("build Tokio runtime");
match runtime.block_on(run()) {
Ok(()) => {}
Err(podup::ComposeError::RunExited(code)) => process::exit(code as i32),
#[cfg(feature = "update")]
Err(e @ podup::ComposeError::Update(_)) => {
eprintln!("podup: error: {e}");
process::exit(podup::update::exit_code(&e));
}
Err(e) => {
eprintln!("podup: error: {e}");
process::exit(1);
}
}
}
async fn run() -> podup::Result<()> {
init_tracing();
let cli = parse_cli();
#[cfg(feature = "completions")]
if let Commands::Completions { shell } = cli.command {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout());
return Ok(());
}
#[cfg(feature = "update")]
if let Commands::Update { check, force } = cli.command {
let opts = podup::update::UpdateOptions {
check_only: check,
force,
};
return tokio::task::spawn_blocking(move || podup::update::run(opts))
.await
.map_err(|e| podup::ComposeError::Update(format!("update task failed: {e}")))?;
}
if let Commands::Ls { all, quiet, format } = &cli.command {
let client = podup::podman::connect(cli.socket.as_deref())?;
return podup::list_projects(
&client,
podup::LsOptions {
all: *all,
quiet: *quiet,
json: *format == OutputFormat::Json,
},
)
.await;
}
let compose_files = resolve_compose_files(&cli.file);
let file = podup::parse_files_with_env_files(&compose_files, &cli.env_file)?;
if let Commands::Config {
format,
services,
quiet,
no_interpolate,
resolve_image_digests,
} = &cli.command
{
let parsed = if *no_interpolate {
podup::parse_files_with_env_files_interp(&compose_files, &cli.env_file, false)?
} else {
file
};
let resolved = if *resolve_image_digests {
let client = podup::podman::connect(cli.socket.as_deref())?;
podup::resolve_image_digests(&client, &parsed).await?
} else {
parsed
};
return startup::render_config(&resolved, format, *services, *quiet);
}
let base_dir = resolve_base_dir(cli.project_directory.as_deref(), &compose_files[0]);
let project = resolve_project_name(cli.project, file.name.as_deref(), &base_dir);
if !podup::is_safe_project_name(&project) {
return Err(podup::ComposeError::Unsupported(format!(
"project name {project:?} is not a safe path component: use only ASCII \
letters, digits, '-', '_', '.', not starting with '.', max 128 chars"
)));
}
if let Commands::Generate {
kind: GenerateCommands::Quadlet { output },
} = &cli.command
{
return write_quadlet(&file, &project, output.as_deref());
}
let client = podup::podman::connect(cli.socket.as_deref())?;
let stop_timeout = match &cli.command {
Commands::Up { timeout, .. }
| Commands::Down { timeout, .. }
| Commands::Stop { timeout, .. }
| Commands::Restart { timeout, .. } => *timeout,
_ => None,
};
let scale_overrides: std::collections::HashMap<String, u32> = match &cli.command {
Commands::Up { scale, .. } => scale.iter().cloned().collect(),
Commands::Scale { pairs } => pairs.iter().cloned().collect(),
_ => std::collections::HashMap::new(),
};
let (pull_override, no_build, quiet_pull) = match &cli.command {
Commands::Up {
pull,
no_build,
quiet_pull,
..
} => (pull.clone(), *no_build, *quiet_pull),
Commands::Pull { quiet, policy, .. } => (policy.clone(), false, *quiet),
_ => (None, false, false),
};
let renew_anon_volumes = matches!(
&cli.command,
Commands::Up {
renew_anon_volumes: true,
..
}
);
let engine = podup::Engine::with_base_dir(client, project, base_dir)
.with_stop_timeout(stop_timeout)
.with_scale_overrides(scale_overrides)
.with_up_overrides(pull_override, no_build, quiet_pull)
.with_run_overrides(startup::run_overrides_for(&cli.command))
.with_renew_anon_volumes(renew_anon_volumes);
let _lock = if is_mutating(&cli.command) {
Some(engine.lock_project()?)
} else {
None
};
dispatch::dispatch(&engine, &file, cli.command, &cli.profile).await
}