use crate::path::AbsolutePath;
use crate::provider::{SecretReference, SecretsProvider};
use crate::secrets::{Secret, SecretError, SecretKey};
use crate::template::Template;
use secrecy::{ExposeSecret, SecretString};
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
pub enum EnvError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("secret error: {0}")]
Secret(#[from] SecretError),
#[error("provider error: {0}")]
Provider(#[from] crate::provider::ProviderError),
#[error("dotenv parse error: {0}")]
Parse(String),
#[error("task join error: {0}")]
Join(#[from] tokio::task::JoinError),
}
#[derive(Clone)]
pub struct EnvManager {
secrets: Vec<Secret>,
provider: Arc<dyn SecretsProvider>,
}
impl EnvManager {
pub fn new(secrets: Vec<Secret>, provider: Arc<dyn SecretsProvider>) -> Self {
Self { secrets, provider }
}
pub fn files(&self) -> Vec<AbsolutePath> {
self.secrets
.iter()
.filter_map(|s| s.source().path().map(|p| AbsolutePath::from(p.clone())))
.collect()
}
pub fn tracks(&self, path: &AbsolutePath) -> bool {
self.files().iter().any(|p| p == path)
}
pub async fn resolve(&self) -> Result<HashMap<SecretKey, SecretString>, EnvError> {
let secrets = self.secrets.clone();
let map = tokio::task::spawn_blocking(move || {
let mut inner: HashMap<SecretKey, String> = HashMap::new();
for secret in secrets {
let content = secret.source().read().fetch()?;
let content = match content {
Some(c) => c,
None => continue,
};
match &secret {
Secret::Anonymous(_) => {
let cursor = std::io::Cursor::new(content.as_bytes());
for item in dotenvy::from_read_iter(cursor) {
let (k, v) = item.map_err(|e| EnvError::Parse(e.to_string()))?;
inner.insert(k.try_into()?, v);
}
}
Secret::Named { key, .. } => {
inner.insert(key.clone(), content.into_owned());
}
}
}
Ok::<HashMap<SecretKey, String>, EnvError>(inner)
})
.await??;
let mut references = HashSet::new();
for v in map.values() {
let tpl = Template::parse(v, &*self.provider);
if tpl.has_secrets() {
for r in tpl.references() {
references.insert(r);
}
} else if let Some(r) = self.provider.parse(v.trim()) {
references.insert(r);
}
}
if references.is_empty() {
return Ok(wrap_all(map));
}
let ref_vec: Vec<SecretReference> = references.into_iter().collect();
let secrets_map = self.provider.fetch_map(&ref_vec).await?;
let mut result: HashMap<SecretKey, SecretString> = HashMap::with_capacity(map.len());
for (k, v) in map {
let tpl = Template::parse(&v, &*self.provider);
if tpl.has_secrets() {
let rendered = tpl.render_with(|k| secrets_map.get(k).map(|s| s.expose_secret()));
result.insert(k, SecretString::new(rendered.into_owned().into()));
} else {
let trimmed = v.trim();
if let Some(r) = self.provider.parse(trimmed)
&& let Some(val) = secrets_map.get(&r)
{
result.insert(k, val.clone());
continue;
}
result.insert(k, SecretString::new(v.into()));
}
}
Ok(result)
}
}
fn wrap_all(map: HashMap<SecretKey, String>) -> HashMap<SecretKey, SecretString> {
map.into_iter()
.map(|(k, v)| (k, SecretString::new(v.into())))
.collect()
}