Skip to main content

via/providers/
mod.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::config::{Config, ProviderConfig};
4use crate::error::ViaError;
5use crate::secrets::SecretValue;
6
7mod onepassword;
8
9pub trait SecretProvider {
10    fn resolve(&self, reference: &str) -> Result<SecretValue, ViaError>;
11}
12
13pub struct ProviderRegistry {
14    providers: BTreeMap<String, Box<dyn SecretProvider>>,
15}
16
17impl ProviderRegistry {
18    pub fn from_config(config: &Config) -> Result<Self, ViaError> {
19        let mut providers: BTreeMap<String, Box<dyn SecretProvider>> = BTreeMap::new();
20        for (name, provider) in &config.providers {
21            match provider {
22                ProviderConfig::OnePassword {
23                    account,
24                    cache,
25                    cache_ttl_seconds,
26                } => {
27                    providers.insert(
28                        name.clone(),
29                        Box::new(onepassword::OnePasswordCliProvider::new(
30                            account.clone(),
31                            *cache,
32                            *cache_ttl_seconds,
33                            provider_secret_references(config, name),
34                        )),
35                    );
36                }
37            }
38        }
39
40        Ok(Self { providers })
41    }
42
43    pub fn get(&self, name: &str) -> Result<&dyn SecretProvider, ViaError> {
44        self.providers
45            .get(name)
46            .map(|provider| provider.as_ref())
47            .ok_or_else(|| ViaError::InvalidConfig(format!("unknown provider `{name}`")))
48    }
49}
50
51fn provider_secret_references(config: &Config, provider_name: &str) -> Vec<String> {
52    config
53        .services
54        .values()
55        .filter(|service| service.provider == provider_name)
56        .flat_map(|service| service.secrets.values().cloned())
57        .collect::<BTreeSet<_>>()
58        .into_iter()
59        .collect()
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    fn config() -> Config {
67        Config::from_toml_str(
68            r#"
69version = 1
70
71[providers.onepassword]
72type = "1password"
73"#,
74        )
75        .unwrap()
76    }
77
78    #[test]
79    fn builds_registry_from_config() {
80        let registry = ProviderRegistry::from_config(&config()).unwrap();
81
82        assert!(registry.get("onepassword").is_ok());
83    }
84
85    #[test]
86    fn reports_missing_provider() {
87        let registry = ProviderRegistry::from_config(&config()).unwrap();
88        let error = match registry.get("missing") {
89            Ok(_) => panic!("expected missing provider error"),
90            Err(error) => error,
91        };
92
93        assert!(
94            matches!(error, ViaError::InvalidConfig(message) if message.contains("unknown provider"))
95        );
96    }
97}