mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-02 21:00:55 +00:00
kratactl: implement output formats
This commit is contained in:
parent
3b5e3a077a
commit
6c0e14da6a
@ -43,10 +43,12 @@ memchr = "2"
|
||||
netlink-packet-route = "0.19.0"
|
||||
nix = "0.28.0"
|
||||
oci-spec = "0.6.4"
|
||||
once_cell = "1.19.0"
|
||||
path-absolutize = "3.1.1"
|
||||
path-clean = "1.0.1"
|
||||
prost = "0.12.3"
|
||||
prost-build = "0.12.3"
|
||||
prost-reflect-build = "0.13.0"
|
||||
rand = "0.8.5"
|
||||
redb = "1.5.0"
|
||||
rtnetlink = "0.14.1"
|
||||
@ -78,6 +80,10 @@ version = "0.4.6"
|
||||
version = "4.4.18"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.prost-reflect]
|
||||
version = "0.13.0"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12.0"
|
||||
default-features = false
|
||||
|
@ -8,7 +8,9 @@ resolver = "2"
|
||||
anyhow = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
prost = { workspace = true }
|
||||
prost-reflect = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tonic = { workspace = true }
|
||||
@ -16,6 +18,8 @@ url = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { workspace = true }
|
||||
prost-build = { workspace = true }
|
||||
prost-reflect-build = { workspace = true }
|
||||
|
||||
[lib]
|
||||
name = "krata"
|
||||
|
@ -1,6 +1,14 @@
|
||||
use std::io::Result;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
tonic_build::configure().compile(&["proto/krata/control.proto"], &["proto/"])?;
|
||||
let mut config = prost_build::Config::new();
|
||||
prost_reflect_build::Builder::new()
|
||||
.descriptor_pool("crate::DESCRIPTOR_POOL")
|
||||
.configure(&mut config, &["proto/krata/control.proto"], &["proto/"])?;
|
||||
tonic_build::configure().compile_with_config(
|
||||
config,
|
||||
&["proto/krata/control.proto"],
|
||||
&["proto/"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use prost_reflect::DescriptorPool;
|
||||
|
||||
pub mod common;
|
||||
pub mod control;
|
||||
pub mod dial;
|
||||
@ -5,3 +8,10 @@ pub mod launchcfg;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod ethtool;
|
||||
|
||||
pub static DESCRIPTOR_POOL: Lazy<DescriptorPool> = Lazy::new(|| {
|
||||
DescriptorPool::decode(
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
@ -14,7 +14,10 @@ ctrlc = { workspace = true, features = ["termination"] }
|
||||
env_logger = { workspace = true }
|
||||
krata = { path = "../krata" }
|
||||
log = { workspace = true }
|
||||
prost-reflect = { workspace = true, features = ["serde"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
signal-hook = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
@ -1,18 +1,37 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cli_tables::Table;
|
||||
use krata::{
|
||||
common::guest_image_spec::Image,
|
||||
common::{guest_image_spec::Image, Guest},
|
||||
control::{control_service_client::ControlServiceClient, ListGuestsRequest},
|
||||
};
|
||||
|
||||
use serde_json::Value;
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::events::EventStream;
|
||||
use crate::{
|
||||
events::EventStream,
|
||||
format::{proto2dynamic, proto2kv},
|
||||
};
|
||||
|
||||
use super::pretty::guest_state_text;
|
||||
|
||||
#[derive(ValueEnum, Clone, Default, Debug, PartialEq, Eq)]
|
||||
enum ListFormat {
|
||||
#[default]
|
||||
CliTable,
|
||||
Json,
|
||||
JsonPretty,
|
||||
Jsonl,
|
||||
Yaml,
|
||||
KeyValue,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct ListCommand {}
|
||||
pub struct ListCommand {
|
||||
#[arg(short, long)]
|
||||
format: ListFormat,
|
||||
}
|
||||
|
||||
impl ListCommand {
|
||||
pub async fn run(
|
||||
@ -24,10 +43,49 @@ impl ListCommand {
|
||||
.list_guests(Request::new(ListGuestsRequest {}))
|
||||
.await?
|
||||
.into_inner();
|
||||
let mut table = cli_tables::Table::new();
|
||||
|
||||
match self.format {
|
||||
ListFormat::CliTable => {
|
||||
self.print_guest_table(response.guests)?;
|
||||
}
|
||||
|
||||
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
|
||||
let mut values = Vec::new();
|
||||
for guest in response.guests {
|
||||
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 response.guests {
|
||||
let message = proto2dynamic(guest)?;
|
||||
println!("{}", serde_json::to_string(&message)?);
|
||||
}
|
||||
}
|
||||
|
||||
ListFormat::KeyValue => {
|
||||
self.print_key_value(response.guests)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_guest_table(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
let mut table = Table::new();
|
||||
let header = vec!["name", "uuid", "state", "ipv4", "ipv6", "image"];
|
||||
table.push_row(&header)?;
|
||||
for guest in response.guests {
|
||||
for guest in guests {
|
||||
let ipv4 = guest
|
||||
.network
|
||||
.as_ref()
|
||||
@ -67,4 +125,18 @@ impl ListCommand {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
for guest in guests {
|
||||
let kvs = proto2kv(guest)?;
|
||||
println!(
|
||||
"{}",
|
||||
kvs.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
51
crates/kratactl/src/format.rs
Normal file
51
crates/kratactl/src/format.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use prost_reflect::{DynamicMessage, ReflectMessage, Value};
|
||||
|
||||
pub fn proto2dynamic(proto: impl ReflectMessage) -> Result<DynamicMessage> {
|
||||
Ok(DynamicMessage::decode(
|
||||
proto.descriptor(),
|
||||
proto.encode_to_vec().as_slice(),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn proto2kv(proto: impl ReflectMessage) -> Result<HashMap<String, String>> {
|
||||
let message = proto2dynamic(proto)?;
|
||||
let mut map = HashMap::new();
|
||||
|
||||
fn crawl(prefix: &str, map: &mut HashMap<String, String>, message: &DynamicMessage) {
|
||||
for (field, value) in message.fields() {
|
||||
let path = if prefix.is_empty() {
|
||||
field.name().to_string()
|
||||
} else {
|
||||
format!("{}.{}", prefix, field.name())
|
||||
};
|
||||
match value {
|
||||
Value::Message(child) => {
|
||||
crawl(&path, map, child);
|
||||
}
|
||||
|
||||
Value::EnumNumber(number) => {
|
||||
if let Some(e) = field.kind().as_enum() {
|
||||
if let Some(value) = e.get_value(*number) {
|
||||
map.insert(path, value.name().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Value::String(value) => {
|
||||
map.insert(path, value.clone());
|
||||
}
|
||||
|
||||
_ => {
|
||||
map.insert(path, value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crawl("", &mut map, &message);
|
||||
|
||||
Ok(map)
|
||||
}
|
@ -2,3 +2,4 @@ pub mod cli;
|
||||
pub mod client;
|
||||
pub mod console;
|
||||
pub mod events;
|
||||
pub mod format;
|
||||
|
Loading…
Reference in New Issue
Block a user