mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
kratactl: implement guest resolution and rename console to attach
This commit is contained in:
parent
7b2de22320
commit
e25cbf087d
@ -22,6 +22,14 @@ message ListGuestsReply {
|
|||||||
repeated krata.common.Guest guests = 1;
|
repeated krata.common.Guest guests = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ResolveGuestRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveGuestReply {
|
||||||
|
krata.common.Guest guest = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message DestroyGuestRequest {
|
message DestroyGuestRequest {
|
||||||
string guest_id = 1;
|
string guest_id = 1;
|
||||||
}
|
}
|
||||||
@ -53,6 +61,7 @@ service ControlService {
|
|||||||
rpc CreateGuest(CreateGuestRequest) returns (CreateGuestReply);
|
rpc CreateGuest(CreateGuestRequest) returns (CreateGuestReply);
|
||||||
rpc DestroyGuest(DestroyGuestRequest) returns (DestroyGuestReply);
|
rpc DestroyGuest(DestroyGuestRequest) returns (DestroyGuestReply);
|
||||||
rpc ListGuests(ListGuestsRequest) returns (ListGuestsReply);
|
rpc ListGuests(ListGuestsRequest) returns (ListGuestsReply);
|
||||||
|
rpc ResolveGuest(ResolveGuestRequest) returns (ResolveGuestReply);
|
||||||
rpc ConsoleData(stream ConsoleDataRequest) returns (stream ConsoleDataReply);
|
rpc ConsoleData(stream ConsoleDataRequest) returns (stream ConsoleDataReply);
|
||||||
rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply);
|
rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply);
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,26 @@ use tonic::transport::Channel;
|
|||||||
|
|
||||||
use crate::{console::StdioConsoleStream, events::EventStream};
|
use crate::{console::StdioConsoleStream, events::EventStream};
|
||||||
|
|
||||||
|
use super::resolve_guest;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct ConsoleCommand {
|
pub struct AttachCommand {
|
||||||
#[arg()]
|
#[arg()]
|
||||||
guest: String,
|
guest: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConsoleCommand {
|
impl AttachCommand {
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
self,
|
self,
|
||||||
mut client: ControlServiceClient<Channel>,
|
mut client: ControlServiceClient<Channel>,
|
||||||
events: EventStream,
|
events: EventStream,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let input = StdioConsoleStream::stdin_stream(self.guest.clone()).await;
|
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 output = client.console_data(input).await?.into_inner();
|
||||||
let stdout_handle =
|
let stdout_handle =
|
||||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||||
let exit_hook_task =
|
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
|
||||||
StdioConsoleStream::guest_exit_hook(self.guest.clone(), events).await?;
|
|
||||||
let code = select! {
|
let code = select! {
|
||||||
x = stdout_handle => {
|
x = stdout_handle => {
|
||||||
x??;
|
x??;
|
@ -4,7 +4,7 @@ use krata::control::{control_service_client::ControlServiceClient, DestroyGuestR
|
|||||||
|
|
||||||
use tonic::{transport::Channel, Request};
|
use tonic::{transport::Channel, Request};
|
||||||
|
|
||||||
use crate::events::EventStream;
|
use crate::cli::resolve_guest;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct DestroyCommand {
|
pub struct DestroyCommand {
|
||||||
@ -13,15 +13,10 @@ pub struct DestroyCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DestroyCommand {
|
impl DestroyCommand {
|
||||||
pub async fn run(
|
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||||
self,
|
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||||
mut client: ControlServiceClient<Channel>,
|
|
||||||
_events: EventStream,
|
|
||||||
) -> Result<()> {
|
|
||||||
let _ = client
|
let _ = client
|
||||||
.destroy_guest(Request::new(DestroyGuestRequest {
|
.destroy_guest(Request::new(DestroyGuestRequest { guest_id }))
|
||||||
guest_id: self.guest.clone(),
|
|
||||||
}))
|
|
||||||
.await?
|
.await?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
println!("destroyed guest: {}", self.guest);
|
println!("destroyed guest: {}", self.guest);
|
||||||
|
@ -11,11 +11,9 @@ use tonic::{transport::Channel, Request};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::EventStream,
|
events::EventStream,
|
||||||
format::{kv2line, proto2dynamic, proto2kv},
|
format::{guest_state_text, kv2line, proto2dynamic, proto2kv},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::pretty::guest_state_text;
|
|
||||||
|
|
||||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||||
enum ListFormat {
|
enum ListFormat {
|
||||||
CliTable,
|
CliTable,
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
pub mod console;
|
pub mod attach;
|
||||||
pub mod destroy;
|
pub mod destroy;
|
||||||
pub mod launch;
|
pub mod launch;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod pretty;
|
pub mod resolve;
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use krata::control::WatchEventsRequest;
|
use krata::control::{
|
||||||
|
control_service_client::ControlServiceClient, ResolveGuestRequest, WatchEventsRequest,
|
||||||
|
};
|
||||||
|
use tonic::{transport::Channel, Request};
|
||||||
|
|
||||||
use crate::{client::ControlClientProvider, events::EventStream};
|
use crate::{client::ControlClientProvider, events::EventStream};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
console::ConsoleCommand, destroy::DestroyCommand, launch::LauchCommand, list::ListCommand,
|
attach::AttachCommand, destroy::DestroyCommand, launch::LauchCommand, list::ListCommand,
|
||||||
watch::WatchCommand,
|
resolve::ResolveCommand, watch::WatchCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -31,8 +34,9 @@ pub enum Commands {
|
|||||||
Launch(LauchCommand),
|
Launch(LauchCommand),
|
||||||
Destroy(DestroyCommand),
|
Destroy(DestroyCommand),
|
||||||
List(ListCommand),
|
List(ListCommand),
|
||||||
Console(ConsoleCommand),
|
Attach(AttachCommand),
|
||||||
Watch(WatchCommand),
|
Watch(WatchCommand),
|
||||||
|
Resolve(ResolveCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlCommand {
|
impl ControlCommand {
|
||||||
@ -52,11 +56,11 @@ impl ControlCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Commands::Destroy(destroy) => {
|
Commands::Destroy(destroy) => {
|
||||||
destroy.run(client, events).await?;
|
destroy.run(client).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Console(console) => {
|
Commands::Attach(attach) => {
|
||||||
console.run(client, events).await?;
|
attach.run(client, events).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::List(list) => {
|
Commands::List(list) => {
|
||||||
@ -66,7 +70,29 @@ impl ControlCommand {
|
|||||||
Commands::Watch(watch) => {
|
Commands::Watch(watch) => {
|
||||||
watch.run(events).await?;
|
watch.run(events).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Commands::Resolve(resolve) => {
|
||||||
|
resolve.run(client).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_guest(
|
||||||
|
client: &mut ControlServiceClient<Channel>,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
let reply = client
|
||||||
|
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||||
|
name: name.to_string(),
|
||||||
|
}))
|
||||||
|
.await?
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
|
if let Some(guest) = reply.guest {
|
||||||
|
Ok(guest.id)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("unable to resolve guest {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
use krata::common::{GuestState, GuestStatus};
|
|
||||||
|
|
||||||
pub fn guest_status_text(status: GuestStatus) -> String {
|
|
||||||
match status {
|
|
||||||
GuestStatus::Starting => "starting",
|
|
||||||
GuestStatus::Started => "started",
|
|
||||||
GuestStatus::Destroying => "destroying",
|
|
||||||
GuestStatus::Destroyed => "destroyed",
|
|
||||||
GuestStatus::Exited => "exited",
|
|
||||||
GuestStatus::Failed => "failed",
|
|
||||||
_ => "unknown",
|
|
||||||
}
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn guest_state_text(state: Option<&GuestState>) -> String {
|
|
||||||
let state = state.cloned().unwrap_or_default();
|
|
||||||
let mut text = guest_status_text(state.status());
|
|
||||||
|
|
||||||
if let Some(exit) = state.exit_info {
|
|
||||||
text.push_str(&format!(" (exit code: {})", exit.code));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(error) = state.error_info {
|
|
||||||
text.push_str(&format!(" (error: {})", error.message));
|
|
||||||
}
|
|
||||||
text
|
|
||||||
}
|
|
28
crates/kratactl/src/cli/resolve.rs
Normal file
28
crates/kratactl/src/cli/resolve.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use krata::control::{control_service_client::ControlServiceClient, ResolveGuestRequest};
|
||||||
|
|
||||||
|
use tonic::{transport::Channel, Request};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct ResolveCommand {
|
||||||
|
#[arg()]
|
||||||
|
guest: 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(),
|
||||||
|
}))
|
||||||
|
.await?
|
||||||
|
.into_inner();
|
||||||
|
if let Some(guest) = reply.guest {
|
||||||
|
println!("{}", guest.id);
|
||||||
|
} else {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,8 @@ use prost_reflect::ReflectMessage;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::pretty::guest_state_text,
|
|
||||||
events::EventStream,
|
events::EventStream,
|
||||||
format::{kv2line, proto2dynamic, proto2kv},
|
format::{guest_state_text, kv2line, proto2dynamic, proto2kv},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use krata::common::{GuestState, GuestStatus};
|
||||||
use prost_reflect::{DynamicMessage, ReflectMessage, Value};
|
use prost_reflect::{DynamicMessage, ReflectMessage, Value};
|
||||||
|
|
||||||
pub fn proto2dynamic(proto: impl ReflectMessage) -> Result<DynamicMessage> {
|
pub fn proto2dynamic(proto: impl ReflectMessage) -> Result<DynamicMessage> {
|
||||||
@ -56,3 +57,30 @@ pub fn kv2line(map: HashMap<String, String>) -> String {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn guest_status_text(status: GuestStatus) -> String {
|
||||||
|
match status {
|
||||||
|
GuestStatus::Starting => "starting",
|
||||||
|
GuestStatus::Started => "started",
|
||||||
|
GuestStatus::Destroying => "destroying",
|
||||||
|
GuestStatus::Destroyed => "destroyed",
|
||||||
|
GuestStatus::Exited => "exited",
|
||||||
|
GuestStatus::Failed => "failed",
|
||||||
|
_ => "unknown",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guest_state_text(state: Option<&GuestState>) -> String {
|
||||||
|
let state = state.cloned().unwrap_or_default();
|
||||||
|
let mut text = guest_status_text(state.status());
|
||||||
|
|
||||||
|
if let Some(exit) = state.exit_info {
|
||||||
|
text.push_str(&format!(" (exit code: {})", exit.code));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(error) = state.error_info {
|
||||||
|
text.push_str(&format!(" (error: {})", error.message));
|
||||||
|
}
|
||||||
|
text
|
||||||
|
}
|
||||||
|
@ -7,7 +7,8 @@ use krata::{
|
|||||||
control::{
|
control::{
|
||||||
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
|
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
|
||||||
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
|
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
|
||||||
ListGuestsReply, ListGuestsRequest, WatchEventsReply, WatchEventsRequest,
|
ListGuestsReply, ListGuestsRequest, ResolveGuestReply, ResolveGuestRequest,
|
||||||
|
WatchEventsReply, WatchEventsRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use kratart::Runtime;
|
use kratart::Runtime;
|
||||||
@ -179,6 +180,26 @@ impl ControlService for RuntimeControlService {
|
|||||||
Ok(Response::new(ListGuestsReply { guests }))
|
Ok(Response::new(ListGuestsReply { guests }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_guest(
|
||||||
|
&self,
|
||||||
|
request: Request<ResolveGuestRequest>,
|
||||||
|
) -> Result<Response<ResolveGuestReply>, Status> {
|
||||||
|
let request = request.into_inner();
|
||||||
|
let guests = self.guests.list().await.map_err(ApiError::from)?;
|
||||||
|
let guests = guests
|
||||||
|
.into_values()
|
||||||
|
.filter_map(|entry| entry.guest)
|
||||||
|
.filter(|x| {
|
||||||
|
let comparison_spec = x.spec.as_ref().cloned().unwrap_or_default();
|
||||||
|
(!request.name.is_empty() && comparison_spec.name == request.name)
|
||||||
|
|| x.id == request.name
|
||||||
|
})
|
||||||
|
.collect::<Vec<Guest>>();
|
||||||
|
Ok(Response::new(ResolveGuestReply {
|
||||||
|
guest: guests.first().cloned(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
async fn console_data(
|
async fn console_data(
|
||||||
&self,
|
&self,
|
||||||
request: Request<Streaming<ConsoleDataRequest>>,
|
request: Request<Streaming<ConsoleDataRequest>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user