Files
krata/crates/ctl/src/cli/list.rs

175 lines
5.4 KiB
Rust
Raw Normal View History

use anyhow::{anyhow, Result};
2024-03-23 04:07:48 +00:00
use clap::{Parser, ValueEnum};
2024-04-02 00:56:18 +00:00
use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
use krata::{
events::EventStream,
v1::{
2024-04-02 00:56:18 +00:00
common::{Guest, GuestStatus},
control::{
control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest,
},
},
2024-03-15 15:59:18 +00:00
};
2024-03-23 04:07:48 +00:00
use serde_json::Value;
2024-03-15 15:59:18 +00:00
use tonic::{transport::Channel, Request};
2024-04-02 00:56:18 +00:00
use crate::format::{guest_simple_line, guest_status_text, kv2line, proto2dynamic, proto2kv};
2024-03-15 15:59:18 +00:00
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
2024-03-23 04:07:48 +00:00
enum ListFormat {
2024-04-02 00:56:18 +00:00
Table,
2024-03-23 04:07:48 +00:00
Json,
JsonPretty,
Jsonl,
Yaml,
KeyValue,
2024-03-30 03:49:13 +00:00
Simple,
2024-03-23 04:07:48 +00:00
}
2024-03-15 15:59:18 +00:00
#[derive(Parser)]
#[command(about = "List the guests on the isolation engine")]
2024-03-23 04:07:48 +00:00
pub struct ListCommand {
#[arg(short, long, default_value = "table", help = "Output format")]
2024-03-23 04:07:48 +00:00
format: ListFormat,
#[arg(help = "Limit to a single guest, either the name or the uuid")]
guest: Option<String>,
2024-03-23 04:07:48 +00:00
}
2024-03-15 15:59:18 +00:00
impl ListCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
_events: EventStream,
) -> Result<()> {
2024-03-30 03:49:13 +00:00
let mut guests = if let Some(ref guest) = self.guest {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: guest.clone(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
vec![guest]
} else {
return Err(anyhow!("unable to resolve guest '{}'", guest));
}
} else {
client
.list_guests(Request::new(ListGuestsRequest {}))
.await?
.into_inner()
.guests
};
2024-03-23 04:07:48 +00:00
2024-03-30 03:49:13 +00:00
guests.sort_by(|a, b| {
a.spec
.as_ref()
.map(|x| x.name.as_str())
.unwrap_or("")
.cmp(b.spec.as_ref().map(|x| x.name.as_str()).unwrap_or(""))
});
2024-03-23 04:07:48 +00:00
match self.format {
2024-04-02 00:56:18 +00:00
ListFormat::Table => {
self.print_guest_table(guests)?;
2024-03-23 04:07:48 +00:00
}
2024-03-30 03:49:13 +00:00
ListFormat::Simple => {
for guest in guests {
2024-03-31 01:11:50 +00:00
println!("{}", guest_simple_line(&guest));
2024-03-30 03:49:13 +00:00
}
}
2024-03-23 04:07:48 +00:00
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
let mut values = Vec::new();
for guest in guests {
2024-03-23 04:07:48 +00:00
let message = proto2dynamic(guest)?;
values.push(serde_json::to_value(message)?);
}
let value = Value::Array(values);
let encoded = if self.format == ListFormat::JsonPretty {
serde_json::to_string_pretty(&value)?
} else if self.format == ListFormat::Yaml {
serde_yaml::to_string(&value)?
} else {
serde_json::to_string(&value)?
};
println!("{}", encoded.trim());
}
ListFormat::Jsonl => {
for guest in guests {
2024-03-23 04:07:48 +00:00
let message = proto2dynamic(guest)?;
println!("{}", serde_json::to_string(&message)?);
}
}
ListFormat::KeyValue => {
self.print_key_value(guests)?;
2024-03-23 04:07:48 +00:00
}
}
Ok(())
}
fn print_guest_table(&self, guests: Vec<Guest>) -> Result<()> {
let mut table = Table::new();
2024-04-02 00:56:18 +00:00
table.load_preset(UTF8_FULL_CONDENSED);
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["name", "uuid", "status", "ipv4", "ipv6"]);
2024-03-23 04:07:48 +00:00
for guest in guests {
2024-03-15 15:59:18 +00:00
let ipv4 = guest
.state
2024-03-15 15:59:18 +00:00
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv4.as_str())
2024-04-02 00:56:18 +00:00
.unwrap_or("n/a");
2024-03-15 15:59:18 +00:00
let ipv6 = guest
.state
2024-03-15 15:59:18 +00:00
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv6.as_str())
2024-04-02 00:56:18 +00:00
.unwrap_or("n/a");
2024-03-15 15:59:18 +00:00
let Some(spec) = guest.spec else {
continue;
};
2024-04-02 00:56:18 +00:00
let status = guest.state.as_ref().cloned().unwrap_or_default().status();
let status_text = guest_status_text(status);
let status_color = match status {
GuestStatus::Destroyed | GuestStatus::Failed => Color::Red,
GuestStatus::Destroying | GuestStatus::Exited | GuestStatus::Starting => {
Color::Yellow
}
GuestStatus::Started => Color::Green,
_ => Color::Reset,
};
table.add_row(vec![
Cell::new(spec.name),
Cell::new(guest.id),
Cell::new(status_text).fg(status_color),
Cell::new(ipv4.to_string()),
Cell::new(ipv6.to_string()),
]);
2024-03-15 15:59:18 +00:00
}
2024-04-02 00:56:18 +00:00
if table.is_empty() {
if self.guest.is_none() {
println!("no guests have been launched");
}
2024-03-15 15:59:18 +00:00
} else {
2024-04-02 00:56:18 +00:00
println!("{}", table);
2024-03-15 15:59:18 +00:00
}
Ok(())
}
2024-03-23 04:07:48 +00:00
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
for guest in guests {
let kvs = proto2kv(guest)?;
println!("{}", kv2line(kvs),);
2024-03-23 04:07:48 +00:00
}
Ok(())
}
2024-03-15 15:59:18 +00:00
}