pub mod arglang;
use std::{env, fs, path::PathBuf, str::FromStr};
use anyhow::{self, bail, Context};
use regex::Regex;
use structopt::StructOpt;
use toml::{value::Table, Value};
use tracing::{info, trace};
use crate::config;
use casper_node::{
logging,
reactor::{initializer, joiner, validator, Runner},
setup_signal_hooks,
utils::WithDir,
};
use prometheus::Registry;
#[derive(Debug, StructOpt)]
#[structopt(version = casper_node::VERSION_STRING_COLOR.as_str())]
pub enum Cli {
Validator {
config: PathBuf,
#[structopt(
short = "C",
long,
env = "NODE_CONFIG",
use_delimiter(true),
value_delimiter(";")
)]
config_ext: Vec<ConfigExt>,
},
}
#[derive(Debug)]
pub struct ConfigExt {
section: String,
key: String,
value: String,
}
impl ConfigExt {
fn update_toml_table(&self, toml_value: &mut Value) -> anyhow::Result<()> {
let table = toml_value
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("configuration table is not a table"))?;
if !table.contains_key(&self.section) {
table.insert(self.section.clone(), Value::Table(Table::new()));
}
let val = arglang::parse(&self.value)?;
table[&self.section]
.as_table_mut()
.ok_or_else(|| {
anyhow::anyhow!("configuration section {} is not a table", self.section)
})?
.insert(self.key.clone(), val);
Ok(())
}
}
impl FromStr for ConfigExt {
type Err = anyhow::Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let re = Regex::new(r"^([^.]+)\.([^=]+)=(.+)$").unwrap();
let captures = re
.captures(input)
.context("could not parse config_ext (see README.md)")?;
Ok(ConfigExt {
section: captures
.get(1)
.context("failed to find section")?
.as_str()
.to_owned(),
key: captures
.get(2)
.context("failed to find key")?
.as_str()
.to_owned(),
value: captures
.get(3)
.context("failed to find value")?
.as_str()
.to_owned(),
})
}
}
impl Cli {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Cli::Validator { config, config_ext } => {
setup_signal_hooks();
let root = config
.parent()
.map(|path| path.to_owned())
.unwrap_or_else(|| "/".into());
let config_raw: String = fs::read_to_string(&config)
.context("could not read configuration file")
.with_context(|| config.display().to_string())?;
let mut config_table: Value = toml::from_str(&config_raw)?;
for item in config_ext {
item.update_toml_table(&mut config_table)?;
}
let validator_config: validator::Config = config_table.try_into()?;
logging::init_with_config(&validator_config.logging)?;
info!(version = %env!("CARGO_PKG_VERSION"), "node starting up");
trace!("{}", config::to_string(&validator_config)?);
let mut rng = casper_node::new_rng();
let registry = Registry::new();
let mut initializer_runner = Runner::<initializer::Reactor>::with_metrics(
WithDir::new(root.clone(), validator_config),
&mut rng,
®istry,
)
.await?;
initializer_runner.run(&mut rng).await;
info!("finished initialization");
let initializer = initializer_runner.into_inner();
if !initializer.stopped_successfully() {
bail!("failed to initialize successfully");
}
let mut joiner_runner = Runner::<joiner::Reactor>::with_metrics(
WithDir::new(root, initializer),
&mut rng,
®istry,
)
.await?;
joiner_runner.run(&mut rng).await;
info!("finished joining");
let config = joiner_runner.into_inner().into_validator_config().await;
let mut validator_runner =
Runner::<validator::Reactor>::with_metrics(config, &mut rng, ®istry).await?;
validator_runner.run(&mut rng).await;
}
}
Ok(())
}
}