diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index f35fbef1d4fb..4a01609be79b 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -12,7 +12,7 @@ use std::ffi::OsString; use std::fs::OpenOptions; use std::io::Write; use std::path::{Path, PathBuf}; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use thiserror::Error; const KEYRING_SERVICE: &str = "goose"; @@ -105,6 +105,7 @@ pub struct Config { config_path: PathBuf, secrets: SecretStorage, guard: Mutex<()>, + secrets_cache: Arc>>>, } enum SecretStorage { @@ -133,6 +134,7 @@ impl Default for Config { config_path, secrets, guard: Mutex::new(()), + secrets_cache: Arc::new(Mutex::new(None)), } } } @@ -236,6 +238,7 @@ impl Config { service: service.to_string(), }, guard: Mutex::new(()), + secrets_cache: Arc::new(Mutex::new(None)), }) } @@ -253,6 +256,7 @@ impl Config { path: secrets_path.as_ref().to_path_buf(), }, guard: Mutex::new(()), + secrets_cache: Arc::new(Mutex::new(None)), }) } @@ -540,28 +544,43 @@ impl Config { } pub fn all_secrets(&self) -> Result, ConfigError> { - match &self.secrets { - SecretStorage::Keyring { service } => { - let result = - self.handle_keyring_operation(|entry| entry.get_password(), service, None); + let mut cache = self.secrets_cache.lock().unwrap(); - match result { - Ok(content) => { - let values: HashMap = serde_json::from_str(&content)?; - Ok(values) - } - Err(ConfigError::FallbackToFileStorage) => self.fallback_to_file_storage(), - Err(ConfigError::KeyringError(msg)) - if msg.contains("No entry found") - || msg.contains("No matching entry found") => - { - Ok(HashMap::new()) + let values = if let Some(ref cached_secrets) = *cache { + cached_secrets.clone() + } else { + tracing::debug!("secrets cache miss, fetching from storage"); + + let loaded = match &self.secrets { + SecretStorage::Keyring { service } => { + let result = + self.handle_keyring_operation(|entry| entry.get_password(), service, None); + + match result { + Ok(content) => { + let values: HashMap = serde_json::from_str(&content)?; + values + } + Err(ConfigError::FallbackToFileStorage) => { + self.fallback_to_file_storage()? + } + Err(ConfigError::KeyringError(msg)) + if msg.contains("No entry found") + || msg.contains("No matching entry found") => + { + HashMap::new() + } + Err(e) => return Err(e), } - Err(e) => Err(e), } - } - SecretStorage::File { path } => self.read_secrets_from_file(path), - } + SecretStorage::File { path } => self.read_secrets_from_file(path)?, + }; + + *cache = Some(loaded.clone()); + loaded + }; + + Ok(values) } /// Parse an environment variable value into a JSON Value. @@ -786,6 +805,9 @@ impl Config { std::fs::write(path, yaml_value)?; } }; + + self.invalidate_secrets_cache(); + Ok(()) } @@ -820,6 +842,9 @@ impl Config { std::fs::write(path, yaml_value)?; } }; + + self.invalidate_secrets_cache(); + Ok(()) } @@ -858,6 +883,11 @@ impl Config { Ok(()) } + fn invalidate_secrets_cache(&self) { + let mut cache = self.secrets_cache.lock().unwrap(); + *cache = None; + } + /// Check if an error string indicates a keyring availability issue that should trigger fallback fn is_keyring_availability_error(&self, error_str: &str) -> bool { error_str.contains("keyring")