auxon_sdk/reflector_config/
resolve.rs1use crate::auth_token::{
2 decode_auth_token_hex, token_user_file::REFLECTOR_AUTH_TOKEN_DEFAULT_FILE_NAME, AuthToken,
3};
4use crate::reflector_config::{try_from_file, Config, ConfigLoadError, CONFIG_ENV_VAR};
5use std::{
6 env,
7 path::{Path, PathBuf},
8};
9
10const CONFIG_FILE_NAME: &str = "config.toml";
11const CONFIG_DIR: &str = "modality-reflector";
12const SYS_CONFIG_BASE_PATH: &str = "/etc";
13
14pub const MODALITY_HOST_ENV_VAR: &str = "MODALITY_HOST";
15pub const MODALITY_REFLECTOR_PLUGINS_DIR_ENV_VAR: &str = "MODALITY_REFLECTOR_PLUGINS_DIR";
16
17pub fn load_config_and_auth_token(
21 manually_provided_config_path: Option<PathBuf>,
22 manually_provided_auth_token_path: Option<PathBuf>,
23) -> Result<
24 (crate::reflector_config::refined::Config, AuthToken),
25 Box<dyn std::error::Error + Send + Sync>,
26> {
27 let ConfigContext {
28 config: cfg,
29 config_file_parent_dir,
30 ..
31 } = ConfigContext::load_default(manually_provided_config_path)?;
32
33 let auth_token =
34 resolve_reflector_auth_token(manually_provided_auth_token_path, &config_file_parent_dir)?;
35 Ok((cfg, auth_token))
36}
37
38pub fn load_config(
49 manually_provided_config_path: Option<PathBuf>,
50) -> Result<Option<ConfigContext>, ExpandedConfigLoadError> {
51 let mut cfg = load_system_config()?;
52 if let Some(other_cfg) = load_user_config()? {
53 cfg.replace(other_cfg);
54 }
55 if let Some(other_cfg) = load_env_config()? {
56 cfg.replace(other_cfg);
57 }
58 if let Some(other_cfg_path) = manually_provided_config_path {
59 if let Some(config_file_parent_dir) = other_cfg_path.parent().map(ToOwned::to_owned) {
60 let other_cfg = ConfigContext {
61 config: try_from_file(other_cfg_path.as_path())?,
62 config_file: Some(other_cfg_path),
63 config_file_parent_dir,
64 };
65 cfg.replace(other_cfg);
66 }
67 }
68 Ok(cfg)
69}
70
71pub struct ConfigContext {
72 pub config: Config,
73 pub config_file: Option<PathBuf>,
74 pub config_file_parent_dir: PathBuf,
75}
76
77impl ConfigContext {
78 pub fn load_default(
79 config_file_override: Option<PathBuf>,
80 ) -> Result<Self, ExpandedConfigLoadError> {
81 let mut cc = if let Some(cc) = load_config(config_file_override)? {
82 cc
83 } else {
84 let config_file_parent_dir = env::current_dir().map_err(|ioerr| {
85 ExpandedConfigLoadError::ConfigLoadError(ConfigLoadError::Io(ioerr))
86 })?;
87 ConfigContext {
88 config: Default::default(),
89 config_file: None,
90 config_file_parent_dir,
91 }
92 };
93
94 cc.apply_environment_variable_overrides()?;
95
96 Ok(cc)
97 }
98
99 pub fn apply_environment_variable_overrides(&mut self) -> Result<(), ExpandedConfigLoadError> {
107 if let Some(modality_host) = env_str(MODALITY_HOST_ENV_VAR)? {
108 if self.config.ingest.is_none() {
109 self.config.ingest = Some(Default::default());
110 }
111
112 let ingest = self.config.ingest.as_mut().unwrap();
113 ingest.protocol_parent_url = Some(
114 url::Url::parse(&format!("modality-ingest://{modality_host}")).map_err(|_| {
115 ExpandedConfigLoadError::InvalidHostNameFromEnv {
116 var: MODALITY_HOST_ENV_VAR,
117 value: modality_host.clone(),
118 }
119 })?,
120 );
121
122 if self.config.mutation.is_none() {
123 self.config.mutation = Some(Default::default());
124 }
125
126 let mutation = self.config.mutation.as_mut().unwrap();
127 mutation.protocol_parent_url = Some(
128 url::Url::parse(&format!("modality-mutation://{modality_host}")).map_err(|_| {
129 ExpandedConfigLoadError::InvalidHostNameFromEnv {
130 var: MODALITY_HOST_ENV_VAR,
131 value: modality_host,
132 }
133 })?,
134 );
135 }
136
137 if let Some(plugins_dir) = env_str(MODALITY_REFLECTOR_PLUGINS_DIR_ENV_VAR)? {
138 if self.config.plugins.is_none() {
139 self.config.plugins = Some(Default::default());
140 }
141 let plugins = self.config.plugins.as_mut().unwrap();
142 plugins.plugins_dir = Some(plugins_dir.into());
143 }
144
145 Ok(())
146 }
147}
148
149fn load_system_config() -> Result<Option<ConfigContext>, ConfigLoadError> {
150 let cfg_path = system_config_path();
151 if cfg_path.exists() {
152 tracing::trace!("Load system configuration file {}", cfg_path.display());
153 if let Some(config_file_parent_dir) = cfg_path.parent().map(ToOwned::to_owned) {
154 Ok(Some(ConfigContext {
155 config: try_from_file(&cfg_path)?,
156 config_file: Some(cfg_path),
157 config_file_parent_dir,
158 }))
159 } else {
160 Ok(None)
161 }
162 } else {
163 Ok(None)
164 }
165}
166
167fn load_user_config() -> Result<Option<ConfigContext>, ExpandedConfigLoadError> {
168 load_user_or_env_config(UserOrEnvPath::User)
169}
170
171fn load_env_config() -> Result<Option<ConfigContext>, ExpandedConfigLoadError> {
172 load_user_or_env_config(UserOrEnvPath::Env)
173}
174
175fn load_user_or_env_config(
176 loc: UserOrEnvPath,
177) -> Result<Option<ConfigContext>, ExpandedConfigLoadError> {
178 let cfg_path = match loc {
179 UserOrEnvPath::User => user_config_path(),
180 UserOrEnvPath::Env => env_config_path(),
181 };
182 match cfg_path {
183 Some(p) if p.exists() => {
184 tracing::trace!("Load {} configuration file {}", loc, p.display());
185 if let Some(config_file_parent_dir) = p.as_path().parent().map(ToOwned::to_owned) {
186 Ok(Some(ConfigContext {
187 config: try_from_file(&p)?,
188 config_file: Some(p),
189 config_file_parent_dir,
190 }))
191 } else {
192 Ok(None)
193 }
194 }
195 _ => Ok(None),
196 }
197}
198
199fn system_config_path() -> PathBuf {
200 PathBuf::from(SYS_CONFIG_BASE_PATH)
201 .join(CONFIG_DIR)
202 .join(CONFIG_FILE_NAME)
203}
204
205fn user_config_path() -> Option<PathBuf> {
206 dirs::config_dir().map(|d| d.join(CONFIG_DIR).join(CONFIG_FILE_NAME))
207}
208
209fn env_config_path() -> Option<PathBuf> {
210 env::var_os(CONFIG_ENV_VAR).map(PathBuf::from)
211}
212
213fn env_str(var: &'static str) -> Result<Option<String>, ExpandedConfigLoadError> {
214 match env::var(var) {
215 Ok(s) => Ok(Some(s)),
216 Err(env::VarError::NotPresent) => Ok(None),
217 Err(env::VarError::NotUnicode(_)) => Err(ExpandedConfigLoadError::NonUnicodeEnvVar { var }),
218 }
219}
220
221#[derive(Debug, thiserror::Error)]
222pub enum ExpandedConfigLoadError {
223 #[error("Configuration environment variable '{var}' contained a non-unicode value")]
224 NonUnicodeEnvVar { var: &'static str },
225
226 #[error("Invalid hostname '{value}' specified in environment variable '{var}'")]
227 InvalidHostNameFromEnv { var: &'static str, value: String },
228
229 #[error("Config loading error.")]
230 ConfigLoadError(
231 #[source]
232 #[from]
233 ConfigLoadError,
234 ),
235}
236
237#[derive(Copy, Clone, Debug)]
238enum UserOrEnvPath {
239 User,
240 Env,
241}
242
243impl std::fmt::Display for UserOrEnvPath {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 UserOrEnvPath::User => f.write_str("user"),
247 UserOrEnvPath::Env => f.write_str("environment"),
248 }
249 }
250}
251
252pub fn resolve_reflector_auth_token(
257 cli_override_auth_token_file_path: Option<PathBuf>,
258 config_file_parent_directory: &Path,
259) -> Result<AuthToken, Box<dyn std::error::Error + Send + Sync>> {
260 if let Some(path) = cli_override_auth_token_file_path {
261 if !path.exists() {
262 return Err(ReflectorAuthTokenLoadError::CliProvidedAuthTokenFileDidNotExist.into());
263 }
264 if let Some(token_file_contents) =
265 crate::auth_token::token_user_file::read_user_auth_token_file(&path)?
266 {
267 return Ok(token_file_contents.auth_token);
268 }
269 }
270
271 match env::var(crate::auth_token::MODALITY_AUTH_TOKEN_ENV_VAR) {
272 Ok(val) => {
273 tracing::trace!("Loading CLI env context auth token");
274 return Ok(decode_auth_token_hex(&val)?);
275 }
276 Err(env::VarError::NotUnicode(_)) => {
277 return Err(
278 ReflectorAuthTokenLoadError::EnvVarSpecifiedModalityAuthTokenNonUtf8.into(),
279 );
280 }
281 Err(env::VarError::NotPresent) => {
282 }
284 }
285 if let Ok(cwd) = std::env::current_dir() {
286 let cwd_relative_path = cwd.join(REFLECTOR_AUTH_TOKEN_DEFAULT_FILE_NAME);
287 if cwd_relative_path.exists() {
288 if let Some(token_file_contents) =
289 crate::auth_token::token_user_file::read_user_auth_token_file(&cwd_relative_path)?
290 {
291 return Ok(token_file_contents.auth_token);
292 }
293 }
294 }
295
296 let config_relative_path =
297 config_file_parent_directory.join(REFLECTOR_AUTH_TOKEN_DEFAULT_FILE_NAME);
298
299 if let Some(token_file_contents) =
300 crate::auth_token::token_user_file::read_user_auth_token_file(&config_relative_path)?
301 {
302 return Ok(token_file_contents.auth_token);
303 }
304
305 if let Ok(auth_token) = AuthToken::load() {
307 return Ok(auth_token);
308 }
309
310 Err(ReflectorAuthTokenLoadError::Underspecified.into())
311}
312
313#[derive(Debug, thiserror::Error)]
314pub enum ReflectorAuthTokenLoadError {
315 #[error("CLI provided auth token file did not exist")]
316 CliProvidedAuthTokenFileDidNotExist,
317
318 #[error(
319 "The MODALITY_AUTH_TOKEN environment variable contained a non-UTF-8-compatible string"
320 )]
321 EnvVarSpecifiedModalityAuthTokenNonUtf8,
322
323 #[error("No auth token was specified. Provide a path to a token file as a CLI argument or put the token hex contents into the MODALITY_AUTH_TOKEN environment path")]
324 Underspecified,
325}