Skip to main content

helm_schema_cli/
lib.rs

1//! Command-line argument model and invocation policy for `helm-schema`.
2
3/// Typed command-line arguments and option validation.
4pub mod cli;
5mod diag_emit;
6
7use std::io::{BufWriter, Write};
8use std::path::Path;
9
10use helm_schema::diagnostics::DiagnosticSink;
11use helm_schema::output::{FetchPolicy, PolicyInputOptions, write_schema_json};
12use helm_schema::{AnalysisSession, EngineResult};
13use tracing_subscriber::Layer as _;
14use tracing_subscriber::layer::SubscriberExt as _;
15use vfs::VfsPath;
16
17pub use cli::Cli;
18pub use helm_schema::generation::GenerateOptions;
19pub use helm_schema::provider::ProviderOptions;
20pub use helm_schema::{CliError, flatten, schema_override};
21
22/// Run the CLI.
23///
24/// # Errors
25///
26/// Returns an error if chart discovery fails, a template/values file cannot be
27/// read/parsed, the schema cannot be generated, or output cannot be written.
28pub fn run(cli: Cli) -> EngineResult<()> {
29    let trace_output = cli.perf.trace_output.clone();
30    if let Some(trace_output) = trace_output {
31        let trace_file = create_output_file(&trace_output)?;
32        let perfetto_layer =
33            tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(trace_file))
34                .with_debug_annotations(true)
35                .with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
36                    metadata.target().starts_with("helm_schema")
37                        || metadata
38                            .target()
39                            .starts_with("helm_schema_json_schema_minify")
40                }));
41        let subscriber = tracing_subscriber::registry().with(perfetto_layer);
42        let dispatch = tracing::Dispatch::new(subscriber);
43        return tracing::dispatcher::with_default(&dispatch, || run_inner(cli));
44    }
45
46    run_inner(cli)
47}
48
49fn run_inner(cli: Cli) -> EngineResult<()> {
50    let run_span = tracing::info_span!(
51        "helm_schema_run",
52        chart_dir = %cli.chart_dir.display()
53    );
54    let _entered = run_span.enter();
55
56    cli.crd.validate().map_err(CliError::CliValidation)?;
57    let fallback_window = cli
58        .k8s
59        .resolved_fallback_window()
60        .map_err(CliError::CliValidation)?;
61
62    let chart_dir_str = cli.chart_dir.to_string_lossy().to_string();
63    let chart_dir = VfsPath::new(vfs::PhysicalFS::new(&chart_dir_str));
64
65    let provider_options = ProviderOptions {
66        k8s_versions: cli.k8s.k8s_version.clone(),
67        k8s_version_fallback_window: fallback_window,
68        k8s_schema_mirrors: cli.k8s.k8s_schema_mirror.clone(),
69        k8s_schema_cache_dir: cli.k8s.k8s_schema_cache_dir.clone(),
70        no_cache: cli.k8s.no_cache,
71        allow_net: !cli.k8s.offline,
72        disable_k8s_schemas: cli.k8s.no_k8s_schemas,
73        crd_lookup_loose: matches!(cli.crd.lookup_mode(), cli::CrdVersionLookup::Loose),
74        crd_catalog_mirrors: cli.crd.crd_catalog_mirror.clone(),
75        crd_catalog_cache_dir: cli.crd.crd_catalog_cache_dir.clone(),
76        crd_override_dir: cli.crd.crd_override_dir.clone(),
77        local_schema_universe: helm_schema::provider::LocalSchemaUniverse::default(),
78        crd_cache_record_source: cli.crd.crd_cache_record_source,
79        api_version_guess: cli.inference.enabled(),
80    };
81
82    let opts = GenerateOptions {
83        chart_dir,
84        include_tests: !cli.chart.exclude_tests,
85        include_subchart_values: !cli.chart.no_subchart_values,
86        values_files: cli.chart.values_files.clone(),
87        infer_required: cli.chart.infer_required,
88        provider: provider_options,
89    };
90
91    let diagnostics = DiagnosticSink::new();
92    let session = AnalysisSession::with_diagnostics(opts, diagnostics.clone());
93    let policy_input_options: PolicyInputOptions = cli
94        .output
95        .policy_input_options(FetchPolicy::input_assembly(!cli.k8s.offline));
96    let output_options = cli.output.pipeline_options();
97    let schema = session.emit_with_policy_paths(
98        &cli.override_schema,
99        policy_input_options,
100        output_options,
101    )?;
102
103    diag_emit::emit_to_stderr(&diagnostics, cli.diag.diag_format);
104
105    let json_format = cli.output.json_format();
106
107    if let Some(path) = cli.output.output {
108        let mut out = BufWriter::new(create_output_file(&path)?);
109        write_schema_json(&mut out, &schema, json_format)
110            .map_err(|err| write_output_error_with_path(err, &path))?;
111        out.flush().map_err(|err| CliError::WriteOutput {
112            path: path.clone(),
113            source: err,
114        })?;
115    } else {
116        let stdout = std::io::stdout();
117        let mut out = BufWriter::new(stdout.lock());
118        write_schema_json(&mut out, &schema, json_format)?;
119        out.flush()?;
120    }
121
122    Ok(())
123}
124
125fn create_output_file(path: &Path) -> EngineResult<std::fs::File> {
126    if let Some(parent) = path.parent() {
127        std::fs::create_dir_all(parent).map_err(|err| CliError::CreateOutputDir {
128            path: parent.to_path_buf(),
129            source: err,
130        })?;
131    }
132    std::fs::File::create(path).map_err(|err| CliError::WriteOutput {
133        path: path.to_path_buf(),
134        source: err,
135    })
136}
137
138fn write_output_error_with_path(err: CliError, path: &Path) -> CliError {
139    match err {
140        CliError::Io(source) => CliError::WriteOutput {
141            path: path.to_path_buf(),
142            source,
143        },
144        err => err,
145    }
146}