enact-config 0.0.2

Unified configuration management for Enact - secure storage with keychain and encrypted files
Documentation
//! Secrets Management - Environment variable based storage for secrets
//!
//! Replaces OS keychain with .env file support as requested by user.
//! Secrets are read from:
//! 1. Environment variables directly
//! 2. .env file loaded at startup
//!
//! Setting secrets via API is no longer supported (must be set in .env or environment).

use anyhow::Result;
use dotenv::dotenv;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tracing::{debug, warn};

/// Convert a key identifier to an environment variable name
///
/// Converts keys like `providers.azure.apiKey` to `ENACT_PROVIDERS_AZURE_APIKEY`
fn key_to_env_var_name(key: &str) -> String {
    let env_name = key.to_uppercase().replace('.', "_");
    format!("ENACT_{}", env_name)
}

/// Secret manager for retrieving secrets from environment (ENACT_* vars only)
#[derive(Clone)]
pub struct SecretManager {
    mock_store: Option<Arc<Mutex<HashMap<String, String>>>>,
}

impl SecretManager {
    /// Create a new secret manager
    pub fn new() -> Self {
        // Load .env file if present
        if let Err(e) = dotenv() {
            debug!("No .env file found or error loading it: {}", e);
        } else {
            debug!("Loaded .env file");
        }

        Self { mock_store: None }
    }

    /// Create a new secret manager with in-memory mock storage for testing
    pub fn new_mock() -> Self {
        Self {
            mock_store: Some(Arc::new(Mutex::new(HashMap::new()))),
        }
    }

    /// Set a secret (only supported in mock mode)
    ///
    /// In production, secrets must be set via environment variables.
    pub fn set(&self, key: &str, value: &str) -> Result<()> {
        if let Some(ref store) = self.mock_store {
            let mut map = store.lock().unwrap();
            map.insert(key.to_string(), value.to_string());
            debug!("Stored secret in mock store: {}", key);
            return Ok(());
        }

        // We cannot write to environment variables persistently from here.
        // Warn the user or return error?
        // Returning error is safer to indicate this operation is not supported.
        warn!("Setting secrets programmatically is not supported with .env auth. Please update your .env file or environment manually.");
        Err(anyhow::anyhow!(
            "Setting secrets is not supported in .env mode"
        ))
    }

    /// Retrieve a secret
    ///
    /// Checks mock store first (if enabled), then environment variables.
    pub fn get(&self, key: &str) -> Result<Option<String>> {
        // Check mock store first if enabled
        if let Some(ref store) = self.mock_store {
            let map = store.lock().unwrap();
            let value = map.get(key).cloned();
            return Ok(value);
        }

        let env_var_name = key_to_env_var_name(key);
        if let Ok(value) = std::env::var(&env_var_name) {
            debug!(
                "Retrieved secret from environment variable {}: {}",
                env_var_name, key
            );
            return Ok(Some(value));
        }

        Ok(None)
    }

    /// Delete a secret (only supported in mock mode)
    pub fn delete(&self, key: &str) -> Result<()> {
        if let Some(ref store) = self.mock_store {
            let mut map = store.lock().unwrap();
            map.remove(key);
            return Ok(());
        }

        Err(anyhow::anyhow!(
            "Deleting secrets is not supported in .env mode"
        ))
    }
}

impl Default for SecretManager {
    fn default() -> Self {
        Self::new()
    }
}