use console::style;
use miette::{IntoDiagnostic, Result, WrapErr};
use sherpack_core::{LoadedPack, ReleaseInfo, SchemaValidator, TemplateContext, Values};
use sherpack_engine::{Engine, PackRenderer};
use std::fs;
use std::path::Path;
use crate::display::display_render_report;
#[allow(clippy::too_many_arguments)]
pub fn run(
name: &str,
pack_path: &Path,
values_files: &[std::path::PathBuf],
set_values: &[String],
namespace: &str,
output_dir: Option<&Path>,
show_only: Option<&str>,
show_values: bool,
skip_schema: bool,
debug: bool,
) -> Result<()> {
let pack = LoadedPack::load(pack_path)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to load pack from {}", pack_path.display()))?;
if debug {
eprintln!(
"{} Loaded pack: {} v{}",
style("DEBUG").dim(),
pack.pack.metadata.name,
pack.pack.metadata.version
);
}
let schema_validator = if !skip_schema {
if let Some(schema_path) = &pack.schema_path {
match pack.load_schema() {
Ok(Some(schema)) => match SchemaValidator::new(schema) {
Ok(validator) => {
if debug {
eprintln!(
"{} Loaded schema from {}",
style("DEBUG").dim(),
schema_path.display()
);
}
Some(validator)
}
Err(e) => {
eprintln!("{} Schema compilation warning: {}", style("⚠").yellow(), e);
None
}
},
Ok(None) => None,
Err(e) => {
eprintln!("{} Failed to load schema: {}", style("⚠").yellow(), e);
None
}
}
} else {
None
}
} else {
None
};
let mut values = if let Some(ref validator) = schema_validator {
validator.defaults_as_values()
} else {
Values::new()
};
if debug && schema_validator.is_some() {
eprintln!("{} Applied schema defaults", style("DEBUG").dim());
}
if pack.values_path.exists() {
let default_values = Values::from_file(&pack.values_path)
.into_diagnostic()
.wrap_err("Failed to load default values.yaml")?;
values.merge(&default_values);
if debug {
eprintln!(
"{} Loaded default values from {}",
style("DEBUG").dim(),
pack.values_path.display()
);
}
}
for values_file in values_files {
let file_values = Values::from_file(values_file)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to load values file: {}", values_file.display()))?;
values.merge(&file_values);
if debug {
eprintln!(
"{} Merged values from {}",
style("DEBUG").dim(),
values_file.display()
);
}
}
if !set_values.is_empty() {
let set_vals = sherpack_core::values::parse_set_values(set_values)
.into_diagnostic()
.wrap_err("Failed to parse --set values")?;
values.merge(&set_vals);
if debug {
eprintln!(
"{} Applied {} --set values",
style("DEBUG").dim(),
set_values.len()
);
}
}
if let Some(ref validator) = schema_validator {
let result = validator.validate(values.inner());
if !result.is_valid {
eprintln!("{} Values validation failed:", style("✗").red());
for err in &result.errors {
eprintln!(" - {}: {}", err.path, err.message);
}
return Err(miette::miette!(
"Values do not match schema. Use --skip-schema to bypass validation."
));
} else if debug {
eprintln!("{} Values validated against schema", style("DEBUG").dim());
}
}
if show_values {
println!("{}", style("# Computed Values").cyan().bold());
println!("---");
let yaml = serde_yaml::to_string(values.inner())
.into_diagnostic()
.wrap_err("Failed to serialize values")?;
println!("{}", yaml);
println!("---");
println!();
}
let release = ReleaseInfo::for_install(name, namespace);
let context = TemplateContext::new(values, release, &pack.pack.metadata);
let secret_state = sherpack_engine::SecretFunctionState::new();
let engine = Engine::builder()
.strict(pack.pack.engine.strict)
.with_secret_state(secret_state)
.build();
let renderer = PackRenderer::new(engine);
let render_result = renderer.render_collect_errors(&pack, &context);
if debug {
let discovery = &render_result.discovery;
if !discovery.subcharts.is_empty() {
eprintln!(
"{} Found {} subchart(s):",
style("DEBUG").dim(),
discovery.subcharts.len()
);
for subchart in &discovery.subcharts {
let status = if subchart.enabled {
style("enabled").green()
} else {
style("disabled").dim()
};
eprintln!(" - {} ({})", subchart.name, status);
}
}
for warning in &discovery.warnings {
eprintln!("{} {}", style("⚠").yellow(), warning);
}
}
if !render_result.is_success() {
display_render_report(&render_result.report);
return Err(miette::miette!(
"Template rendering failed with {} error(s)",
render_result.report.total_errors
));
}
let result = sherpack_engine::RenderResult {
manifests: render_result.manifests,
notes: render_result.notes,
};
if let Some(output_path) = output_dir {
fs::create_dir_all(output_path)
.into_diagnostic()
.wrap_err_with(|| {
format!(
"Failed to create output directory: {}",
output_path.display()
)
})?;
for (filename, content) in &result.manifests {
if let Some(filter) = show_only
&& !filename.contains(filter)
{
continue;
}
let file_path = output_path.join(filename);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).into_diagnostic()?;
}
fs::write(&file_path, content)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to write {}", file_path.display()))?;
println!("{} {}", style("wrote").green(), file_path.display());
}
if let Some(notes) = &result.notes {
let notes_path = output_path.join("NOTES.txt");
fs::write(¬es_path, notes).into_diagnostic()?;
println!("{} {}", style("wrote").green(), notes_path.display());
}
} else {
let mut first = true;
for (filename, content) in &result.manifests {
if let Some(filter) = show_only
&& !filename.contains(filter)
{
continue;
}
if !first {
println!();
}
first = false;
println!("{}", style(format!("# Source: {}", filename)).dim());
println!("{}", content.trim());
}
if let Some(notes) = &result.notes {
if !first {
println!();
}
println!("{}", style("# NOTES:").yellow().bold());
println!("{}", notes);
}
}
Ok(())
}