Files
krata/crates/ctl/src/cli/launch.rs

193 lines
5.8 KiB
Rust
Raw Normal View History

2024-03-24 05:25:48 +00:00
use std::collections::HashMap;
2024-03-15 15:59:18 +00:00
use anyhow::Result;
use clap::Parser;
use krata::{
events::EventStream,
v1::{
common::{
guest_image_spec::Image, GuestImageSpec, GuestOciImageFormat, GuestOciImageSpec,
GuestSpec, GuestStatus, GuestTaskSpec, GuestTaskSpecEnvVar,
},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
CreateGuestRequest, PullImageRequest,
},
2024-03-15 15:59:18 +00:00
},
};
use log::error;
use tokio::select;
2024-03-15 15:59:18 +00:00
use tonic::{transport::Channel, Request};
use crate::{console::StdioConsoleStream, pull::pull_interactive_progress};
use super::pull::PullImageFormat;
2024-03-15 15:59:18 +00:00
#[derive(Parser)]
#[command(about = "Launch a new guest")]
2024-03-15 15:59:18 +00:00
pub struct LauchCommand {
#[arg(short = 'S', long, default_value = "squashfs", help = "Image format")]
image_format: PullImageFormat,
#[arg(short, long, help = "Name of the guest")]
2024-03-15 15:59:18 +00:00
name: Option<String>,
#[arg(
short,
long,
default_value_t = 1,
help = "vCPUs available to the guest"
)]
2024-03-15 15:59:18 +00:00
cpus: u32,
#[arg(
short,
long,
default_value_t = 512,
help = "Memory available to the guest, in megabytes"
)]
2024-03-15 15:59:18 +00:00
mem: u64,
#[arg[short, long, help = "Environment variables set in the guest"]]
2024-03-15 15:59:18 +00:00
env: Option<Vec<String>>,
#[arg(
short,
long,
help = "Attach to the guest after guest starts, implies --wait"
)]
2024-03-15 15:59:18 +00:00
attach: bool,
#[arg(
short = 'W',
long,
help = "Wait for the guest to start, implied by --attach"
)]
wait: bool,
#[arg(help = "Container image for guest to use")]
2024-03-15 15:59:18 +00:00
oci: String,
#[arg(
allow_hyphen_values = true,
trailing_var_arg = true,
help = "Command to run inside the guest"
)]
command: Vec<String>,
2024-03-15 15:59:18 +00:00
}
impl LauchCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let response = client
.pull_image(PullImageRequest {
image: self.oci.clone(),
format: match self.image_format {
PullImageFormat::Squashfs => GuestOciImageFormat::Squashfs.into(),
PullImageFormat::Erofs => GuestOciImageFormat::Erofs.into(),
},
})
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;
2024-03-15 15:59:18 +00:00
let request = CreateGuestRequest {
spec: Some(GuestSpec {
name: self.name.unwrap_or_default(),
image: Some(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec {
digest: reply.digest,
format: reply.format,
})),
2024-03-15 15:59:18 +00:00
}),
vcpus: self.cpus,
mem: self.mem,
task: Some(GuestTaskSpec {
environment: env_map(&self.env.unwrap_or_default())
.iter()
.map(|(key, value)| GuestTaskSpecEnvVar {
key: key.clone(),
value: value.clone(),
})
.collect(),
command: self.command,
}),
annotations: vec![],
2024-03-15 15:59:18 +00:00
}),
};
let response = client
.create_guest(Request::new(request))
.await?
.into_inner();
let id = response.guest_id;
if self.wait || self.attach {
2024-03-15 15:59:18 +00:00
wait_guest_started(&id, events.clone()).await?;
}
let code = if self.attach {
2024-03-15 15:59:18 +00:00
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
let output = client.console_data(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
2024-03-15 15:59:18 +00:00
let exit_hook_task = StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
select! {
x = stdout_handle => {
x??;
None
},
x = exit_hook_task => x?
}
2024-03-15 15:59:18 +00:00
} else {
println!("{}", id);
None
};
2024-03-21 22:49:37 -07:00
StdioConsoleStream::restore_terminal_mode();
std::process::exit(code.unwrap_or(0));
2024-03-15 15:59:18 +00:00
}
}
async fn wait_guest_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 {
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!("launch failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
}
2024-03-15 15:59:18 +00:00
}
if state.status() == GuestStatus::Destroyed {
error!("guest destroyed");
std::process::exit(1);
}
if state.status() == GuestStatus::Started {
break;
}
}
}
}
Ok(())
}
2024-03-24 05:25:48 +00:00
fn env_map(env: &[String]) -> HashMap<String, String> {
let mut map = HashMap::<String, String>::new();
for item in env {
if let Some((key, value)) = item.split_once('=') {
map.insert(key.to_string(), value.to_string());
}
}
map
}