allow input in `trvmctl run`

This commit is contained in:
Ryan Heywood 2025-05-12 00:01:10 -04:00
parent cfe35b3edb
commit bc41781df0
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 28 additions and 3 deletions

View File

@ -56,6 +56,10 @@ pub enum Commands {
}, },
/// Run a command in a currently running VM. /// Run a command in a currently running VM.
///
/// If standard input is not a terminal, all standard input is collected before invoking the
/// program. All standard output is collected before returning from the function. Interactive
/// programs are not possible at this time..
Run { Run {
/// The command to run. /// The command to run.
command: String, command: String,

View File

@ -2,13 +2,14 @@
use clap::Parser; use clap::Parser;
use eyre::WrapErr; use eyre::WrapErr;
use std::io::Write; use std::io::{IsTerminal, Read, Write};
mod cli; mod cli;
mod vm; mod vm;
use vm::{SpawnArguments, VirtualMachine}; use vm::{SpawnArguments, VirtualMachine};
#[allow(clippy::too_many_lines)]
fn main() -> eyre::Result<()> { fn main() -> eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
@ -54,7 +55,7 @@ fn main() -> eyre::Result<()> {
.ok_or(eyre::eyre!("no hostname"))? .ok_or(eyre::eyre!("no hostname"))?
.as_str() .as_str()
.ok_or(eyre::eyre!("hostname is not str"))?; .ok_or(eyre::eyre!("hostname is not str"))?;
let uptime = vm.run_command("uptime", [])?; let uptime = vm.run_command("uptime", [], None)?;
eprintln!("hostname: {hostname}"); eprintln!("hostname: {hostname}");
eprint!("{}", String::from_utf8_lossy(&uptime.0)); eprint!("{}", String::from_utf8_lossy(&uptime.0));
} }
@ -91,7 +92,24 @@ fn main() -> eyre::Result<()> {
cli::Commands::Run { command, args } => { cli::Commands::Run { command, args } => {
let spawn_arguments = SpawnArguments::default(); let spawn_arguments = SpawnArguments::default();
let mut vm = VirtualMachine::load(spawn_arguments, None)?; let mut vm = VirtualMachine::load(spawn_arguments, None)?;
let (response, exit_code) = vm.run_command(&command, args)?;
let mut stdin = std::io::stdin();
let buffered_stdin = if stdin.is_terminal() {
None
} else {
let mut bytes = vec![];
stdin
.read_to_end(&mut bytes)
.context(eyre::eyre!("could not read input"))?;
if bytes.is_empty() {
None
} else {
Some(bytes)
}
};
let (response, exit_code) =
vm.run_command(&command, args, buffered_stdin.as_deref())?;
std::io::stdout().write_all(&response)?; std::io::stdout().write_all(&response)?;
std::process::exit( std::process::exit(
i32::try_from(exit_code).context(eyre::eyre!("bad PID: pid < i32::MAX << 1"))?, i32::try_from(exit_code).context(eyre::eyre!("bad PID: pid < i32::MAX << 1"))?,

View File

@ -574,13 +574,16 @@ impl VirtualMachine {
&mut self, &mut self,
command: &str, command: &str,
args: impl IntoIterator<Item = String>, args: impl IntoIterator<Item = String>,
stdin: Option<&[u8]>,
) -> Result<(Vec<u8>, u64)> { ) -> Result<(Vec<u8>, u64)> {
let args = args.into_iter().collect::<Vec<_>>(); let args = args.into_iter().collect::<Vec<_>>();
let input = stdin.map(|bytes| BASE64_STANDARD.encode(bytes));
let payload = serde_json::json!({ let payload = serde_json::json!({
"path": command, "path": command,
"arg": args, "arg": args,
"capture-output": true, "capture-output": true,
"input-data": input,
}); });
let bar = spinner(format!("Running: {command:?} {args:?}")); let bar = spinner(format!("Running: {command:?} {args:?}"));