Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.

Commit 3b58fc7

Browse files
Merge pull request #873 from connorsmith256/feat/get-all-inventories
feat: query all host inventories
2 parents c2d3383 + 5c0ccc5 commit 3b58fc7

File tree

9 files changed

+162
-129
lines changed

9 files changed

+162
-129
lines changed

crates/wash-lib/src/cli/get.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ pub struct GetClaimsCommand {
1313
}
1414

1515
#[derive(Debug, Clone, Parser)]
16-
pub struct GetHostInventoryCommand {
16+
pub struct GetHostInventoriesCommand {
1717
#[clap(flatten)]
1818
pub opts: CliConnectionOpts,
1919

20-
/// Host ID to retrieve inventory for. If not provided, wash will attempt to query the inventory of a single running host.
21-
/// If more than one host is found, an error will be returned prompting for a specific ID.
20+
/// Host ID to retrieve inventory for. If not provided, wash will query the inventories of all running hosts.
2221
#[clap(name = "host-id", value_parser)]
2322
pub host_id: Option<ServerId>,
2423
}
@@ -50,34 +49,38 @@ pub enum GetCommand {
5049
Hosts(GetHostsCommand),
5150

5251
/// Retrieve inventory a given host on in the lattice
53-
#[clap(name = "inventory")]
54-
HostInventory(GetHostInventoryCommand),
52+
#[clap(name = "inventory", alias = "inventories")]
53+
HostInventories(GetHostInventoriesCommand),
5554
}
5655

5756
/// Retreive host inventory
58-
pub async fn get_host_inventory(cmd: GetHostInventoryCommand) -> Result<HostInventory> {
57+
pub async fn get_host_inventories(cmd: GetHostInventoriesCommand) -> Result<Vec<HostInventory>> {
5958
let wco: WashConnectionOptions = cmd.opts.try_into()?;
6059
let client = wco.into_ctl_client(None).await?;
6160

62-
let host_id = if let Some(host_id) = cmd.host_id {
63-
host_id.to_string()
61+
let host_ids = if let Some(host_id) = cmd.host_id {
62+
vec![host_id.to_string()]
6463
} else {
6564
let hosts = client.get_hosts().await.map_err(boxed_err_to_anyhow)?;
6665
match hosts.len() {
6766
0 => anyhow::bail!("No hosts are available for inventory query."),
68-
// SAFETY: We know that the length is 1, so we can safely unwrap the first element
69-
1 => hosts.first().unwrap().id.clone(),
70-
_ => {
71-
anyhow::bail!("No host id provided and more than one host is available. Please specify a host id.")
72-
}
67+
_ => hosts.iter().map(|h| h.id.clone()).collect(),
7368
}
7469
};
7570

76-
client
77-
.get_host_inventory(&host_id)
71+
let futs = host_ids
72+
.into_iter()
73+
.map(|host_id| (client.clone(), host_id))
74+
.map(|(client, host_id)| async move {
75+
client
76+
.get_host_inventory(&host_id.clone())
77+
.await
78+
.map_err(boxed_err_to_anyhow)
79+
});
80+
futures::future::join_all(futs)
7881
.await
79-
.map_err(boxed_err_to_anyhow)
80-
.context("Was able to connect to NATS, but failed to get host inventory.")
82+
.into_iter()
83+
.collect::<Result<Vec<HostInventory>>>()
8184
}
8285

8386
/// Retrieve hosts

crates/wash-lib/src/cli/output.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ pub struct GetHostsCommandOutput {
5252

5353
/// JSON output representation of the `wash get inventory` command
5454
#[derive(Debug, Clone, Deserialize)]
55-
pub struct GetHostInventoryCommandOutput {
55+
pub struct GetHostInventoriesCommandOutput {
5656
pub success: bool,
57-
pub inventory: HostInventory,
57+
pub inventories: Vec<HostInventory>,
5858
}
5959

6060
/// JSON output representation of the `wash get claims` command

crates/wash-lib/src/start/wasmcloud.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ mod test {
299299
use tokio::net::TcpStream;
300300
use tokio::time::Duration;
301301

302-
const WASMCLOUD_VERSION: &str = "v0.78.0-rc2";
302+
const WASMCLOUD_VERSION: &str = "v0.79.0-rc3";
303303
const RANDOM_PORT_RANGE_START: u16 = 5000;
304304
const RANDOM_PORT_RANGE_END: u16 = 6000;
305305
const LOCALHOST: &str = "127.0.0.1";

src/common/get_cmd.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use anyhow::Result;
22
use wash_lib::cli::{
33
claims::get_claims,
4-
get::{get_host_inventory, get_hosts, GetCommand, GetLinksCommand},
4+
get::{get_host_inventories, get_hosts, GetCommand, GetLinksCommand},
55
link::{LinkCommand, LinkQueryCommand},
66
};
77

88
use crate::{
99
appearance::spinner::Spinner,
1010
common::link_cmd::handle_command as handle_link_command,
11-
ctl::{get_claims_output, get_host_inventory_output, get_hosts_output},
11+
ctl::{get_claims_output, get_host_inventories_output, get_hosts_output},
1212
CommandOutput, OutputKind,
1313
};
1414

@@ -31,14 +31,14 @@ pub(crate) async fn handle_command(
3131
let hosts = get_hosts(cmd).await?;
3232
get_hosts_output(hosts)
3333
}
34-
GetCommand::HostInventory(cmd) => {
34+
GetCommand::HostInventories(cmd) => {
3535
if let Some(id) = cmd.host_id.as_ref() {
3636
sp.update_spinner_message(format!(" Retrieving inventory for host {} ...", id));
3737
} else {
3838
sp.update_spinner_message(" Retrieving hosts for inventory query ...".to_string());
3939
}
40-
let inv = get_host_inventory(cmd).await?;
41-
get_host_inventory_output(inv)
40+
let invs = get_host_inventories(cmd).await?;
41+
get_host_inventories_output(invs)
4242
}
4343
};
4444

src/ctl/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Result;
22
use clap::Subcommand;
33
use wash_lib::cli::{
4-
get::{GetClaimsCommand, GetCommand, GetHostInventoryCommand, GetHostsCommand},
4+
get::{GetClaimsCommand, GetCommand, GetHostInventoriesCommand, GetHostsCommand},
55
link::LinkCommand,
66
scale::{handle_scale_actor, ScaleCommand},
77
start::StartCommand,
@@ -56,7 +56,7 @@ pub(crate) enum CtlGetCommand {
5656

5757
/// Query a single host for its inventory of labels, actors and providers
5858
#[clap(name = "inventory")]
59-
HostInventory(GetHostInventoryCommand),
59+
HostInventories(GetHostInventoriesCommand),
6060

6161
/// Query lattice for its claims cache
6262
#[clap(name = "claims")]
@@ -74,9 +74,9 @@ pub(crate) async fn handle_command(
7474
eprintln!("[warn] `wash ctl get hosts` has been deprecated in favor of `wash get hosts` and will be removed in a future version.");
7575
handle_get_command(GetCommand::Hosts(cmd), output_kind).await?
7676
}
77-
Get(CtlGetCommand::HostInventory(cmd)) => {
77+
Get(CtlGetCommand::HostInventories(cmd)) => {
7878
eprintln!("[warn] `wash ctl get inventory` has been deprecated in favor of `wash get inventory` and will be removed in a future version.");
79-
handle_get_command(GetCommand::HostInventory(cmd), output_kind).await?
79+
handle_get_command(GetCommand::HostInventories(cmd), output_kind).await?
8080
}
8181
Get(CtlGetCommand::Claims(cmd)) => {
8282
eprintln!("[warn] `wash ctl get claims` has been deprecated in favor of `wash get claims` and will be removed in a future version.");
@@ -279,7 +279,7 @@ mod test {
279279
HOST_ID,
280280
])?;
281281
match get_host_inventory_all.command {
282-
CtlCliCommand::Get(CtlGetCommand::HostInventory(GetHostInventoryCommand {
282+
CtlCliCommand::Get(CtlGetCommand::HostInventories(GetHostInventoriesCommand {
283283
opts,
284284
host_id,
285285
})) => {

src/ctl/output.rs

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ pub(crate) fn get_hosts_output(hosts: Vec<Host>) -> CommandOutput {
1919
CommandOutput::new(hosts_table(hosts), map)
2020
}
2121

22-
pub(crate) fn get_host_inventory_output(inv: HostInventory) -> CommandOutput {
22+
pub(crate) fn get_host_inventories_output(invs: Vec<HostInventory>) -> CommandOutput {
2323
let mut map = HashMap::new();
24-
map.insert("inventory".to_string(), json!(inv));
25-
CommandOutput::new(host_inventory_table(inv), map)
24+
map.insert("inventories".to_string(), json!(invs));
25+
CommandOutput::new(host_inventories_table(invs), map)
2626
}
2727

2828
pub(crate) fn get_claims_output(claims: Vec<HashMap<String, String>>) -> CommandOutput {
@@ -96,90 +96,97 @@ pub(crate) fn hosts_table(hosts: Vec<Host>) -> String {
9696
}
9797

9898
/// Helper function to transform a HostInventory into a table string for printing
99-
pub(crate) fn host_inventory_table(inv: HostInventory) -> String {
99+
pub(crate) fn host_inventories_table(invs: Vec<HostInventory>) -> String {
100100
let mut table = Table::new();
101101
crate::util::configure_table_style(&mut table);
102102

103-
table.add_row(Row::new(vec![TableCell::new_with_alignment(
104-
format!("Host Inventory ({})", inv.host_id),
105-
4,
106-
Alignment::Center,
107-
)]));
108-
109-
if !inv.labels.is_empty() {
103+
invs.into_iter().for_each(|inv| {
110104
table.add_row(Row::new(vec![TableCell::new_with_alignment(
111-
"",
105+
format!("Host Inventory ({})", inv.host_id),
112106
4,
113107
Alignment::Center,
114108
)]));
115-
inv.labels.iter().for_each(|(k, v)| {
116-
table.add_row(Row::new(vec![
117-
TableCell::new_with_alignment(k, 2, Alignment::Left),
118-
TableCell::new_with_alignment(v, 2, Alignment::Left),
119-
]))
120-
});
121-
} else {
109+
110+
if !inv.labels.is_empty() {
111+
table.add_row(Row::new(vec![TableCell::new_with_alignment(
112+
"",
113+
4,
114+
Alignment::Center,
115+
)]));
116+
inv.labels.iter().for_each(|(k, v)| {
117+
table.add_row(Row::new(vec![
118+
TableCell::new_with_alignment(k, 2, Alignment::Left),
119+
TableCell::new_with_alignment(v, 2, Alignment::Left),
120+
]))
121+
});
122+
} else {
123+
table.add_row(Row::new(vec![TableCell::new_with_alignment(
124+
"No labels present",
125+
4,
126+
Alignment::Center,
127+
)]));
128+
}
129+
122130
table.add_row(Row::new(vec![TableCell::new_with_alignment(
123-
"No labels present",
131+
"",
124132
4,
125133
Alignment::Center,
126134
)]));
127-
}
128-
129-
table.add_row(Row::new(vec![TableCell::new_with_alignment(
130-
"",
131-
4,
132-
Alignment::Center,
133-
)]));
134-
if !inv.actors.is_empty() {
135-
table.add_row(Row::new(vec![
136-
TableCell::new_with_alignment("Actor ID", 1, Alignment::Left),
137-
TableCell::new_with_alignment("Name", 1, Alignment::Left),
138-
TableCell::new_with_alignment("Image Reference", 2, Alignment::Left),
139-
]));
140-
inv.actors.iter().for_each(|a| {
141-
let a = a.clone();
135+
if !inv.actors.is_empty() {
142136
table.add_row(Row::new(vec![
143-
TableCell::new_with_alignment(a.id, 1, Alignment::Left),
144-
TableCell::new_with_alignment(format_optional(a.name), 1, Alignment::Left),
145-
TableCell::new_with_alignment(format_optional(a.image_ref), 2, Alignment::Left),
146-
]))
147-
});
148-
} else {
137+
TableCell::new_with_alignment("Actor ID", 1, Alignment::Left),
138+
TableCell::new_with_alignment("Name", 1, Alignment::Left),
139+
TableCell::new_with_alignment("Image Reference", 2, Alignment::Left),
140+
]));
141+
inv.actors.iter().for_each(|a| {
142+
let a = a.clone();
143+
table.add_row(Row::new(vec![
144+
TableCell::new_with_alignment(a.id, 1, Alignment::Left),
145+
TableCell::new_with_alignment(format_optional(a.name), 1, Alignment::Left),
146+
TableCell::new_with_alignment(format_optional(a.image_ref), 2, Alignment::Left),
147+
]))
148+
});
149+
} else {
150+
table.add_row(Row::new(vec![TableCell::new_with_alignment(
151+
"No actors found",
152+
4,
153+
Alignment::Left,
154+
)]));
155+
}
149156
table.add_row(Row::new(vec![TableCell::new_with_alignment(
150-
"No actors found",
157+
"",
151158
4,
152159
Alignment::Left,
153160
)]));
154-
}
155-
table.add_row(Row::new(vec![TableCell::new_with_alignment(
156-
"",
157-
4,
158-
Alignment::Left,
159-
)]));
160-
if !inv.providers.is_empty() {
161-
table.add_row(Row::new(vec![
162-
TableCell::new_with_alignment("Provider ID", 1, Alignment::Left),
163-
TableCell::new_with_alignment("Name", 1, Alignment::Left),
164-
TableCell::new_with_alignment("Link Name", 1, Alignment::Left),
165-
TableCell::new_with_alignment("Image Reference", 1, Alignment::Left),
166-
]));
167-
inv.providers.iter().for_each(|p| {
168-
let p = p.clone();
161+
if !inv.providers.is_empty() {
169162
table.add_row(Row::new(vec![
170-
TableCell::new_with_alignment(p.id, 1, Alignment::Left),
171-
TableCell::new_with_alignment(format_optional(p.name), 1, Alignment::Left),
172-
TableCell::new_with_alignment(p.link_name, 1, Alignment::Left),
173-
TableCell::new_with_alignment(format_optional(p.image_ref), 1, Alignment::Left),
174-
]))
175-
});
176-
} else {
163+
TableCell::new_with_alignment("Provider ID", 1, Alignment::Left),
164+
TableCell::new_with_alignment("Name", 1, Alignment::Left),
165+
TableCell::new_with_alignment("Link Name", 1, Alignment::Left),
166+
TableCell::new_with_alignment("Image Reference", 1, Alignment::Left),
167+
]));
168+
inv.providers.iter().for_each(|p| {
169+
let p = p.clone();
170+
table.add_row(Row::new(vec![
171+
TableCell::new_with_alignment(p.id, 1, Alignment::Left),
172+
TableCell::new_with_alignment(format_optional(p.name), 1, Alignment::Left),
173+
TableCell::new_with_alignment(p.link_name, 1, Alignment::Left),
174+
TableCell::new_with_alignment(format_optional(p.image_ref), 1, Alignment::Left),
175+
]))
176+
});
177+
} else {
178+
table.add_row(Row::new(vec![TableCell::new_with_alignment(
179+
"No providers found",
180+
4,
181+
Alignment::Left,
182+
)]));
183+
}
177184
table.add_row(Row::new(vec![TableCell::new_with_alignment(
178-
"No providers found",
185+
"",
179186
4,
180187
Alignment::Left,
181188
)]));
182-
}
189+
});
183190

184191
table.render()
185192
}

0 commit comments

Comments
 (0)