use crate::cli::OutputFormat;
use crate::consts::{LINE_ENDING, PATH_HEADER, SHA_HEADER};
use crate::secrets;
use crate::secrets::Secret;
use anyhow::{Context, Result};
use dialoguer::{Confirm, Input, Password};
use sha2::{Digest, Sha256};
use shell_escape::escape;
use std::borrow::Cow;
use std::fmt::Write;
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};
pub fn save(
app_name: Option<String>,
secrets: Vec<Secret>,
interactive: bool,
force: bool,
) -> Result<Vec<Secret>> {
let mut new_secrets = Vec::<Secret>::new();
for secret in secrets {
if interactive {
let resolved_secret = secret.to_keycli_str()?;
let new_secret_str = confirm_or_edit("The secret full path is", &resolved_secret)?;
let new_secret = Secret::new(app_name.clone(), &new_secret_str)?;
if !new_secret.exists()?
|| Confirm::new()
.with_prompt(format!(
"{} already exist in the keyring, replace it?",
new_secret.to_keyring_str()
))
.default(false)
.interact()?
{
let value = Password::new()
.with_prompt(format!(
r#"Input the value of "{}""#,
new_secret.to_keycli_str()?
))
.interact()?;
new_secret.push(&value)?;
}
new_secrets.push(new_secret);
} else {
let new_secret = secret;
if !force && new_secret.exists()? {
log::warn!(
"{} already exist in the keyring and force is false, not pushing it",
new_secret.to_keyring_str()
);
} else {
let value = env::var(&new_secret.env).context(format!(
"Environment variable {} is not defined",
new_secret.env
))?;
new_secret.push(&value)?;
}
new_secrets.push(new_secret);
}
}
Ok(new_secrets)
}
pub fn clear(secrets: Vec<Secret>, interactive: bool) -> Result<()> {
for secret in secrets {
if !secret.exists()? {
log::warn!("{} does not exist in the keyring", secret.to_keyring_str());
continue;
}
if !interactive
|| Confirm::new()
.with_prompt(format!(
"Are you sure to delete {}?",
secret.to_keyring_str()
))
.default(false)
.interact()?
{
secret.clear()?;
continue;
}
}
Ok(())
}
pub fn load(secrets: Vec<Secret>, output_format: OutputFormat) -> Result<String> {
let mut result = String::new();
let env = secrets::build_env(secrets)?;
match output_format {
OutputFormat::ShellScript => {
for (env, password) in env {
let escaped = escape(Cow::Borrowed(&password));
write!(&mut result, "export {env}={escaped}{LINE_ENDING}")?;
log::debug!("Processed {env}");
}
Ok(result)
}
OutputFormat::Json => Ok(serde_json::to_string(&env)?),
}
}
pub fn unload(secrets: Vec<Secret>) -> Result<String> {
let mut result = String::new();
for (env, _) in secrets::build_env(secrets)? {
write!(&mut result, "unset {}{LINE_ENDING}", env)?;
log::debug!("Processed {}", env);
}
Ok(result)
}
pub fn list(secrets: Vec<Secret>) -> Result<String> {
let mut result = String::new();
for (env, _) in secrets::build_env(secrets)? {
write!(&mut result, "{}{LINE_ENDING}", env)?;
log::debug!("Processed {}", env);
}
Ok(result)
}
pub fn exec(secrets: Vec<Secret>, binary: &str, args: Vec<String>) -> Result<()> {
let env_map = secrets::build_env(secrets)?;
Command::new(binary).envs(env_map).args(&args).status()?;
Ok(())
}
pub fn init(secrets: Vec<Secret>, template: Option<PathBuf>) -> Result<String> {
let mut result = String::new();
if let Some(path) = template {
let canon_path = std::fs::canonicalize(&path)?;
let content = fs::read(path.clone())?;
let hash = hex::encode(Sha256::digest(&content));
write!(
&mut result,
"{PATH_HEADER}{}{LINE_ENDING}",
canon_path.display()
)?;
write!(&mut result, "{SHA_HEADER}{hash}{LINE_ENDING}")?;
};
write!(&mut result, "{}", secrets::init_str(secrets)?)?;
Ok(result)
}
fn confirm_or_edit(prompt: &str, value: &str) -> Result<String> {
let confirmed = Confirm::new()
.with_prompt(format!(r#"{prompt}: "{value}"?"#))
.default(true)
.interact()?;
if confirmed {
Ok(value.to_string())
} else {
Ok(Input::new()
.with_prompt("Enter new value")
.interact_text()?)
}
}