Skip to content

Commit 652b7ff

Browse files
authored
feat: get config from files, environment vars, & command line flags (#112)
Add `make_config` util function that is used to load config files `make_config` asks for a: 1) default config 2) a list of possible file paths where a config can be loaded 3) a prefix that this config's env vars will be labeled (eg, `IROH_GATEWAY_PORT=4000`, the prefix is `IROH_GATEWAY` & the field that you are trying to set is `port` 4) command line flag overrides The method layers these options, starting with the default config and ending with the command line flags. Alos, allows using `IROH_METRICS_*` env vars to set fields in the metrics config. Also allows for `IROH_INSTANCE_ID` to set `metrics::Config.instance_id` & `IROH_ENV` to set `metrics::Config.service_env` fields.
1 parent b75bbbd commit 652b7ff

38 files changed

+1270
-269
lines changed

examples/src/bin/importer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use clap::Parser;
66
use futures::{stream::TryStreamExt, StreamExt};
77
use indicatif::{ProgressBar, ProgressStyle};
88
use iroh_car::CarReader;
9-
use iroh_rpc_client::{Client, RpcClientConfig};
9+
use iroh_rpc_client::{Client, Config as RpcClientConfig};
1010
use par_stream::prelude::*;
1111

1212
#[derive(Parser, Debug, Clone)]

iroh-bitswap/src/metrics.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
11
use git_version::git_version;
2+
use iroh_metrics::config::Config as MetricsConfig;
23

3-
pub fn metrics_config(logger_only: bool) -> iroh_metrics::config::Config {
4+
pub fn metrics_config_with_compile_time_info(cfg: MetricsConfig) -> MetricsConfig {
45
// compile time configuration
5-
let service_name = env!("CARGO_PKG_NAME").to_string();
6-
let build = git_version!().to_string();
7-
let version = env!("CARGO_PKG_VERSION").to_string();
8-
9-
// runtime configuration
10-
let instance_id = std::env::var("IROH_INSTANCE_ID")
11-
.unwrap_or_else(|_| names::Generator::default().next().unwrap());
12-
let service_env = std::env::var("IROH_ENV").unwrap_or_else(|_| "dev".to_string());
13-
iroh_metrics::config::Config::new(
14-
service_name,
15-
instance_id,
16-
build,
17-
version,
18-
service_env,
19-
logger_only,
20-
)
6+
cfg.with_service_name(env!("CARGO_PKG_NAME").to_string())
7+
.with_build(git_version!().to_string())
8+
.with_version(env!("CARGO_PKG_VERSION").to_string())
219
}

iroh-ctl/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ clap = { version = "3.1.14", features = ["derive"] }
1717
crossterm = "0.23.2"
1818
tonic = "0.7.2"
1919
iroh-rpc-client = { path = "../iroh-rpc-client" }
20+
config = "0.13.1"
21+
iroh-util = { path = "../iroh-util" }
22+
serde = { version = "1.0", features = ["derive"] }
23+
git-version = "0.3.5"
24+
iroh-metrics = { path = "../iroh-metrics" }
25+
prometheus-client = "0.16.0"

iroh-ctl/src/config.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use config::{ConfigError, Map, Source, Value};
2+
use iroh_metrics::config::Config as MetricsConfig;
3+
use iroh_rpc_client::Config as RpcClientConfig;
4+
use iroh_util::insert_into_config_map;
5+
use serde::{Deserialize, Serialize};
6+
7+
/// CONFIG_FILE_NAME is the name of the optional config file located in the iroh home directory
8+
pub const CONFIG_FILE_NAME: &str = "ctl.config.toml";
9+
/// ENV_PREFIX should be used along side the config field name to set a config field using
10+
/// environment variables
11+
pub const ENV_PREFIX: &str = "IROH_CTL";
12+
13+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
14+
pub struct Config {
15+
pub rpc_client: RpcClientConfig,
16+
pub metrics: MetricsConfig,
17+
}
18+
19+
impl Source for Config {
20+
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
21+
Box::new(self.clone())
22+
}
23+
fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
24+
let mut map: Map<String, Value> = Map::new();
25+
insert_into_config_map(&mut map, "rpc_client", self.rpc_client.collect()?);
26+
insert_into_config_map(&mut map, "metrics", self.metrics.collect()?);
27+
Ok(map)
28+
}
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::*;
34+
use config::Config as ConfigBuilder;
35+
36+
#[test]
37+
fn test_collect() {
38+
let default = Config::default();
39+
let mut expect: Map<String, Value> = Map::new();
40+
expect.insert(
41+
"rpc_client".to_string(),
42+
Value::new(None, default.rpc_client.collect().unwrap()),
43+
);
44+
expect.insert(
45+
"metrics".to_string(),
46+
Value::new(None, default.metrics.collect().unwrap()),
47+
);
48+
let got = default.collect().unwrap();
49+
50+
for key in got.keys() {
51+
let left = expect.get(key).unwrap();
52+
let right = got.get(key).unwrap();
53+
assert_eq!(left, right);
54+
}
55+
}
56+
57+
#[test]
58+
fn test_build_config_from_struct() {
59+
let expect = Config::default();
60+
let got: Config = ConfigBuilder::builder()
61+
.add_source(expect.clone())
62+
.build()
63+
.unwrap()
64+
.try_deserialize()
65+
.unwrap();
66+
67+
assert_eq!(expect, got);
68+
}
69+
}

iroh-ctl/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
pub mod config;
2+
pub mod metrics;
13
pub mod status;

iroh-ctl/src/main.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
1+
use std::collections::HashMap;
2+
use std::path::PathBuf;
3+
14
use clap::{Parser, Subcommand};
5+
use iroh_ctl::metrics;
6+
use iroh_rpc_client::Client;
7+
use iroh_util::{iroh_home_path, make_config};
8+
use prometheus_client::registry::Registry;
29

3-
use iroh_ctl::status;
10+
use iroh_ctl::{
11+
config::{Config, CONFIG_FILE_NAME, ENV_PREFIX},
12+
status,
13+
};
414

515
#[derive(Parser, Debug, Clone)]
616
#[clap(author, version, about, long_about = None, propagate_version = true)]
717
struct Cli {
18+
#[clap(long)]
19+
cfg: Option<PathBuf>,
20+
#[clap(long = "no-metrics")]
21+
no_metrics: bool,
822
#[clap(subcommand)]
923
command: Commands,
1024
}
1125

26+
impl Cli {
27+
fn make_overrides_map(&self) -> HashMap<String, String> {
28+
let mut map = HashMap::new();
29+
map.insert("metrics.debug".to_string(), self.no_metrics.to_string());
30+
map
31+
}
32+
}
33+
1234
#[derive(Subcommand, Debug, Clone)]
1335
enum Commands {
1436
/// status checks the health of the differen processes
@@ -23,11 +45,39 @@ enum Commands {
2345
async fn main() -> anyhow::Result<()> {
2446
let cli = Cli::parse();
2547

48+
let sources = vec![iroh_home_path(CONFIG_FILE_NAME), cli.cfg.clone()];
49+
let config = make_config(
50+
// default
51+
Config::default(),
52+
// potential config files
53+
sources,
54+
// env var prefix for this config
55+
ENV_PREFIX,
56+
// map of present command line arguments
57+
cli.make_overrides_map(),
58+
)
59+
.unwrap();
60+
61+
let metrics_config = config.metrics.clone();
62+
63+
// stubbing in metrics
64+
let prom_registry = Registry::default();
65+
// TODO: need to register prometheus metrics
66+
let metrics_handle = iroh_metrics::init_with_registry(
67+
metrics::metrics_config_with_compile_time_info(metrics_config),
68+
prom_registry,
69+
)
70+
.await
71+
.expect("failed to initialize metrics");
72+
73+
let client = Client::new(&config.rpc_client).await?;
74+
2675
match cli.command {
2776
Commands::Status { watch } => {
28-
crate::status::status(watch).await?;
77+
crate::status::status(client, watch).await?;
2978
}
3079
};
3180

81+
metrics_handle.shutdown();
3282
Ok(())
3383
}

iroh-ctl/src/metrics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use git_version::git_version;
2+
use iroh_metrics::config::Config as MetricsConfig;
3+
4+
pub fn metrics_config_with_compile_time_info(cfg: MetricsConfig) -> MetricsConfig {
5+
// compile time configuration
6+
cfg.with_service_name(env!("CARGO_PKG_NAME").to_string())
7+
.with_build(git_version!().to_string())
8+
.with_version(env!("CARGO_PKG_VERSION").to_string())
9+
}

iroh-ctl/src/status.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ use anyhow::Result;
44
use crossterm::terminal::{Clear, ClearType};
55
use crossterm::{cursor, style, style::Stylize, QueueableCommand};
66
use futures::StreamExt;
7-
use iroh_rpc_client::{Client, RpcClientConfig, ServiceStatus, StatusRow, StatusTable};
8-
9-
pub async fn status(watch: bool) -> Result<()> {
10-
let client = Client::new(&RpcClientConfig::default()).await.unwrap();
7+
use iroh_rpc_client::{Client, ServiceStatus, StatusRow, StatusTable};
118

9+
pub async fn status(client: Client, watch: bool) -> Result<()> {
1210
let mut stdout = stdout();
1311
if watch {
1412
let status_stream = client.watch().await;

iroh-gateway/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ async-recursion = "1.0.0"
4747
handlebars = "3"
4848
url = "2.2.2"
4949
urlencoding = "2.1.0"
50+
dirs = "4.0.0"
51+
toml = "0.5.9"
52+
http-serde = "1.1.0"
53+
config = "0.13.1"
5054

5155
[dev-dependencies]
5256
axum-macros = "0.2.0" # use #[axum_macros::debug_handler] for better error messages on handlers

0 commit comments

Comments
 (0)