#![deny(missing_docs)]
use anyhow::{anyhow, Result};
use clap::{App, Arg};
use log::info;
use psup_impl::{SupervisorBuilder, Task};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
#[derive(Debug, Serialize, Deserialize)]
struct Settings {
socket: PathBuf,
task: Vec<RunTask>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct RunTask {
command: String,
args: Option<Vec<String>>,
envs: Option<HashMap<String, String>>,
daemon: Option<bool>,
retry_limit: Option<usize>,
retry_factor: Option<usize>,
}
impl Into<Task> for RunTask {
fn into(self) -> Task {
Task::new(&self.command)
.args(self.args.unwrap_or(Vec::new()))
.envs(self.envs.unwrap_or(HashMap::new()))
.daemon(self.daemon.unwrap_or(false))
.retry_limit(self.retry_limit.unwrap_or(5))
.retry_factor(self.retry_factor.unwrap_or(0))
}
}
#[doc(hidden)]
#[tokio::main]
async fn main() -> Result<()> {
if std::env::var("RUST_LOG").ok().is_none() {
std::env::set_var("RUST_LOG", "info");
}
pretty_env_logger::init();
let matches = App::new("psup")
.version("1.0")
.about("Process supervisor")
.long_about("Reads the TOML configuration file and spawns supervised child processes.")
.arg(Arg::with_name("config")
.help("Configuration file")
.required(true))
.get_matches();
let config = matches
.value_of("config")
.ok_or_else(|| anyhow!("Configuration file is required!"))?;
let config = std::fs::read_to_string(config).map_err(|e| {
anyhow!(
"Failed to read configuration {} ({})",
config,
e.to_string()
)
})?;
let settings: Settings = toml::from_str(&config)?;
info!("Run {} worker(s)", settings.task.len());
let mut builder = SupervisorBuilder::new().path(settings.socket);
for runner in settings.task.into_iter() {
let task: Task = runner.into();
builder = builder.add_worker(task);
}
builder.build().run().await?;
loop {
std::thread::sleep(std::time::Duration::from_secs(60))
}
}