pub mod cli;
mod config;
mod diag_emit;
use std::io::{BufWriter, Write};
use std::path::Path;
use helm_schema::chart_source::RootChartSource;
use helm_schema::diagnostics::DiagnosticSink;
use helm_schema::output::{FetchPolicy, LoadBudget, PolicyInputOptions, write_schema_json};
use helm_schema::{AnalysisSession, EngineResult};
use tracing_subscriber::Layer as _;
use tracing_subscriber::layer::SubscriberExt as _;
pub use cli::Cli;
pub use helm_schema::generation::{GenerateOptions, SchemaProfile};
pub use helm_schema::provider::ProviderOptions;
pub use helm_schema::{CliError, flatten, schema_override};
pub fn run(cli: Cli) -> EngineResult<()> {
let trace_output = cli.perf.trace_output.clone();
if let Some(trace_output) = trace_output {
let trace_file = create_output_file(&trace_output)?;
let perfetto_layer =
tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(trace_file))
.with_debug_annotations(true)
.with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
metadata.target().starts_with("helm_schema")
|| metadata
.target()
.starts_with("helm_schema_json_schema_minify")
}));
let subscriber = tracing_subscriber::registry().with(perfetto_layer);
let dispatch = tracing::Dispatch::new(subscriber);
return tracing::dispatcher::with_default(&dispatch, || run_inner(cli));
}
run_inner(cli)
}
fn run_inner(cli: Cli) -> EngineResult<()> {
let run_span = tracing::info_span!(
"helm_schema_run",
chart_dir = %cli.chart_dir.display()
);
let _entered = run_span.enter();
let root_source = RootChartSource::open(&cli.chart_dir, LoadBudget::default())?;
let effective_config = config::resolve(
&root_source,
&cli.chart_dir,
cli.config.as_deref(),
cli.no_config,
cli.profile,
cli.emission,
)?;
let diagnostics = DiagnosticSink::new();
if !effective_config.file_weakening.is_empty() {
diagnostics.push(
helm_schema::diagnostics::Diagnostic::DiscoveredConfigWeakensEmission {
disabled_knobs: effective_config
.file_weakening
.iter()
.map(|knob| (*knob).to_string())
.collect(),
explicit: effective_config.file_weakening_is_explicit,
},
);
}
if cli.print_effective_config {
let stdout = std::io::stdout();
let mut out = BufWriter::new(stdout.lock());
out.write_all(effective_config.to_yaml()?.as_bytes())?;
out.flush()?;
diag_emit::emit_to_stderr(&diagnostics, cli.diag.diag_format);
return Ok(());
}
let generated = (|| {
cli.crd.validate().map_err(CliError::CliValidation)?;
let fallback_window = cli
.k8s
.resolved_fallback_window()
.map_err(CliError::CliValidation)?;
let chart_dir = root_source.into_chart_dir();
let provider_options = ProviderOptions {
k8s_versions: cli.k8s.k8s_version.clone(),
k8s_version_fallback_window: fallback_window,
k8s_schema_mirrors: cli.k8s.k8s_schema_mirror.clone(),
k8s_schema_cache_dir: cli.k8s.k8s_schema_cache_dir.clone(),
no_cache: cli.k8s.no_cache,
allow_net: !cli.k8s.offline,
disable_k8s_schemas: cli.k8s.no_k8s_schemas,
crd_lookup_loose: matches!(cli.crd.lookup_mode(), cli::CrdVersionLookup::Loose),
crd_catalog_mirrors: cli.crd.crd_catalog_mirror.clone(),
crd_catalog_cache_dir: cli.crd.crd_catalog_cache_dir.clone(),
crd_override_dir: cli.crd.crd_override_dir.clone(),
local_schema_universe: helm_schema::provider::LocalSchemaUniverse::default(),
crd_cache_record_source: cli.crd.crd_cache_record_source,
api_version_guess: cli.inference.enabled(),
};
let opts = GenerateOptions {
chart_dir,
include_tests: !cli.chart.exclude_tests,
include_subchart_values: !cli.chart.no_subchart_values,
values_files: cli.chart.values_files.clone(),
infer_required: cli.chart.infer_required,
emission: effective_config.selection,
provider: provider_options,
};
let session = AnalysisSession::with_diagnostics(opts, diagnostics.clone());
let policy_input_options = PolicyInputOptions {
fetch_policy: FetchPolicy::input_assembly(!cli.k8s.offline),
load_budget: LoadBudget::default(),
};
session.emit_with_policy_paths(
&cli.override_schema,
policy_input_options,
cli.output.emit_request(),
)
})();
diag_emit::emit_to_stderr(&diagnostics, cli.diag.diag_format);
let schema = generated?;
let json_format = cli.output.json_format();
if let Some(path) = cli.output.output {
let mut out = BufWriter::new(create_output_file(&path)?);
write_schema_json(&mut out, &schema, json_format)
.map_err(|err| write_output_error_with_path(err, &path))?;
out.flush().map_err(|err| CliError::WriteOutput {
path: path.clone(),
source: err,
})?;
} else {
let stdout = std::io::stdout();
let mut out = BufWriter::new(stdout.lock());
write_schema_json(&mut out, &schema, json_format)?;
out.flush()?;
}
Ok(())
}
fn create_output_file(path: &Path) -> EngineResult<std::fs::File> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|err| CliError::CreateOutputDir {
path: parent.to_path_buf(),
source: err,
})?;
}
std::fs::File::create(path).map_err(|err| CliError::WriteOutput {
path: path.to_path_buf(),
source: err,
})
}
fn write_output_error_with_path(err: CliError, path: &Path) -> CliError {
match err {
CliError::Io(source) => CliError::WriteOutput {
path: path.to_path_buf(),
source,
},
err => err,
}
}