envvault/cli/commands/
import_cmd.rs1use std::collections::HashMap;
8use std::fs;
9use std::path::Path;
10
11use crate::cli::env_parser;
12use crate::cli::output;
13use crate::cli::{load_keyfile, prompt_password_for_vault, vault_path, Cli};
14use crate::errors::{EnvVaultError, Result};
15use crate::vault::VaultStore;
16
17pub fn execute(cli: &Cli, file_path: &str, format: Option<&str>) -> Result<()> {
19 let vault = vault_path(cli)?;
20 let source = Path::new(file_path);
21
22 if !source.exists() {
23 return Err(EnvVaultError::CommandFailed(format!(
24 "import file not found: {}",
25 source.display()
26 )));
27 }
28
29 let keyfile = load_keyfile(cli)?;
30 let vault_id = vault.to_string_lossy();
31 let password = prompt_password_for_vault(Some(&vault_id))?;
32 let mut store = VaultStore::open(&vault, password.as_bytes(), keyfile.as_deref())?;
33
34 let detected_format = match format {
36 Some(f) => f.to_string(),
37 None => detect_format(source),
38 };
39
40 let secrets = match detected_format.as_str() {
41 "env" => env_parser::parse_env_file(source)?,
42 "json" => parse_json_file(source)?,
43 other => {
44 return Err(EnvVaultError::CommandFailed(format!(
45 "unknown import format '{other}' — use 'env' or 'json'"
46 )));
47 }
48 };
49
50 if secrets.is_empty() {
51 output::warning("No secrets found in the import file.");
52 return Ok(());
53 }
54
55 let mut count = 0;
57 for (key, value) in &secrets {
58 store.set_secret(key, value)?;
59 output::info(&format!(" + {key}"));
60 count += 1;
61 }
62
63 store.save()?;
64
65 crate::audit::log_audit(
66 cli,
67 "import",
68 None,
69 Some(&format!("{count} secrets from {}", source.display())),
70 );
71
72 output::success(&format!(
73 "Imported {} secrets from {} into '{}' vault",
74 count,
75 source.display(),
76 store.environment()
77 ));
78
79 Ok(())
80}
81
82fn detect_format(path: &Path) -> String {
84 match path.extension().and_then(|e| e.to_str()) {
85 Some("json") => "json".to_string(),
86 _ => "env".to_string(), }
88}
89
90fn parse_json_file(path: &Path) -> Result<HashMap<String, String>> {
92 let content = fs::read_to_string(path)
93 .map_err(|e| EnvVaultError::CommandFailed(format!("failed to read file: {e}")))?;
94
95 let map: HashMap<String, serde_json::Value> = serde_json::from_str(&content)
96 .map_err(|e| EnvVaultError::CommandFailed(format!("invalid JSON: {e}")))?;
97
98 let mut secrets = HashMap::new();
99 for (key, value) in map {
100 let string_value = match value {
101 serde_json::Value::String(s) => s,
102 other => other.to_string(), };
104 secrets.insert(key, string_value);
105 }
106
107 Ok(secrets)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::io::Write;
114 use tempfile::NamedTempFile;
115
116 #[test]
117 fn parse_env_file_basic() {
118 let mut file = NamedTempFile::new().unwrap();
119 writeln!(file, "KEY=value").unwrap();
120 writeln!(file, "OTHER=123").unwrap();
121
122 let secrets = env_parser::parse_env_file(file.path()).unwrap();
123 assert_eq!(secrets["KEY"], "value");
124 assert_eq!(secrets["OTHER"], "123");
125 }
126
127 #[test]
128 fn parse_env_file_with_export_and_quotes() {
129 let mut file = NamedTempFile::new().unwrap();
130 writeln!(file, "export A=\"hello world\"").unwrap();
131 writeln!(file, "B='single'").unwrap();
132 writeln!(file, "# comment").unwrap();
133
134 let secrets = env_parser::parse_env_file(file.path()).unwrap();
135 assert_eq!(secrets["A"], "hello world");
136 assert_eq!(secrets["B"], "single");
137 assert!(!secrets.contains_key("# comment"));
138 }
139
140 #[test]
141 fn parse_json_file_basic() {
142 let mut file = NamedTempFile::with_suffix(".json").unwrap();
143 write!(file, r#"{{"KEY": "value", "NUM": "42"}}"#).unwrap();
144
145 let secrets = parse_json_file(file.path()).unwrap();
146 assert_eq!(secrets["KEY"], "value");
147 assert_eq!(secrets["NUM"], "42");
148 }
149
150 #[test]
151 fn detect_format_from_extension() {
152 assert_eq!(detect_format(Path::new("secrets.json")), "json");
153 assert_eq!(detect_format(Path::new(".env")), "env");
154 assert_eq!(detect_format(Path::new("secrets.env")), "env");
155 assert_eq!(detect_format(Path::new("noext")), "env");
156 }
157}