use std::path::PathBuf;
use std::process;
use clap::{Parser, Subcommand};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(
name = "podup",
version,
about = "docker-compose translator for Podman"
)]
struct Cli {
#[arg(short, long, default_value = "docker-compose.yml")]
file: PathBuf,
#[arg(short, long, default_value = "podup")]
project: String,
#[arg(long, env = "PODMAN_SOCKET")]
socket: Option<String>,
#[arg(long, value_delimiter = ',', global = true)]
profile: Vec<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Up {
#[arg(short, long)]
detach: bool,
#[arg(long)]
build: bool,
#[arg(short, long)]
watch: bool,
#[arg(long)]
remove_orphans: bool,
#[arg(long)]
no_recreate: bool,
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Down {
#[arg(short = 'v', long)]
volumes: bool,
},
Start {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Stop {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Build {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Rm {
#[arg(short, long)]
force: bool,
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Kill {
#[arg(short, long, default_value = "SIGKILL")]
signal: String,
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Pause {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Unpause {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Run {
service: String,
#[arg(long, default_value_t = true)]
rm: bool,
#[arg(short, long)]
detach: bool,
#[arg(short, long = "env")]
env_overrides: Vec<String>,
#[arg(long)]
name: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
cmd: Vec<String>,
},
Cp {
src: String,
dst: String,
},
Ps,
Top {
#[arg(trailing_var_arg = true)]
services: Vec<String>,
},
Port {
service: String,
private_port: u16,
#[arg(long, default_value = "tcp")]
proto: String,
},
Images,
Logs {
service: Option<String>,
#[arg(short, long)]
follow: bool,
},
Exec {
service: String,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
cmd: Vec<String>,
},
Pull,
Restart {
service: Option<String>,
},
Config,
Watch,
}
#[tokio::main]
async fn main() {
match run().await {
Ok(()) => {}
Err(podup::ComposeError::RunExited(code)) => process::exit(code as i32),
Err(e) => {
eprintln!("error: {e}");
process::exit(1);
}
}
}
async fn run() -> podup::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
let cli = Cli::parse();
let file = podup::parse_file(&cli.file)?;
if matches!(cli.command, Commands::Config) {
let yaml = serde_yaml::to_string(&file).map_err(podup::ComposeError::Parse)?;
println!("{yaml}");
return Ok(());
}
let docker = podup::podman::connect(cli.socket.as_deref())?;
let base_dir = cli
.file
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_default();
let engine = podup::Engine::with_base_dir(docker, cli.project, base_dir);
match cli.command {
Commands::Up {
detach,
build,
watch,
remove_orphans,
no_recreate,
services,
} => {
if remove_orphans {
engine.remove_orphans(&file).await?;
}
if build {
engine.build_all(&file, &services).await?;
}
engine
.up_with_options(&file, detach, &cli.profile, &services, no_recreate)
.await?;
if watch {
engine.watch(&file).await?;
} else if !detach {
engine.attach_logs(&file).await?;
let _ = engine.stop(&file, &[]).await;
}
}
Commands::Down { volumes } => engine.down_with_options(&file, volumes).await?,
Commands::Start { services } => engine.start(&file, &services).await?,
Commands::Stop { services } => engine.stop(&file, &services).await?,
Commands::Build { services } => engine.build_all(&file, &services).await?,
Commands::Rm { force, services } => engine.rm(&file, &services, force).await?,
Commands::Kill { signal, services } => engine.kill(&file, &services, &signal).await?,
Commands::Pause { services } => engine.pause(&file, &services).await?,
Commands::Unpause { services } => engine.unpause(&file, &services).await?,
Commands::Run {
service,
rm,
detach,
env_overrides,
name,
cmd,
} => {
engine
.run(
&file,
&service,
podup::RunOptions {
cmd,
rm,
detach,
env_overrides,
name_override: name,
},
)
.await?
}
Commands::Cp { src, dst } => engine.cp(&file, &src, &dst).await?,
Commands::Ps => engine.ps(&file).await?,
Commands::Top { services } => engine.top(&file, &services).await?,
Commands::Port {
service,
private_port,
proto,
} => engine.port(&file, &service, private_port, &proto).await?,
Commands::Images => engine.images(&file).await?,
Commands::Logs { service, follow } => {
engine.logs(&file, service.as_deref(), follow).await?
}
Commands::Exec { service, cmd } => engine.exec(&file, &service, cmd).await?,
Commands::Pull => engine.pull(&file).await?,
Commands::Restart { service } => engine.restart(&file, service.as_deref()).await?,
Commands::Config => unreachable!("handled above"),
Commands::Watch => engine.watch(&file).await?,
}
Ok(())
}