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