use isothrum_runner::{Events, Lab, Occupied, Park, RunName, RunOptions, Runner};
use crate::args::{Cli, Command, LabArgs, Mode, OpArgs};
use crate::error::Result;
use crate::exit::Verdict;
use crate::term::{Human, Json, Term};
use crate::text::{human, status_text};
use crate::{locate, values};
pub(crate) async fn run(cli: &Cli, runner: &Runner, term: &Term, var: &dyn Fn(&str) -> Option<String>) -> Result<Verdict> {
let (json, human) = (Json(term), Human(term));
let events: &dyn Events = if cli.json { &json } else { &human };
match &cli.command {
Command::Up { op, replace, lifetime } => {
let lab = open(runner, &op.lab)?;
let mut opts = options(op, var)?;
if *replace {
opts = opts.with_occupied(Occupied::Replace);
}
if let Some(minutes) = lifetime {
opts = opts.with_lifetime(std::time::Duration::from_secs(minutes.saturating_mul(60)));
}
let Ok(run) = lab.up(&opts, events).await else { return Ok(Verdict::Fail) };
if !cli.json {
term.out(run.message.as_deref().unwrap_or("The lab is up.").trim_end());
}
Ok(Verdict::Pass)
}
Command::Down(op) => Ok(Verdict::of(open(runner, &op.lab)?.down(&options(op, var)?, events).await.is_ok())),
Command::Park { lab, mode } => {
let mode = match mode {
Mode::Pause => Park::Pause,
Mode::Shutdown => Park::Shutdown,
};
Ok(Verdict::of(open(runner, lab)?.park(mode, events).await.is_ok()))
}
Command::Resume(op) => {
let Ok(run) = open(runner, &op.lab)?.resume(&options(op, var)?, events).await else {
return Ok(Verdict::Fail);
};
if !cli.json {
term.out(run.message.as_deref().unwrap_or("The lab is up again.").trim_end());
}
Ok(Verdict::Pass)
}
Command::Status(lab) => status(cli.json, &open(runner, lab)?, term).await,
Command::Check(op) => check(cli.json, &open(runner, &op.lab)?, &options(op, var)?, events, term).await,
Command::Provision { op, machine } => {
let lab = open(runner, &op.lab)?;
Ok(Verdict::of(lab.provision(machine.as_deref(), &options(op, var)?, events).await.is_ok()))
}
Command::Events(lab) => print_events(cli.json, runner, &open(runner, lab)?, term),
Command::Reap(args) => reap::run(args, runner, cli.json, term, var).await,
Command::Key => {
term.out(&runner.ssh_public_key().await?);
Ok(Verdict::Pass)
}
}
}
async fn status(json: bool, lab: &Lab<'_>, term: &Term) -> Result<Verdict> {
let status = lab.status().await?;
if json {
term.out(&serde_json::to_string(&status)?);
} else {
for line in status_text(lab.name().as_str(), &status) {
term.out(&line);
}
}
Ok(Verdict::Pass)
}
async fn check(json: bool, lab: &Lab<'_>, opts: &RunOptions, events: &dyn Events, term: &Term) -> Result<Verdict> {
let Ok(report) = lab.check(opts, events).await else {
return Ok(Verdict::Fail);
};
if !json {
let failed = report.results.iter().filter(|r| !r.ok).count();
term.out(&format!("{} passed, {failed} failed", report.results.len() - failed));
}
Ok(Verdict::of(report.ok))
}
fn print_events(json: bool, runner: &Runner, lab: &Lab<'_>, term: &Term) -> Result<Verdict> {
for event in runner.events(lab.name())? {
if json {
term.out(&serde_json::to_string(&event)?);
} else if let Some(line) = human(&event.kind) {
term.out(&format!("{} {:<6} {line}", event.time, event.op.as_str()));
}
}
Ok(Verdict::Pass)
}
fn open<'r>(runner: &'r Runner, args: &LabArgs) -> Result<Lab<'r>> {
let manifest = locate::manifest(&args.lab, args.target.as_deref())?;
let name = args.name.as_deref().map(RunName::new).transpose()?;
Ok(runner.open(&manifest, name)?)
}
fn options(args: &OpArgs, var: &dyn Fn(&str) -> Option<String>) -> Result<RunOptions> {
let mut opts = RunOptions::new()
.with_params(values::params(&args.params)?)
.with_secrets(values::secrets(&args.secrets, var)?);
if let Some(label) = &args.host_label {
opts = opts.with_host_label(label);
}
if let Some(provider) = &args.provider {
opts = opts.with_provider(provider);
}
if let Some(jump) = &args.ssh_jump {
opts = opts.with_ssh_jump(jump);
}
Ok(opts)
}
mod reap;
#[cfg(test)]
mod tests;