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 pub user_stats: bool,
142 pub peer_stats: bool,
143 pub cgroup_stats: bool,
144}
145impl Default for DBusStatsConfig {
146 fn default() -> Self {
147 DBusStatsConfig {
148 enabled: true,
149 user_stats: false,
150 peer_stats: false,
151 cgroup_stats: false,
152 }
153 }
154}
155
156#[derive(Clone, Debug, Default, Eq, PartialEq)]
159pub struct Config {
160 pub machines: MachinesConfig,
161 pub monitord: MonitordConfig,
162 pub networkd: NetworkdConfig,
163 pub pid1: Pid1Config,
164 pub services: Vec<String>,
165 pub system_state: SystemStateConfig,
166 pub timers: TimersConfig,
167 pub units: UnitsConfig,
168 pub dbus_stats: DBusStatsConfig,
169}
170
171impl From<Ini> for Config {
172 fn from(ini_config: Ini) -> Self {
173 let mut config = Config::default();
174
175 if let Some(dbus_address) = ini_config.get("monitord", "dbus_address") {
177 config.monitord.dbus_address = dbus_address;
178 }
179 if let Ok(Some(dbus_timeout)) = ini_config.getuint("monitord", "dbus_timeout") {
180 config.monitord.dbus_timeout = dbus_timeout;
181 }
182 config.monitord.daemon = read_config_bool(
183 &ini_config,
184 String::from("monitord"),
185 String::from("daemon"),
186 );
187 if let Ok(Some(daemon_stats_refresh_secs)) =
188 ini_config.getuint("monitord", "daemon_stats_refresh_secs")
189 {
190 config.monitord.daemon_stats_refresh_secs = daemon_stats_refresh_secs;
191 }
192 if let Some(key_prefix) = ini_config.get("monitord", "key_prefix") {
193 config.monitord.key_prefix = key_prefix;
194 }
195 config.monitord.output_format = MonitordOutputFormat::from_str(
196 &ini_config
197 .get("monitord", "output_format")
198 .expect("Need 'output_format' set in config"),
199 )
200 .expect("Need a valid value for the enum");
201
202 config.networkd.enabled = read_config_bool(
204 &ini_config,
205 String::from("networkd"),
206 String::from("enabled"),
207 );
208 if let Some(link_state_dir) = ini_config.get("networkd", "link_state_dir") {
209 config.networkd.link_state_dir = link_state_dir.into();
210 }
211
212 config.pid1.enabled =
214 read_config_bool(&ini_config, String::from("pid1"), String::from("enabled"));
215
216 let config_map = ini_config.get_map().unwrap_or(IndexMap::from([]));
218 if let Some(services) = config_map.get("services") {
219 config.services = services.keys().map(|s| s.to_string()).collect();
220 }
221
222 config.system_state.enabled = read_config_bool(
224 &ini_config,
225 String::from("system-state"),
226 String::from("enabled"),
227 );
228
229 config.timers.enabled =
231 read_config_bool(&ini_config, String::from("timers"), String::from("enabled"));
232 if let Some(timers_allowlist) = config_map.get("timers.allowlist") {
233 config.timers.allowlist = timers_allowlist.keys().map(|s| s.to_string()).collect();
234 }
235 if let Some(timers_blocklist) = config_map.get("timers.blocklist") {
236 config.timers.blocklist = timers_blocklist.keys().map(|s| s.to_string()).collect();
237 }
238
239 config.units.enabled =
241 read_config_bool(&ini_config, String::from("units"), String::from("enabled"));
242 config.units.state_stats = read_config_bool(
243 &ini_config,
244 String::from("units"),
245 String::from("state_stats"),
246 );
247 if let Some(state_stats_allowlist) = config_map.get("units.state_stats.allowlist") {
248 config.units.state_stats_allowlist = state_stats_allowlist
249 .keys()
250 .map(|s| s.to_string())
251 .collect();
252 }
253 if let Some(state_stats_blocklist) = config_map.get("units.state_stats.blocklist") {
254 config.units.state_stats_blocklist = state_stats_blocklist
255 .keys()
256 .map(|s| s.to_string())
257 .collect();
258 }
259 config.units.state_stats_time_in_state = read_config_bool(
260 &ini_config,
261 String::from("units"),
262 String::from("state_stats_time_in_state"),
263 );
264
265 config.machines.enabled = read_config_bool(
267 &ini_config,
268 String::from("machines"),
269 String::from("enabled"),
270 );
271 if let Some(machines_allowlist) = config_map.get("machines.allowlist") {
272 config.machines.allowlist = machines_allowlist.keys().map(|s| s.to_string()).collect();
273 }
274 if let Some(machines_blocklist) = config_map.get("machines.blocklist") {
275 config.machines.blocklist = machines_blocklist.keys().map(|s| s.to_string()).collect();
276 }
277
278 config.dbus_stats.enabled =
280 read_config_bool(&ini_config, String::from("dbus"), String::from("enabled"));
281 config.dbus_stats.user_stats = read_config_bool(
282 &ini_config,
283 String::from("dbus"),
284 String::from("user_stats"),
285 );
286 config.dbus_stats.peer_stats = read_config_bool(
287 &ini_config,
288 String::from("dbus"),
289 String::from("peer_stats"),
290 );
291 config.dbus_stats.cgroup_stats = read_config_bool(
292 &ini_config,
293 String::from("dbus"),
294 String::from("cgroup_stats"),
295 );
296
297 config
298 }
299}
300
301fn read_config_bool(config: &Ini, section: String, key: String) -> bool {
303 let option_bool = match config.getbool(§ion, &key) {
304 Ok(config_option_bool) => config_option_bool,
305 Err(err) => panic!(
306 "Unable to find '{}' key in '{}' section in config file: {}",
307 key, section, err
308 ),
309 };
310 match option_bool {
311 Some(bool_value) => bool_value,
312 None => {
313 error!(
314 "No value for '{}' in '{}' section ... assuming false",
315 key, section
316 );
317 false
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use std::io::Write;
325
326 use tempfile::NamedTempFile;
327
328 use super::*;
329
330 const FULL_CONFIG: &str = r###"
331[monitord]
332dbus_address = unix:path=/system_bus_socket
333dbus_timeout = 2
334daemon = true
335daemon_stats_refresh_secs = 0
336key_prefix = unittest
337output_format = json-pretty
338
339[networkd]
340enabled = true
341link_state_dir = /links
342
343[pid1]
344enabled = true
345
346[services]
347foo.service
348bar.service
349
350[system-state]
351enabled = true
352
353[timers]
354enabled = true
355
356[timers.allowlist]
357foo.timer
358
359[timers.blocklist]
360bar.timer
361
362[units]
363enabled = true
364state_stats = true
365state_stats_time_in_state = true
366
367[units.state_stats.allowlist]
368foo.service
369
370[units.state_stats.blocklist]
371bar.service
372
373[machines]
374enabled = true
375
376[machines.allowlist]
377foo
378bar
379
380[machines.blocklist]
381foo2
382
383[dbus]
384enabled = true
385user_stats = true
386peer_stats = true
387cgroup_stats = true
388"###;
389
390 const MINIMAL_CONFIG: &str = r###"
391[monitord]
392output_format = json-flat
393"###;
394
395 #[test]
396 fn test_default_config() {
397 assert!(Config::default().units.enabled)
398 }
399
400 #[test]
401 fn test_minimal_config() {
402 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
403 monitord_config
404 .write_all(MINIMAL_CONFIG.as_bytes())
405 .expect("Unable to write out temp config file");
406
407 let mut ini_config = Ini::new();
408 let _config_map = ini_config
409 .load(monitord_config.path())
410 .expect("Unable to load ini config");
411
412 let expected_config: Config = ini_config.into();
413 assert_eq!(
415 expected_config.monitord.output_format,
416 MonitordOutputFormat::JsonFlat,
417 );
418 assert!(!expected_config.networkd.enabled);
420 }
421
422 #[test]
423 fn test_full_config() {
424 let expected_config = Config {
425 monitord: MonitordConfig {
426 dbus_address: String::from("unix:path=/system_bus_socket"),
427 daemon: true,
428 daemon_stats_refresh_secs: u64::MIN,
429 key_prefix: String::from("unittest"),
430 output_format: MonitordOutputFormat::JsonPretty,
431 dbus_timeout: 2 as u64,
432 },
433 networkd: NetworkdConfig {
434 enabled: true,
435 link_state_dir: "/links".into(),
436 },
437 pid1: Pid1Config { enabled: true },
438 services: Vec::from([String::from("foo.service"), String::from("bar.service")]),
439 system_state: SystemStateConfig { enabled: true },
440 timers: TimersConfig {
441 enabled: true,
442 allowlist: Vec::from([String::from("foo.timer")]),
443 blocklist: Vec::from([String::from("bar.timer")]),
444 },
445 units: UnitsConfig {
446 enabled: true,
447 state_stats: true,
448 state_stats_allowlist: Vec::from([String::from("foo.service")]),
449 state_stats_blocklist: Vec::from([String::from("bar.service")]),
450 state_stats_time_in_state: true,
451 },
452 machines: MachinesConfig {
453 enabled: true,
454 allowlist: Vec::from([String::from("foo"), String::from("bar")]),
455 blocklist: Vec::from([String::from("foo2")]),
456 },
457 dbus_stats: DBusStatsConfig {
458 enabled: true,
459 user_stats: true,
460 peer_stats: true,
461 cgroup_stats: true,
462 },
463 };
464
465 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
466 monitord_config
467 .write_all(FULL_CONFIG.as_bytes())
468 .expect("Unable to write out temp config file");
469
470 let mut ini_config = Ini::new();
471 let _config_map = ini_config
472 .load(monitord_config.path())
473 .expect("Unable to load ini config");
474
475 assert_eq!(expected_config, ini_config.into(),);
477 }
478}