feat: Users can now specify a default provider to use with each run config, so they don't need to explicitly specify which to use when wanting to run different applications.
This commit is contained in:
@@ -94,7 +94,8 @@ gman aws sts get-caller-identity
|
|||||||
- [AWS Secrets Manager](#provider-aws_secrets_manager)
|
- [AWS Secrets Manager](#provider-aws_secrets_manager)
|
||||||
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
||||||
- [Azure Key Vault](#provider-azure_key_vault)
|
- [Azure Key Vault](#provider-azure_key_vault)
|
||||||
- [Run Configurations](#run-configurations)
|
- [Run Configurations](#run-configurations)
|
||||||
|
- [Specifying a Default Provider per Run Config](#specifying-a-default-provider-per-run-config)
|
||||||
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
||||||
- [Inject Secrets via Command-Line Flags](#inject-secrets-via-command-line-flags)
|
- [Inject Secrets via Command-Line Flags](#inject-secrets-via-command-line-flags)
|
||||||
- [Inject Secrets into Files](#inject-secrets-into-files)
|
- [Inject Secrets into Files](#inject-secrets-into-files)
|
||||||
@@ -404,6 +405,45 @@ will error out and report that it could not find the run config with that name.
|
|||||||
You can manually specify which run configuration to use with the `--profile` flag. Again, if no profile is found with
|
You can manually specify which run configuration to use with the `--profile` flag. Again, if no profile is found with
|
||||||
that name, `gman` will error out.
|
that name, `gman` will error out.
|
||||||
|
|
||||||
|
|
||||||
|
### Specifying a Default Provider per Run Config
|
||||||
|
All run configs also support the `provider` field, which lets you override the default provider for that specific
|
||||||
|
profile. This is useful if you have multiple providers configured and want to use a different one for a specific command
|
||||||
|
, but that provider may not be the `default_provider`, and you don't want to have to specify `--provider` on the command
|
||||||
|
line every time.
|
||||||
|
|
||||||
|
For Example:
|
||||||
|
```yaml
|
||||||
|
default_provider: local
|
||||||
|
run_configs:
|
||||||
|
# `gman aws ...` uses the `aws` provider instead of `local` if no
|
||||||
|
# `--provider` is given.
|
||||||
|
- name: aws
|
||||||
|
# Can be overridden by explicitly specifying a `--provider`
|
||||||
|
provider: aws
|
||||||
|
secrets:
|
||||||
|
- DB_USERNAME
|
||||||
|
- DB_PASSWORD
|
||||||
|
# `gman docker ...` uses the default_provider `local` because no
|
||||||
|
# `provider` is specified.
|
||||||
|
- name: docker
|
||||||
|
secrets:
|
||||||
|
- MY_APP_API_KEY
|
||||||
|
- MY_APP_DB_PASSWORD
|
||||||
|
# `gman managarr ...` uses the `local` provider; This is useful
|
||||||
|
# if you change the default provider to something else.
|
||||||
|
- name: managarr
|
||||||
|
provider: local
|
||||||
|
secrets:
|
||||||
|
- RADARR_API_KEY
|
||||||
|
- SONARR_API_KEY
|
||||||
|
files:
|
||||||
|
- /home/user/.config/managarr/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Note:** Any run config with a `provider` field can be overridden by specifying `--provider` on the command
|
||||||
|
line.
|
||||||
|
|
||||||
### Environment Variable Secret Injection
|
### Environment Variable Secret Injection
|
||||||
|
|
||||||
By default, secrets are injected as environment variables. The two required fields are `name` and `secrets`.
|
By default, secrets are injected as environment variables. The two required fields are `name` and `secrets`.
|
||||||
|
|||||||
+9
-29
@@ -2,7 +2,6 @@ use crate::command::preview_command;
|
|||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use gman::config::{Config, RunConfig};
|
use gman::config::{Config, RunConfig};
|
||||||
use gman::providers::SecretProvider;
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -15,7 +14,7 @@ const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
|||||||
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
||||||
|
|
||||||
pub async fn wrap_and_run_command(
|
pub async fn wrap_and_run_command(
|
||||||
secrets_provider: &mut dyn SecretProvider,
|
provider: Option<String>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
tokens: Vec<OsString>,
|
tokens: Vec<OsString>,
|
||||||
profile_name: Option<String>,
|
profile_name: Option<String>,
|
||||||
@@ -36,6 +35,8 @@ pub async fn wrap_and_run_command(
|
|||||||
.find(|c| c.name.as_deref() == Some(run_config_profile_name))
|
.find(|c| c.name.as_deref() == Some(run_config_profile_name))
|
||||||
});
|
});
|
||||||
if let Some(run_cfg) = run_config_opt {
|
if let Some(run_cfg) = run_config_opt {
|
||||||
|
let mut provider_config = config.extract_provider_config(provider.or(run_cfg.provider.clone()))?;
|
||||||
|
let secrets_provider = provider_config.extract_provider();
|
||||||
let secrets_result_futures = run_cfg
|
let secrets_result_futures = run_cfg
|
||||||
.secrets
|
.secrets
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -163,7 +164,7 @@ fn generate_files_secret_injections(
|
|||||||
secrets: HashMap<&str, String>,
|
secrets: HashMap<&str, String>,
|
||||||
run_config: &RunConfig,
|
run_config: &RunConfig,
|
||||||
) -> Result<Vec<(PathBuf, String, String)>> {
|
) -> Result<Vec<(PathBuf, String, String)>> {
|
||||||
let re = Regex::new(r"\{\{(.+)\}\}")?;
|
let re = Regex::new(r"\{\{(.+)}}")?;
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for file in run_config
|
for file in run_config
|
||||||
.files
|
.files
|
||||||
@@ -260,26 +261,6 @@ mod tests {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
struct DummyProvider;
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl SecretProvider for DummyProvider {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"Dummy"
|
|
||||||
}
|
|
||||||
async fn get_secret(&self, key: &str) -> Result<String> {
|
|
||||||
Ok(format!("{}_VAL", key))
|
|
||||||
}
|
|
||||||
async fn set_secret(&self, _key: &str, _value: &str) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn delete_secret(&self, _key: &str) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn sync(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_files_secret_injections() {
|
fn test_generate_files_secret_injections() {
|
||||||
let mut secrets = HashMap::new();
|
let mut secrets = HashMap::new();
|
||||||
@@ -290,6 +271,7 @@ mod tests {
|
|||||||
|
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["testing/SOME-secret".to_string()]),
|
secrets: Some(vec!["testing/SOME-secret".to_string()]),
|
||||||
files: Some(vec![file_path.clone()]),
|
files: Some(vec![file_path.clone()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
@@ -309,6 +291,7 @@ mod tests {
|
|||||||
fn test_parse_args_insert_and_append() {
|
fn test_parse_args_insert_and_append() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("docker".into()),
|
name: Some("docker".into()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["api_key".into()]),
|
secrets: Some(vec!["api_key".into()]),
|
||||||
files: None,
|
files: None,
|
||||||
flag: Some("-e".into()),
|
flag: Some("-e".into()),
|
||||||
@@ -347,10 +330,8 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_wrap_and_run_command_no_profile() {
|
async fn test_wrap_and_run_command_no_profile() {
|
||||||
let cfg = Config::default();
|
let cfg = Config::default();
|
||||||
let mut dummy = DummyProvider;
|
|
||||||
let prov: &mut dyn SecretProvider = &mut dummy;
|
|
||||||
let tokens = vec![OsString::from("echo"), OsString::from("hi")];
|
let tokens = vec![OsString::from("echo"), OsString::from("hi")];
|
||||||
let err = wrap_and_run_command(prov, &cfg, tokens, None, true)
|
let err = wrap_and_run_command(None, &cfg, tokens, None, true)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(err.to_string().contains("No run profile found"));
|
assert!(err.to_string().contains("No run profile found"));
|
||||||
@@ -361,6 +342,7 @@ mod tests {
|
|||||||
// Create a config with a matching run profile for command "echo"
|
// Create a config with a matching run profile for command "echo"
|
||||||
let run_cfg = RunConfig {
|
let run_cfg = RunConfig {
|
||||||
name: Some("echo".into()),
|
name: Some("echo".into()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["api_key".into()]),
|
secrets: Some(vec!["api_key".into()]),
|
||||||
files: None,
|
files: None,
|
||||||
flag: None,
|
flag: None,
|
||||||
@@ -371,13 +353,11 @@ mod tests {
|
|||||||
run_configs: Some(vec![run_cfg]),
|
run_configs: Some(vec![run_cfg]),
|
||||||
..Config::default()
|
..Config::default()
|
||||||
};
|
};
|
||||||
let mut dummy = DummyProvider;
|
|
||||||
let prov: &mut dyn SecretProvider = &mut dummy;
|
|
||||||
|
|
||||||
// Capture stderr for dry_run preview
|
// Capture stderr for dry_run preview
|
||||||
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
||||||
// Best-effort: ensure function does not error under dry_run
|
// Best-effort: ensure function does not error under dry_run
|
||||||
let res = wrap_and_run_command(prov, &cfg, tokens, None, true).await;
|
let res = wrap_and_run_command(None, &cfg, tokens, None, true).await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
// Not asserting output text to keep test platform-agnostic
|
// Not asserting output text to keep test platform-agnostic
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,8 +253,7 @@ async fn main() -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Commands::External(tokens) => {
|
Commands::External(tokens) => {
|
||||||
wrap_and_run_command(secrets_provider, &config, tokens, cli.profile, cli.dry_run)
|
wrap_and_run_command(cli.provider, &config, tokens, cli.profile, cli.dry_run).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Commands::Completions { shell } => {
|
Commands::Completions { shell } => {
|
||||||
let mut cmd = Cli::command();
|
let mut cmd = Cli::command();
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ use validator::{Validate, ValidationError};
|
|||||||
pub struct RunConfig {
|
pub struct RunConfig {
|
||||||
#[validate(required)]
|
#[validate(required)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
pub provider: Option<String>,
|
||||||
#[validate(required)]
|
#[validate(required)]
|
||||||
pub secrets: Option<Vec<String>>,
|
pub secrets: Option<Vec<String>>,
|
||||||
pub files: Option<Vec<PathBuf>>,
|
pub files: Option<Vec<PathBuf>>,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ mod tests {
|
|||||||
fn test_run_config_valid() {
|
fn test_run_config_valid() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -23,6 +24,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_name() {
|
fn test_run_config_missing_name() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: None,
|
name: None,
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -37,6 +39,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_secrets() {
|
fn test_run_config_missing_secrets() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: None,
|
secrets: None,
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -51,6 +54,7 @@ mod tests {
|
|||||||
fn test_run_config_invalid_flag_position() {
|
fn test_run_config_invalid_flag_position() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(0),
|
flag_position: Some(0),
|
||||||
@@ -65,6 +69,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_some() {
|
fn test_run_config_flags_or_none_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -79,6 +84,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_none() {
|
fn test_run_config_flags_or_none_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -93,6 +99,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_partial_some() {
|
fn test_run_config_flags_or_none_partial_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -107,6 +114,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_missing_placeholder() {
|
fn test_run_config_flags_or_none_missing_placeholder() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -121,6 +129,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_none() {
|
fn test_run_config_flags_or_files_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -135,6 +144,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_files_is_some() {
|
fn test_run_config_flags_or_files_files_is_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -149,6 +159,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_some() {
|
fn test_run_config_flags_or_files_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
|
|||||||
Reference in New Issue
Block a user