1use std::path::PathBuf;
2use std::str::FromStr;
3
4use configparser::ini::Ini;
5use indexmap::map::IndexMap;
6use int_enum::IntEnum;
7use strum_macros::EnumString;
8use tracing::error;
9
10#[derive(Clone, Debug, Default, EnumString, Eq, IntEnum, PartialEq, strum_macros::Display)]
11#[repr(u8)]
12pub enum MonitordOutputFormat {
13 #[default]
14 #[strum(serialize = "json", serialize = "JSON", serialize = "Json")]
15 Json = 0,
16 #[strum(
17 serialize = "json-flat",
18 serialize = "json_flat",
19 serialize = "jsonflat"
20 )]
21 JsonFlat = 1,
22 #[strum(
23 serialize = "json-pretty",
24 serialize = "json_pretty",
25 serialize = "jsonpretty"
26 )]
27 JsonPretty = 2,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct MonitordConfig {
32 pub dbus_address: String,
33 pub daemon: bool,
34 pub daemon_stats_refresh_secs: u64,
35 pub key_prefix: String,
36 pub output_format: MonitordOutputFormat,
37 pub dbus_timeout: u64,
38}
39impl Default for MonitordConfig {
40 fn default() -> Self {
41 MonitordConfig {
42 dbus_address: crate::DEFAULT_DBUS_ADDRESS.into(),
43 daemon: false,
44 daemon_stats_refresh_secs: 30,
45 key_prefix: "".to_string(),
46 output_format: MonitordOutputFormat::default(),
47 dbus_timeout: 30,
48 }
49 }
50}
51
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct NetworkdConfig {
54 pub enabled: bool,
55 pub link_state_dir: PathBuf,
56}
57impl Default for NetworkdConfig {
58 fn default() -> Self {
59 NetworkdConfig {
60 enabled: false,
61 link_state_dir: crate::networkd::NETWORKD_STATE_FILES.into(),
62 }
63 }
64}
65
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct Pid1Config {
68 pub enabled: bool,
69}
70impl Default for Pid1Config {
71 fn default() -> Self {
72 Pid1Config { enabled: true }
73 }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct SystemStateConfig {
78 pub enabled: bool,
79}
80impl Default for SystemStateConfig {
81 fn default() -> Self {
82 SystemStateConfig { enabled: true }
83 }
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct TimersConfig {
88 pub enabled: bool,
89 pub allowlist: Vec<String>,
90 pub blocklist: Vec<String>,
91}
92impl Default for TimersConfig {
93 fn default() -> Self {
94 TimersConfig {
95 enabled: true,
96 allowlist: Vec::new(),
97 blocklist: Vec::new(),
98 }
99 }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct UnitsConfig {
104 pub enabled: bool,
105 pub state_stats: bool,
106 pub state_stats_allowlist: Vec<String>,
107 pub state_stats_blocklist: Vec<String>,
108 pub state_stats_time_in_state: bool,
109}
110impl Default for UnitsConfig {
111 fn default() -> Self {
112 UnitsConfig {
113 enabled: true,
114 state_stats: false,
115 state_stats_allowlist: Vec::new(),
116 state_stats_blocklist: Vec::new(),
117 state_stats_time_in_state: true,
118 }
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
123pub struct MachinesConfig {
124 pub enabled: bool,
125 pub allowlist: Vec<String>,
126 pub blocklist: Vec<String>,
127}
128impl Default for MachinesConfig {
129 fn default() -> Self {
130 MachinesConfig {
131 enabled: true,
132 allowlist: Vec::new(),
133 blocklist: Vec::new(),
134 }
135 }
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct DBusStatsConfig {
140 pub enabled: bool,
141}
142impl Default for DBusStatsConfig {
143 fn default() -> Self {
144 DBusStatsConfig { enabled: true }
145 }
146}
147
148#[derive(Clone, Debug, Default, Eq, PartialEq)]
151pub struct Config {
152 pub machines: MachinesConfig,
153 pub monitord: MonitordConfig,
154 pub networkd: NetworkdConfig,
155 pub pid1: Pid1Config,
156 pub services: Vec<String>,
157 pub system_state: SystemStateConfig,
158 pub timers: TimersConfig,
159 pub units: UnitsConfig,
160 pub dbus_stats: DBusStatsConfig,
161}
162
163impl From<Ini> for Config {
164 fn from(ini_config: Ini) -> Self {
165 let mut config = Config::default();
166
167 if let Some(dbus_address) = ini_config.get("monitord", "dbus_address") {
169 config.monitord.dbus_address = dbus_address;
170 }
171 if let Ok(Some(dbus_timeout)) = ini_config.getuint("monitord", "dbus_timeout") {
172 config.monitord.dbus_timeout = dbus_timeout;
173 }
174 config.monitord.daemon = read_config_bool(
175 &ini_config,
176 String::from("monitord"),
177 String::from("daemon"),
178 );
179 if let Ok(Some(daemon_stats_refresh_secs)) =
180 ini_config.getuint("monitord", "daemon_stats_refresh_secs")
181 {
182 config.monitord.daemon_stats_refresh_secs = daemon_stats_refresh_secs;
183 }
184 if let Some(key_prefix) = ini_config.get("monitord", "key_prefix") {
185 config.monitord.key_prefix = key_prefix;
186 }
187 config.monitord.output_format = MonitordOutputFormat::from_str(
188 &ini_config
189 .get("monitord", "output_format")
190 .expect("Need 'output_format' set in config"),
191 )
192 .expect("Need a valid value for the enum");
193
194 config.networkd.enabled = read_config_bool(
196 &ini_config,
197 String::from("networkd"),
198 String::from("enabled"),
199 );
200 if let Some(link_state_dir) = ini_config.get("networkd", "link_state_dir") {
201 config.networkd.link_state_dir = link_state_dir.into();
202 }
203
204 config.pid1.enabled =
206 read_config_bool(&ini_config, String::from("pid1"), String::from("enabled"));
207
208 let config_map = ini_config.get_map().unwrap_or(IndexMap::from([]));
210 if let Some(services) = config_map.get("services") {
211 config.services = services.keys().map(|s| s.to_string()).collect();
212 }
213
214 config.system_state.enabled = read_config_bool(
216 &ini_config,
217 String::from("system-state"),
218 String::from("enabled"),
219 );
220
221 config.timers.enabled =
223 read_config_bool(&ini_config, String::from("timers"), String::from("enabled"));
224 if let Some(timers_allowlist) = config_map.get("timers.allowlist") {
225 config.timers.allowlist = timers_allowlist.keys().map(|s| s.to_string()).collect();
226 }
227 if let Some(timers_blocklist) = config_map.get("timers.blocklist") {
228 config.timers.blocklist = timers_blocklist.keys().map(|s| s.to_string()).collect();
229 }
230
231 config.units.enabled =
233 read_config_bool(&ini_config, String::from("units"), String::from("enabled"));
234 config.units.state_stats = read_config_bool(
235 &ini_config,
236 String::from("units"),
237 String::from("state_stats"),
238 );
239 if let Some(state_stats_allowlist) = config_map.get("units.state_stats.allowlist") {
240 config.units.state_stats_allowlist = state_stats_allowlist
241 .keys()
242 .map(|s| s.to_string())
243 .collect();
244 }
245 if let Some(state_stats_blocklist) = config_map.get("units.state_stats.blocklist") {
246 config.units.state_stats_blocklist = state_stats_blocklist
247 .keys()
248 .map(|s| s.to_string())
249 .collect();
250 }
251 config.units.state_stats_time_in_state = read_config_bool(
252 &ini_config,
253 String::from("units"),
254 String::from("state_stats_time_in_state"),
255 );
256
257 config.machines.enabled = read_config_bool(
259 &ini_config,
260 String::from("machines"),
261 String::from("enabled"),
262 );
263 if let Some(machines_allowlist) = config_map.get("machines.allowlist") {
264 config.machines.allowlist = machines_allowlist.keys().map(|s| s.to_string()).collect();
265 }
266 if let Some(machines_blocklist) = config_map.get("machines.blocklist") {
267 config.machines.blocklist = machines_blocklist.keys().map(|s| s.to_string()).collect();
268 }
269
270 config.dbus_stats.enabled =
272 read_config_bool(&ini_config, String::from("dbus"), String::from("enabled"));
273
274 config
275 }
276}
277
278fn read_config_bool(config: &Ini, section: String, key: String) -> bool {
280 let option_bool = match config.getbool(§ion, &key) {
281 Ok(config_option_bool) => config_option_bool,
282 Err(err) => panic!(
283 "Unable to find '{}' key in '{}' section in config file: {}",
284 key, section, err
285 ),
286 };
287 match option_bool {
288 Some(bool_value) => bool_value,
289 None => {
290 error!(
291 "No value for '{}' in '{}' section ... assuming false",
292 key, section
293 );
294 false
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use std::io::Write;
302
303 use tempfile::NamedTempFile;
304
305 use super::*;
306
307 const FULL_CONFIG: &str = r###"
308[monitord]
309dbus_address = unix:path=/system_bus_socket
310dbus_timeout = 2
311daemon = true
312daemon_stats_refresh_secs = 0
313key_prefix = unittest
314output_format = json-pretty
315
316[networkd]
317enabled = true
318link_state_dir = /links
319
320[pid1]
321enabled = true
322
323[services]
324foo.service
325bar.service
326
327[system-state]
328enabled = true
329
330[timers]
331enabled = true
332
333[timers.allowlist]
334foo.timer
335
336[timers.blocklist]
337bar.timer
338
339[units]
340enabled = true
341state_stats = true
342state_stats_time_in_state = true
343
344[units.state_stats.allowlist]
345foo.service
346
347[units.state_stats.blocklist]
348bar.service
349
350[machines]
351enabled = true
352
353[machines.allowlist]
354foo
355bar
356
357[machines.blocklist]
358foo2
359
360[dbus]
361enabled = true
362"###;
363
364 const MINIMAL_CONFIG: &str = r###"
365[monitord]
366output_format = json-flat
367"###;
368
369 #[test]
370 fn test_default_config() {
371 assert!(Config::default().units.enabled)
372 }
373
374 #[test]
375 fn test_minimal_config() {
376 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
377 monitord_config
378 .write_all(MINIMAL_CONFIG.as_bytes())
379 .expect("Unable to write out temp config file");
380
381 let mut ini_config = Ini::new();
382 let _config_map = ini_config
383 .load(monitord_config.path())
384 .expect("Unable to load ini config");
385
386 let expected_config: Config = ini_config.into();
387 assert_eq!(
389 expected_config.monitord.output_format,
390 MonitordOutputFormat::JsonFlat,
391 );
392 assert!(!expected_config.networkd.enabled);
394 }
395
396 #[test]
397 fn test_full_config() {
398 let expected_config = Config {
399 monitord: MonitordConfig {
400 dbus_address: String::from("unix:path=/system_bus_socket"),
401 daemon: true,
402 daemon_stats_refresh_secs: u64::MIN,
403 key_prefix: String::from("unittest"),
404 output_format: MonitordOutputFormat::JsonPretty,
405 dbus_timeout: 2 as u64,
406 },
407 networkd: NetworkdConfig {
408 enabled: true,
409 link_state_dir: "/links".into(),
410 },
411 pid1: Pid1Config { enabled: true },
412 services: Vec::from([String::from("foo.service"), String::from("bar.service")]),
413 system_state: SystemStateConfig { enabled: true },
414 timers: TimersConfig {
415 enabled: true,
416 allowlist: Vec::from([String::from("foo.timer")]),
417 blocklist: Vec::from([String::from("bar.timer")]),
418 },
419 units: UnitsConfig {
420 enabled: true,
421 state_stats: true,
422 state_stats_allowlist: Vec::from([String::from("foo.service")]),
423 state_stats_blocklist: Vec::from([String::from("bar.service")]),
424 state_stats_time_in_state: true,
425 },
426 machines: MachinesConfig {
427 enabled: true,
428 allowlist: Vec::from([String::from("foo"), String::from("bar")]),
429 blocklist: Vec::from([String::from("foo2")]),
430 },
431 dbus_stats: DBusStatsConfig { enabled: true },
432 };
433
434 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
435 monitord_config
436 .write_all(FULL_CONFIG.as_bytes())
437 .expect("Unable to write out temp config file");
438
439 let mut ini_config = Ini::new();
440 let _config_map = ini_config
441 .load(monitord_config.path())
442 .expect("Unable to load ini config");
443
444 assert_eq!(expected_config, ini_config.into(),);
446 }
447}