Skip to main content

prosa_utils/
config.rs

1//! Module for ProSA configuration object
2//!
3//! <svg width="40" height="40">
4#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5//! </svg>
6
7use std::{io, path::PathBuf, process::Command};
8
9use thiserror::Error;
10use uuid::Uuid;
11
12// Feature openssl or rusttls,...
13pub mod ssl;
14
15// Feature opentelemetry
16#[cfg(feature = "config-observability")]
17pub mod observability;
18
19// Feature tracing
20#[cfg(feature = "config-observability")]
21pub mod tracing;
22
23pub mod url;
24pub use url::url_authentication;
25
26/// Error define for configuration object
27#[derive(Debug, Error)]
28pub enum ConfigError {
29    /// Error that indicate a wrong path format in filesystem
30    #[error("The config parameter {0} have an incorrect value `{1}`")]
31    WrongValue(String, String),
32    /// Error that indicate a wrong path format pattern in filesystem
33    #[error("The path `{0}` provided don't match the pattern `{1}`")]
34    WrongPathPattern(String, glob::PatternError),
35    /// Error that indicate a wrong path format in filesystem
36    #[error("The path `{0}` provided is not correct")]
37    WrongPath(PathBuf),
38    /// Error on a file read
39    #[error("The file `{0}` can't be read `{1}`")]
40    IoFile(String, std::io::Error),
41    #[cfg(feature = "config-openssl")]
42    /// SSL error
43    #[error("Openssl error `{0}`")]
44    OpenSsl(#[from] openssl::error::ErrorStack),
45}
46
47impl From<ConfigError> for io::Error {
48    fn from(err: ConfigError) -> Self {
49        io::Error::new(
50            io::ErrorKind::InvalidInput,
51            format!("ProSA Config error: {}", err),
52        )
53    }
54}
55
56/// Method to try get the country name from the OS
57pub fn os_country() -> Option<String> {
58    if let Some(lang) = option_env!("LANG") {
59        let language = if let Some(pos) = lang.find('.') {
60            &lang[..pos]
61        } else {
62            lang
63        };
64
65        if let Some(pos) = language.find('_') {
66            return Some(String::from(&language[pos + 1..]));
67        }
68    }
69
70    None
71}
72
73/// Method to try get the hostname from the OS
74pub fn hostname() -> Option<String> {
75    cfg_select! {
76        target_family = "unix" => {
77            if let Ok(host) = std::env::var("HOSTNAME").map(|h| h.trim().to_string())
78                && !host.is_empty()
79                && !host.contains('\n')
80            {
81                return Some(host);
82            }
83
84            Command::new("hostname")
85                .arg("-s")
86                .output()
87                .ok()
88                .and_then(|h| {
89                    str::from_utf8(h.stdout.trim_ascii())
90                        .ok()
91                        .filter(|h| !h.is_empty() && !h.contains('\n'))
92                        .map(|h| h.to_string())
93                })
94        }
95        target_family = "windows" => {
96            Command::new("hostname").output().ok().and_then(|h| {
97                str::from_utf8(h.stdout.trim_ascii())
98                    .ok()
99                    .filter(|h| !h.is_empty() && !h.contains('\n'))
100                    .map(|h| h.to_string())
101            })
102        }
103        _ => None
104    }
105}
106
107/// Method to get a consistant host ID (UUID v1 or UUID v4) useful for `service.instance.id`
108pub fn hostid() -> String {
109    cfg_select! {
110        target_os = "linux" => {
111            if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id")
112                && let Ok(machine_uuid) = Uuid::parse_str(machine_id.trim())
113            {
114                return machine_uuid.to_string();
115            }
116        }
117        target_os = "macos" => {
118            if let Ok(output) = Command::new("ioreg")
119                .args(["-rd1", "-c", "IOPlatformExpertDevice"])
120                .output()
121                && output.status.success()
122                && let Ok(output_str) = String::from_utf8(output.stdout)
123            {
124                for line in output_str.lines() {
125                    if line.contains("IOPlatformUUID")
126                        && let Some(value) = line.split('"').nth(3)
127                        && let Ok(machine_uuid) = Uuid::parse_str(value.trim())
128                    {
129                        return machine_uuid.to_string();
130                    }
131                }
132            }
133        }
134        _ => {}
135    }
136
137    if let Some(hostname) = hostname() {
138        let mut node_id = [0u8; 6];
139        let len = hostname.len().min(6);
140        node_id[..len].copy_from_slice(&hostname.as_bytes()[hostname.len() - len..]);
141
142        Uuid::now_v1(&node_id).to_string()
143    } else {
144        Uuid::new_v4().to_string()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_os_country() {
154        let country = os_country();
155        if let Some(cn) = country {
156            assert_eq!(2, cn.len());
157        }
158    }
159
160    #[test]
161    fn test_hostname() {
162        let host = hostname();
163        if let Some(hn) = host {
164            assert!(!hn.is_empty());
165        }
166    }
167
168    #[test]
169    fn test_hostid() {
170        let host_id = hostid();
171        assert_eq!(host_id.len(), 36);
172    }
173}