150 lines
5.0 KiB
Rust
150 lines
5.0 KiB
Rust
//! `brokerd`: the broker role. `serve` takes the audit lock, binds both sockets, and serves.
|
|
|
|
use std::ffi::OsString;
|
|
use std::path::PathBuf;
|
|
use std::process::ExitCode;
|
|
use std::sync::Arc;
|
|
|
|
use brokerd::audit::{AuditError, RECOVERED_NOTICE};
|
|
use brokerd::config::Config;
|
|
use brokerd::container::Podman;
|
|
use brokerd::runner::{Refusing, Runtime};
|
|
use brokerd::serve::{self, ServeError};
|
|
|
|
const USAGE: &str = "usage: brokerd serve --config <path> [--accept-break]";
|
|
/// Where the owner looks when brokerd cannot start: its config, a directory or a socket.
|
|
const START_FAILED: &str = "see docs/runbook.md#brokerd-start-failed";
|
|
/// Where the owner looks when brokerd stops serving after it started.
|
|
const LISTENER_LOST: &str = "see docs/runbook.md#brokerd-listener-lost";
|
|
|
|
fn main() -> ExitCode {
|
|
// The first argument must be `serve`.
|
|
// `args_os`: a path need not be UTF-8, and `args` would panic on one that is not.
|
|
let mut args = std::env::args_os().skip(1);
|
|
match args.next() {
|
|
Some(command) if command == "serve" => {}
|
|
_ => return usage(),
|
|
}
|
|
|
|
// `--config <path>` exactly once and `--accept-break` at most once, in any order.
|
|
let (config_path, accept_break) = match parse_serve(args) {
|
|
Ok(pair) => pair,
|
|
Err(()) => return usage(),
|
|
};
|
|
|
|
// Read the config before touching the filesystem: a bad config must make nothing.
|
|
let cfg = match Config::load(&config_path) {
|
|
Ok(cfg) => cfg,
|
|
Err(e) => {
|
|
eprintln!("brokerd: {e}\n{START_FAILED}");
|
|
return ExitCode::from(1);
|
|
}
|
|
};
|
|
|
|
// Keep the socket paths for the serving notice, before `cfg` is moved into `start`.
|
|
let broker_path = cfg.broker_socket();
|
|
let admin_path = cfg.admin_socket();
|
|
|
|
// The runtime and the line that says which. A `[runner]` runs calls in containers; without it,
|
|
// every call is refused, as in M3a.
|
|
let log: Arc<dyn Fn(&str) + Send + Sync> = Arc::new(|line: &str| eprintln!("{line}"));
|
|
let runtime: Box<dyn Runtime>;
|
|
let runtime_notice: String;
|
|
match &cfg.runner {
|
|
Some(runner) => {
|
|
let image = runner.image.clone();
|
|
runtime = Box::new(Podman::new(
|
|
runner.clone(),
|
|
cfg.egress_dir(),
|
|
Arc::clone(&log),
|
|
));
|
|
runtime_notice = format!("tools run in containers from {image}");
|
|
}
|
|
None => {
|
|
runtime = Box::new(Refusing);
|
|
runtime_notice = "no [runner] section: every tool call is refused".to_string();
|
|
}
|
|
}
|
|
|
|
let started = match serve::start(cfg, accept_break, runtime, log) {
|
|
Ok(started) => started,
|
|
Err(ServeError::Audit(e @ AuditError::NothingToAccept)) => {
|
|
eprintln!("brokerd: {e}");
|
|
return ExitCode::from(2);
|
|
}
|
|
// The audit errors carry their own entry; the rest are this one.
|
|
Err(ServeError::Audit(e)) => {
|
|
eprintln!("brokerd: {e}");
|
|
return ExitCode::from(1);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("brokerd: {e}\n{START_FAILED}");
|
|
return ExitCode::from(1);
|
|
}
|
|
};
|
|
|
|
if started.recovered {
|
|
eprintln!("{RECOVERED_NOTICE}");
|
|
}
|
|
if let Some(failure) = &started.accepted {
|
|
eprintln!(
|
|
"audit: accepted the break at {}:{}: {}",
|
|
failure.file, failure.line, failure.what
|
|
);
|
|
}
|
|
eprintln!(
|
|
"brokerd: serving tools on {} and approvals on {}",
|
|
broker_path.display(),
|
|
admin_path.display()
|
|
);
|
|
eprintln!("brokerd: {runtime_notice}");
|
|
|
|
match started.run() {
|
|
Ok(()) => ExitCode::SUCCESS,
|
|
Err(e) => {
|
|
eprintln!("brokerd: stopped serving: {e}\n{LISTENER_LOST}");
|
|
ExitCode::from(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The argument list was not `serve --config <path> [--accept-break]`.
|
|
fn usage() -> ExitCode {
|
|
eprintln!("{USAGE}");
|
|
ExitCode::from(2)
|
|
}
|
|
|
|
/// The `--config` and `--accept-break` flags after `serve`, or `Err(())` for a bad list.
|
|
/// Flags are compared as `OsStr`, so one that is not UTF-8 is simply unknown; the path is kept as
|
|
/// given.
|
|
fn parse_serve<I: Iterator<Item = OsString>>(mut args: I) -> Result<(PathBuf, bool), ()> {
|
|
let mut config: Option<OsString> = None;
|
|
let mut accept_break = false;
|
|
loop {
|
|
match args.next() {
|
|
None => {
|
|
return match config {
|
|
Some(path) => Ok((PathBuf::from(path), accept_break)),
|
|
None => Err(()),
|
|
};
|
|
}
|
|
Some(arg) if arg == "--config" => match args.next() {
|
|
Some(path) => {
|
|
if config.is_some() {
|
|
return Err(());
|
|
}
|
|
config = Some(path);
|
|
}
|
|
None => return Err(()),
|
|
},
|
|
Some(arg) if arg == "--accept-break" => {
|
|
if accept_break {
|
|
return Err(());
|
|
}
|
|
accept_break = true;
|
|
}
|
|
Some(_) => return Err(()),
|
|
}
|
|
}
|
|
}
|