mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-04 05:31:32 +00:00
feature(krata): rename guest to zone (#266)
This commit is contained in:
@ -7,13 +7,13 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use super::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Attach to the guest console")]
|
||||
#[command(about = "Attach to the zone console")]
|
||||
pub struct AttachCommand {
|
||||
#[arg(help = "Guest to attach to, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to attach to, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl AttachCommand {
|
||||
@ -22,12 +22,12 @@ impl AttachCommand {
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let input = StdioConsoleStream::stdin_stream(guest_id.clone()).await;
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let input = StdioConsoleStream::stdin_stream(zone_id.clone()).await;
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
|
||||
let code = select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
||||
|
@ -3,10 +3,10 @@ use clap::Parser;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::GuestStatus,
|
||||
common::ZoneStatus,
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
DestroyGuestRequest,
|
||||
DestroyZoneRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -14,19 +14,19 @@ use krata::{
|
||||
use log::error;
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::cli::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Destroy a guest")]
|
||||
#[command(about = "Destroy a zone")]
|
||||
pub struct DestroyCommand {
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long,
|
||||
help = "Wait for the destruction of the guest to complete"
|
||||
help = "Wait for the destruction of the zone to complete"
|
||||
)]
|
||||
wait: bool,
|
||||
#[arg(help = "Guest to destroy, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to destroy, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl DestroyCommand {
|
||||
@ -35,46 +35,46 @@ impl DestroyCommand {
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let _ = client
|
||||
.destroy_guest(Request::new(DestroyGuestRequest {
|
||||
guest_id: guest_id.clone(),
|
||||
.destroy_zone(Request::new(DestroyZoneRequest {
|
||||
zone_id: zone_id.clone(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
if self.wait {
|
||||
wait_guest_destroyed(&guest_id, events).await?;
|
||||
wait_zone_destroyed(&zone_id, events).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_guest_destroyed(id: &str, events: EventStream) -> Result<()> {
|
||||
async fn wait_zone_destroyed(id: &str, events: EventStream) -> Result<()> {
|
||||
let mut stream = events.subscribe();
|
||||
while let Ok(event) = stream.recv().await {
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let Some(guest) = changed.guest else {
|
||||
let Event::ZoneChanged(changed) = event;
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
if zone.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
let Some(state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(ref error) = state.error_info {
|
||||
if state.status() == GuestStatus::Failed {
|
||||
if state.status() == ZoneStatus::Failed {
|
||||
error!("destroy failed: {}", error.message);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
error!("guest error: {}", error.message);
|
||||
error!("zone error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
if state.status() == ZoneStatus::Destroyed {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -4,42 +4,42 @@ use anyhow::Result;
|
||||
|
||||
use clap::Parser;
|
||||
use krata::v1::{
|
||||
common::{GuestTaskSpec, GuestTaskSpecEnvVar},
|
||||
control::{control_service_client::ControlServiceClient, ExecGuestRequest},
|
||||
common::{ZoneTaskSpec, ZoneTaskSpecEnvVar},
|
||||
control::{control_service_client::ControlServiceClient, ExecZoneRequest},
|
||||
};
|
||||
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use super::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Execute a command inside the guest")]
|
||||
#[command(about = "Execute a command inside the zone")]
|
||||
pub struct ExecCommand {
|
||||
#[arg[short, long, help = "Environment variables"]]
|
||||
env: Option<Vec<String>>,
|
||||
#[arg(short = 'w', long, help = "Working directory")]
|
||||
working_directory: Option<String>,
|
||||
#[arg(help = "Guest to exec inside, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to exec inside, either the name or the uuid")]
|
||||
zone: String,
|
||||
#[arg(
|
||||
allow_hyphen_values = true,
|
||||
trailing_var_arg = true,
|
||||
help = "Command to run inside the guest"
|
||||
help = "Command to run inside the zone"
|
||||
)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExecCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let initial = ExecGuestRequest {
|
||||
guest_id,
|
||||
task: Some(GuestTaskSpec {
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let initial = ExecZoneRequest {
|
||||
zone_id,
|
||||
task: Some(ZoneTaskSpec {
|
||||
environment: env_map(&self.env.unwrap_or_default())
|
||||
.iter()
|
||||
.map(|(key, value)| GuestTaskSpecEnvVar {
|
||||
.map(|(key, value)| ZoneTaskSpecEnvVar {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
@ -52,7 +52,7 @@ impl ExecCommand {
|
||||
|
||||
let stream = StdioConsoleStream::stdin_stream_exec(initial).await;
|
||||
|
||||
let response = client.exec_guest(Request::new(stream)).await?.into_inner();
|
||||
let response = client.exec_zone(Request::new(stream)).await?.into_inner();
|
||||
|
||||
let code = StdioConsoleStream::exec_output(response).await?;
|
||||
std::process::exit(code);
|
||||
|
@ -6,12 +6,12 @@ use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::{
|
||||
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestSpecDevice,
|
||||
GuestStatus, GuestTaskSpec, GuestTaskSpecEnvVar, OciImageFormat,
|
||||
zone_image_spec::Image, OciImageFormat, ZoneImageSpec, ZoneOciImageSpec, ZoneSpec,
|
||||
ZoneSpecDevice, ZoneStatus, ZoneTaskSpec, ZoneTaskSpecEnvVar,
|
||||
},
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
CreateGuestRequest, PullImageRequest,
|
||||
CreateZoneRequest, PullImageRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -28,56 +28,51 @@ pub enum LaunchImageFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Launch a new guest")]
|
||||
#[command(about = "Launch a new zone")]
|
||||
pub struct LaunchCommand {
|
||||
#[arg(long, default_value = "squashfs", help = "Image format")]
|
||||
image_format: LaunchImageFormat,
|
||||
#[arg(long, help = "Overwrite image cache on pull")]
|
||||
pull_overwrite_cache: bool,
|
||||
#[arg(short, long, help = "Name of the guest")]
|
||||
#[arg(short, long, help = "Name of the zone")]
|
||||
name: Option<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 1,
|
||||
help = "vCPUs available to the guest"
|
||||
)]
|
||||
#[arg(short, long, default_value_t = 1, help = "vCPUs available to the zone")]
|
||||
cpus: u32,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 512,
|
||||
help = "Memory available to the guest, in megabytes"
|
||||
help = "Memory available to the zone, in megabytes"
|
||||
)]
|
||||
mem: u64,
|
||||
#[arg[short = 'D', long = "device", help = "Devices to request for the guest"]]
|
||||
#[arg[short = 'D', long = "device", help = "Devices to request for the zone"]]
|
||||
device: Vec<String>,
|
||||
#[arg[short, long, help = "Environment variables set in the guest"]]
|
||||
#[arg[short, long, help = "Environment variables set in the zone"]]
|
||||
env: Option<Vec<String>>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Attach to the guest after guest starts, implies --wait"
|
||||
help = "Attach to the zone after zone starts, implies --wait"
|
||||
)]
|
||||
attach: bool,
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long,
|
||||
help = "Wait for the guest to start, implied by --attach"
|
||||
help = "Wait for the zone to start, implied by --attach"
|
||||
)]
|
||||
wait: bool,
|
||||
#[arg(short = 'k', long, help = "OCI kernel image for guest to use")]
|
||||
#[arg(short = 'k', long, help = "OCI kernel image for zone to use")]
|
||||
kernel: Option<String>,
|
||||
#[arg(short = 'I', long, help = "OCI initrd image for guest to use")]
|
||||
#[arg(short = 'I', long, help = "OCI initrd image for zone to use")]
|
||||
initrd: Option<String>,
|
||||
#[arg(short = 'w', long, help = "Working directory")]
|
||||
working_directory: Option<String>,
|
||||
#[arg(help = "Container image for guest to use")]
|
||||
#[arg(help = "Container image for zone to use")]
|
||||
oci: String,
|
||||
#[arg(
|
||||
allow_hyphen_values = true,
|
||||
trailing_var_arg = true,
|
||||
help = "Command to run inside the guest"
|
||||
help = "Command to run inside the zone"
|
||||
)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
@ -117,18 +112,18 @@ impl LaunchCommand {
|
||||
None
|
||||
};
|
||||
|
||||
let request = CreateGuestRequest {
|
||||
spec: Some(GuestSpec {
|
||||
let request = CreateZoneRequest {
|
||||
spec: Some(ZoneSpec {
|
||||
name: self.name.unwrap_or_default(),
|
||||
image: Some(image),
|
||||
kernel,
|
||||
initrd,
|
||||
vcpus: self.cpus,
|
||||
mem: self.mem,
|
||||
task: Some(GuestTaskSpec {
|
||||
task: Some(ZoneTaskSpec {
|
||||
environment: env_map(&self.env.unwrap_or_default())
|
||||
.iter()
|
||||
.map(|(key, value)| GuestTaskSpecEnvVar {
|
||||
.map(|(key, value)| ZoneTaskSpecEnvVar {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
@ -140,26 +135,26 @@ impl LaunchCommand {
|
||||
devices: self
|
||||
.device
|
||||
.iter()
|
||||
.map(|name| GuestSpecDevice { name: name.clone() })
|
||||
.map(|name| ZoneSpecDevice { name: name.clone() })
|
||||
.collect(),
|
||||
}),
|
||||
};
|
||||
let response = client
|
||||
.create_guest(Request::new(request))
|
||||
.create_zone(Request::new(request))
|
||||
.await?
|
||||
.into_inner();
|
||||
let id = response.guest_id;
|
||||
let id = response.zone_id;
|
||||
|
||||
if self.wait || self.attach {
|
||||
wait_guest_started(&id, events.clone()).await?;
|
||||
wait_zone_started(&id, events.clone()).await?;
|
||||
}
|
||||
|
||||
let code = if self.attach {
|
||||
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(id.clone(), events).await?;
|
||||
select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
||||
@ -180,7 +175,7 @@ impl LaunchCommand {
|
||||
client: &mut ControlServiceClient<Channel>,
|
||||
image: &str,
|
||||
format: OciImageFormat,
|
||||
) -> Result<GuestImageSpec> {
|
||||
) -> Result<ZoneImageSpec> {
|
||||
let response = client
|
||||
.pull_image(PullImageRequest {
|
||||
image: image.to_string(),
|
||||
@ -189,8 +184,8 @@ impl LaunchCommand {
|
||||
})
|
||||
.await?;
|
||||
let reply = pull_interactive_progress(response.into_inner()).await?;
|
||||
Ok(GuestImageSpec {
|
||||
image: Some(Image::Oci(GuestOciImageSpec {
|
||||
Ok(ZoneImageSpec {
|
||||
image: Some(Image::Oci(ZoneOciImageSpec {
|
||||
digest: reply.digest,
|
||||
format: reply.format,
|
||||
})),
|
||||
@ -198,38 +193,38 @@ impl LaunchCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
|
||||
async fn wait_zone_started(id: &str, events: EventStream) -> Result<()> {
|
||||
let mut stream = events.subscribe();
|
||||
while let Ok(event) = stream.recv().await {
|
||||
match event {
|
||||
Event::GuestChanged(changed) => {
|
||||
let Some(guest) = changed.guest else {
|
||||
Event::ZoneChanged(changed) => {
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
if zone.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
let Some(state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(ref error) = state.error_info {
|
||||
if state.status() == GuestStatus::Failed {
|
||||
if state.status() == ZoneStatus::Failed {
|
||||
error!("launch failed: {}", error.message);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
error!("guest error: {}", error.message);
|
||||
error!("zone error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
error!("guest destroyed");
|
||||
if state.status() == ZoneStatus::Destroyed {
|
||||
error!("zone destroyed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Started {
|
||||
if state.status() == ZoneStatus::Started {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::{Guest, GuestStatus},
|
||||
common::{Zone, ZoneStatus},
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest,
|
||||
control_service_client::ControlServiceClient, ListZonesRequest, ResolveZoneRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -14,7 +14,7 @@ use krata::{
|
||||
use serde_json::Value;
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::format::{guest_simple_line, guest_status_text, kv2line, proto2dynamic, proto2kv};
|
||||
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line, zone_status_text};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum ListFormat {
|
||||
@ -28,12 +28,12 @@ enum ListFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "List the guests on the isolation engine")]
|
||||
#[command(about = "List the zones on the isolation engine")]
|
||||
pub struct ListCommand {
|
||||
#[arg(short, long, default_value = "table", help = "Output format")]
|
||||
format: ListFormat,
|
||||
#[arg(help = "Limit to a single guest, either the name or the uuid")]
|
||||
guest: Option<String>,
|
||||
#[arg(help = "Limit to a single zone, either the name or the uuid")]
|
||||
zone: Option<String>,
|
||||
}
|
||||
|
||||
impl ListCommand {
|
||||
@ -42,27 +42,25 @@ impl ListCommand {
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
_events: EventStream,
|
||||
) -> Result<()> {
|
||||
let mut guests = if let Some(ref guest) = self.guest {
|
||||
let mut zones = if let Some(ref zone) = self.zone {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
name: guest.clone(),
|
||||
}))
|
||||
.resolve_zone(Request::new(ResolveZoneRequest { name: zone.clone() }))
|
||||
.await?
|
||||
.into_inner();
|
||||
if let Some(guest) = reply.guest {
|
||||
vec![guest]
|
||||
if let Some(zone) = reply.zone {
|
||||
vec![zone]
|
||||
} else {
|
||||
return Err(anyhow!("unable to resolve guest '{}'", guest));
|
||||
return Err(anyhow!("unable to resolve zone '{}'", zone));
|
||||
}
|
||||
} else {
|
||||
client
|
||||
.list_guests(Request::new(ListGuestsRequest {}))
|
||||
.list_zones(Request::new(ListZonesRequest {}))
|
||||
.await?
|
||||
.into_inner()
|
||||
.guests
|
||||
.zones
|
||||
};
|
||||
|
||||
guests.sort_by(|a, b| {
|
||||
zones.sort_by(|a, b| {
|
||||
a.spec
|
||||
.as_ref()
|
||||
.map(|x| x.name.as_str())
|
||||
@ -72,19 +70,19 @@ impl ListCommand {
|
||||
|
||||
match self.format {
|
||||
ListFormat::Table => {
|
||||
self.print_guest_table(guests)?;
|
||||
self.print_zone_table(zones)?;
|
||||
}
|
||||
|
||||
ListFormat::Simple => {
|
||||
for guest in guests {
|
||||
println!("{}", guest_simple_line(&guest));
|
||||
for zone in zones {
|
||||
println!("{}", zone_simple_line(&zone));
|
||||
}
|
||||
}
|
||||
|
||||
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
|
||||
let mut values = Vec::new();
|
||||
for guest in guests {
|
||||
let message = proto2dynamic(guest)?;
|
||||
for zone in zones {
|
||||
let message = proto2dynamic(zone)?;
|
||||
values.push(serde_json::to_value(message)?);
|
||||
}
|
||||
let value = Value::Array(values);
|
||||
@ -99,64 +97,62 @@ impl ListCommand {
|
||||
}
|
||||
|
||||
ListFormat::Jsonl => {
|
||||
for guest in guests {
|
||||
let message = proto2dynamic(guest)?;
|
||||
for zone in zones {
|
||||
let message = proto2dynamic(zone)?;
|
||||
println!("{}", serde_json::to_string(&message)?);
|
||||
}
|
||||
}
|
||||
|
||||
ListFormat::KeyValue => {
|
||||
self.print_key_value(guests)?;
|
||||
self.print_key_value(zones)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_guest_table(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
fn print_zone_table(&self, zones: Vec<Zone>) -> Result<()> {
|
||||
let mut table = Table::new();
|
||||
table.load_preset(UTF8_FULL_CONDENSED);
|
||||
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
|
||||
table.set_header(vec!["name", "uuid", "status", "ipv4", "ipv6"]);
|
||||
for guest in guests {
|
||||
let ipv4 = guest
|
||||
for zone in zones {
|
||||
let ipv4 = zone
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|x| x.network.as_ref())
|
||||
.map(|x| x.guest_ipv4.as_str())
|
||||
.map(|x| x.zone_ipv4.as_str())
|
||||
.unwrap_or("n/a");
|
||||
let ipv6 = guest
|
||||
let ipv6 = zone
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|x| x.network.as_ref())
|
||||
.map(|x| x.guest_ipv6.as_str())
|
||||
.map(|x| x.zone_ipv6.as_str())
|
||||
.unwrap_or("n/a");
|
||||
let Some(spec) = guest.spec else {
|
||||
let Some(spec) = zone.spec else {
|
||||
continue;
|
||||
};
|
||||
let status = guest.state.as_ref().cloned().unwrap_or_default().status();
|
||||
let status_text = guest_status_text(status);
|
||||
let status = zone.state.as_ref().cloned().unwrap_or_default().status();
|
||||
let status_text = zone_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,
|
||||
ZoneStatus::Destroyed | ZoneStatus::Failed => Color::Red,
|
||||
ZoneStatus::Destroying | ZoneStatus::Exited | ZoneStatus::Starting => Color::Yellow,
|
||||
ZoneStatus::Started => Color::Green,
|
||||
_ => Color::Reset,
|
||||
};
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new(spec.name),
|
||||
Cell::new(guest.id),
|
||||
Cell::new(zone.id),
|
||||
Cell::new(status_text).fg(status_color),
|
||||
Cell::new(ipv4.to_string()),
|
||||
Cell::new(ipv6.to_string()),
|
||||
]);
|
||||
}
|
||||
if table.is_empty() {
|
||||
if self.guest.is_none() {
|
||||
println!("no guests have been launched");
|
||||
if self.zone.is_none() {
|
||||
println!("no zones have been launched");
|
||||
}
|
||||
} else {
|
||||
println!("{}", table);
|
||||
@ -164,9 +160,9 @@ impl ListCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
for guest in guests {
|
||||
let kvs = proto2kv(guest)?;
|
||||
fn print_key_value(&self, zones: Vec<Zone>) -> Result<()> {
|
||||
for zone in zones {
|
||||
let kvs = proto2kv(zone)?;
|
||||
println!("{}", kv2line(kvs),);
|
||||
}
|
||||
Ok(())
|
||||
|
@ -3,7 +3,7 @@ use async_stream::stream;
|
||||
use clap::Parser;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::control::{control_service_client::ControlServiceClient, ConsoleDataRequest},
|
||||
v1::control::{control_service_client::ControlServiceClient, ZoneConsoleRequest},
|
||||
};
|
||||
|
||||
use tokio::select;
|
||||
@ -12,15 +12,15 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use super::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "View the logs of a guest")]
|
||||
#[command(about = "View the logs of a zone")]
|
||||
pub struct LogsCommand {
|
||||
#[arg(short, long, help = "Follow output from the guest")]
|
||||
#[arg(short, long, help = "Follow output from the zone")]
|
||||
follow: bool,
|
||||
#[arg(help = "Guest to show logs for, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to show logs for, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl LogsCommand {
|
||||
@ -29,22 +29,22 @@ impl LogsCommand {
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let guest_id_stream = guest_id.clone();
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let zone_id_stream = zone_id.clone();
|
||||
let follow = self.follow;
|
||||
let input = stream! {
|
||||
yield ConsoleDataRequest { guest_id: guest_id_stream, data: Vec::new() };
|
||||
yield ZoneConsoleRequest { zone_id: zone_id_stream, data: Vec::new() };
|
||||
if follow {
|
||||
let mut pending = pending::<ConsoleDataRequest>();
|
||||
let mut pending = pending::<ZoneConsoleRequest>();
|
||||
while let Some(x) = pending.next().await {
|
||||
yield x;
|
||||
}
|
||||
}
|
||||
};
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
|
||||
let code = select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
||||
|
@ -3,8 +3,8 @@ use clap::{Parser, ValueEnum};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::GuestMetricNode,
|
||||
control::{control_service_client::ControlServiceClient, ReadGuestMetricsRequest},
|
||||
common::ZoneMetricNode,
|
||||
control::{control_service_client::ControlServiceClient, ReadZoneMetricsRequest},
|
||||
},
|
||||
};
|
||||
|
||||
@ -12,7 +12,7 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::format::{kv2line, metrics_flat, metrics_tree, proto2dynamic};
|
||||
|
||||
use super::resolve_guest;
|
||||
use super::resolve_zone;
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum MetricsFormat {
|
||||
@ -24,12 +24,12 @@ enum MetricsFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Read metrics from the guest")]
|
||||
#[command(about = "Read metrics from the zone")]
|
||||
pub struct MetricsCommand {
|
||||
#[arg(short, long, default_value = "tree", help = "Output format")]
|
||||
format: MetricsFormat,
|
||||
#[arg(help = "Guest to read metrics for, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to read metrics for, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl MetricsCommand {
|
||||
@ -38,9 +38,9 @@ impl MetricsCommand {
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
_events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let root = client
|
||||
.read_guest_metrics(ReadGuestMetricsRequest { guest_id })
|
||||
.read_zone_metrics(ReadZoneMetricsRequest { zone_id })
|
||||
.await?
|
||||
.into_inner()
|
||||
.root
|
||||
@ -70,12 +70,12 @@ impl MetricsCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_metrics_tree(&self, root: GuestMetricNode) -> Result<()> {
|
||||
fn print_metrics_tree(&self, root: ZoneMetricNode) -> Result<()> {
|
||||
print!("{}", metrics_tree(root));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key_value(&self, metrics: GuestMetricNode) -> Result<()> {
|
||||
fn print_key_value(&self, metrics: ZoneMetricNode) -> Result<()> {
|
||||
let kvs = metrics_flat(metrics);
|
||||
println!("{}", kv2line(kvs));
|
||||
Ok(())
|
||||
|
@ -19,7 +19,7 @@ use clap::{Parser, Subcommand};
|
||||
use krata::{
|
||||
client::ControlClientProvider,
|
||||
events::EventStream,
|
||||
v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest},
|
||||
v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest},
|
||||
};
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
@ -135,20 +135,20 @@ impl ControlCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_guest(
|
||||
pub async fn resolve_zone(
|
||||
client: &mut ControlServiceClient<Channel>,
|
||||
name: &str,
|
||||
) -> Result<String> {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
.resolve_zone(Request::new(ResolveZoneRequest {
|
||||
name: name.to_string(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
if let Some(guest) = reply.guest {
|
||||
Ok(guest.id)
|
||||
if let Some(zone) = reply.zone {
|
||||
Ok(zone.id)
|
||||
} else {
|
||||
Err(anyhow!("unable to resolve guest '{}'", name))
|
||||
Err(anyhow!("unable to resolve zone '{}'", name))
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest};
|
||||
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest};
|
||||
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Resolve a guest name to a uuid")]
|
||||
#[command(about = "Resolve a zone name to a uuid")]
|
||||
pub struct ResolveCommand {
|
||||
#[arg(help = "Guest name")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone name")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl ResolveCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
name: self.guest.clone(),
|
||||
.resolve_zone(Request::new(ResolveZoneRequest {
|
||||
name: self.zone.clone(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
if let Some(guest) = reply.guest {
|
||||
println!("{}", guest.id);
|
||||
if let Some(zone) = reply.zone {
|
||||
println!("{}", zone.id);
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
@ -24,14 +24,14 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
format::guest_status_text,
|
||||
format::zone_status_text,
|
||||
metrics::{
|
||||
lookup_metric_value, MultiMetricCollector, MultiMetricCollectorHandle, MultiMetricState,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Dashboard for running guests")]
|
||||
#[command(about = "Dashboard for running zones")]
|
||||
pub struct TopCommand {}
|
||||
|
||||
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||
@ -46,7 +46,7 @@ impl TopCommand {
|
||||
let collector = collector.launch().await?;
|
||||
let mut tui = TopCommand::init()?;
|
||||
let mut app = TopApp {
|
||||
metrics: MultiMetricState { guests: vec![] },
|
||||
metrics: MultiMetricState { zones: vec![] },
|
||||
exit: false,
|
||||
table: TableState::new(),
|
||||
};
|
||||
@ -152,12 +152,12 @@ impl Widget for &mut TopApp {
|
||||
|
||||
let mut rows = vec![];
|
||||
|
||||
for ms in &self.metrics.guests {
|
||||
let Some(ref spec) = ms.guest.spec else {
|
||||
for ms in &self.metrics.zones {
|
||||
let Some(ref spec) = ms.zone.spec else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref state) = ms.guest.state else {
|
||||
let Some(ref state) = ms.zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -176,8 +176,8 @@ impl Widget for &mut TopApp {
|
||||
|
||||
let row = Row::new(vec![
|
||||
spec.name.clone(),
|
||||
ms.guest.id.clone(),
|
||||
guest_status_text(state.status()),
|
||||
ms.zone.id.clone(),
|
||||
zone_status_text(state.status()),
|
||||
memory_total.unwrap_or_default(),
|
||||
memory_used.unwrap_or_default(),
|
||||
memory_free.unwrap_or_default(),
|
||||
|
@ -2,12 +2,12 @@ use anyhow::Result;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{common::Guest, control::watch_events_reply::Event},
|
||||
v1::{common::Zone, control::watch_events_reply::Event},
|
||||
};
|
||||
use prost_reflect::ReflectMessage;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::format::{guest_simple_line, kv2line, proto2dynamic, proto2kv};
|
||||
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum WatchFormat {
|
||||
@ -17,7 +17,7 @@ enum WatchFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Watch for guest changes")]
|
||||
#[command(about = "Watch for zone changes")]
|
||||
pub struct WatchCommand {
|
||||
#[arg(short, long, default_value = "simple", help = "Output format")]
|
||||
format: WatchFormat,
|
||||
@ -29,22 +29,17 @@ impl WatchCommand {
|
||||
loop {
|
||||
let event = stream.recv().await?;
|
||||
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let guest = changed.guest.clone();
|
||||
self.print_event("guest.changed", changed, guest)?;
|
||||
let Event::ZoneChanged(changed) = event;
|
||||
let zone = changed.zone.clone();
|
||||
self.print_event("zone.changed", changed, zone)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn print_event(
|
||||
&self,
|
||||
typ: &str,
|
||||
event: impl ReflectMessage,
|
||||
guest: Option<Guest>,
|
||||
) -> Result<()> {
|
||||
fn print_event(&self, typ: &str, event: impl ReflectMessage, zone: Option<Zone>) -> Result<()> {
|
||||
match self.format {
|
||||
WatchFormat::Simple => {
|
||||
if let Some(guest) = guest {
|
||||
println!("{}", guest_simple_line(&guest));
|
||||
if let Some(zone) = zone {
|
||||
println!("{}", zone_simple_line(&zone));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user