From 9e11648a7c6cbad82f7d2e2a1cc7e77589c8b22d Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 30 Sep 2025 15:35:48 -0600 Subject: [PATCH] refactor: Environment variable interpolation in config file works globally, not based on type --- README.md | 4 +- src/bin/gman/cli.rs | 10 +- src/bin/gman/main.rs | 23 +- src/config.rs | 340 +++------------------------ src/providers/aws_secrets_manager.rs | 3 - src/providers/azure_key_vault.rs | 2 - src/providers/gcp_secret_manager.rs | 2 - src/providers/gopass.rs | 2 - src/providers/local.rs | 25 +- tests/bin/cli_tests.rs | 12 - 10 files changed, 47 insertions(+), 376 deletions(-) diff --git a/README.md b/README.md index f3bcc65..49e2ba0 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ gman aws sts get-caller-identity - [Features](#features) - [Installation](#installation) - [Configuration](#configuration) + - [Environment Variable Interpolation](#environment-variable-interpolation) - [Providers](#providers) - [Local](#provider-local) - [AWS Secrets Manager](#provider-aws_secrets_manager) @@ -264,9 +265,6 @@ providers: aws_region: us-east-1 ``` -**Important Note:** Environment variable interpolation is only supported in string or numeric fields. It is not -supported in lists or maps. - ## Providers `gman` supports multiple providers for secret storage. The default provider is `local`, which stores secrets in an encrypted file on your filesystem. The CLI and config format are designed to be extensible so new providers can be diff --git a/src/bin/gman/cli.rs b/src/bin/gman/cli.rs index ecfb1b5..b83d9ae 100644 --- a/src/bin/gman/cli.rs +++ b/src/bin/gman/cli.rs @@ -1,8 +1,8 @@ use crate::command::preview_command; -use anyhow::{Context, Result, anyhow}; +use anyhow::{anyhow, Context, Result}; use clap_complete::CompletionCandidate; use futures::future::join_all; -use gman::config::{Config, RunConfig, load_config}; +use gman::config::{load_config, Config, RunConfig}; use log::{debug, error}; use regex::Regex; use std::collections::HashMap; @@ -257,7 +257,7 @@ pub fn parse_args( pub fn run_config_completer(current: &OsStr) -> Vec { let cur = current.to_string_lossy(); - match load_config() { + match load_config(true) { Ok(config) => { if let Some(run_configs) = config.run_configs { run_configs @@ -282,7 +282,7 @@ pub fn run_config_completer(current: &OsStr) -> Vec { pub fn provider_completer(current: &OsStr) -> Vec { let cur = current.to_string_lossy(); - match load_config() { + match load_config(true) { Ok(config) => config .providers .iter() @@ -300,7 +300,7 @@ pub fn provider_completer(current: &OsStr) -> Vec { pub fn secrets_completer(current: &OsStr) -> Vec { let cur = current.to_string_lossy(); - match load_config() { + match load_config(true) { Ok(config) => { let mut provider_config = match config.extract_provider_config(None) { Ok(pc) => pc, diff --git a/src/bin/gman/main.rs b/src/bin/gman/main.rs index e9181ed..21d1531 100644 --- a/src/bin/gman/main.rs +++ b/src/bin/gman/main.rs @@ -4,12 +4,12 @@ use crate::cli::secrets_completer; use anyhow::{Context, Result}; use clap::Subcommand; use clap::{ - CommandFactory, Parser, ValueEnum, crate_authors, crate_description, crate_name, crate_version, + crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser, ValueEnum, }; use clap_complete::{ArgValueCompleter, CompleteEnv}; use crossterm::execute; -use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode}; -use gman::config::{Config, get_config_file_path, load_config}; +use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; +use gman::config::{get_config_file_path, load_config, Config}; use std::ffi::OsString; use std::io::{self, IsTerminal, Read, Write}; use std::panic::PanicHookInfo; @@ -123,13 +123,6 @@ enum Commands { /// configured in a corresponding run profile #[command(external_subcommand)] External(Vec), - - /// Generate shell completion scripts - Completions { - /// The shell to generate the script for - #[arg(value_enum)] - shell: clap_complete::Shell, - }, } #[tokio::main] @@ -157,7 +150,7 @@ async fn main() -> Result<()> { exit(1); } - let config = load_config()?; + let config = load_config(true)?; let mut provider_config = config.extract_provider_config(cli.provider.clone())?; let secrets_provider = provider_config.extract_provider(); @@ -238,7 +231,8 @@ async fn main() -> Result<()> { } } Commands::Config {} => { - let config_yaml = serde_yaml::to_string(&config) + let uninterpolated_config = load_config(false)?; + let config_yaml = serde_yaml::to_string(&uninterpolated_config) .with_context(|| "failed to serialize existing configuration")?; let new_config = Editor::new() .edit(&config_yaml) @@ -267,11 +261,6 @@ async fn main() -> Result<()> { Commands::External(tokens) => { wrap_and_run_command(cli.provider, &config, tokens, cli.profile, cli.dry_run).await?; } - Commands::Completions { shell } => { - let mut cmd = Cli::command(); - let bin_name = cmd.get_name().to_string(); - clap_complete::generate(shell, &mut cmd, bin_name, &mut io::stdout()); - } } Ok(()) diff --git a/src/config.rs b/src/config.rs index 2097c01..314eb93 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,19 +46,14 @@ use validator::{Validate, ValidationError}; #[validate(schema(function = "flags_or_files"))] pub struct RunConfig { #[validate(required)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub name: Option, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub provider: Option, #[validate(required)] pub secrets: Option>, pub files: Option>, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub flag: Option, #[validate(range(min = 1))] - #[serde(default, deserialize_with = "deserialize_optional_usize_env_var")] pub flag_position: Option, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub arg_format: Option, } @@ -198,7 +193,6 @@ impl ProviderConfig { #[validate(schema(function = "default_provider_exists"))] #[validate(schema(function = "providers_names_are_unique"))] pub struct Config { - #[serde(deserialize_with = "deserialize_optional_env_var")] pub default_provider: Option, #[validate(length(min = 1))] #[validate(nested)] @@ -293,10 +287,11 @@ impl Config { /// /// ```no_run /// # use gman::config::load_config; -/// let config = load_config().unwrap(); +/// // Load config with environment variable interpolation enabled +/// let config = load_config(true).unwrap(); /// println!("loaded config: {:?}", config); /// ``` -pub fn load_config() -> Result { +pub fn load_config(interpolate: bool) -> Result { let xdg_path = env::var_os("XDG_CONFIG_HOME").map(PathBuf::from); let mut config: Config = if let Some(base) = xdg_path.as_ref() { @@ -305,17 +300,22 @@ pub fn load_config() -> Result { let yaml = app_dir.join("config.yaml"); if yml.exists() || yaml.exists() { let load_path = if yml.exists() { &yml } else { &yaml }; - let content = fs::read_to_string(load_path) - .with_context(|| format!("failed to read config file '{}'", load_path.display()))?; + let mut content = + fs::read_to_string(load_path).with_context(|| { + format!("failed to read config file '{}'", load_path.display()) + })?; + if interpolate { + content = interpolate_env_vars(&content); + } let cfg: Config = serde_yaml::from_str(&content).with_context(|| { format!("failed to parse YAML config at '{}'", load_path.display()) })?; cfg } else { - confy::load("gman", "config")? + load_confy_config(interpolate)? } } else { - confy::load("gman", "config")? + load_confy_config(interpolate)? }; config.validate()?; @@ -338,6 +338,20 @@ pub fn load_config() -> Result { Ok(config) } +fn load_confy_config(interpolate: bool) -> Result { + let load_path = confy::get_configuration_file_path("gman", "config")?; + let mut content = + fs::read_to_string(&load_path) + .with_context(|| format!("failed to read config file '{}'", load_path.display()))?; + if interpolate { + content = interpolate_env_vars(&content); + } + let cfg: Config = serde_yaml::from_str(&content) + .with_context(|| format!("failed to parse YAML config at '{}'", load_path.display()))?; + + Ok(cfg) +} + /// Returns the configuration file path that `confy` will use pub fn get_config_file_path() -> Result { if let Some(base) = env::var_os("XDG_CONFIG_HOME").map(PathBuf::from) { @@ -352,53 +366,6 @@ pub fn get_config_file_path() -> Result { Ok(confy::get_configuration_file_path("gman", "config")?) } -pub fn deserialize_optional_env_var<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let s: Option = Option::deserialize(deserializer)?; - match s { - Some(value) => { - let interpolated = interpolate_env_vars(&value); - Ok(Some(interpolated)) - } - None => Ok(None), - } -} - -pub fn deserialize_optional_pathbuf_env_var<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let s: Option = Option::deserialize(deserializer)?; - match s { - Some(value) => { - let interpolated = interpolate_env_vars(&value); - Ok(Some(interpolated.parse().unwrap())) - } - None => Ok(None), - } -} - -fn deserialize_optional_usize_env_var<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let s: Option = Option::deserialize(deserializer)?; - match s { - Some(value) => { - let interpolated = interpolate_env_vars(&value); - interpolated - .parse::() - .map(Some) - .map_err(serde::de::Error::custom) - } - None => Ok(None), - } -} - pub fn interpolate_env_vars(s: &str) -> String { let result = s.to_string(); let scrubbing_regex = Regex::new(r#"[\s{}^()\[\]\\|`'"]+"#).unwrap(); @@ -430,261 +397,8 @@ pub fn interpolate_env_vars(s: &str) -> String { #[cfg(test)] mod tests { use super::*; - use indoc::indoc; - use pretty_assertions::{assert_eq, assert_str_eq}; - use serde::Deserialize; + use pretty_assertions::assert_str_eq; use serial_test::serial; - use std::path::PathBuf; - - #[derive(Default, Deserialize, PartialEq, Eq, Debug)] - struct TestConfig { - #[serde(default, deserialize_with = "deserialize_optional_env_var")] - string_var: Option, - #[serde(default, deserialize_with = "deserialize_optional_pathbuf_env_var")] - path_var: Option, - #[serde(default, deserialize_with = "deserialize_optional_usize_env_var")] - usize_var: Option, - } - - #[test] - #[serial] - fn test_deserialize_optional_env_var_is_present() { - unsafe { env::set_var("TEST_VAR_DESERIALIZE_OPTION", "localhost") }; - let yaml_data = indoc!( - r#" - string_var: ${TEST_VAR_DESERIALIZE_OPTION} - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.string_var, Some("localhost".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.usize_var, Some(123)); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION") }; - } - - #[test] - fn test_deserialize_optional_env_var_empty_env_var_uses_default_value_if_provided() { - let yaml_data = indoc!( - r#" - string_var: ${TEST_VAR_DESERIALIZE_OPTION_UNDEFINED:-localhost} - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.string_var, Some("localhost".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.usize_var, Some(123)); - } - - #[test] - #[serial] - fn test_deserialize_optional_env_var_does_not_overwrite_non_env_value() { - unsafe { env::set_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE", "localhost") }; - let yaml_data = indoc!( - r#" - string_var: www.example.com - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.string_var, Some("www.example.com".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.usize_var, Some(123)); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE") }; - } - - #[test] - fn test_deserialize_optional_env_var_empty() { - let yaml_data = indoc!( - r#" - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.string_var, None); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.usize_var, Some(123)); - } - - #[test] - #[serial] - fn test_deserialize_optional_pathbuf_env_var_is_present() { - unsafe { env::set_var("TEST_VAR_DESERIALIZE_OPTION_PATHBUF", "/some/path") }; - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: ${TEST_VAR_DESERIALIZE_OPTION_PATHBUF} - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.usize_var, Some(123)); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION_PATHBUF") }; - } - - #[test] - fn test_deserialize_optional_pathbuf_env_var_empty_env_var_uses_default_value_if_provided() { - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: ${TEST_VAR_DESERIALIZE_OPTION_PATHBUF_UNDEFINED:-/some/path} - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.usize_var, Some(123)); - } - - #[test] - #[serial] - fn test_deserialize_optional_pathbuf_env_var_does_not_overwrite_non_env_value() { - unsafe { - env::set_var( - "TEST_VAR_DESERIALIZE_OPTION_PATHBUF_NO_OVERWRITE", - "/something/else", - ) - }; - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.usize_var, Some(123)); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION_PATHBUF_NO_OVERWRITE") }; - } - - #[test] - fn test_deserialize_optional_pathbuf_env_var_empty() { - let yaml_data = indoc!( - r#" - string_var: hithere - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.path_var, None); - assert_eq!(config.usize_var, Some(123)); - } - - #[test] - #[serial] - fn test_deserialize_optional_usize_env_var_is_present() { - unsafe { env::set_var("TEST_VAR_DESERIALIZE_OPTION_USIZE", "123") }; - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - usize_var: ${TEST_VAR_DESERIALIZE_OPTION_USIZE} - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.usize_var, Some(123)); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION_USIZE") }; - } - - #[test] - fn test_deserialize_optional_usize_env_var_uses_default_value_if_provided() { - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - usize_var: ${TEST_VAR_DESERIALIZE_OPTION_USIZE_UNDEFINED:-123} - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.usize_var, Some(123)); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - } - - #[test] - #[serial] - fn test_deserialize_optional_usize_env_var_does_not_overwrite_non_env_value() { - unsafe { env::set_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE_USIZE", "456") }; - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - usize_var: 123 - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.usize_var, Some(123)); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - unsafe { env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE_USIZE") }; - } - - #[test] - fn test_deserialize_optional_usize_env_var_invalid_number() { - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - usize_var: "holo" - "# - ); - let result: Result = serde_yaml::from_str(yaml_data); - - assert!(result.is_err()); - let err = result.unwrap_err().to_string(); - assert!(err.contains("invalid digit found in string")); - } - - #[test] - fn test_deserialize_optional_usize_env_var_empty() { - let yaml_data = indoc!( - r#" - string_var: hithere - path_var: /some/path - "# - ); - - let config: TestConfig = serde_yaml::from_str(yaml_data).unwrap(); - - assert_eq!(config.usize_var, None); - assert_eq!(config.string_var, Some("hithere".to_string())); - assert_eq!(config.path_var, Some(PathBuf::from("/some/path"))); - } #[test] fn test_interpolate_env_vars_defaults_to_original_string_if_not_in_yaml_interpolation_format() { diff --git a/src/providers/aws_secrets_manager.rs b/src/providers/aws_secrets_manager.rs index e4d4ec8..588d333 100644 --- a/src/providers/aws_secrets_manager.rs +++ b/src/providers/aws_secrets_manager.rs @@ -1,4 +1,3 @@ -use crate::config::deserialize_optional_env_var; use crate::providers::SecretProvider; use anyhow::Context; use anyhow::Result; @@ -33,10 +32,8 @@ use validator::Validate; #[serde(deny_unknown_fields)] pub struct AwsSecretsManagerProvider { #[validate(required)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub aws_profile: Option, #[validate(required)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub aws_region: Option, } diff --git a/src/providers/azure_key_vault.rs b/src/providers/azure_key_vault.rs index d1a78e2..07e60fa 100644 --- a/src/providers/azure_key_vault.rs +++ b/src/providers/azure_key_vault.rs @@ -1,4 +1,3 @@ -use crate::config::deserialize_optional_env_var; use crate::providers::SecretProvider; use anyhow::{Context, Result}; use azure_identity::DefaultAzureCredential; @@ -31,7 +30,6 @@ use validator::Validate; #[serde(deny_unknown_fields)] pub struct AzureKeyVaultProvider { #[validate(required)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub vault_name: Option, } diff --git a/src/providers/gcp_secret_manager.rs b/src/providers/gcp_secret_manager.rs index 4cc3ec5..df3cec6 100644 --- a/src/providers/gcp_secret_manager.rs +++ b/src/providers/gcp_secret_manager.rs @@ -1,4 +1,3 @@ -use crate::config::deserialize_optional_env_var; use crate::providers::SecretProvider; use anyhow::{Context, Result, anyhow}; use gcloud_sdk::google::cloud::secretmanager::v1; @@ -40,7 +39,6 @@ type SecretsManagerClient = GoogleApi, } diff --git a/src/providers/gopass.rs b/src/providers/gopass.rs index 3c21971..a0de4e1 100644 --- a/src/providers/gopass.rs +++ b/src/providers/gopass.rs @@ -1,4 +1,3 @@ -use crate::config::deserialize_optional_env_var; use crate::providers::{ENV_PATH, SecretProvider}; use anyhow::{Context, Result, anyhow}; use serde::{Deserialize, Serialize}; @@ -29,7 +28,6 @@ use validator::Validate; #[derive(Debug, Default, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct GopassProvider { - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub store: Option, } diff --git a/src/providers/local.rs b/src/providers/local.rs index 0040d9a..b260ae5 100644 --- a/src/providers/local.rs +++ b/src/providers/local.rs @@ -1,16 +1,14 @@ -use crate::config::deserialize_optional_env_var; -use crate::config::deserialize_optional_pathbuf_env_var; -use anyhow::{Context, anyhow, bail}; +use anyhow::{anyhow, bail, Context}; use secrecy::{ExposeSecret, SecretString}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::{env, fs}; use zeroize::Zeroize; -use crate::config::{Config, get_config_file_path, load_config}; +use crate::config::{get_config_file_path, load_config, Config}; use crate::providers::git_sync::{ - SyncOpts, default_git_email, default_git_username, ensure_git_available, repo_name_from_url, - resolve_git, sync_and_push, + default_git_email, default_git_username, ensure_git_available, repo_name_from_url, resolve_git, + sync_and_push, SyncOpts, }; use crate::providers::{SecretProvider, SupportedProvider}; use crate::{ @@ -18,13 +16,13 @@ use crate::{ }; use anyhow::Result; use argon2::{Algorithm, Argon2, Params, Version}; -use base64::{Engine as _, engine::general_purpose::STANDARD as B64}; +use base64::{engine::general_purpose::STANDARD as B64, Engine as _}; use chacha20poly1305::aead::rand_core::RngCore; use chacha20poly1305::{ - Key, XChaCha20Poly1305, XNonce, aead::{Aead, KeyInit, OsRng}, + Key, XChaCha20Poly1305, XNonce, }; -use dialoguer::{Input, theme}; +use dialoguer::{theme, Input}; use log::{debug, error}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -52,21 +50,14 @@ use validator::Validate; #[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct LocalProvider { - #[serde(default, deserialize_with = "deserialize_optional_pathbuf_env_var")] pub password_file: Option, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub git_branch: Option, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub git_remote_url: Option, - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub git_user_name: Option, #[validate(email)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub git_user_email: Option, - #[serde(default, deserialize_with = "deserialize_optional_pathbuf_env_var")] pub git_executable: Option, #[serde(skip)] - #[serde(default, deserialize_with = "deserialize_optional_env_var")] pub runtime_provider_name: Option, } @@ -256,7 +247,7 @@ 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 mut cfg = load_config(true).with_context(|| "failed to load existing config")?; let target_name = self.runtime_provider_name.clone(); let mut updated = false; diff --git a/tests/bin/cli_tests.rs b/tests/bin/cli_tests.rs index ff42b5d..dca4175 100644 --- a/tests/bin/cli_tests.rs +++ b/tests/bin/cli_tests.rs @@ -130,18 +130,6 @@ fn cli_shows_help() { .stdout(predicate::str::contains("Usage").or(predicate::str::contains("Add"))); } -#[test] -fn cli_completions_bash() { - let (_td, cfg, cache) = setup_env(); - let mut cmd = Command::cargo_bin("gman").unwrap(); - cmd.env("XDG_CACHE_HOME", &cache) - .env("XDG_CONFIG_HOME", &cfg) - .args(["completions", "bash"]); - cmd.assert() - .success() - .stdout(predicate::str::contains("_gman").or(predicate::str::contains("complete -F"))); -} - #[test] fn cli_add_get_list_update_delete_roundtrip() { let (td, xdg_cfg, xdg_cache) = setup_env();