1use std::collections::HashSet;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use configparser::ini::Ini;
6use indexmap::map::IndexMap;
7use int_enum::IntEnum;
8use strum_macros::EnumString;
9use thiserror::Error;
10use tracing::error;
11
12#[derive(Error, Debug)]
13pub enum MonitordConfigError {
14 #[error("Invalid value for '{key}' in '{section}': {reason}")]
15 InvalidValue {
16 section: String,
17 key: String,
18 reason: String,
19 },
20 #[error("Missing key '{key}' in '{section}'")]
21 MissingKey { section: String, key: String },
22}
23
24#[derive(Clone, Debug, Default, EnumString, Eq, IntEnum, PartialEq, strum_macros::Display)]
25#[repr(u8)]
26pub enum MonitordOutputFormat {
27 #[default]
28 #[strum(serialize = "json", serialize = "JSON", serialize = "Json")]
29 Json = 0,
30 #[strum(
31 serialize = "json-flat",
32 serialize = "json_flat",
33 serialize = "jsonflat"
34 )]
35 JsonFlat = 1,
36 #[strum(
37 serialize = "json-pretty",
38 serialize = "json_pretty",
39 serialize = "jsonpretty"
40 )]
41 JsonPretty = 2,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct MonitordConfig {
46 pub dbus_address: String,
47 pub daemon: bool,
48 pub daemon_stats_refresh_secs: u64,
49 pub key_prefix: String,
50 pub output_format: MonitordOutputFormat,
51 pub dbus_timeout: u64,
52}
53impl Default for MonitordConfig {
54 fn default() -> Self {
55 MonitordConfig {
56 dbus_address: crate::DEFAULT_DBUS_ADDRESS.into(),
57 daemon: false,
58 daemon_stats_refresh_secs: 30,
59 key_prefix: "".to_string(),
60 output_format: MonitordOutputFormat::default(),
61 dbus_timeout: 30,
62 }
63 }
64}
65
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct NetworkdConfig {
68 pub enabled: bool,
69 pub link_state_dir: PathBuf,
70}
71impl Default for NetworkdConfig {
72 fn default() -> Self {
73 NetworkdConfig {
74 enabled: false,
75 link_state_dir: crate::networkd::NETWORKD_STATE_FILES.into(),
76 }
77 }
78}
79
80#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct Pid1Config {
82 pub enabled: bool,
83}
84impl Default for Pid1Config {
85 fn default() -> Self {
86 Pid1Config { enabled: true }
87 }
88}
89
90#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct SystemStateConfig {
92 pub enabled: bool,
93}
94impl Default for SystemStateConfig {
95 fn default() -> Self {
96 SystemStateConfig { enabled: true }
97 }
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
101pub struct TimersConfig {
102 pub enabled: bool,
103 pub allowlist: HashSet<String>,
104 pub blocklist: HashSet<String>,
105}
106impl Default for TimersConfig {
107 fn default() -> Self {
108 TimersConfig {
109 enabled: true,
110 allowlist: HashSet::new(),
111 blocklist: HashSet::new(),
112 }
113 }
114}
115
116#[derive(Clone, Debug, Eq, PartialEq)]
117pub struct UnitsConfig {
118 pub enabled: bool,
119 pub state_stats: bool,
120 pub state_stats_allowlist: HashSet<String>,
121 pub state_stats_blocklist: HashSet<String>,
122 pub state_stats_time_in_state: bool,
123 pub ignore_inactive_oneshot_services: bool,
124 pub unit_files: bool,
125 pub per_unit_concurrency: u64,
130 pub slowest_units_count: u64,
133}
134impl Default for UnitsConfig {
135 fn default() -> Self {
136 UnitsConfig {
137 enabled: true,
138 state_stats: false,
139 state_stats_allowlist: HashSet::new(),
140 state_stats_blocklist: HashSet::new(),
141 state_stats_time_in_state: true,
142 ignore_inactive_oneshot_services: true,
143 unit_files: true,
144 per_unit_concurrency: 8,
145 slowest_units_count: 5,
146 }
147 }
148}
149
150#[derive(Clone, Debug, Eq, PartialEq)]
151pub struct MachinesConfig {
152 pub enabled: bool,
153 pub allowlist: HashSet<String>,
154 pub blocklist: HashSet<String>,
155}
156impl Default for MachinesConfig {
157 fn default() -> Self {
158 MachinesConfig {
159 enabled: true,
160 allowlist: HashSet::new(),
161 blocklist: HashSet::new(),
162 }
163 }
164}
165
166#[derive(Clone, Debug, Eq, PartialEq)]
167pub struct DBusStatsConfig {
168 pub enabled: bool,
169 pub stale_fd_stats: bool,
170
171 pub user_stats: bool,
172 pub user_allowlist: HashSet<String>,
173 pub user_blocklist: HashSet<String>,
174
175 pub peer_stats: bool,
176 pub peer_well_known_names_only: bool,
177 pub peer_allowlist: HashSet<String>,
178 pub peer_blocklist: HashSet<String>,
179 pub peer_name_concurrency: u64,
184
185 pub cgroup_stats: bool,
186 pub cgroup_allowlist: HashSet<String>,
187 pub cgroup_blocklist: HashSet<String>,
188}
189impl Default for DBusStatsConfig {
190 fn default() -> Self {
191 DBusStatsConfig {
192 enabled: true,
193 stale_fd_stats: true,
194
195 user_stats: false,
196 user_allowlist: HashSet::new(),
197 user_blocklist: HashSet::new(),
198
199 peer_stats: false,
200 peer_well_known_names_only: false,
201 peer_allowlist: HashSet::new(),
202 peer_blocklist: HashSet::new(),
203 peer_name_concurrency: 8,
204
205 cgroup_stats: false,
206 cgroup_allowlist: HashSet::new(),
207 cgroup_blocklist: HashSet::new(),
208 }
209 }
210}
211
212#[derive(Clone, Debug, Eq, PartialEq)]
213pub struct BootBlameConfig {
214 pub enabled: bool,
215 pub cache_enabled: bool,
216 pub cache_dir: String,
217 pub num_slowest_units: u64,
218 pub allowlist: HashSet<String>,
219 pub blocklist: HashSet<String>,
220}
221impl Default for BootBlameConfig {
222 fn default() -> Self {
223 BootBlameConfig {
224 enabled: false,
225 cache_enabled: true,
226 cache_dir: "/run/monitord".to_string(),
227 num_slowest_units: 5,
228 allowlist: HashSet::new(),
229 blocklist: HashSet::new(),
230 }
231 }
232}
233
234#[derive(Clone, Debug, Default, Eq, PartialEq)]
235pub struct VerifyConfig {
236 pub enabled: bool,
237 pub allowlist: HashSet<String>,
238 pub blocklist: HashSet<String>,
239}
240
241#[derive(Clone, Debug, Default, Eq, PartialEq)]
242pub struct VarlinkConfig {
243 pub enabled: bool,
244}
245
246#[derive(Clone, Debug, Default, Eq, PartialEq)]
249pub struct Config {
250 pub machines: MachinesConfig,
251 pub monitord: MonitordConfig,
252 pub networkd: NetworkdConfig,
253 pub pid1: Pid1Config,
254 pub services: HashSet<String>,
255 pub system_state: SystemStateConfig,
256 pub timers: TimersConfig,
257 pub units: UnitsConfig,
258 pub dbus_stats: DBusStatsConfig,
259 pub boot_blame: BootBlameConfig,
260 pub verify: VerifyConfig,
261 pub varlink: VarlinkConfig,
262}
263
264impl TryFrom<Ini> for Config {
265 type Error = MonitordConfigError;
266
267 fn try_from(ini_config: Ini) -> Result<Self, MonitordConfigError> {
268 let mut config = Config::default();
269
270 if let Some(dbus_address) = ini_config.get("monitord", "dbus_address") {
272 config.monitord.dbus_address = dbus_address;
273 }
274 if let Ok(Some(dbus_timeout)) = ini_config.getuint("monitord", "dbus_timeout") {
275 config.monitord.dbus_timeout = dbus_timeout;
276 }
277 config.monitord.daemon = read_config_bool(&ini_config, "monitord", "daemon")?;
278 if let Ok(Some(daemon_stats_refresh_secs)) =
279 ini_config.getuint("monitord", "daemon_stats_refresh_secs")
280 {
281 config.monitord.daemon_stats_refresh_secs = daemon_stats_refresh_secs;
282 }
283 if let Some(key_prefix) = ini_config.get("monitord", "key_prefix") {
284 config.monitord.key_prefix = key_prefix;
285 }
286 let output_format_str = ini_config.get("monitord", "output_format").ok_or_else(|| {
287 MonitordConfigError::MissingKey {
288 section: "monitord".into(),
289 key: "output_format".into(),
290 }
291 })?;
292 config.monitord.output_format = MonitordOutputFormat::from_str(&output_format_str)
293 .map_err(|e| MonitordConfigError::InvalidValue {
294 section: "monitord".into(),
295 key: "output_format".into(),
296 reason: e.to_string(),
297 })?;
298
299 config.networkd.enabled = read_config_bool(&ini_config, "networkd", "enabled")?;
301 if let Some(link_state_dir) = ini_config.get("networkd", "link_state_dir") {
302 config.networkd.link_state_dir = link_state_dir.into();
303 }
304
305 config.pid1.enabled = read_config_bool(&ini_config, "pid1", "enabled")?;
307
308 let config_map = ini_config.get_map().unwrap_or(IndexMap::from([]));
310 if let Some(services) = config_map.get("services") {
311 config.services = services.keys().map(|s| s.to_string()).collect();
312 }
313
314 config.system_state.enabled = read_config_bool(&ini_config, "system-state", "enabled")?;
316
317 config.timers.enabled = read_config_bool(&ini_config, "timers", "enabled")?;
319 if let Some(timers_allowlist) = config_map.get("timers.allowlist") {
320 config.timers.allowlist = timers_allowlist.keys().map(|s| s.to_string()).collect();
321 }
322 if let Some(timers_blocklist) = config_map.get("timers.blocklist") {
323 config.timers.blocklist = timers_blocklist.keys().map(|s| s.to_string()).collect();
324 }
325
326 config.units.enabled = read_config_bool(&ini_config, "units", "enabled")?;
328 config.units.state_stats = read_config_bool(&ini_config, "units", "state_stats")?;
329 if let Some(state_stats_allowlist) = config_map.get("units.state_stats.allowlist") {
330 config.units.state_stats_allowlist = state_stats_allowlist
331 .keys()
332 .map(|s| s.to_string())
333 .collect();
334 }
335 if let Some(state_stats_blocklist) = config_map.get("units.state_stats.blocklist") {
336 config.units.state_stats_blocklist = state_stats_blocklist
337 .keys()
338 .map(|s| s.to_string())
339 .collect();
340 }
341 config.units.state_stats_time_in_state =
342 read_config_bool(&ini_config, "units", "state_stats_time_in_state")?;
343 if let Some(ignore_inactive_oneshot_services) =
344 read_config_optional_bool(&ini_config, "units", "ignore_inactive_oneshot_services")?
345 {
346 config.units.ignore_inactive_oneshot_services = ignore_inactive_oneshot_services;
347 }
348 if let Some(unit_files) = read_config_optional_bool(&ini_config, "units", "unit_files")? {
349 config.units.unit_files = unit_files;
350 }
351 if let Ok(Some(per_unit_concurrency)) = ini_config.getuint("units", "per_unit_concurrency")
352 {
353 config.units.per_unit_concurrency = per_unit_concurrency;
354 }
355 if let Ok(Some(slowest_units_count)) = ini_config.getuint("units", "slowest_units_count") {
356 config.units.slowest_units_count = slowest_units_count;
357 }
358
359 config.machines.enabled = read_config_bool(&ini_config, "machines", "enabled")?;
361 if let Some(machines_allowlist) = config_map.get("machines.allowlist") {
362 config.machines.allowlist = machines_allowlist.keys().map(|s| s.to_string()).collect();
363 }
364 if let Some(machines_blocklist) = config_map.get("machines.blocklist") {
365 config.machines.blocklist = machines_blocklist.keys().map(|s| s.to_string()).collect();
366 }
367
368 config.dbus_stats.enabled = read_config_bool(&ini_config, "dbus", "enabled")?;
370 if let Some(stale_fd_stats) =
371 read_config_optional_bool(&ini_config, "dbus", "stale_fd_stats")?
372 {
373 config.dbus_stats.stale_fd_stats = stale_fd_stats;
374 }
375
376 config.dbus_stats.user_stats = read_config_bool(&ini_config, "dbus", "user_stats")?;
377 if let Some(user_allowlist) = config_map.get("dbus.user.allowlist") {
378 config.dbus_stats.user_allowlist =
379 user_allowlist.keys().map(|s| s.to_string()).collect();
380 }
381 if let Some(user_blocklist) = config_map.get("dbus.user.blocklist") {
382 config.dbus_stats.user_blocklist =
383 user_blocklist.keys().map(|s| s.to_string()).collect();
384 }
385
386 config.dbus_stats.peer_stats = read_config_bool(&ini_config, "dbus", "peer_stats")?;
387 config.dbus_stats.peer_well_known_names_only =
388 read_config_bool(&ini_config, "dbus", "peer_well_known_names_only")?;
389 if let Some(peer_allowlist) = config_map.get("dbus.peer.allowlist") {
390 config.dbus_stats.peer_allowlist =
391 peer_allowlist.keys().map(|s| s.to_string()).collect();
392 }
393 if let Some(peer_blocklist) = config_map.get("dbus.peer.blocklist") {
394 config.dbus_stats.peer_blocklist =
395 peer_blocklist.keys().map(|s| s.to_string()).collect();
396 }
397 if let Ok(Some(peer_name_concurrency)) = ini_config.getuint("dbus", "peer_name_concurrency")
398 {
399 config.dbus_stats.peer_name_concurrency = peer_name_concurrency;
400 }
401
402 config.dbus_stats.cgroup_stats = read_config_bool(&ini_config, "dbus", "cgroup_stats")?;
403 if let Some(cgroup_allowlist) = config_map.get("dbus.cgroup.allowlist") {
404 config.dbus_stats.cgroup_allowlist =
405 cgroup_allowlist.keys().map(|s| s.to_string()).collect();
406 }
407 if let Some(cgroup_blocklist) = config_map.get("dbus.cgroup.blocklist") {
408 config.dbus_stats.cgroup_blocklist =
409 cgroup_blocklist.keys().map(|s| s.to_string()).collect();
410 }
411
412 config.boot_blame.enabled = read_config_bool(&ini_config, "boot", "enabled")?;
414 if let Some(cache_enabled) =
415 read_config_optional_bool(&ini_config, "boot", "cache_enabled")?
416 {
417 config.boot_blame.cache_enabled = cache_enabled;
418 }
419 if let Some(cache_dir) = ini_config.get("boot", "cache_dir") {
420 config.boot_blame.cache_dir = cache_dir;
421 }
422 if let Ok(Some(num_slowest_units)) = ini_config.getuint("boot", "num_slowest_units") {
423 config.boot_blame.num_slowest_units = num_slowest_units;
424 }
425 if let Some(boot_allowlist) = config_map.get("boot.allowlist") {
426 config.boot_blame.allowlist = boot_allowlist.keys().map(|s| s.to_string()).collect();
427 }
428 if let Some(boot_blocklist) = config_map.get("boot.blocklist") {
429 config.boot_blame.blocklist = boot_blocklist.keys().map(|s| s.to_string()).collect();
430 }
431
432 config.verify.enabled = read_config_bool(&ini_config, "verify", "enabled")?;
434 if let Some(verify_allowlist) = config_map.get("verify.allowlist") {
435 config.verify.allowlist = verify_allowlist.keys().map(|s| s.to_string()).collect();
436 }
437 if let Some(verify_blocklist) = config_map.get("verify.blocklist") {
438 config.verify.blocklist = verify_blocklist.keys().map(|s| s.to_string()).collect();
439 }
440
441 config.varlink.enabled = read_config_bool(&ini_config, "varlink", "enabled")?;
443
444 Ok(config)
445 }
446}
447
448fn read_config_bool(config: &Ini, section: &str, key: &str) -> Result<bool, MonitordConfigError> {
450 let option_bool =
451 config
452 .getbool(section, key)
453 .map_err(|err| MonitordConfigError::InvalidValue {
454 section: section.into(),
455 key: key.into(),
456 reason: err,
457 })?;
458 match option_bool {
459 Some(bool_value) => Ok(bool_value),
460 None => {
461 error!(
462 "No value for '{}' in '{}' section ... assuming false",
463 key, section
464 );
465 Ok(false)
466 }
467 }
468}
469
470fn read_config_optional_bool(
472 config: &Ini,
473 section: &str,
474 key: &str,
475) -> Result<Option<bool>, MonitordConfigError> {
476 config
477 .getbool(section, key)
478 .map_err(|err| MonitordConfigError::InvalidValue {
479 section: section.into(),
480 key: key.into(),
481 reason: err,
482 })
483}
484
485#[cfg(test)]
486mod tests {
487 use std::io::Write;
488
489 use tempfile::NamedTempFile;
490
491 use super::*;
492
493 const FULL_CONFIG: &str = r###"
494[monitord]
495dbus_address = unix:path=/system_bus_socket
496dbus_timeout = 2
497daemon = true
498daemon_stats_refresh_secs = 0
499key_prefix = unittest
500output_format = json-pretty
501
502[networkd]
503enabled = true
504link_state_dir = /links
505
506[pid1]
507enabled = true
508
509[services]
510foo.service
511bar.service
512
513[system-state]
514enabled = true
515
516[timers]
517enabled = true
518
519[timers.allowlist]
520foo.timer
521
522[timers.blocklist]
523bar.timer
524
525[units]
526enabled = true
527state_stats = true
528state_stats_time_in_state = true
529ignore_inactive_oneshot_services = true
530unit_files = true
531per_unit_concurrency = 16
532slowest_units_count = 3
533
534[units.state_stats.allowlist]
535foo.service
536
537[units.state_stats.blocklist]
538bar.service
539
540[machines]
541enabled = true
542
543[machines.allowlist]
544foo
545bar
546
547[machines.blocklist]
548foo2
549
550[dbus]
551enabled = true
552stale_fd_stats = true
553user_stats = true
554peer_stats = true
555peer_well_known_names_only = true
556peer_name_concurrency = 12
557cgroup_stats = true
558
559[dbus.user.allowlist]
560foo
561bar
562
563[dbus.user.blocklist]
564foo2
565
566[dbus.peer.allowlist]
567foo
568bar
569
570[dbus.peer.blocklist]
571foo2
572
573[dbus.cgroup.allowlist]
574foo
575bar
576
577[dbus.cgroup.blocklist]
578foo2
579
580[boot]
581enabled = true
582cache_enabled = false
583cache_dir = /tmp/monitord-test
584num_slowest_units = 10
585
586[boot.allowlist]
587foo.service
588
589[boot.blocklist]
590bar.service
591
592[varlink]
593enabled = true
594"###;
595
596 const MINIMAL_CONFIG: &str = r###"
597[monitord]
598output_format = json-flat
599"###;
600
601 #[test]
602 fn test_default_config() {
603 assert!(Config::default().units.enabled)
604 }
605
606 #[test]
607 fn test_minimal_config() {
608 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
609 monitord_config
610 .write_all(MINIMAL_CONFIG.as_bytes())
611 .expect("Unable to write out temp config file");
612
613 let mut ini_config = Ini::new();
614 let _config_map = ini_config
615 .load(monitord_config.path())
616 .expect("Unable to load ini config");
617
618 let expected_config: Config = ini_config.try_into().expect("Failed to parse config");
619 assert_eq!(
621 expected_config.monitord.output_format,
622 MonitordOutputFormat::JsonFlat,
623 );
624 assert!(!expected_config.networkd.enabled);
626 assert!(expected_config.boot_blame.cache_enabled);
628 assert!(expected_config.units.ignore_inactive_oneshot_services);
630 }
631
632 #[test]
633 fn test_units_ignore_inactive_oneshot_services_override() {
634 let units_override_config = r###"
635[monitord]
636output_format = json
637
638[units]
639ignore_inactive_oneshot_services = false
640"###;
641 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
642 monitord_config
643 .write_all(units_override_config.as_bytes())
644 .expect("Unable to write out temp config file");
645
646 let mut ini_config = Ini::new();
647 let _config_map = ini_config
648 .load(monitord_config.path())
649 .expect("Unable to load ini config");
650
651 let parsed_config: Config = ini_config.try_into().expect("Failed to parse config");
652 assert!(!parsed_config.units.ignore_inactive_oneshot_services);
653 }
654
655 #[test]
656 fn test_units_per_unit_concurrency_override() {
657 let units_override_config = r###"
658[monitord]
659output_format = json
660
661[units]
662per_unit_concurrency = 32
663slowest_units_count = 0
664"###;
665 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
666 monitord_config
667 .write_all(units_override_config.as_bytes())
668 .expect("Unable to write out temp config file");
669
670 let mut ini_config = Ini::new();
671 let _config_map = ini_config
672 .load(monitord_config.path())
673 .expect("Unable to load ini config");
674
675 let parsed_config: Config = ini_config.try_into().expect("Failed to parse config");
676 assert_eq!(parsed_config.units.per_unit_concurrency, 32);
677 assert_eq!(parsed_config.units.slowest_units_count, 0);
678 }
679
680 #[test]
681 fn test_full_config() {
682 let expected_config = Config {
683 monitord: MonitordConfig {
684 dbus_address: String::from("unix:path=/system_bus_socket"),
685 daemon: true,
686 daemon_stats_refresh_secs: u64::MIN,
687 key_prefix: String::from("unittest"),
688 output_format: MonitordOutputFormat::JsonPretty,
689 dbus_timeout: 2 as u64,
690 },
691 networkd: NetworkdConfig {
692 enabled: true,
693 link_state_dir: "/links".into(),
694 },
695 pid1: Pid1Config { enabled: true },
696 services: HashSet::from([String::from("foo.service"), String::from("bar.service")]),
697 system_state: SystemStateConfig { enabled: true },
698 timers: TimersConfig {
699 enabled: true,
700 allowlist: HashSet::from([String::from("foo.timer")]),
701 blocklist: HashSet::from([String::from("bar.timer")]),
702 },
703 units: UnitsConfig {
704 enabled: true,
705 state_stats: true,
706 state_stats_allowlist: HashSet::from([String::from("foo.service")]),
707 state_stats_blocklist: HashSet::from([String::from("bar.service")]),
708 state_stats_time_in_state: true,
709 ignore_inactive_oneshot_services: true,
710 unit_files: true,
711 per_unit_concurrency: 16,
712 slowest_units_count: 3,
713 },
714 machines: MachinesConfig {
715 enabled: true,
716 allowlist: HashSet::from([String::from("foo"), String::from("bar")]),
717 blocklist: HashSet::from([String::from("foo2")]),
718 },
719 dbus_stats: DBusStatsConfig {
720 enabled: true,
721 stale_fd_stats: true,
722 user_stats: true,
723 user_allowlist: HashSet::from([String::from("foo"), String::from("bar")]),
724 user_blocklist: HashSet::from([String::from("foo2")]),
725 peer_stats: true,
726 peer_well_known_names_only: true,
727 peer_allowlist: HashSet::from([String::from("foo"), String::from("bar")]),
728 peer_blocklist: HashSet::from([String::from("foo2")]),
729 peer_name_concurrency: 12,
730 cgroup_stats: true,
731 cgroup_allowlist: HashSet::from([String::from("foo"), String::from("bar")]),
732 cgroup_blocklist: HashSet::from([String::from("foo2")]),
733 },
734 boot_blame: BootBlameConfig {
735 enabled: true,
736 cache_enabled: false,
737 cache_dir: "/tmp/monitord-test".to_string(),
738 num_slowest_units: 10,
739 allowlist: HashSet::from([String::from("foo.service")]),
740 blocklist: HashSet::from([String::from("bar.service")]),
741 },
742 verify: VerifyConfig {
743 enabled: false,
744 allowlist: HashSet::new(),
745 blocklist: HashSet::new(),
746 },
747 varlink: VarlinkConfig { enabled: true },
748 };
749
750 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
751 monitord_config
752 .write_all(FULL_CONFIG.as_bytes())
753 .expect("Unable to write out temp config file");
754
755 let mut ini_config = Ini::new();
756 let _config_map = ini_config
757 .load(monitord_config.path())
758 .expect("Unable to load ini config");
759
760 let actual_config: Config = ini_config.try_into().expect("Failed to parse config");
762 assert_eq!(expected_config, actual_config);
763 }
764
765 #[test]
766 fn test_invalid_config_returns_error() {
767 let invalid_config = "[monitord]\ndaemon = notabool\noutput_format = json\n";
768 let mut monitord_config = NamedTempFile::new().expect("Unable to make named tempfile");
769 monitord_config
770 .write_all(invalid_config.as_bytes())
771 .expect("Unable to write out temp config file");
772
773 let mut ini_config = Ini::new();
774 let _config_map = ini_config
775 .load(monitord_config.path())
776 .expect("Unable to load ini config");
777
778 let result: Result<Config, _> = ini_config.try_into();
779 assert!(result.is_err());
780 }
781}