1use crate::query::get_single_instance_from_query;
6use crate::realm::{
7 ConfigField, ExecutionInfo, ResolvedInfo, Runtime, get_config_fields, get_merkle_root,
8 get_outgoing_capabilities, get_resolved_declaration, get_runtime,
9};
10use ansi_term::Colour;
11use anyhow::Result;
12use cm_rust::ExposeDeclCommon;
13use flex_fuchsia_sys2 as fsys;
14use moniker::Moniker;
15use prettytable::format::FormatBuilder;
16use prettytable::{Table, cell, row};
17
18#[cfg(feature = "serde")]
19use {schemars::JsonSchema, serde::Serialize};
20
21#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
22pub struct ShowCmdInstance {
23 pub moniker: Moniker,
24 pub url: String,
25 pub environment: Option<String>,
26 pub instance_id: Option<String>,
27 pub resolved: Option<ShowCmdResolvedInfo>,
28}
29
30#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
31pub struct ShowCmdResolvedInfo {
32 pub resolved_url: String,
33 pub merkle_root: Option<String>,
34 pub runner: Option<String>,
35 pub incoming_capabilities: Vec<String>,
36 pub exposed_capabilities: Vec<String>,
37 pub config: Option<Vec<ConfigField>>,
38 pub started: Option<ShowCmdExecutionInfo>,
39 pub collections: Vec<String>,
40 pub manifest_sources: Option<Vec<String>>,
41}
42
43#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
44pub struct ShowCmdExecutionInfo {
45 pub runtime: Runtime,
46 pub outgoing_capabilities: Vec<String>,
47 pub start_reason: String,
48}
49
50pub async fn show_cmd_print<W: std::io::Write>(
51 query: String,
52 realm_query: fsys::RealmQueryProxy,
53 mut writer: W,
54 with_style: bool,
55) -> Result<()> {
56 let instance = get_instance_by_query(query, realm_query).await?;
57 let table = create_table(instance, with_style);
58 table.print(&mut writer)?;
59 writeln!(&mut writer, "")?;
60
61 Ok(())
62}
63
64pub async fn show_cmd_serialized(
65 query: String,
66 realm_query: fsys::RealmQueryProxy,
67) -> Result<ShowCmdInstance> {
68 let instance = get_instance_by_query(query, realm_query).await?;
69 Ok(instance)
70}
71
72pub(crate) async fn config_table_print<W: std::io::Write>(
73 query: String,
74 realm_query: fsys::RealmQueryProxy,
75 mut writer: W,
76) -> Result<()> {
77 let instance = get_instance_by_query(query, realm_query).await?;
78 let table = create_config_table(instance);
79 table.print(&mut writer)?;
80 writeln!(&mut writer, "")?;
81
82 Ok(())
83}
84
85async fn get_instance_by_query(
86 query: String,
87 realm_query: fsys::RealmQueryProxy,
88) -> Result<ShowCmdInstance> {
89 let instance = get_single_instance_from_query(&query, &realm_query).await?;
90
91 let resolved_info = match instance.resolved_info {
92 Some(ResolvedInfo { execution_info, resolved_url }) => {
93 let manifest = get_resolved_declaration(&instance.moniker, &realm_query).await?;
95 let structured_config = get_config_fields(&instance.moniker, &realm_query).await?;
96 let merkle_root = get_merkle_root(&instance.moniker, &realm_query).await.ok();
97 let runner = if let Some(runner) = manifest.program.and_then(|p| p.runner) {
98 Some(runner.to_string())
99 } else if let Some(runner) = manifest.uses.iter().find_map(|u| match u {
100 cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl { source_name, .. }) => {
101 Some(source_name)
102 }
103 _ => None,
104 }) {
105 Some(runner.to_string())
106 } else {
107 None
108 };
109 let incoming_capabilities = IntoIterator::into_iter(manifest.uses)
110 .filter_map(|u| u.path().map(|n| n.to_string()))
111 .collect();
112 let exposed_capabilities = IntoIterator::into_iter(manifest.exposes)
113 .map(|e| e.target_name().to_string())
114 .collect();
115
116 let execution_info = match execution_info {
117 Some(ExecutionInfo { start_reason }) => {
118 let runtime = get_runtime(&instance.moniker, &realm_query)
119 .await
120 .unwrap_or(Runtime::Unknown);
121 let outgoing_capabilities =
122 get_outgoing_capabilities(&instance.moniker, &realm_query)
123 .await
124 .unwrap_or(vec![]);
125 Some(ShowCmdExecutionInfo { start_reason, runtime, outgoing_capabilities })
126 }
127 None => None,
128 };
129
130 let collections =
131 IntoIterator::into_iter(manifest.collections).map(|c| c.name.to_string()).collect();
132
133 let manifest_sources =
134 manifest.debug_info.and_then(|i| i.manifest_sources).map(|s| s.into_vec());
135
136 Some(ShowCmdResolvedInfo {
137 resolved_url,
138 runner,
139 incoming_capabilities,
140 exposed_capabilities,
141 merkle_root,
142 config: structured_config,
143 started: execution_info,
144 collections,
145 manifest_sources,
146 })
147 }
148 None => None,
149 };
150
151 Ok(ShowCmdInstance {
152 moniker: instance.moniker,
153 url: instance.url,
154 environment: instance.environment,
155 instance_id: instance.instance_id,
156 resolved: resolved_info,
157 })
158}
159
160fn create_table(instance: ShowCmdInstance, with_style: bool) -> Table {
161 let mut table = Table::new();
162 table.set_format(FormatBuilder::new().padding(2, 0).build());
163
164 table.add_row(row!(r->"Moniker:", instance.moniker));
165 table.add_row(row!(r->"URL:", instance.url));
166 table.add_row(
167 row!(r->"Environment:", instance.environment.unwrap_or_else(|| "N/A".to_string())),
168 );
169
170 if let Some(instance_id) = instance.instance_id {
171 table.add_row(row!(r->"Instance ID:", instance_id));
172 } else {
173 table.add_row(row!(r->"Instance ID:", "None"));
174 }
175
176 add_resolved_info_to_table(&mut table, instance.resolved, with_style);
177
178 table
179}
180
181fn create_config_table(instance: ShowCmdInstance) -> Table {
182 let mut table = Table::new();
183 table.set_format(FormatBuilder::new().padding(2, 0).build());
184 if let Some(resolved) = instance.resolved {
185 add_config_info_to_table(&mut table, &resolved);
186 }
187 table
188}
189
190fn colorized(string: &str, color: Colour, with_style: bool) -> String {
191 if with_style { color.paint(string).to_string() } else { string.to_string() }
192}
193
194fn add_resolved_info_to_table(
195 table: &mut Table,
196 resolved: Option<ShowCmdResolvedInfo>,
197 with_style: bool,
198) {
199 if let Some(resolved) = resolved {
200 table
201 .add_row(row!(r->"Component State:", colorized("Resolved", Colour::Green, with_style)));
202 table.add_row(row!(r->"Resolved URL:", resolved.resolved_url));
203
204 if let Some(runner) = &resolved.runner {
205 table.add_row(row!(r->"Runner:", runner));
206 }
207
208 let namespace_capabilities = resolved.incoming_capabilities.join("\n");
209 table.add_row(row!(r->"Namespace Capabilities:", namespace_capabilities));
210
211 let exposed_capabilities = resolved.exposed_capabilities.join("\n");
212 table.add_row(row!(r->"Exposed Capabilities:", exposed_capabilities));
213
214 if let Some(merkle_root) = &resolved.merkle_root {
215 table.add_row(row!(r->"Merkle root:", merkle_root));
216 } else {
217 table.add_row(row!(r->"Merkle root:", "Unknown"));
218 }
219
220 add_config_info_to_table(table, &resolved);
221
222 if !resolved.collections.is_empty() {
223 table.add_row(row!(r->"Collections:", resolved.collections.join("\n")));
224 }
225
226 if let Some(sources) = &resolved.manifest_sources {
227 if !sources.is_empty() {
228 table.add_row(row!(r->"Manifest Sources:", sources.join("\n")));
229 }
230 }
231
232 add_execution_info_to_table(table, resolved.started, with_style)
233 } else {
234 table
235 .add_row(row!(r->"Component State:", colorized("Unresolved", Colour::Red, with_style)));
236 }
237}
238
239fn add_config_info_to_table(table: &mut Table, resolved: &ShowCmdResolvedInfo) {
240 if let Some(config) = &resolved.config {
241 if !config.is_empty() {
242 let mut config_table = Table::new();
243 let format = FormatBuilder::new().padding(0, 0).build();
244 config_table.set_format(format);
245
246 for field in config {
247 config_table.add_row(row!(field.key, " -> ", field.value));
248 }
249
250 table.add_row(row!(r->"Configuration:", config_table));
251 }
252 }
253}
254
255fn add_execution_info_to_table(
256 table: &mut Table,
257 exec: Option<ShowCmdExecutionInfo>,
258 with_style: bool,
259) {
260 if let Some(exec) = exec {
261 table.add_row(row!(r->"Execution State:", colorized("Running", Colour::Green, with_style)));
262 table.add_row(row!(r->"Start reason:", exec.start_reason));
263
264 let outgoing_capabilities = exec.outgoing_capabilities.join("\n");
265 table.add_row(row!(r->"Outgoing Capabilities:", outgoing_capabilities));
266
267 match exec.runtime {
268 Runtime::Elf {
269 job_id,
270 process_id,
271 process_start_time,
272 process_start_time_utc_estimate,
273 } => {
274 table.add_row(row!(r->"Runtime:", "ELF"));
275 if let Some(utc_estimate) = process_start_time_utc_estimate {
276 table.add_row(row!(r->"Running since:", utc_estimate));
277 } else if let Some(nanos_since_boot) = process_start_time {
278 table.add_row(
279 row!(r->"Running since:", format!("{} ns since boot", nanos_since_boot)),
280 );
281 }
282
283 table.add_row(row!(r->"Job ID:", job_id));
284
285 if let Some(process_id) = process_id {
286 table.add_row(row!(r->"Process ID:", process_id));
287 }
288 }
289 Runtime::Unknown => {
290 table.add_row(row!(r->"Runtime:", "Unknown"));
291 }
292 }
293 } else {
294 table.add_row(row!(r->"Execution State:", colorized("Stopped", Colour::Red, with_style)));
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::test_utils::*;
302 use fidl_fuchsia_component_decl as fdecl;
303 use std::collections::HashMap;
304 use std::fs;
305 use tempfile::TempDir;
306
307 pub fn create_pkg_dir() -> TempDir {
308 let temp_dir = TempDir::new_in("/tmp").unwrap();
309 let root = temp_dir.path();
310
311 fs::write(root.join("meta"), "1234").unwrap();
312
313 temp_dir
314 }
315
316 pub fn create_out_dir() -> TempDir {
317 let temp_dir = TempDir::new_in("/tmp").unwrap();
318 let root = temp_dir.path();
319
320 fs::create_dir(root.join("diagnostics")).unwrap();
321
322 temp_dir
323 }
324
325 pub fn create_runtime_dir() -> TempDir {
326 let temp_dir = TempDir::new_in("/tmp").unwrap();
327 let root = temp_dir.path();
328
329 fs::create_dir_all(root.join("elf")).unwrap();
330 fs::write(root.join("elf/job_id"), "1234").unwrap();
331 fs::write(root.join("elf/process_id"), "2345").unwrap();
332 fs::write(root.join("elf/process_start_time"), "3456").unwrap();
333 fs::write(root.join("elf/process_start_time_utc_estimate"), "abcd").unwrap();
334
335 temp_dir
336 }
337
338 fn create_query() -> fsys::RealmQueryProxy {
339 let out_dir = create_out_dir();
341 let pkg_dir = create_pkg_dir();
342 let runtime_dir = create_runtime_dir();
343
344 let query = serve_realm_query(
345 vec![
346 fsys::Instance {
347 moniker: Some("./my_foo".to_string()),
348 url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
349 instance_id: Some("1234567890".to_string()),
350 resolved_info: Some(fsys::ResolvedInfo {
351 resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
352 execution_info: Some(fsys::ExecutionInfo {
353 start_reason: Some("Debugging Workflow".to_string()),
354 ..Default::default()
355 }),
356 ..Default::default()
357 }),
358 ..Default::default()
359 },
360 fsys::Instance {
361 moniker: Some("./core/appmgr".to_string()),
362 url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
363 instance_id: None,
364 resolved_info: Some(fsys::ResolvedInfo {
365 resolved_url: Some(
366 "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
367 ),
368 execution_info: Some(fsys::ExecutionInfo {
369 start_reason: Some("Debugging Workflow".to_string()),
370 ..Default::default()
371 }),
372 ..Default::default()
373 }),
374 ..Default::default()
375 },
376 ],
377 HashMap::from([(
378 "./my_foo".to_string(),
379 fdecl::Component {
380 uses: Some(vec![
381 fdecl::Use::Protocol(fdecl::UseProtocol {
382 source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
383 source_name: Some("fuchsia.foo.bar".to_string()),
384 target_path: Some("/svc/fuchsia.foo.bar".to_string()),
385 dependency_type: Some(fdecl::DependencyType::Strong),
386 availability: Some(fdecl::Availability::Required),
387 ..Default::default()
388 }),
389 fdecl::Use::Runner(fdecl::UseRunner {
390 source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
391 source_name: Some("elf".to_string()),
392 ..Default::default()
393 }),
394 ]),
395 exposes: Some(vec![fdecl::Expose::Protocol(fdecl::ExposeProtocol {
396 source: Some(fdecl::Ref::Self_(fdecl::SelfRef)),
397 source_name: Some("fuchsia.bar.baz".to_string()),
398 target: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
399 target_name: Some("fuchsia.bar.baz".to_string()),
400 ..Default::default()
401 })]),
402 capabilities: Some(vec![fdecl::Capability::Protocol(fdecl::Protocol {
403 name: Some("fuchsia.bar.baz".to_string()),
404 source_path: Some("/svc/fuchsia.bar.baz".to_string()),
405 ..Default::default()
406 })]),
407 collections: Some(vec![fdecl::Collection {
408 name: Some("my-collection".to_string()),
409 durability: Some(fdecl::Durability::Transient),
410 ..Default::default()
411 }]),
412 debug_info: Some(fdecl::DebugInfo {
413 manifest_sources: Some(vec![
414 "tools/cmc/meta/example.cml".to_string(),
415 "sdk/lib/syslog/client.shard.cml".to_string(),
416 ]),
417 ..Default::default()
418 }),
419 ..Default::default()
420 },
421 )]),
422 HashMap::from([(
423 "./my_foo".to_string(),
424 fdecl::ResolvedConfig {
425 fields: vec![fdecl::ResolvedConfigField {
426 key: "foo".to_string(),
427 value: fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false)),
428 }],
429 checksum: fdecl::ConfigChecksum::Sha256([0; 32]),
430 },
431 )]),
432 HashMap::from([
433 (("./my_foo".to_string(), fsys::OpenDirType::RuntimeDir), runtime_dir),
434 (("./my_foo".to_string(), fsys::OpenDirType::PackageDir), pkg_dir),
435 (("./my_foo".to_string(), fsys::OpenDirType::OutgoingDir), out_dir),
436 ]),
437 );
438 query
439 }
440
441 #[fuchsia::test]
442 async fn basic_cml() {
443 let query = create_query();
444
445 let instance = get_instance_by_query("foo.cm".to_string(), query).await.unwrap();
446
447 assert_eq!(instance.moniker, Moniker::parse_str("/my_foo").unwrap());
448 assert_eq!(instance.url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
449 assert_eq!(instance.instance_id.unwrap(), "1234567890");
450 assert!(instance.resolved.is_some());
451
452 let resolved = instance.resolved.unwrap();
453 assert_eq!(resolved.runner.unwrap(), "elf");
454 assert_eq!(resolved.incoming_capabilities.len(), 1);
455 assert_eq!(resolved.incoming_capabilities[0], "/svc/fuchsia.foo.bar");
456
457 assert_eq!(resolved.exposed_capabilities.len(), 1);
458 assert_eq!(resolved.exposed_capabilities[0], "fuchsia.bar.baz");
459
460 assert_eq!(resolved.merkle_root.unwrap(), "1234");
461
462 let config = resolved.config.unwrap();
463 assert_eq!(
464 config,
465 vec![ConfigField { key: "foo".to_string(), value: "Bool(false)".to_string() }]
466 );
467
468 assert_eq!(resolved.collections, vec!["my-collection"]);
469 assert_eq!(
470 resolved.manifest_sources.unwrap(),
471 vec![
472 "tools/cmc/meta/example.cml".to_string(),
473 "sdk/lib/syslog/client.shard.cml".to_string(),
474 ]
475 );
476
477 let started = resolved.started.unwrap();
478 assert_eq!(started.outgoing_capabilities, vec!["diagnostics".to_string()]);
479 assert_eq!(started.start_reason, "Debugging Workflow".to_string());
480
481 match started.runtime {
482 Runtime::Elf {
483 job_id,
484 process_id,
485 process_start_time,
486 process_start_time_utc_estimate,
487 } => {
488 assert_eq!(job_id, 1234);
489 assert_eq!(process_id, Some(2345));
490 assert_eq!(process_start_time, Some(3456));
491 assert_eq!(process_start_time_utc_estimate, Some("abcd".to_string()));
492 }
493 _ => panic!("unexpected runtime"),
494 }
495 }
496
497 #[fuchsia::test]
498 async fn find_by_moniker() {
499 let query = create_query();
500
501 let instance = get_instance_by_query("my_foo".to_string(), query).await.unwrap();
502
503 assert_eq!(instance.moniker, Moniker::parse_str("/my_foo").unwrap());
504 assert_eq!(instance.url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
505 assert_eq!(instance.instance_id.unwrap(), "1234567890");
506 }
507
508 #[fuchsia::test]
509 async fn find_by_instance_id() {
510 let query = create_query();
511
512 let instance = get_instance_by_query("1234567".to_string(), query).await.unwrap();
513
514 assert_eq!(instance.moniker, Moniker::parse_str("/my_foo").unwrap());
515 assert_eq!(instance.url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
516 assert_eq!(instance.instance_id.unwrap(), "1234567890");
517 }
518}