1pub mod cli;
5mod config;
6mod diag_emit;
7
8use std::io::{BufWriter, Write};
9use std::path::Path;
10
11use helm_schema::chart_source::RootChartSource;
12use helm_schema::diagnostics::DiagnosticSink;
13use helm_schema::output::{FetchPolicy, LoadBudget, PolicyInputOptions, write_schema_json};
14use helm_schema::{AnalysisSession, EngineResult};
15use tracing_subscriber::Layer as _;
16use tracing_subscriber::layer::SubscriberExt as _;
17
18pub use cli::Cli;
19pub use helm_schema::generation::{GenerateOptions, SchemaProfile};
20pub use helm_schema::provider::ProviderOptions;
21pub use helm_schema::{CliError, flatten, schema_override};
22
23pub fn run(cli: Cli) -> EngineResult<()> {
30 let trace_output = cli.perf.trace_output.clone();
31 if let Some(trace_output) = trace_output {
32 let trace_file = create_output_file(&trace_output)?;
33 let perfetto_layer =
34 tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(trace_file))
35 .with_debug_annotations(true)
36 .with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
37 metadata.target().starts_with("helm_schema")
38 || metadata
39 .target()
40 .starts_with("helm_schema_json_schema_minify")
41 }));
42 let subscriber = tracing_subscriber::registry().with(perfetto_layer);
43 let dispatch = tracing::Dispatch::new(subscriber);
44 return tracing::dispatcher::with_default(&dispatch, || run_inner(cli));
45 }
46
47 run_inner(cli)
48}
49
50fn run_inner(cli: Cli) -> EngineResult<()> {
51 let run_span = tracing::info_span!(
52 "helm_schema_run",
53 chart_dir = %cli.chart_dir.display()
54 );
55 let _entered = run_span.enter();
56
57 let root_source = RootChartSource::open(&cli.chart_dir, LoadBudget::default())?;
58 let effective_config = config::resolve(
59 &root_source,
60 &cli.chart_dir,
61 cli.config.as_deref(),
62 cli.no_config,
63 cli.profile,
64 cli.emission,
65 )?;
66
67 let diagnostics = DiagnosticSink::new();
68 if !effective_config.file_weakening.is_empty() {
69 diagnostics.push(
70 helm_schema::diagnostics::Diagnostic::DiscoveredConfigWeakensEmission {
71 disabled_knobs: effective_config
72 .file_weakening
73 .iter()
74 .map(|knob| (*knob).to_string())
75 .collect(),
76 explicit: effective_config.file_weakening_is_explicit,
77 },
78 );
79 }
80 if cli.print_effective_config {
81 let stdout = std::io::stdout();
82 let mut out = BufWriter::new(stdout.lock());
83 out.write_all(effective_config.to_yaml()?.as_bytes())?;
84 out.flush()?;
85 diag_emit::emit_to_stderr(&diagnostics, cli.diag.diag_format);
86 return Ok(());
87 }
88
89 let generated = (|| {
90 cli.crd.validate().map_err(CliError::CliValidation)?;
91 let fallback_window = cli
92 .k8s
93 .resolved_fallback_window()
94 .map_err(CliError::CliValidation)?;
95 let chart_dir = root_source.into_chart_dir();
96 let provider_options = ProviderOptions {
97 k8s_versions: cli.k8s.k8s_version.clone(),
98 k8s_version_fallback_window: fallback_window,
99 k8s_schema_mirrors: cli.k8s.k8s_schema_mirror.clone(),
100 k8s_schema_cache_dir: cli.k8s.k8s_schema_cache_dir.clone(),
101 no_cache: cli.k8s.no_cache,
102 allow_net: !cli.k8s.offline,
103 disable_k8s_schemas: cli.k8s.no_k8s_schemas,
104 crd_lookup_loose: matches!(cli.crd.lookup_mode(), cli::CrdVersionLookup::Loose),
105 crd_catalog_mirrors: cli.crd.crd_catalog_mirror.clone(),
106 crd_catalog_cache_dir: cli.crd.crd_catalog_cache_dir.clone(),
107 crd_override_dir: cli.crd.crd_override_dir.clone(),
108 local_schema_universe: helm_schema::provider::LocalSchemaUniverse::default(),
109 crd_cache_record_source: cli.crd.crd_cache_record_source,
110 api_version_guess: cli.inference.enabled(),
111 };
112 let opts = GenerateOptions {
113 chart_dir,
114 include_tests: !cli.chart.exclude_tests,
115 include_subchart_values: !cli.chart.no_subchart_values,
116 values_files: cli.chart.values_files.clone(),
117 infer_required: cli.chart.infer_required,
118 emission: effective_config.selection,
119 provider: provider_options,
120 };
121 let session = AnalysisSession::with_diagnostics(opts, diagnostics.clone());
122 let policy_input_options = PolicyInputOptions {
123 fetch_policy: FetchPolicy::input_assembly(!cli.k8s.offline),
124 load_budget: LoadBudget::default(),
125 };
126 session.emit_with_policy_paths(
127 &cli.override_schema,
128 policy_input_options,
129 cli.output.emit_request(),
130 )
131 })();
132 diag_emit::emit_to_stderr(&diagnostics, cli.diag.diag_format);
133 let schema = generated?;
134
135 let json_format = cli.output.json_format();
136
137 if let Some(path) = cli.output.output {
138 let mut out = BufWriter::new(create_output_file(&path)?);
139 write_schema_json(&mut out, &schema, json_format)
140 .map_err(|err| write_output_error_with_path(err, &path))?;
141 out.flush().map_err(|err| CliError::WriteOutput {
142 path: path.clone(),
143 source: err,
144 })?;
145 } else {
146 let stdout = std::io::stdout();
147 let mut out = BufWriter::new(stdout.lock());
148 write_schema_json(&mut out, &schema, json_format)?;
149 out.flush()?;
150 }
151
152 Ok(())
153}
154
155fn create_output_file(path: &Path) -> EngineResult<std::fs::File> {
156 if let Some(parent) = path.parent() {
157 std::fs::create_dir_all(parent).map_err(|err| CliError::CreateOutputDir {
158 path: parent.to_path_buf(),
159 source: err,
160 })?;
161 }
162 std::fs::File::create(path).map_err(|err| CliError::WriteOutput {
163 path: path.to_path_buf(),
164 source: err,
165 })
166}
167
168fn write_output_error_with_path(err: CliError, path: &Path) -> CliError {
169 match err {
170 CliError::Io(source) => CliError::WriteOutput {
171 path: path.to_path_buf(),
172 source,
173 },
174 err => err,
175 }
176}