1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5use std::{io, path::PathBuf, process::Command};
8
9use thiserror::Error;
10use uuid::Uuid;
11
12pub mod ssl;
14
15#[cfg(feature = "config-observability")]
17pub mod observability;
18
19#[cfg(feature = "config-observability")]
21pub mod tracing;
22
23pub mod url;
24pub use url::url_authentication;
25
26#[derive(Debug, Error)]
28pub enum ConfigError {
29 #[error("The config parameter {0} have an incorrect value `{1}`")]
31 WrongValue(String, String),
32 #[error("The path `{0}` provided don't match the pattern `{1}`")]
34 WrongPathPattern(String, glob::PatternError),
35 #[error("The path `{0}` provided is not correct")]
37 WrongPath(PathBuf),
38 #[error("The file `{0}` can't be read `{1}`")]
40 IoFile(String, std::io::Error),
41 #[cfg(feature = "config-openssl")]
42 #[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
56pub 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
73pub 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
107pub 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}