barreleye_common/
settings.rs

1use config::{Config, Environment, File};
2use directories::BaseDirs;
3use eyre::Result;
4use serde::Deserialize;
5use std::{env::var, fs, fs::OpenOptions, path::PathBuf};
6use url::Url;
7
8use crate::{
9	cache::Driver as CacheDriver, db::Driver as DatabaseDriver, errors::AppError, quit, utils,
10	warehouse::Driver as WarehouseDriver,
11};
12
13pub static DEFAULT_SETTINGS_FILENAME: &str = "barreleye.toml";
14pub static DEFAULT_SETTINGS_CONTENT: &str = r#"
15sdn_refresh_rate = 3600 # in seconds
16primary_ping = 2 # in seconds
17primary_promotion = 20 # in seconds
18
19[server]
20ip_v4 = "0.0.0.0"
21ip_v6 = "::"
22port = 22775
23
24[cache]
25driver = "rocksdb"
26
27[db]
28driver = "sqlite" # or "postgres" or "mysql"
29min_connections = 5
30max_connections = 100
31connect_timeout = 8
32idle_timeout = 8
33max_lifetime = 8
34
35[warehouse]
36driver = "clickhouse"
37
38[dsn]
39rocksdb = "rocksdb://barreleye_cache"
40sqlite = "sqlite://barreleye_database?mode=rwc"
41postgres = "" # eg: "postgres://USERNAME[:PASSWORD]@localhost:5432/database"
42mysql = "" # eg: "mysql://USERNAME[:PASSWORD]@localhost:3306/database"
43clickhouse = "" # eg: "http://USERNAME[:PASSWORD]@localhost:8123/database"
44"#;
45
46#[derive(Debug, Deserialize)]
47pub struct Settings {
48	pub sdn_refresh_rate: u64,
49	pub primary_ping: u64,
50	pub primary_promotion: u64,
51	pub server: Server,
52	pub cache: Cache,
53	pub db: Db,
54	pub warehouse: Warehouse,
55	pub dsn: Dsn,
56}
57
58#[derive(Debug, Deserialize)]
59pub struct Server {
60	pub ip_v4: String,
61	pub ip_v6: String,
62	pub port: u16,
63}
64
65#[derive(Debug, Deserialize)]
66pub struct Cache {
67	pub driver: CacheDriver,
68}
69
70#[derive(Debug, Deserialize)]
71pub struct Db {
72	pub driver: DatabaseDriver,
73	pub min_connections: u32,
74	pub max_connections: u32,
75	pub connect_timeout: u64,
76	pub idle_timeout: u64,
77	pub max_lifetime: u64,
78}
79
80#[derive(Debug, Deserialize)]
81pub struct Warehouse {
82	pub driver: WarehouseDriver,
83}
84
85#[derive(Debug, Deserialize)]
86pub struct Dsn {
87	pub rocksdb: String,
88	pub sqlite: String,
89	pub postgres: String,
90	pub mysql: String,
91	pub clickhouse: String,
92}
93
94impl Settings {
95	pub fn new(config_path: Option<String>) -> Result<Self> {
96		// figure out the config path
97		let config_path = {
98			let mut ret = None;
99
100			// try custom a config file (if provided)
101			if let Some(filename) = config_path {
102				let try_filename = PathBuf::from(filename.clone());
103				if try_filename.exists() {
104					ret = Some(try_filename);
105				} else {
106					quit(AppError::MissingConfigFile { filename });
107				}
108			} else {
109				// load a few places to check and/or create
110				let mut paths = vec![];
111				if let Ok(manifest_dir) = var("CARGO_MANIFEST_DIR") {
112					paths.push(PathBuf::from(manifest_dir).join(DEFAULT_SETTINGS_FILENAME))
113				}
114				paths.push(std::env::current_exe()?.join(DEFAULT_SETTINGS_FILENAME));
115				if let Some(base_dir) = BaseDirs::new() {
116					paths.push(
117						PathBuf::from(base_dir.config_dir())
118							.join("barreleye")
119							.join(DEFAULT_SETTINGS_FILENAME),
120					);
121				}
122
123				// check if any of those paths exist
124				for path in paths.clone().into_iter() {
125					if path.exists() {
126						ret = Some(path);
127						break;
128					}
129				}
130
131				// if none found, try to create a default config
132				if ret.is_none() {
133					for path in paths.into_iter() {
134						if OpenOptions::new()
135							.write(true)
136							.create_new(true)
137							.open(path.clone())
138							.is_ok()
139						{
140							fs::write(path.clone(), DEFAULT_SETTINGS_CONTENT.trim())?;
141
142							ret = Some(path);
143							break;
144						}
145					}
146				}
147
148				// if still nothing, we failed
149				if ret.is_none() {
150					quit(AppError::DefaultConfigFile);
151				}
152			}
153
154			ret.unwrap()
155		};
156
157		// builder settings
158		let s = Config::builder()
159			.add_source(File::from(config_path).required(false))
160			.add_source(Environment::with_prefix("BARRELEYE"));
161
162		// try to create a struct
163		let settings: Settings = s.build()?.try_deserialize()?;
164
165		// test: dsn for cache
166		if settings.cache.driver == CacheDriver::RocksDB &&
167			utils::get_db_path(&settings.dsn.rocksdb).is_empty()
168		{
169			quit(AppError::InvalidSetting {
170				key: "dsn.rocksdb".to_string(),
171				value: settings.dsn.rocksdb.clone(),
172			});
173		}
174
175		// test: dsn for warehouse
176		if settings.warehouse.driver == WarehouseDriver::Clickhouse &&
177			Url::parse(&settings.dsn.clickhouse).is_err()
178		{
179			quit(AppError::InvalidSetting {
180				key: "dsn.clickhouse".to_string(),
181				value: settings.dsn.clickhouse.clone(),
182			});
183		}
184
185		// test: dsn for db
186		let db_url = match settings.db.driver {
187			DatabaseDriver::SQLite => settings.dsn.sqlite.clone(),
188			DatabaseDriver::PostgreSQL => settings.dsn.postgres.clone(),
189			DatabaseDriver::MySQL => settings.dsn.mysql.clone(),
190		};
191		if Url::parse(&db_url).is_err() {
192			quit(AppError::InvalidSetting {
193				key: format!("dsn.{}", settings.db.driver),
194				value: db_url,
195			});
196		}
197
198		// test: primary settings
199		if settings.primary_promotion < settings.primary_ping * 3 {
200			quit(AppError::InvalidPrimaryConfigs);
201		}
202
203		Ok(settings)
204	}
205}