Skip to main content

alien_bindings/providers/vault/
local.rs

1use crate::error::{ErrorData, Result};
2use alien_error::{AlienError, Context, IntoAlienError};
3use async_trait::async_trait;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7/// Local vault binding implementation for development and testing.
8///
9/// Secrets are stored in a JSON file in the vault directory without encryption.
10/// Encryption will be added in a future iteration.
11#[derive(Debug)]
12pub struct LocalVault {
13    vault_name: String,
14    vault_dir: PathBuf,
15}
16
17impl LocalVault {
18    /// Create a new local vault binding.
19    ///
20    /// # Arguments
21    /// * `vault_name` - Name of the vault
22    /// * `vault_dir` - Directory where secrets are stored
23    pub fn new(vault_name: String, vault_dir: PathBuf) -> Self {
24        Self {
25            vault_name,
26            vault_dir,
27        }
28    }
29
30    /// Get the path to the secrets file.
31    fn secrets_file_path(&self) -> PathBuf {
32        self.vault_dir.join("secrets.json")
33    }
34
35    /// Load secrets from disk.
36    async fn load_secrets(&self) -> Result<HashMap<String, String>> {
37        let secrets_file = self.secrets_file_path();
38
39        if !secrets_file.exists() {
40            return Ok(HashMap::new());
41        }
42
43        let content = tokio::fs::read_to_string(&secrets_file)
44            .await
45            .into_alien_error()
46            .context(ErrorData::CloudPlatformError {
47                message: format!(
48                    "Failed to read vault secrets file: {}",
49                    secrets_file.display()
50                ),
51                resource_id: None,
52            })?;
53
54        serde_json::from_str(&content)
55            .into_alien_error()
56            .context(ErrorData::CloudPlatformError {
57                message: format!(
58                    "Failed to parse vault secrets file: {}",
59                    secrets_file.display()
60                ),
61                resource_id: None,
62            })
63    }
64
65    /// Save secrets to disk.
66    async fn save_secrets(&self, secrets: &HashMap<String, String>) -> Result<()> {
67        let secrets_file = self.secrets_file_path();
68
69        // Ensure directory exists
70        if let Some(parent) = secrets_file.parent() {
71            tokio::fs::create_dir_all(parent)
72                .await
73                .into_alien_error()
74                .context(ErrorData::CloudPlatformError {
75                    message: format!("Failed to create vault directory: {}", parent.display()),
76                    resource_id: None,
77                })?;
78        }
79
80        let json = serde_json::to_string_pretty(secrets)
81            .into_alien_error()
82            .context(ErrorData::CloudPlatformError {
83                message: "Failed to serialize vault secrets".to_string(),
84                resource_id: None,
85            })?;
86
87        let path = secrets_file.clone();
88        let data = json.into_bytes();
89        tokio::task::spawn_blocking(move || {
90            alien_core::file_utils::write_secret_file(&path, &data)
91        })
92        .await
93        .into_alien_error()
94        .context(ErrorData::CloudPlatformError {
95            message: "Failed to spawn blocking write task".to_string(),
96            resource_id: None,
97        })?
98        .into_alien_error()
99        .context(ErrorData::CloudPlatformError {
100            message: format!(
101                "Failed to write vault secrets file: {}",
102                secrets_file.display()
103            ),
104            resource_id: None,
105        })
106    }
107}
108
109#[async_trait]
110impl crate::traits::Binding for LocalVault {}
111
112#[async_trait]
113impl crate::traits::Vault for LocalVault {
114    /// Get a secret value by name
115    async fn get_secret(&self, secret_name: &str) -> Result<String> {
116        let secrets = self.load_secrets().await?;
117
118        secrets.get(secret_name).cloned().ok_or_else(|| {
119            AlienError::new(ErrorData::CloudPlatformError {
120                message: format!(
121                    "Secret '{}' not found in vault '{}'",
122                    secret_name, self.vault_name
123                ),
124                resource_id: None,
125            })
126        })
127    }
128
129    /// Set a secret value
130    async fn set_secret(&self, secret_name: &str, value: &str) -> Result<()> {
131        let mut secrets = self.load_secrets().await?;
132        secrets.insert(secret_name.to_string(), value.to_string());
133        self.save_secrets(&secrets).await
134    }
135
136    /// Delete a secret
137    async fn delete_secret(&self, secret_name: &str) -> Result<()> {
138        let mut secrets = self.load_secrets().await?;
139
140        secrets.remove(secret_name).ok_or_else(|| {
141            AlienError::new(ErrorData::CloudPlatformError {
142                message: format!(
143                    "Secret '{}' not found in vault '{}'",
144                    secret_name, self.vault_name
145                ),
146                resource_id: None,
147            })
148        })?;
149
150        self.save_secrets(&secrets).await
151    }
152}