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