use anyhow::{Context, Error, bail};
use crossterm::style::Stylize;
use crate::cli::common;
use crate::cli::http_client::MantaClient;
use crate::cli::output::action_result;
use manta_shared::common::app_context::AppContext;
use manta_shared::shared::sat_file::{
apply_sat_file_filters, render_jinja2_sat_file_yaml,
};
#[allow(clippy::struct_excessive_bools)]
pub struct SatApplyOptions<'a> {
pub sat_file_content: &'a str,
pub values_file_content_opt: Option<&'a str>,
pub values_cli_opt: Option<&'a [String]>,
pub ansible_verbosity_opt: Option<u8>,
pub ansible_passthrough_opt: Option<&'a str>,
pub reboot: bool,
pub watch_logs: bool,
pub timestamps: bool,
pub prehook_opt: Option<&'a str>,
pub posthook_opt: Option<&'a str>,
pub image_only: bool,
pub session_template_only: bool,
pub overwrite: bool,
pub dry_run: bool,
pub assume_yes: bool,
pub output_opt: Option<&'a str>,
}
fn validate_hook(hook_opt: Option<&str>, label: &str) -> Result<(), Error> {
if let Some(hook) = hook_opt {
crate::cli::common::hooks::check_hook_perms(hook_opt)
.map_err(|e| anyhow::anyhow!("{e}. File: {hook}"))?;
println!("{label}-hook script '{hook}' exists and is executable.");
}
Ok(())
}
fn run_hook_if_present(
hook_opt: Option<&str>,
label: &str,
) -> Result<(), Error> {
if let Some(hook) = hook_opt {
println!("Running the {label}-hook '{hook}'");
let code = crate::cli::common::hooks::run_hook(hook_opt)?;
tracing::debug!("{}-hook script completed ok. RT={}", label, code);
}
Ok(())
}
pub async fn exec(
ctx: &AppContext<'_>,
token: &str,
opts: &SatApplyOptions<'_>,
) -> Result<(), Error> {
let server_url = ctx.manta_server_url;
validate_hook(opts.prehook_opt, "Pre")?;
validate_hook(opts.posthook_opt, "Post")?;
tracing::info!("Render SAT template file");
let rendered_yaml = render_jinja2_sat_file_yaml(
opts.sat_file_content,
opts.values_file_content_opt,
opts.values_cli_opt,
)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let mut sat_file: serde_json::Value = serde_yaml::from_str(&rendered_yaml)
.context("Rendered SAT template is not valid YAML")?;
apply_sat_file_filters(
&mut sat_file,
opts.image_only,
opts.session_template_only,
)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let preview = serde_yaml::to_string(&sat_file)
.context("Failed to serialize filtered SAT value for preview")?;
println!("{}\n{}", "#### SAT file content ####".blue(), &preview,);
if !common::user_interaction::confirm(
"Please review the rendered SAT file above and confirm to proceed.",
opts.assume_yes,
) {
bail!("Operation cancelled by user");
}
if sat_file.get("session_templates").is_some()
&& opts.reboot
&& !common::user_interaction::confirm(
"This operation will reboot nodes. Please confirm to proceed.",
opts.assume_yes,
)
{
bail!("Operation cancelled by user");
}
run_hook_if_present(opts.prehook_opt, "pre")?;
let result = MantaClient::new(server_url, ctx.site_name)?
.apply_sat_file(
token,
sat_file,
opts.ansible_verbosity_opt,
opts.ansible_passthrough_opt,
opts.reboot,
opts.watch_logs,
opts.timestamps,
opts.overwrite,
opts.dry_run,
)
.await?;
run_hook_if_present(opts.posthook_opt, "post")?;
let message = if opts.dry_run {
"Dry-run enabled. No changes persisted into the system."
} else {
"SAT file applied."
};
action_result::print_with_data(message, &result, opts.output_opt)?;
Ok(())
}