fix: the vault's init_bare should try to load the provisioned secret_provider from the config file without also interpolating any of the rest of the configuration file. It should only fail if the user has not yet created a configuration file; i.e. done a first-time run.
This commit is contained in:
@@ -731,7 +731,7 @@ fn merge_mcp_json(
|
||||
serde_json::to_string_pretty(&merged).context("failed to serialize merged mcp.json")?;
|
||||
write_atomically(&final_path, &serialized)?;
|
||||
|
||||
let vault = Vault::init_bare();
|
||||
let vault = Vault::init_bare()?;
|
||||
let (_parsed, missing) = interpolate_secrets(&serialized, &vault)?;
|
||||
let mut deduped: Vec<String> = Vec::new();
|
||||
for s in missing {
|
||||
@@ -860,7 +860,7 @@ fn handle_missing_secrets(missing: &[String]) -> Result<()> {
|
||||
}
|
||||
|
||||
fn prompt_for_each_secret(missing: &[String]) -> Result<(Vec<String>, Vec<String>)> {
|
||||
let mut vault = Vault::init_bare();
|
||||
let mut vault = Vault::init_bare()?;
|
||||
let mut password_file_ensured = false;
|
||||
let mut added = Vec::new();
|
||||
let mut deferred = Vec::new();
|
||||
@@ -914,6 +914,62 @@ fn print_secret_summary(added: &[String], deferred: &[String]) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::get_env_name;
|
||||
use serial_test::serial;
|
||||
use std::env;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
struct TestVaultConfigGuard {
|
||||
dir_key: String,
|
||||
file_key: String,
|
||||
previous_dir: Option<OsString>,
|
||||
previous_file: Option<OsString>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TestVaultConfigGuard {
|
||||
fn new(label: &str) -> Self {
|
||||
let dir_key = get_env_name("config_dir");
|
||||
let file_key = get_env_name("config_file");
|
||||
let previous_dir = env::var_os(&dir_key);
|
||||
let previous_file = env::var_os(&file_key);
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = env::temp_dir().join(format!("coyote-vault-test-{label}-{unique}"));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
let config_path = path.join("config.yaml");
|
||||
fs::write(&config_path, "{}").unwrap();
|
||||
unsafe {
|
||||
env::set_var(&dir_key, &path);
|
||||
env::set_var(&file_key, &config_path);
|
||||
}
|
||||
Self {
|
||||
dir_key,
|
||||
file_key,
|
||||
previous_dir,
|
||||
previous_file,
|
||||
path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestVaultConfigGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.previous_dir {
|
||||
Some(p) => env::set_var(&self.dir_key, p),
|
||||
None => env::remove_var(&self.dir_key),
|
||||
}
|
||||
match &self.previous_file {
|
||||
Some(p) => env::set_var(&self.file_key, p),
|
||||
None => env::remove_var(&self.file_key),
|
||||
}
|
||||
}
|
||||
let _ = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_url_no_ref() {
|
||||
@@ -1253,7 +1309,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn merge_into_empty_local_adds_all_remote_servers() {
|
||||
let _guard = TestVaultConfigGuard::new("merge-empty");
|
||||
let dir = fresh_temp_dir("merge-empty-");
|
||||
let remote = dir.join("remote.json");
|
||||
let target = dir.join("target.json");
|
||||
@@ -1270,7 +1328,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn merge_force_replaces_local_on_conflict() {
|
||||
let _guard = TestVaultConfigGuard::new("merge-force");
|
||||
let dir = fresh_temp_dir("merge-force-");
|
||||
let remote = dir.join("remote.json");
|
||||
let target = dir.join("target.json");
|
||||
@@ -1336,7 +1396,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[serial]
|
||||
async fn merge_detects_missing_secrets_in_output() {
|
||||
let _guard = TestVaultConfigGuard::new("merge-secret");
|
||||
let dir = fresh_temp_dir("merge-secret-");
|
||||
let remote = dir.join("remote.json");
|
||||
let target = dir.join("target.json");
|
||||
@@ -1352,7 +1414,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn merge_is_idempotent_on_re_run() {
|
||||
let _guard = TestVaultConfigGuard::new("merge-idempotent");
|
||||
let dir = fresh_temp_dir("merge-idempotent-");
|
||||
let remote = dir.join("remote.json");
|
||||
let target = dir.join("target.json");
|
||||
|
||||
+1
-1
@@ -685,7 +685,7 @@ pub async fn create_config_file(config_path: &Path) -> Result<()> {
|
||||
|
||||
let provider_choice = prompt_provider_choice()?;
|
||||
let mut vault = match &provider_choice {
|
||||
None => Vault::init_bare(),
|
||||
None => Vault::default_local(),
|
||||
Some(provider) => Vault {
|
||||
provider: provider.clone(),
|
||||
},
|
||||
|
||||
+43
-7
@@ -1,6 +1,9 @@
|
||||
mod utils;
|
||||
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::paths;
|
||||
pub use utils::create_vault_password_file;
|
||||
pub use utils::interpolate_secrets;
|
||||
pub use utils::prompt_provider_choice;
|
||||
@@ -14,6 +17,7 @@ use gman::providers::SecretProvider;
|
||||
use gman::providers::SupportedProvider;
|
||||
use gman::providers::local::LocalProvider;
|
||||
use inquire::{Password, PasswordDisplayMode, required};
|
||||
use serde_yaml::Value;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use tokio::runtime::Handle;
|
||||
use uuid::Uuid;
|
||||
@@ -28,17 +32,49 @@ pub struct Vault {
|
||||
pub type GlobalVault = Arc<Vault>;
|
||||
|
||||
impl Vault {
|
||||
pub fn init_bare() -> Self {
|
||||
let vault_password_file = AppConfig::default().vault_password_file();
|
||||
let local_provider = LocalProvider {
|
||||
password_file: Some(vault_password_file),
|
||||
git_branch: None,
|
||||
..LocalProvider::default()
|
||||
pub fn init_bare() -> Result<Self> {
|
||||
let config_path = paths::config_file();
|
||||
if !config_path.exists() {
|
||||
bail!(
|
||||
"Coyote config not found at {}. Run first-run setup before using the vault.",
|
||||
config_path.display()
|
||||
);
|
||||
}
|
||||
let content = read_to_string(&config_path)
|
||||
.with_context(|| format!("failed to read config at {}", config_path.display()))?;
|
||||
let value: Value = serde_yaml::from_str(&content)
|
||||
.with_context(|| format!("failed to parse config at {}", config_path.display()))?;
|
||||
|
||||
let provider = match value.get("secrets_provider") {
|
||||
Some(v) if !v.is_null() => serde_yaml::from_value::<SupportedProvider>(v.clone())
|
||||
.with_context(|| "failed to parse 'secrets_provider' from config")?,
|
||||
_ => {
|
||||
let password_file = value
|
||||
.get("vault_password_file")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| AppConfig::default().vault_password_file());
|
||||
SupportedProvider::Local {
|
||||
provider_def: LocalProvider {
|
||||
password_file: Some(password_file),
|
||||
git_branch: None,
|
||||
..LocalProvider::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { provider })
|
||||
}
|
||||
|
||||
pub fn default_local() -> Self {
|
||||
Self {
|
||||
provider: SupportedProvider::Local {
|
||||
provider_def: local_provider,
|
||||
provider_def: LocalProvider {
|
||||
password_file: Some(AppConfig::default().vault_password_file()),
|
||||
git_branch: None,
|
||||
..LocalProvider::default()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user