Migrated some CLI specific code to a CLI module to make the code cleaner and easier to test
This commit is contained in:
@@ -0,0 +1,245 @@
|
|||||||
|
use crate::command::preview_command;
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use heck::ToSnakeCase;
|
||||||
|
use log::{debug, error};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use gman::config::{Config, RunConfig};
|
||||||
|
use gman::providers::SecretProvider;
|
||||||
|
|
||||||
|
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
||||||
|
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
||||||
|
pub fn wrap_and_run_command(
|
||||||
|
secrets_provider: Box<dyn SecretProvider>,
|
||||||
|
config: &Config,
|
||||||
|
tokens: Vec<OsString>,
|
||||||
|
profile_name: Option<String>,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (prog, args) = tokens
|
||||||
|
.split_first()
|
||||||
|
.with_context(|| "need a command to run")?;
|
||||||
|
let run_config_profile_name = if let Some(ref profile_name) = profile_name {
|
||||||
|
profile_name.as_str()
|
||||||
|
} else {
|
||||||
|
prog.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert program name to string"))?
|
||||||
|
};
|
||||||
|
let run_config_opt = config.run_configs.as_ref().and_then(|configs| {
|
||||||
|
configs.iter().filter(|c| c.name.is_some()).find(|c| {
|
||||||
|
c.name.as_ref().expect("failed to unwrap run config name") == run_config_profile_name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if let Some(run_cfg) = run_config_opt {
|
||||||
|
let secrets_result = run_cfg
|
||||||
|
.secrets
|
||||||
|
.as_ref()
|
||||||
|
.expect("no secrets configured for run profile")
|
||||||
|
.iter()
|
||||||
|
.map(|key| {
|
||||||
|
let secret_name = key.to_snake_case().to_uppercase();
|
||||||
|
debug!(
|
||||||
|
"Retrieving secret '{secret_name}' for run profile '{}'",
|
||||||
|
run_config_profile_name
|
||||||
|
);
|
||||||
|
secrets_provider
|
||||||
|
.get_secret(config, key.to_snake_case().to_uppercase().as_str())
|
||||||
|
.ok()
|
||||||
|
.map_or_else(
|
||||||
|
|| {
|
||||||
|
debug!("Failed to fetch secret '{secret_name}' from secret provider");
|
||||||
|
(
|
||||||
|
key.to_uppercase(),
|
||||||
|
Err(anyhow!(
|
||||||
|
"Failed to fetch secret '{secret_name}' from secret provider"
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|value| {
|
||||||
|
if dry_run {
|
||||||
|
(key.to_uppercase(), Ok("*****".into()))
|
||||||
|
} else {
|
||||||
|
(key.to_uppercase(), Ok(value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let err = secrets_result
|
||||||
|
.clone()
|
||||||
|
.filter(|(_, r)| r.is_err())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !err.is_empty() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Failed to fetch {} secrets from secret provider. {}",
|
||||||
|
err.len(),
|
||||||
|
err.iter()
|
||||||
|
.map(|(k, _)| format!("\n'{}'", k))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let secrets = secrets_result
|
||||||
|
.map(|(k, r)| (k, r.unwrap()))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let mut cmd_def = Command::new(prog);
|
||||||
|
if run_cfg.flag.is_some() {
|
||||||
|
let args = parse_args(args, run_cfg, secrets.clone(), dry_run)?;
|
||||||
|
run_cmd(cmd_def.args(&args), dry_run)?;
|
||||||
|
} else if run_cfg.files.is_some() {
|
||||||
|
let injected_files = generate_files_secret_injections(secrets.clone(), run_cfg)
|
||||||
|
.with_context(|| "failed to inject secrets into files")?;
|
||||||
|
for (file, original_content, new_content) in &injected_files {
|
||||||
|
if dry_run {
|
||||||
|
println!("Would inject secrets into file '{}'", file.display());
|
||||||
|
} else {
|
||||||
|
match fs::write(file, new_content).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to write injected content to file '{}'",
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("Injected secrets into file '{}'", file.display());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Failed to inject secrets into file '{}': {}",
|
||||||
|
file.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
debug!("Restoring original content to file '{}'", file.display());
|
||||||
|
fs::write(file, original_content) .with_context(|| format!("failed to restore original content to file '{}' after injection failure: {}", file.display(), e))?;
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match run_cmd(cmd_def.args(args), dry_run) {
|
||||||
|
Ok(_) => {
|
||||||
|
if !dry_run {
|
||||||
|
for (file, original_content, _) in &injected_files {
|
||||||
|
debug!("Restoring original content to file '{}'", file.display());
|
||||||
|
fs::write(file, original_content).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to restore original content to file '{}'",
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if !dry_run {
|
||||||
|
for (file, original_content, _) in &injected_files {
|
||||||
|
error!(
|
||||||
|
"Command execution failed, restoring original content to file '{}'",
|
||||||
|
file.display()
|
||||||
|
);
|
||||||
|
debug!("Restoring original content to file '{}'", file.display());
|
||||||
|
fs::write(file, original_content) .with_context(|| format!("failed to restore original content to file '{}' after command execution failure: {}", file.display(), e))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
run_cmd(cmd_def.args(args).envs(secrets), dry_run)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("No run profile found for '{run_config_profile_name}'");
|
||||||
|
return Err(anyhow!(
|
||||||
|
"No run profile found for '{run_config_profile_name}'"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn generate_files_secret_injections(
|
||||||
|
secrets: HashMap<String, String>,
|
||||||
|
run_config: &RunConfig,
|
||||||
|
) -> Result<Vec<(&PathBuf, String, String)>> {
|
||||||
|
let re = Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}")?;
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for file in run_config
|
||||||
|
.files
|
||||||
|
.as_ref()
|
||||||
|
.with_context(|| "no files configured for run profile")?
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
"Generating file with injected secrets for '{}'",
|
||||||
|
file.display()
|
||||||
|
);
|
||||||
|
let original_content = fs::read_to_string(file).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to read file for secrets injection: '{}'",
|
||||||
|
file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let new_content = re.replace_all(&original_content, |caps: ®ex::Captures| {
|
||||||
|
secrets
|
||||||
|
.get(&caps[1].to_snake_case().to_uppercase())
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or(&caps[0])
|
||||||
|
.to_string()
|
||||||
|
});
|
||||||
|
results.push((file, original_content.to_string(), new_content.to_string()));
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
pub fn run_cmd(cmd: &mut Command, dry_run: bool) -> Result<()> {
|
||||||
|
if dry_run {
|
||||||
|
eprintln!("Command to be executed: {}", preview_command(cmd));
|
||||||
|
} else {
|
||||||
|
cmd.status()
|
||||||
|
.with_context(|| format!("failed to execute command '{:?}'", cmd))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn parse_args(
|
||||||
|
args: &[OsString],
|
||||||
|
run_config: &RunConfig,
|
||||||
|
secrets: HashMap<String, String>,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<Vec<OsString>> {
|
||||||
|
let args = args.to_vec();
|
||||||
|
let flag = run_config
|
||||||
|
.flag
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("flag must be set if arg_format is set"))?;
|
||||||
|
let flag_position = run_config
|
||||||
|
.flag_position
|
||||||
|
.ok_or_else(|| anyhow!("flag_position must be set if flag is set"))?;
|
||||||
|
let arg_format = run_config
|
||||||
|
.arg_format
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("arg_format must be set if flag is set"))?;
|
||||||
|
let mut args = args.to_vec();
|
||||||
|
if flag_position > args.len() {
|
||||||
|
secrets.iter().for_each(|(k, v)| {
|
||||||
|
let v = if dry_run { "*****" } else { v };
|
||||||
|
args.push(OsString::from(flag));
|
||||||
|
args.push(OsString::from(
|
||||||
|
arg_format
|
||||||
|
.replace(ARG_FORMAT_PLACEHOLDER_KEY, k)
|
||||||
|
.replace(ARG_FORMAT_PLACEHOLDER_VALUE, v),
|
||||||
|
));
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
secrets.iter().for_each(|(k, v)| {
|
||||||
|
let v = if dry_run { "*****" } else { v };
|
||||||
|
args.insert(
|
||||||
|
flag_position,
|
||||||
|
OsString::from(
|
||||||
|
arg_format
|
||||||
|
.replace(ARG_FORMAT_PLACEHOLDER_KEY, k)
|
||||||
|
.replace(ARG_FORMAT_PLACEHOLDER_VALUE, v),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
args.insert(flag_position, OsString::from(flag));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(args)
|
||||||
|
}
|
||||||
+9
-262
@@ -1,33 +1,27 @@
|
|||||||
use clap::{
|
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 std::collections::HashMap;
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use crate::command::preview_command;
|
use anyhow::{Context, Result};
|
||||||
use anyhow::{Context, Result, anyhow};
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen};
|
||||||
use gman::config::{Config, RunConfig};
|
use gman::config::Config;
|
||||||
use gman::providers::local::LocalProvider;
|
use gman::providers::local::LocalProvider;
|
||||||
use gman::providers::{SecretProvider, SupportedProvider};
|
use gman::providers::SupportedProvider;
|
||||||
use heck::ToSnakeCase;
|
use heck::ToSnakeCase;
|
||||||
use log::{debug, error};
|
|
||||||
use regex::Regex;
|
|
||||||
use std::io::{self, IsTerminal, Read, Write};
|
use std::io::{self, IsTerminal, Read, Write};
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use crate::cli::wrap_and_run_command;
|
||||||
use std::{fs, panic};
|
use std::panic;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
mod command;
|
mod command;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
|
||||||
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, ValueEnum)]
|
#[derive(Debug, Clone, ValueEnum)]
|
||||||
enum OutputFormat {
|
enum OutputFormat {
|
||||||
Text,
|
Text,
|
||||||
@@ -251,253 +245,6 @@ fn load_config(cli: &Cli) -> Result<Config> {
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_and_run_command(
|
|
||||||
secrets_provider: Box<dyn SecretProvider>,
|
|
||||||
config: &Config,
|
|
||||||
tokens: Vec<OsString>,
|
|
||||||
profile_name: Option<String>,
|
|
||||||
dry_run: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let (prog, args) = tokens
|
|
||||||
.split_first()
|
|
||||||
.with_context(|| "need a command to run")?;
|
|
||||||
let run_config_profile_name = if let Some(ref profile_name) = profile_name {
|
|
||||||
profile_name.as_str()
|
|
||||||
} else {
|
|
||||||
prog.to_str()
|
|
||||||
.ok_or_else(|| anyhow!("failed to convert program name to string"))?
|
|
||||||
};
|
|
||||||
let run_config_opt = config.run_configs.as_ref().and_then(|configs| {
|
|
||||||
configs.iter().filter(|c| c.name.is_some()).find(|c| {
|
|
||||||
c.name.as_ref().expect("failed to unwrap run config name") == run_config_profile_name
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(run_cfg) = run_config_opt {
|
|
||||||
let secrets_result = run_cfg
|
|
||||||
.secrets
|
|
||||||
.as_ref()
|
|
||||||
.expect("no secrets configured for run profile")
|
|
||||||
.iter()
|
|
||||||
.map(|key| {
|
|
||||||
let secret_name = key.to_snake_case().to_uppercase();
|
|
||||||
debug!(
|
|
||||||
"Retrieving secret '{secret_name}' for run profile '{}'",
|
|
||||||
run_config_profile_name
|
|
||||||
);
|
|
||||||
secrets_provider
|
|
||||||
.get_secret(config, key.to_snake_case().to_uppercase().as_str())
|
|
||||||
.ok()
|
|
||||||
.map_or_else(
|
|
||||||
|| {
|
|
||||||
debug!("Failed to fetch secret '{secret_name}' from secret provider");
|
|
||||||
(
|
|
||||||
key.to_uppercase(),
|
|
||||||
Err(anyhow!(
|
|
||||||
"Failed to fetch secret '{secret_name}' from secret provider"
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|value| {
|
|
||||||
if dry_run {
|
|
||||||
(key.to_uppercase(), Ok("*****".into()))
|
|
||||||
} else {
|
|
||||||
(key.to_uppercase(), Ok(value))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let err = secrets_result
|
|
||||||
.clone()
|
|
||||||
.filter(|(_, r)| r.is_err())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !err.is_empty() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Failed to fetch {} secrets from secret provider. {}",
|
|
||||||
err.len(),
|
|
||||||
err.iter()
|
|
||||||
.map(|(k, _)| format!("\n'{}'", k))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let secrets = secrets_result
|
|
||||||
.map(|(k, r)| (k, r.unwrap()))
|
|
||||||
.collect::<HashMap<_, _>>();
|
|
||||||
let mut cmd_def = Command::new(prog);
|
|
||||||
|
|
||||||
if run_cfg.flag.is_some() {
|
|
||||||
let args = parse_args(args, run_cfg, secrets.clone(), dry_run)?;
|
|
||||||
run_cmd(cmd_def.args(&args), dry_run)?;
|
|
||||||
} else if run_cfg.files.is_some() {
|
|
||||||
let injected_files = generate_files_secret_injections(secrets.clone(), run_cfg)
|
|
||||||
.with_context(|| "failed to inject secrets into files")?;
|
|
||||||
|
|
||||||
for (file, original_content, new_content) in &injected_files {
|
|
||||||
if dry_run {
|
|
||||||
println!("Would inject secrets into file '{}'", file.display());
|
|
||||||
} else {
|
|
||||||
match fs::write(file, new_content).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to write injected content to file '{}'",
|
|
||||||
file.display()
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("Injected secrets into file '{}'", file.display());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Failed to inject secrets into file '{}': {}",
|
|
||||||
file.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
debug!("Restoring original content to file '{}'", file.display());
|
|
||||||
fs::write(file, original_content)
|
|
||||||
.with_context(|| format!("failed to restore original content to file '{}' after injection failure: {}", file.display(), e))?;
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match run_cmd(cmd_def.args(args), dry_run) {
|
|
||||||
Ok(_) => {
|
|
||||||
if !dry_run {
|
|
||||||
for (file, original_content, _) in &injected_files {
|
|
||||||
debug!("Restoring original content to file '{}'", file.display());
|
|
||||||
fs::write(file, original_content).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to restore original content to file '{}'",
|
|
||||||
file.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if !dry_run {
|
|
||||||
for (file, original_content, _) in &injected_files {
|
|
||||||
error!(
|
|
||||||
"Command execution failed, restoring original content to file '{}'",
|
|
||||||
file.display()
|
|
||||||
);
|
|
||||||
debug!("Restoring original content to file '{}'", file.display());
|
|
||||||
fs::write(file, original_content)
|
|
||||||
.with_context(|| format!("failed to restore original content to file '{}' after command execution failure: {}", file.display(), e))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
run_cmd(cmd_def.args(args).envs(secrets), dry_run)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("No run profile found for '{run_config_profile_name}'");
|
|
||||||
return Err(anyhow!(
|
|
||||||
"No run profile found for '{run_config_profile_name}'"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_files_secret_injections(
|
|
||||||
secrets: HashMap<String, String>,
|
|
||||||
run_config: &RunConfig,
|
|
||||||
) -> Result<Vec<(&PathBuf, String, String)>> {
|
|
||||||
let re = Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}")?;
|
|
||||||
let mut results = Vec::new();
|
|
||||||
for file in run_config
|
|
||||||
.files
|
|
||||||
.as_ref()
|
|
||||||
.with_context(|| "no files configured for run profile")?
|
|
||||||
{
|
|
||||||
debug!(
|
|
||||||
"Generating file with injected secrets for '{}'",
|
|
||||||
file.display()
|
|
||||||
);
|
|
||||||
let original_content = fs::read_to_string(file).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to read file for secrets injection: '{}'",
|
|
||||||
file.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let new_content = re.replace_all(&original_content, |caps: ®ex::Captures| {
|
|
||||||
secrets
|
|
||||||
.get(&caps[1].to_snake_case().to_uppercase())
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or(&caps[0])
|
|
||||||
.to_string()
|
|
||||||
});
|
|
||||||
|
|
||||||
results.push((file, original_content.to_string(), new_content.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_cmd(cmd: &mut Command, dry_run: bool) -> Result<()> {
|
|
||||||
if dry_run {
|
|
||||||
eprintln!("Command to be executed: {}", preview_command(cmd));
|
|
||||||
} else {
|
|
||||||
cmd.status()
|
|
||||||
.with_context(|| format!("failed to execute command '{:?}'", cmd))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_args(
|
|
||||||
args: &[OsString],
|
|
||||||
run_config: &RunConfig,
|
|
||||||
secrets: HashMap<String, String>,
|
|
||||||
dry_run: bool,
|
|
||||||
) -> Result<Vec<OsString>> {
|
|
||||||
let args = args.to_vec();
|
|
||||||
let flag = run_config
|
|
||||||
.flag
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| anyhow!("flag must be set if arg_format is set"))?;
|
|
||||||
let flag_position = run_config
|
|
||||||
.flag_position
|
|
||||||
.ok_or_else(|| anyhow!("flag_position must be set if flag is set"))?;
|
|
||||||
let arg_format = run_config
|
|
||||||
.arg_format
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| anyhow!("arg_format must be set if flag is set"))?;
|
|
||||||
let mut args = args.to_vec();
|
|
||||||
|
|
||||||
if flag_position > args.len() {
|
|
||||||
secrets.iter().for_each(|(k, v)| {
|
|
||||||
let v = if dry_run { "*****" } else { v };
|
|
||||||
args.push(OsString::from(flag));
|
|
||||||
args.push(OsString::from(
|
|
||||||
arg_format
|
|
||||||
.replace(ARG_FORMAT_PLACEHOLDER_KEY, k)
|
|
||||||
.replace(ARG_FORMAT_PLACEHOLDER_VALUE, v),
|
|
||||||
));
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
secrets.iter().for_each(|(k, v)| {
|
|
||||||
let v = if dry_run { "*****" } else { v };
|
|
||||||
args.insert(
|
|
||||||
flag_position,
|
|
||||||
OsString::from(
|
|
||||||
arg_format
|
|
||||||
.replace(ARG_FORMAT_PLACEHOLDER_KEY, k)
|
|
||||||
.replace(ARG_FORMAT_PLACEHOLDER_VALUE, v),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
args.insert(flag_position, OsString::from(flag));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_all_stdin() -> Result<String> {
|
fn read_all_stdin() -> Result<String> {
|
||||||
if io::stdin().is_terminal() {
|
if io::stdin().is_terminal() {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
|||||||
Reference in New Issue
Block a user