feat(grid-agent): establish architecture and config (#118)
This commit is contained in:
135
crates/metacrate-grid-agent/src/main.rs
Normal file
135
crates/metacrate-grid-agent/src/main.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use metacrate_grid_agent::{
|
||||
AgentService, ConfigLoader, GridEventKind, ObservableEvent, OperatingMode,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const MAX_ARGUMENTS: usize = 8;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CliError(String);
|
||||
|
||||
impl fmt::Display for CliError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CliError {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
config: Option<PathBuf>,
|
||||
check_config: bool,
|
||||
run_once: bool,
|
||||
}
|
||||
|
||||
fn options() -> Result<Option<Options>, CliError> {
|
||||
let mut result = Options::default();
|
||||
let mut arguments = std::env::args_os().skip(1);
|
||||
let mut count = 0;
|
||||
while let Some(argument) = arguments.next() {
|
||||
count += 1;
|
||||
if count > MAX_ARGUMENTS {
|
||||
return Err(CliError(format!(
|
||||
"at most {MAX_ARGUMENTS} command-line arguments are accepted"
|
||||
)));
|
||||
}
|
||||
if argument == "--help" || argument == "-h" {
|
||||
println!(
|
||||
"metacrate-grid-agent [--config PATH] [--check-config | --run-once]\n\
|
||||
Configuration precedence: defaults < JSON < secret files < environment."
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
if argument == "--check-config" {
|
||||
result.check_config = true;
|
||||
} else if argument == "--run-once" {
|
||||
result.run_once = true;
|
||||
} else if argument == "--config" {
|
||||
let path = arguments
|
||||
.next()
|
||||
.ok_or_else(|| CliError("--config requires a path".into()))?;
|
||||
count += 1;
|
||||
result.config = Some(PathBuf::from(path));
|
||||
} else {
|
||||
let argument = PathBuf::from(argument);
|
||||
return Err(CliError(format!(
|
||||
"unknown argument {}; use --help",
|
||||
argument.display()
|
||||
)));
|
||||
}
|
||||
}
|
||||
if result.check_config && result.run_once {
|
||||
return Err(CliError(
|
||||
"--check-config and --run-once are mutually exclusive".into(),
|
||||
));
|
||||
}
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let Some(options) = options()? else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut loader = ConfigLoader::new();
|
||||
if let Some(path) = options.config {
|
||||
loader = loader.with_file(path);
|
||||
}
|
||||
let config = loader.load()?;
|
||||
if options.check_config {
|
||||
println!("configuration is valid for {:?} mode", config.mode);
|
||||
return Ok(());
|
||||
}
|
||||
if config.mode != OperatingMode::OfflineFake {
|
||||
return Err(CliError(
|
||||
"this architecture issue starts only the offline backend; live login is owned by a later milestone issue"
|
||||
.into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let startup_timeout = config.timeouts.startup;
|
||||
let mut handle = AgentService::offline(config)?.start()?;
|
||||
if options.run_once {
|
||||
tokio::time::timeout(startup_timeout, async {
|
||||
loop {
|
||||
match handle.next_event().await {
|
||||
Some(ObservableEvent::Grid(event))
|
||||
if event.kind == GridEventKind::BackendReady =>
|
||||
{
|
||||
return Ok::<(), CliError>(());
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
return Err(CliError("service stopped before backend readiness".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| CliError("timed out waiting for offline backend readiness".into()))??;
|
||||
handle.shutdown().await?;
|
||||
println!("grid agent completed one offline startup/shutdown cycle");
|
||||
return Ok(());
|
||||
}
|
||||
println!("grid agent started in offline/fake mode; press Ctrl-C to stop");
|
||||
loop {
|
||||
tokio::select! {
|
||||
signal = tokio::signal::ctrl_c() => {
|
||||
signal?;
|
||||
break;
|
||||
}
|
||||
event = handle.next_event() => {
|
||||
if event.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handle.shutdown().await?;
|
||||
println!("grid agent stopped cleanly");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user