Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker

feat: add cp command for file transfer to/from Firecracker MicroVM and refactor exec command

+119 -27
+1
README.md
··· 69 69 serve Start fireup HTTP API server 70 70 inspect Inspect the Firecracker MicroVM details 71 71 exec Execute a command inside the Firecracker MicroVM 72 + cp Copy files to/from the Firecracker MicroVM 72 73 help Print this message or the help of the given subcommand(s) 73 74 74 75 Options:
+77
crates/firecracker-up/src/cmd/cp.rs
··· 1 + use anyhow::Error; 2 + use owo_colors::OwoColorize; 1 3 4 + use crate::{command::run_command_with_stdout_inherit, ssh::get_private_key_path}; 5 + 6 + pub async fn cp(from: &str, to: &str) -> Result<(), Error> { 7 + let pool = firecracker_state::create_connection_pool().await?; 8 + 9 + let vm_name = if from.contains(':') { 10 + from.split(':').next().unwrap_or("") 11 + } else if to.contains(':') { 12 + to.split(':').next().unwrap_or("") 13 + } else { 14 + return Err(anyhow::anyhow!( 15 + "Either source or destination must be in the format <vm_name>:<path>" 16 + )); 17 + }; 18 + 19 + let vm = firecracker_state::repo::virtual_machine::find(&pool, vm_name).await?; 20 + 21 + if vm.is_none() { 22 + println!("[-] MicroVM '{}' not found.", vm_name); 23 + std::process::exit(1); 24 + } 25 + 26 + if !firecracker_process::vm_is_running(vm_name).await? { 27 + println!("[-] MicroVM '{}' is not running.", vm_name); 28 + let start_cmd = format!("fireup start {}", vm_name); 29 + println!(" Start it with {}", start_cmd.cyan()); 30 + std::process::exit(1); 31 + } 32 + 33 + let guest_ip = format!("{}.firecracker", vm_name); 34 + let key_path = get_private_key_path()?; 35 + 36 + let scp_args = if from.contains(':') { 37 + let remote_path = format!( 38 + "root@{}:{}", 39 + guest_ip, 40 + from.splitn(2, ':').nth(1).unwrap() 41 + ); 42 + vec!["-r", remote_path.as_str(), to] 43 + .iter() 44 + .map(|s| s.to_string()) 45 + .collect::<Vec<String>>() 46 + } else { 47 + let remote_path = format!( 48 + "root@{}:{}", 49 + guest_ip, 50 + to.splitn(2, ':').nth(1).unwrap() 51 + ); 52 + vec!["-r", from, remote_path.as_str()] 53 + .iter() 54 + .map(|s| s.to_string()) 55 + .collect::<Vec<String>>() 56 + }; 57 + 58 + run_command_with_stdout_inherit( 59 + "scp", 60 + &[ 61 + "-q", 62 + "-i", 63 + &key_path, 64 + "-o", 65 + "StrictHostKeyChecking=no", 66 + "-o", 67 + "UserKnownHostsFile=/dev/null", 68 + ] 69 + .iter() 70 + .copied() 71 + .chain(scp_args.iter().map(|s| s.as_str())) 72 + .collect::<Vec<&str>>() 73 + .as_slice(), 74 + false, 75 + )?; 76 + 77 + Ok(()) 78 + }
+2 -23
crates/firecracker-up/src/cmd/exec.rs
··· 1 - use anyhow::{anyhow, Context, Error, Result}; 1 + use anyhow::{Error, Result}; 2 2 use firecracker_state::repo; 3 3 use owo_colors::OwoColorize; 4 - use std::fs; 5 4 6 - use crate::command::run_ssh_command; 5 + use crate::{command::run_ssh_command, ssh::get_private_key_path}; 7 6 8 7 pub async fn exec(name: &str, args: Vec<String>) -> Result<(), Error> { 9 8 let pool = firecracker_state::create_connection_pool().await?; ··· 26 25 27 26 Ok(()) 28 27 } 29 - 30 - fn get_private_key_path() -> Result<String, Error> { 31 - let home_dir = dirs::home_dir().ok_or_else(|| anyhow!("Failed to get home directory"))?; 32 - let app_dir = format!("{}/.fireup", home_dir.display()); 33 - let key_name = glob::glob(format!("{}/id_rsa", app_dir).as_str()) 34 - .with_context(|| "Failed to glob ssh key files")? 35 - .last() 36 - .ok_or_else(|| anyhow!("No SSH key file found"))? 37 - .with_context(|| "Failed to get SSH key path")?; 38 - let key_name = fs::canonicalize(&key_name) 39 - .with_context(|| { 40 - format!( 41 - "Failed to resolve absolute path for SSH key: {}", 42 - key_name.display() 43 - ) 44 - })? 45 - .display() 46 - .to_string(); 47 - Ok(key_name) 48 - }
+16 -4
crates/firecracker-up/src/main.rs
··· 4 4 use owo_colors::OwoColorize; 5 5 6 6 use crate::cmd::{ 7 - down::down, init::init, inspect::inspect_microvm, logs::logs, ps::list_all_instances, 8 - reset::reset, rm::remove, serve::serve, ssh::ssh, start::start, status::status, stop::stop, 9 - up::up, 7 + cp::cp, down::down, exec::exec, init::init, inspect::inspect_microvm, logs::logs, 8 + ps::list_all_instances, reset::reset, rm::remove, serve::serve, ssh::ssh, start::start, 9 + status::status, stop::stop, up::up, 10 10 }; 11 11 12 12 pub mod cmd; 13 13 pub mod command; 14 14 pub mod config; 15 15 pub mod date; 16 + pub mod ssh; 16 17 17 18 fn cli() -> Command { 18 19 let banner = format!( ··· 180 181 ) 181 182 .about("Execute a command inside the Firecracker MicroVM"), 182 183 ) 184 + .subcommand( 185 + Command::new("cp") 186 + .arg(arg!(<source> "Source file path").required(true)) 187 + .arg(arg!(<destination> "Destination file path").required(true)) 188 + .about("Copy files to/from the Firecracker MicroVM"), 189 + ) 183 190 .arg(arg!(--debian "Prepare Debian MicroVM").default_value("false")) 184 191 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 185 192 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) ··· 362 369 .unwrap() 363 370 .map(|s| s.to_string()) 364 371 .collect(); 365 - cmd::exec::exec(&name, cmd_args).await?; 372 + exec(&name, cmd_args).await?; 373 + } 374 + Some(("cp", args)) => { 375 + let source = args.get_one::<String>("source").cloned().unwrap(); 376 + let destination = args.get_one::<String>("destination").cloned().unwrap(); 377 + cp(&source, &destination).await?; 366 378 } 367 379 _ => { 368 380 let debian = matches.get_one::<bool>("debian").copied().unwrap_or(false);
+23
crates/firecracker-up/src/ssh.rs
··· 1 + use anyhow::Error; 2 + use anyhow::{anyhow, Context, Result}; 3 + use std::fs; 4 + 5 + pub fn get_private_key_path() -> Result<String, Error> { 6 + let home_dir = dirs::home_dir().ok_or_else(|| anyhow!("Failed to get home directory"))?; 7 + let app_dir = format!("{}/.fireup", home_dir.display()); 8 + let key_name = glob::glob(format!("{}/id_rsa", app_dir).as_str()) 9 + .with_context(|| "Failed to glob ssh key files")? 10 + .last() 11 + .ok_or_else(|| anyhow!("No SSH key file found"))? 12 + .with_context(|| "Failed to get SSH key path")?; 13 + let key_name = fs::canonicalize(&key_name) 14 + .with_context(|| { 15 + format!( 16 + "Failed to resolve absolute path for SSH key: {}", 17 + key_name.display() 18 + ) 19 + })? 20 + .display() 21 + .to_string(); 22 + Ok(key_name) 23 + }