greentic_component/cmd/
inspect.rs1use std::path::PathBuf;
2
3use clap::{Args, Parser};
4use serde_json::Value;
5
6use super::path::strip_file_scheme;
7use crate::{ComponentError, PreparedComponent, prepare_component_with_manifest};
8
9#[derive(Args, Debug, Clone)]
10#[command(about = "Inspect a Greentic component artifact")]
11pub struct InspectArgs {
12 pub target: String,
14 #[arg(long)]
16 pub manifest: Option<PathBuf>,
17 #[arg(long)]
19 pub json: bool,
20 #[arg(long)]
22 pub strict: bool,
23}
24
25#[derive(Parser, Debug)]
26struct InspectCli {
27 #[command(flatten)]
28 args: InspectArgs,
29}
30
31pub fn parse_from_cli() -> InspectArgs {
32 InspectCli::parse().args
33}
34
35#[derive(Default)]
36pub struct InspectResult {
37 pub warnings: Vec<String>,
38}
39
40pub fn run(args: &InspectArgs) -> Result<InspectResult, ComponentError> {
41 let manifest_override = args.manifest.as_deref().map(strip_file_scheme);
42 let prepared = prepare_component_with_manifest(&args.target, manifest_override.as_deref())?;
43 if args.json {
44 let json = serde_json::to_string_pretty(&build_report(&prepared))
45 .expect("serializing inspect report");
46 println!("{json}");
47 } else {
48 println!("component: {}", prepared.manifest.id.as_str());
49 println!(" wasm: {}", prepared.wasm_path.display());
50 println!(" world ok: {}", prepared.world_ok);
51 println!(" hash: {}", prepared.wasm_hash);
52 println!(" supports: {:?}", prepared.manifest.supports);
53 println!(
54 " profiles: default={:?} supported={:?}",
55 prepared.manifest.profiles.default, prepared.manifest.profiles.supported
56 );
57 println!(
58 " lifecycle: init={} health={} shutdown={}",
59 prepared.lifecycle.init, prepared.lifecycle.health, prepared.lifecycle.shutdown
60 );
61 let caps = &prepared.manifest.capabilities;
62 println!(
63 " capabilities: wasi(fs={}, env={}, random={}, clocks={}) host(secrets={}, state={}, messaging={}, events={}, http={}, telemetry={}, iac={})",
64 caps.wasi.filesystem.is_some(),
65 caps.wasi.env.is_some(),
66 caps.wasi.random,
67 caps.wasi.clocks,
68 caps.host.secrets.is_some(),
69 caps.host.state.is_some(),
70 caps.host.messaging.is_some(),
71 caps.host.events.is_some(),
72 caps.host.http.is_some(),
73 caps.host.telemetry.is_some(),
74 caps.host.iac.is_some(),
75 );
76 println!(
77 " limits: {}",
78 prepared
79 .manifest
80 .limits
81 .as_ref()
82 .map(|l| format!("{} MB / {} ms", l.memory_mb, l.wall_time_ms))
83 .unwrap_or_else(|| "default".into())
84 );
85 println!(
86 " telemetry prefix: {}",
87 prepared
88 .manifest
89 .telemetry
90 .as_ref()
91 .map(|t| t.span_prefix.as_str())
92 .unwrap_or("<none>")
93 );
94 println!(" describe versions: {}", prepared.describe.versions.len());
95 println!(" redaction paths: {}", prepared.redaction_paths().len());
96 println!(" defaults applied: {}", prepared.defaults_applied().len());
97 }
98 Ok(InspectResult::default())
99}
100
101pub fn emit_warnings(warnings: &[String]) {
102 for warning in warnings {
103 eprintln!("warning: {warning}");
104 }
105}
106
107pub fn build_report(prepared: &PreparedComponent) -> Value {
108 let caps = &prepared.manifest.capabilities;
109 serde_json::json!({
110 "manifest": &prepared.manifest,
111 "manifest_path": prepared.manifest_path,
112 "wasm_path": prepared.wasm_path,
113 "wasm_hash": prepared.wasm_hash,
114 "hash_verified": prepared.hash_verified,
115 "world": {
116 "expected": prepared.manifest.world.as_str(),
117 "ok": prepared.world_ok,
118 },
119 "lifecycle": {
120 "init": prepared.lifecycle.init,
121 "health": prepared.lifecycle.health,
122 "shutdown": prepared.lifecycle.shutdown,
123 },
124 "describe": prepared.describe,
125 "capabilities": prepared.manifest.capabilities,
126 "limits": prepared.manifest.limits,
127 "telemetry": prepared.manifest.telemetry,
128 "redactions": prepared
129 .redaction_paths()
130 .iter()
131 .map(|p| p.as_str().to_string())
132 .collect::<Vec<_>>(),
133 "defaults_applied": prepared.defaults_applied(),
134 "summary": {
135 "supports": prepared.manifest.supports,
136 "profiles": prepared.manifest.profiles,
137 "capabilities": {
138 "wasi": {
139 "filesystem": caps.wasi.filesystem.is_some(),
140 "env": caps.wasi.env.is_some(),
141 "random": caps.wasi.random,
142 "clocks": caps.wasi.clocks
143 },
144 "host": {
145 "secrets": caps.host.secrets.is_some(),
146 "state": caps.host.state.is_some(),
147 "messaging": caps.host.messaging.is_some(),
148 "events": caps.host.events.is_some(),
149 "http": caps.host.http.is_some(),
150 "telemetry": caps.host.telemetry.is_some(),
151 "iac": caps.host.iac.is_some()
152 }
153 },
154 }
155 })
156}