1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Loading facts about the system that we are currently running on.

use failure::{bail, Error};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::hash::Hash;
use std::io;
use std::path::Path;

pub const DISTRO: &'static str = "distro";

/// The holder of all the facts detected in the system.
pub struct Facts(HashMap<String, String>);

impl Facts {
    /// Construct a set of custom facts.
    pub fn new(facts: impl IntoIterator<Item = (String, String)>) -> Self {
        Facts(facts.into_iter().collect())
    }

    /// Load facts about the system.
    pub fn load() -> Result<Facts, Error> {
        let mut facts = HashMap::new();

        if let Some(distro) = detect_distro()? {
            facts.insert(DISTRO.to_string(), distro);
        }

        return Ok(Facts(facts));

        /// Detect which distro we appear to be running.
        fn detect_distro() -> Result<Option<String>, Error> {
            if metadata("/etc/redhat-release")?
                .map(|m| m.is_file())
                .unwrap_or(false)
            {
                return Ok(Some("fedora".to_string()));
            }

            if metadata("/etc/gentoo-release")?
                .map(|m| m.is_file())
                .unwrap_or(false)
            {
                return Ok(Some("gentoo".to_string()));
            }

            if metadata("/etc/debian_version")?
                .map(|m| m.is_file())
                .unwrap_or(false)
            {
                return Ok(Some("debian".to_string()));
            }

            if environ("OSTYPE")?
                .map(|s| s.starts_with("darwin"))
                .unwrap_or(false)
            {
                return Ok(Some("osx".to_string()));
            }

            Ok(None)
        }

        fn metadata<P: AsRef<Path>>(path: P) -> Result<Option<fs::Metadata>, Error> {
            let p = path.as_ref();

            let m = match fs::metadata(p) {
                Ok(m) => m,
                Err(e) => match e.kind() {
                    io::ErrorKind::NotFound => return Ok(None),
                    _ => bail!("failed to load file metadata: {}: {}", p.display(), e),
                },
            };

            Ok(Some(m))
        }

        fn environ(key: &str) -> Result<Option<String>, Error> {
            let value = match env::var(key) {
                Ok(value) => value,
                Err(env::VarError::NotPresent) => return Ok(None),
                Err(e) => bail!("failed to load environment var: {}: {}", key, e),
            };

            Ok(Some(value))
        }
    }

    /// Get the specified fact, if present.
    pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&str>
    where
        String: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.0.get(k).map(|s| s.as_str())
    }
}