use anyhow::{Context as _, Result};
use colored::Colorize;
use tsafe_cli::tsafe_aws::{pull_ssm_parameters, AwsConfig, AwsCredentials, AwsError};
use tsafe_core::{audit::AuditEntry, events::emit_event};
use crate::helpers::*;
use tsafe_cli::cli::PullOnError;
pub(crate) fn cmd_ssm_pull(
profile: &str,
region: Option<&str>,
path: Option<&str>,
overwrite: bool,
on_error: PullOnError,
) -> Result<()> {
cmd_ssm_pull_ns(profile, region, path, overwrite, on_error, None)
}
pub(crate) fn cmd_ssm_pull_ns(
profile: &str,
region: Option<&str>,
path: Option<&str>,
overwrite: bool,
on_error: PullOnError,
ns: Option<&str>,
) -> Result<()> {
let cfg = match region {
Some(r) => {
let endpoint = format!("https://ssm.{r}.amazonaws.com");
AwsConfig::with_endpoint(r, endpoint)
}
None => {
let mut cfg = AwsConfig::from_env().with_context(|| {
"AWS region is not configured\n\
\n Fix: export AWS_DEFAULT_REGION=us-east-1 (or pass --region)\
\n Help: tsafe explain pull-auth"
})?;
cfg.endpoint = format!("https://ssm.{}.amazonaws.com", cfg.region);
cfg
}
};
let params = match pull_ssm_parameters(
&cfg,
&|| AwsCredentials::from_env_or_imds().map_err(|e| AwsError::Auth(format!("{e}"))),
path,
)
.with_context(|| {
"failed to pull parameters from AWS SSM Parameter Store\n\
\n Credential setup: tsafe explain pull-auth\
\n Required policy: ssm:GetParametersByPath + kms:Decrypt (for SecureString)"
}) {
Ok(params) => params,
Err(err) => match on_error {
PullOnError::FailAll => return Err(err),
PullOnError::SkipFailed | PullOnError::WarnOnly => {
eprintln!("{} SSM pull failed: {err}", "!".yellow());
return Ok(());
}
},
};
if params.is_empty() {
println!(
"{} No parameters found in SSM Parameter Store matching the path filter",
"i".blue()
);
return Ok(());
}
let mut vault = open_vault(profile)?;
let mut imported = 0usize;
let mut skipped = 0usize;
for (raw_key, value) in ¶ms {
let key = match ns {
Some(prefix) => format!("{prefix}.{raw_key}"),
None => raw_key.clone(),
};
let exists = vault.list().contains(&key.as_str());
if exists && !overwrite {
skipped += 1;
continue;
}
vault.set(&key, value, std::collections::HashMap::new())?;
imported += 1;
}
audit(profile)
.append(&AuditEntry::success(profile, "ssm-pull", None))
.ok();
emit_event(profile, "ssm-pull", None);
println!(
"{} Imported {imported} parameter(s) from SSM Parameter Store (region: {}){}",
"✓".green(),
cfg.region,
if skipped > 0 {
format!(" ({skipped} skipped — use --overwrite to replace)")
} else {
String::new()
}
);
Ok(())
}