From 7543fccfaf049e569441661bc4fc086baf4d0f0d Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 23 Mar 2024 10:09:00 +0000 Subject: [PATCH] kratactl: make things nicer to use from a scripting standpoint --- crates/kratactl/src/cli/destroy.rs | 62 +++++++++++++++++++++++++++--- crates/kratactl/src/cli/launch.rs | 10 ++++- crates/kratactl/src/cli/list.rs | 43 +++++++++++++++------ crates/kratactl/src/cli/mod.rs | 4 +- 4 files changed, 99 insertions(+), 20 deletions(-) diff --git a/crates/kratactl/src/cli/destroy.rs b/crates/kratactl/src/cli/destroy.rs index 6b237da..c04b347 100644 --- a/crates/kratactl/src/cli/destroy.rs +++ b/crates/kratactl/src/cli/destroy.rs @@ -1,25 +1,77 @@ use anyhow::Result; use clap::Parser; -use krata::control::{control_service_client::ControlServiceClient, DestroyGuestRequest}; +use krata::{ + common::GuestStatus, + control::{ + control_service_client::ControlServiceClient, watch_events_reply::Event, + DestroyGuestRequest, + }, +}; +use log::error; use tonic::{transport::Channel, Request}; -use crate::cli::resolve_guest; +use crate::{cli::resolve_guest, events::EventStream}; #[derive(Parser)] pub struct DestroyCommand { + #[arg(short = 'W', long)] + wait: bool, #[arg()] guest: String, } impl DestroyCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { + pub async fn run( + self, + mut client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { let guest_id: String = resolve_guest(&mut client, &self.guest).await?; let _ = client - .destroy_guest(Request::new(DestroyGuestRequest { guest_id })) + .destroy_guest(Request::new(DestroyGuestRequest { + guest_id: guest_id.clone(), + })) .await? .into_inner(); - println!("destroyed guest: {}", self.guest); + if self.wait { + wait_guest_destroyed(&guest_id, events).await?; + } Ok(()) } } + +async fn wait_guest_destroyed(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 { + continue; + }; + + if guest.id != id { + continue; + } + + let Some(state) = guest.state else { + continue; + }; + + if let Some(ref error) = state.error_info { + if state.status() == GuestStatus::Failed { + error!("destroy failed: {}", error.message); + std::process::exit(1); + } else { + error!("guest error: {}", error.message); + } + } + + if state.status() == GuestStatus::Destroyed { + std::process::exit(0); + } + } + } + } + Ok(()) +} diff --git a/crates/kratactl/src/cli/launch.rs b/crates/kratactl/src/cli/launch.rs index 71ced84..66694e1 100644 --- a/crates/kratactl/src/cli/launch.rs +++ b/crates/kratactl/src/cli/launch.rs @@ -24,6 +24,8 @@ pub struct LauchCommand { env: Option>, #[arg(short, long)] attach: bool, + #[arg(short = 'W', long)] + wait: bool, #[arg()] oci: String, #[arg(allow_hyphen_values = true, trailing_var_arg = true)] @@ -53,8 +55,12 @@ impl LauchCommand { .await? .into_inner(); let id = response.guest_id; - let code = if self.attach { + + if self.wait || self.attach { wait_guest_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 stdout_handle = @@ -68,7 +74,7 @@ impl LauchCommand { x = exit_hook_task => x? } } else { - println!("created guest: {}", id); + println!("{}", id); None }; StdioConsoleStream::restore_terminal_mode(); diff --git a/crates/kratactl/src/cli/list.rs b/crates/kratactl/src/cli/list.rs index 1ba2546..8526191 100644 --- a/crates/kratactl/src/cli/list.rs +++ b/crates/kratactl/src/cli/list.rs @@ -1,9 +1,11 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::{Parser, ValueEnum}; use cli_tables::Table; use krata::{ common::{guest_image_spec::Image, Guest}, - control::{control_service_client::ControlServiceClient, ListGuestsRequest}, + control::{ + control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest, + }, }; use serde_json::Value; @@ -28,6 +30,8 @@ enum ListFormat { pub struct ListCommand { #[arg(short, long, default_value = "cli-table")] format: ListFormat, + #[arg()] + guest: Option, } impl ListCommand { @@ -36,19 +40,34 @@ impl ListCommand { mut client: ControlServiceClient, _events: EventStream, ) -> Result<()> { - let response = client - .list_guests(Request::new(ListGuestsRequest {})) - .await? - .into_inner(); + let 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 + }; match self.format { ListFormat::CliTable => { - self.print_guest_table(response.guests)?; + self.print_guest_table(guests)?; } ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => { let mut values = Vec::new(); - for guest in response.guests { + for guest in guests { let message = proto2dynamic(guest)?; values.push(serde_json::to_value(message)?); } @@ -64,14 +83,14 @@ impl ListCommand { } ListFormat::Jsonl => { - for guest in response.guests { + for guest in guests { let message = proto2dynamic(guest)?; println!("{}", serde_json::to_string(&message)?); } } ListFormat::KeyValue => { - self.print_key_value(response.guests)?; + self.print_key_value(guests)?; } } @@ -118,7 +137,9 @@ impl ListCommand { ])?; } if table.num_records() == 1 { - println!("no guests have been launched"); + if self.guest.is_none() { + println!("no guests have been launched"); + } } else { println!("{}", table.to_string()); } diff --git a/crates/kratactl/src/cli/mod.rs b/crates/kratactl/src/cli/mod.rs index 63bf5e6..22ba98f 100644 --- a/crates/kratactl/src/cli/mod.rs +++ b/crates/kratactl/src/cli/mod.rs @@ -56,7 +56,7 @@ impl ControlCommand { } Commands::Destroy(destroy) => { - destroy.run(client).await?; + destroy.run(client, events).await?; } Commands::Attach(attach) => { @@ -93,6 +93,6 @@ pub async fn resolve_guest( if let Some(guest) = reply.guest { Ok(guest.id) } else { - Err(anyhow!("unable to resolve guest {}", name)) + Err(anyhow!("unable to resolve guest '{}'", name)) } }