diff --git a/src/config.rs b/src/config.rs index 1d3c7ce..2b086ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -144,6 +144,7 @@ impl ProviderConfig { match &mut self.provider_type { SupportedProvider::Local { provider_def } => { debug!("Using local secret provider"); + provider_def.runtime_provider_name = self.name.clone(); provider_def } SupportedProvider::AwsSecretsManager { provider_def } => { diff --git a/src/providers/local.rs b/src/providers/local.rs index 298e799..e57b9e5 100644 --- a/src/providers/local.rs +++ b/src/providers/local.rs @@ -5,9 +5,9 @@ use std::path::{Path, PathBuf}; use std::{env, fs}; use zeroize::Zeroize; -use crate::config::Config; -use crate::providers::SecretProvider; +use crate::config::{Config, get_config_file_path, load_config}; use crate::providers::git_sync::{SyncOpts, repo_name_from_url, sync_and_push}; +use crate::providers::{SecretProvider, SupportedProvider}; use crate::{ ARGON_M_COST_KIB, ARGON_P, ARGON_T_COST, HEADER, KDF, KEY_LEN, NONCE_LEN, SALT_LEN, VERSION, }; @@ -54,6 +54,8 @@ pub struct LocalProvider { #[validate(email)] pub git_user_email: Option, pub git_executable: Option, + #[serde(skip)] + pub runtime_provider_name: Option, } impl Default for LocalProvider { @@ -65,6 +67,7 @@ impl Default for LocalProvider { git_user_name: None, git_user_email: None, git_executable: None, + runtime_provider_name: None, } } } @@ -185,9 +188,7 @@ impl SecretProvider for LocalProvider { } if config_changed { - debug!("Saving updated config"); - confy::store("gman", "config", &self) - .with_context(|| "failed to save updated config")?; + self.persist_git_settings_to_config()?; } let sync_opts = SyncOpts { @@ -203,6 +204,53 @@ impl SecretProvider for LocalProvider { } impl LocalProvider { + fn persist_git_settings_to_config(&self) -> Result<()> { + debug!("Saving updated config (only current local provider)"); + + let mut cfg = load_config().with_context(|| "failed to load existing config")?; + + let target_name = self.runtime_provider_name.clone(); + let mut updated = false; + for pc in cfg.providers.iter_mut() { + if let SupportedProvider::Local { provider_def } = &mut pc.provider_type { + let matches_name = match (&pc.name, &target_name) { + (Some(n), Some(t)) => n == t, + (Some(_), None) => false, + _ => false, + }; + if matches_name || target_name.is_none() { + provider_def.git_branch = self.git_branch.clone(); + provider_def.git_remote_url = self.git_remote_url.clone(); + provider_def.git_user_name = self.git_user_name.clone(); + provider_def.git_user_email = self.git_user_email.clone(); + provider_def.git_executable = self.git_executable.clone(); + updated = true; + if matches_name { + break; + } + } + } + } + + if !updated { + bail!("unable to find matching local provider in config to update"); + } + + let path = get_config_file_path()?; + let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); + if ext.eq_ignore_ascii_case("yml") || ext.eq_ignore_ascii_case("yaml") { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let s = serde_yaml::to_string(&cfg)?; + fs::write(&path, s).with_context(|| format!("failed to write {}", path.display()))?; + } else { + confy::store("gman", "config", &cfg) + .with_context(|| "failed to save updated config via confy")?; + } + + Ok(()) + } fn repo_dir_for_config(&self) -> Result> { if let Some(remote) = &self.git_remote_url { let name = repo_name_from_url(remote); @@ -424,6 +472,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; use secrecy::{ExposeSecret, SecretString}; + use std::env as std_env; use tempfile::tempdir; #[test] @@ -458,9 +507,105 @@ mod tests { fs::write(&file, "secretpw\n").unwrap(); let provider = LocalProvider { password_file: Some(file), + runtime_provider_name: None, ..LocalProvider::default() }; let pw = provider.get_password().unwrap(); assert_eq!(pw.expose_secret(), "secretpw"); } + + #[test] + fn persist_only_target_local_provider_git_settings() { + let td = tempdir().unwrap(); + let xdg = td.path().join("xdg"); + let app_dir = xdg.join("gman"); + fs::create_dir_all(&app_dir).unwrap(); + unsafe { + std_env::set_var("XDG_CONFIG_HOME", &xdg); + } + + let initial_yaml = indoc::indoc! { + "--- + default_provider: local + providers: + - name: local + type: local + password_file: /tmp/.gman_pass + git_branch: main + git_remote_url: null + git_user_name: null + git_user_email: null + git_executable: null + - name: other + type: local + git_branch: main + git_remote_url: git@github.com:someone/else.git + run_configs: + - name: echo + secrets: [API_KEY] + " + }; + let cfg_path = app_dir.join("config.yml"); + fs::write(&cfg_path, initial_yaml).unwrap(); + + let provider = LocalProvider { + password_file: None, + git_branch: Some("dev".into()), + git_remote_url: Some("git@github.com:user/repo.git".into()), + git_user_name: Some("Test User".into()), + git_user_email: Some("test@example.com".into()), + git_executable: Some(PathBuf::from("/usr/bin/git")), + runtime_provider_name: Some("local".into()), + }; + + provider + .persist_git_settings_to_config() + .expect("persist ok"); + + let content = fs::read_to_string(&cfg_path).unwrap(); + let cfg: crate::config::Config = serde_yaml::from_str(&content).unwrap(); + + assert_eq!(cfg.default_provider.as_deref(), Some("local")); + assert!(cfg.run_configs.is_some()); + assert_eq!(cfg.run_configs.as_ref().unwrap().len(), 1); + + let p0 = &cfg.providers[0]; + assert_eq!(p0.name.as_deref(), Some("local")); + match &p0.provider_type { + SupportedProvider::Local { provider_def } => { + assert_eq!(provider_def.git_branch.as_deref(), Some("dev")); + assert_eq!( + provider_def.git_remote_url.as_deref(), + Some("git@github.com:user/repo.git") + ); + assert_eq!(provider_def.git_user_name.as_deref(), Some("Test User")); + assert_eq!( + provider_def.git_user_email.as_deref(), + Some("test@example.com") + ); + assert_eq!( + provider_def.git_executable.as_ref(), + Some(&PathBuf::from("/usr/bin/git")) + ); + } + _ => panic!("expected local provider"), + } + + let p1 = &cfg.providers[1]; + assert_eq!(p1.name.as_deref(), Some("other")); + match &p1.provider_type { + SupportedProvider::Local { provider_def } => { + assert_eq!(provider_def.git_branch.as_deref(), Some("main")); + assert_eq!( + provider_def.git_remote_url.as_deref(), + Some("git@github.com:someone/else.git") + ); + } + _ => panic!("expected local provider"), + } + + unsafe { + std_env::remove_var("XDG_CONFIG_HOME"); + } + } } diff --git a/tests/providers/local_tests.rs b/tests/providers/local_tests.rs index 96685c3..8ca0d7e 100644 --- a/tests/providers/local_tests.rs +++ b/tests/providers/local_tests.rs @@ -34,6 +34,7 @@ fn test_local_provider_valid() { git_user_name: None, git_user_email: Some("test@example.com".to_string()), git_executable: None, + runtime_provider_name: None, }; assert!(provider.validate().is_ok()); @@ -48,6 +49,7 @@ fn test_local_provider_invalid_email() { git_user_name: None, git_user_email: Some("test".to_string()), git_executable: None, + runtime_provider_name: None, }; assert!(config.validate().is_err());