#[cfg(unix)]
mod cli {
use anyhow::{bail, Context};
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
use std::time::Duration;
const POLL_INTERVAL: Duration = Duration::from_millis(50);
#[derive(Parser)]
#[command(
name = "cuttlefish",
version,
about = "Client for the cuttlefish daemon"
)]
pub struct Cli {
#[command(subcommand)]
command: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Run {
#[arg(long, default_value = "/tmp/cuttlefish.sock")]
socket: PathBuf,
#[arg(long)]
spec: String,
#[arg(long)]
input: String,
},
Specs {
#[arg(long, default_value = "/tmp/cuttlefish.sock")]
socket: PathBuf,
},
}
pub async fn main() -> anyhow::Result<()> {
match Cli::parse().command {
Cmd::Specs { socket } => specs(&socket).await,
Cmd::Run {
socket,
spec,
input,
} => run(&socket, &spec, &input).await,
}
}
fn client(socket: &Path) -> anyhow::Result<reqwest::Client> {
reqwest::Client::builder()
.unix_socket(socket)
.build()
.context("building the unix-socket client")
}
async fn specs(socket: &Path) -> anyhow::Result<()> {
let body: serde_json::Value = client(socket)?
.get("http://localhost/specs")
.send()
.await
.with_context(|| format!("connecting to daemon at {}", socket.display()))?
.json()
.await?;
println!("{}", serde_json::to_string_pretty(&body)?);
Ok(())
}
async fn run(socket: &Path, spec: &str, input: &str) -> anyhow::Result<()> {
let input: serde_json::Value =
serde_json::from_str(input).context("--input must be JSON")?;
let client = client(socket)?;
let submitted = client
.post("http://localhost/jobs")
.json(&serde_json::json!({ "spec": spec, "input": input }))
.send()
.await
.with_context(|| format!("connecting to daemon at {}", socket.display()))?;
if !submitted.status().is_success() {
let status = submitted.status();
bail!(
"daemon rejected the job: {status} {}",
submitted.text().await?
);
}
let job_id = submitted.json::<serde_json::Value>().await?["job_id"]
.as_str()
.context("daemon response had no job_id")?
.to_string();
loop {
let body: serde_json::Value = client
.get(format!("http://localhost/jobs/{job_id}"))
.send()
.await?
.json()
.await?;
let status = body["status"].as_str().unwrap_or("running");
match status {
"completed" | "failed" | "cancelled" => {
println!("{}", serde_json::to_string_pretty(&body["envelope"])?);
std::process::exit(match status {
"completed" => 0,
"failed" => 1,
_ => 2,
});
}
_ => tokio::time::sleep(POLL_INTERVAL).await,
}
}
}
}
#[cfg(unix)]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
cli::main().await
}
#[cfg(not(unix))]
fn main() {
eprintln!(
"cuttlefish talks to the daemon over a unix domain socket and does not \
run on this platform yet. The library crates are cross-platform; only \
the transport is unix-only."
);
std::process::exit(1);
}