use anyhow::Result; use serde_json::Value; use sqlx::PgPool; use std::collections::HashMap; use uuid::Uuid; use crate::crypto; use crate::models::Entry; use crate::service::search::{fetch_entries, fetch_secrets_for_entries}; /// Build an env variable map from entry secrets (for dry-run preview or injection). #[allow(clippy::too_many_arguments)] pub async fn build_env_map( pool: &PgPool, namespace: Option<&str>, kind: Option<&str>, name: Option<&str>, tags: &[String], only_fields: &[String], prefix: &str, master_key: &[u8; 32], user_id: Option, ) -> Result> { let entries = fetch_entries(pool, namespace, kind, name, tags, None, user_id).await?; let mut combined: HashMap = HashMap::new(); for entry in &entries { let entry_map = build_entry_env_map(pool, entry, only_fields, prefix, master_key).await?; combined.extend(entry_map); } Ok(combined) } async fn build_entry_env_map( pool: &PgPool, entry: &Entry, only_fields: &[String], prefix: &str, master_key: &[u8; 32], ) -> Result> { let entry_ids = vec![entry.id]; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let all_fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]); let fields: Vec<_> = if only_fields.is_empty() { all_fields.iter().collect() } else { all_fields .iter() .filter(|f| only_fields.contains(&f.field_name)) .collect() }; let effective_prefix = env_prefix(entry, prefix); let mut map = HashMap::new(); for f in fields { let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?; let key = format!( "{}_{}", effective_prefix, f.field_name.to_uppercase().replace(['-', '.'], "_") ); map.insert(key, json_to_env_string(&decrypted)); } // Resolve key_ref if let Some(key_ref) = entry.metadata.get("key_ref").and_then(|v| v.as_str()) { let key_entries = fetch_entries( pool, Some(&entry.namespace), Some("key"), Some(key_ref), &[], None, None, ) .await?; if let Some(key_entry) = key_entries.first() { let key_ids = vec![key_entry.id]; let key_fields_map = fetch_secrets_for_entries(pool, &key_ids).await?; let empty = vec![]; let key_fields = key_fields_map.get(&key_entry.id).unwrap_or(&empty); let key_prefix = env_prefix(key_entry, prefix); for f in key_fields { let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?; let key_var = format!( "{}_{}", key_prefix, f.field_name.to_uppercase().replace(['-', '.'], "_") ); map.insert(key_var, json_to_env_string(&decrypted)); } } else { tracing::warn!(key_ref, "key_ref target not found"); } } Ok(map) } fn env_prefix(entry: &Entry, prefix: &str) -> String { let name_part = entry.name.to_uppercase().replace(['-', '.', ' '], "_"); if prefix.is_empty() { name_part } else { let normalized = prefix.to_uppercase().replace(['-', '.', ' '], "_"); let normalized = normalized.trim_end_matches('_'); format!("{}_{}", normalized, name_part) } } fn json_to_env_string(v: &Value) -> String { match v { Value::String(s) => s.clone(), Value::Null => String::new(), other => other.to_string(), } }