feat: vault_password_file or nothing at all is shorthand for just using the local gman provider for secret management

This commit is contained in:
2026-06-02 14:52:36 -06:00
parent 658ca7fec3
commit bba094086d
4 changed files with 83 additions and 108 deletions
+28 -66
View File
@@ -30,7 +30,7 @@ pub struct AppConfig {
pub wrap: Option<String>, pub wrap: Option<String>,
pub wrap_code: bool, pub wrap_code: bool,
pub(crate) vault_password_file: Option<PathBuf>, pub(crate) vault_password_file: Option<PathBuf>,
pub(crate) secrets_provider: SupportedProvider, pub(crate) secrets_provider: Option<SupportedProvider>,
pub function_calling_support: bool, pub function_calling_support: bool,
pub mapping_tools: IndexMap<String, String>, pub mapping_tools: IndexMap<String, String>,
@@ -96,7 +96,7 @@ impl Default for AppConfig {
wrap: None, wrap: None,
wrap_code: false, wrap_code: false,
vault_password_file: None, vault_password_file: None,
secrets_provider: SupportedProvider::default(), secrets_provider: None,
function_calling_support: true, function_calling_support: true,
mapping_tools: Default::default(), mapping_tools: Default::default(),
@@ -212,7 +212,6 @@ impl AppConfig {
clients: config.clients, clients: config.clients,
}; };
app_config.migrate_legacy_password_file();
app_config.load_envs(); app_config.load_envs();
if let Some(wrap) = app_config.wrap.clone() { if let Some(wrap) = app_config.wrap.clone() {
app_config.set_wrap(&wrap)?; app_config.set_wrap(&wrap)?;
@@ -223,17 +222,6 @@ impl AppConfig {
Ok(app_config) Ok(app_config)
} }
fn migrate_legacy_password_file(&mut self) {
let Some(legacy) = self.vault_password_file.take() else {
return;
};
if let SupportedProvider::Local { provider_def } = &mut self.secrets_provider
&& provider_def.password_file.is_none()
{
provider_def.password_file = Some(legacy);
}
}
pub fn resolve_model(&mut self) -> Result<()> { pub fn resolve_model(&mut self) -> Result<()> {
if self.model_id.is_empty() { if self.model_id.is_empty() {
let models = list_models(self, crate::client::ModelType::Chat); let models = list_models(self, crate::client::ModelType::Chat);
@@ -790,66 +778,40 @@ mod tests {
} }
#[test] #[test]
fn migrate_legacy_copies_into_empty_local_provider() { fn default_secrets_provider_is_none() {
let mut app = AppConfig { let app = AppConfig::default();
vault_password_file: Some(PathBuf::from("/tmp/test-coyote-password")), assert!(app.secrets_provider.is_none());
..AppConfig::default()
};
app.migrate_legacy_password_file();
match &app.secrets_provider {
SupportedProvider::Local { provider_def } => {
assert_eq!(
provider_def.password_file,
Some(PathBuf::from("/tmp/test-coyote-password"))
);
}
_ => panic!("expected Local provider"),
}
assert!(app.vault_password_file.is_none());
} }
#[test] #[test]
fn migrate_legacy_does_not_overwrite_existing_password_file() { fn secrets_provider_can_hold_non_local_variant() {
let mut app = AppConfig { let app = AppConfig {
vault_password_file: Some(PathBuf::from("/tmp/legacy")), secrets_provider: Some(SupportedProvider::Gopass {
..AppConfig::default()
};
if let SupportedProvider::Local { provider_def } = &mut app.secrets_provider {
provider_def.password_file = Some(PathBuf::from("/tmp/explicit"));
}
app.migrate_legacy_password_file();
match &app.secrets_provider {
SupportedProvider::Local { provider_def } => {
assert_eq!(
provider_def.password_file,
Some(PathBuf::from("/tmp/explicit"))
);
}
_ => panic!("expected Local provider"),
}
assert!(app.vault_password_file.is_none());
}
#[test]
fn migrate_legacy_noop_for_non_local_provider() {
let mut app = AppConfig {
vault_password_file: Some(PathBuf::from("/tmp/orphaned")),
secrets_provider: SupportedProvider::Gopass {
provider_def: Default::default(), provider_def: Default::default(),
}, }),
..AppConfig::default() ..AppConfig::default()
}; };
app.migrate_legacy_password_file();
assert!(app.vault_password_file.is_none());
assert!(matches!( assert!(matches!(
app.secrets_provider, app.secrets_provider,
SupportedProvider::Gopass { .. } Some(SupportedProvider::Gopass { .. })
));
}
#[test]
fn from_config_copies_secrets_provider() {
let cfg = Config {
model_id: "test-model".to_string(),
clients: vec![ClientConfig::default()],
secrets_provider: Some(SupportedProvider::Gopass {
provider_def: Default::default(),
}),
..Config::default()
};
let app = AppConfig::from_config(cfg).unwrap();
assert!(matches!(
app.secrets_provider,
Some(SupportedProvider::Gopass { .. })
)); ));
} }
} }
+2 -2
View File
@@ -190,7 +190,7 @@ pub struct Config {
pub(super) vault_password_file: Option<PathBuf>, pub(super) vault_password_file: Option<PathBuf>,
#[serde(default)] #[serde(default)]
pub(super) secrets_provider: SupportedProvider, pub(super) secrets_provider: Option<SupportedProvider>,
pub function_calling_support: bool, pub function_calling_support: bool,
pub mapping_tools: IndexMap<String, String>, pub mapping_tools: IndexMap<String, String>,
@@ -256,7 +256,7 @@ impl Default for Config {
wrap: None, wrap: None,
wrap_code: false, wrap_code: false,
vault_password_file: None, vault_password_file: None,
secrets_provider: SupportedProvider::default(), secrets_provider: None,
function_calling_support: true, function_calling_support: true,
mapping_tools: Default::default(), mapping_tools: Default::default(),
+9 -1
View File
@@ -907,8 +907,14 @@ impl RequestContext {
("messages_file", display_path(&self.messages_file())), ("messages_file", display_path(&self.messages_file())),
]; ];
items.push(("secrets_provider", app.secrets_provider.to_string()));
match &app.secrets_provider { match &app.secrets_provider {
None => {
items.push(("secrets_provider", "local".to_string()));
items.push(("vault_password_file", display_path(&app.vault_password_file())));
}
Some(provider) => {
items.push(("secrets_provider", provider.to_string()));
match provider {
SupportedProvider::Local { provider_def } => { SupportedProvider::Local { provider_def } => {
let path = provider_def let path = provider_def
.password_file .password_file
@@ -948,6 +954,8 @@ impl RequestContext {
} }
} }
} }
}
}
if let Ok((_, Some(log_path))) = paths::log_config() { if let Ok((_, Some(log_path))) = paths::log_config() {
items.push(("log_path", display_path(&log_path))); items.push(("log_path", display_path(&log_path)));
+9 -4
View File
@@ -42,12 +42,17 @@ impl Vault {
} }
pub fn init(config: &AppConfig) -> Self { pub fn init(config: &AppConfig) -> Self {
let mut provider = config.secrets_provider.clone(); let mut provider = match &config.secrets_provider {
Some(p) => p.clone(),
None => SupportedProvider::Local {
provider_def: LocalProvider {
password_file: Some(config.vault_password_file()),
..LocalProvider::default()
},
},
};
if let SupportedProvider::Local { provider_def } = &mut provider { if let SupportedProvider::Local { provider_def } = &mut provider {
if provider_def.password_file.is_none() {
provider_def.password_file = Some(config.vault_password_file());
}
ensure_password_file_initialized(provider_def) ensure_password_file_initialized(provider_def)
.expect("Failed to initialize password file"); .expect("Failed to initialize password file");
} }