mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
kratactl: implement output formats
This commit is contained in:
@ -43,10 +43,12 @@ memchr = "2"
|
|||||||
netlink-packet-route = "0.19.0"
|
netlink-packet-route = "0.19.0"
|
||||||
nix = "0.28.0"
|
nix = "0.28.0"
|
||||||
oci-spec = "0.6.4"
|
oci-spec = "0.6.4"
|
||||||
|
once_cell = "1.19.0"
|
||||||
path-absolutize = "3.1.1"
|
path-absolutize = "3.1.1"
|
||||||
path-clean = "1.0.1"
|
path-clean = "1.0.1"
|
||||||
prost = "0.12.3"
|
prost = "0.12.3"
|
||||||
prost-build = "0.12.3"
|
prost-build = "0.12.3"
|
||||||
|
prost-reflect-build = "0.13.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
redb = "1.5.0"
|
redb = "1.5.0"
|
||||||
rtnetlink = "0.14.1"
|
rtnetlink = "0.14.1"
|
||||||
@ -78,6 +80,10 @@ version = "0.4.6"
|
|||||||
version = "4.4.18"
|
version = "4.4.18"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
|
[workspace.dependencies.prost-reflect]
|
||||||
|
version = "0.13.0"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
[workspace.dependencies.reqwest]
|
[workspace.dependencies.reqwest]
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
@ -8,7 +8,9 @@ resolver = "2"
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
prost = { workspace = true }
|
prost = { workspace = true }
|
||||||
|
prost-reflect = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tonic = { workspace = true }
|
tonic = { workspace = true }
|
||||||
@ -16,6 +18,8 @@ url = { workspace = true }
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { workspace = true }
|
tonic-build = { workspace = true }
|
||||||
|
prost-build = { workspace = true }
|
||||||
|
prost-reflect-build = { workspace = true }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "krata"
|
name = "krata"
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
|
|
||||||
fn main() -> 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use prost_reflect::DescriptorPool;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
pub mod dial;
|
pub mod dial;
|
||||||
@ -5,3 +8,10 @@ pub mod launchcfg;
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub mod ethtool;
|
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 }
|
env_logger = { workspace = true }
|
||||||
krata = { path = "../krata" }
|
krata = { path = "../krata" }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
prost-reflect = { workspace = true, features = ["serde"] }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
serde_yaml = { workspace = true }
|
||||||
signal-hook = { workspace = true }
|
signal-hook = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
|
@ -1,18 +1,37 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::{Parser, ValueEnum};
|
||||||
|
use cli_tables::Table;
|
||||||
use krata::{
|
use krata::{
|
||||||
common::guest_image_spec::Image,
|
common::{guest_image_spec::Image, Guest},
|
||||||
control::{control_service_client::ControlServiceClient, ListGuestsRequest},
|
control::{control_service_client::ControlServiceClient, ListGuestsRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
use tonic::{transport::Channel, Request};
|
use tonic::{transport::Channel, Request};
|
||||||
|
|
||||||
use crate::events::EventStream;
|
use crate::{
|
||||||
|
events::EventStream,
|
||||||
|
format::{proto2dynamic, proto2kv},
|
||||||
|
};
|
||||||
|
|
||||||
use super::pretty::guest_state_text;
|
use super::pretty::guest_state_text;
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Default, Debug, PartialEq, Eq)]
|
||||||
|
enum ListFormat {
|
||||||
|
#[default]
|
||||||
|
CliTable,
|
||||||
|
Json,
|
||||||
|
JsonPretty,
|
||||||
|
Jsonl,
|
||||||
|
Yaml,
|
||||||
|
KeyValue,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct ListCommand {}
|
pub struct ListCommand {
|
||||||
|
#[arg(short, long)]
|
||||||
|
format: ListFormat,
|
||||||
|
}
|
||||||
|
|
||||||
impl ListCommand {
|
impl ListCommand {
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
@ -24,10 +43,49 @@ impl ListCommand {
|
|||||||
.list_guests(Request::new(ListGuestsRequest {}))
|
.list_guests(Request::new(ListGuestsRequest {}))
|
||||||
.await?
|
.await?
|
||||||
.into_inner();
|
.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"];
|
let header = vec!["name", "uuid", "state", "ipv4", "ipv6", "image"];
|
||||||
table.push_row(&header)?;
|
table.push_row(&header)?;
|
||||||
for guest in response.guests {
|
for guest in guests {
|
||||||
let ipv4 = guest
|
let ipv4 = guest
|
||||||
.network
|
.network
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -67,4 +125,18 @@ impl ListCommand {
|
|||||||
}
|
}
|
||||||
Ok(())
|
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 client;
|
||||||
pub mod console;
|
pub mod console;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
pub mod format;
|
||||||
|
Reference in New Issue
Block a user