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

feat: add etcd configuration support and update related functionalities

+123 -25
+11
crates/fire-config/src/lib.rs
··· 5 5 use owo_colors::OwoColorize; 6 6 use serde::{Deserialize, Serialize}; 7 7 8 + #[derive(Debug, Clone, Serialize, Deserialize)] 9 + pub struct EtcdConfig { 10 + pub endpoints: Option<Vec<String>>, 11 + pub user: Option<String>, 12 + pub password: Option<String>, 13 + pub cacert: Option<String>, 14 + pub cert: Option<String>, 15 + } 16 + 8 17 #[derive(Debug, Serialize, Deserialize)] 9 18 pub struct Vm { 10 19 pub vcpu: Option<u16>, ··· 22 31 pub struct FireConfig { 23 32 pub distro: Distro, 24 33 pub vm: Vm, 34 + pub etcd: Option<EtcdConfig>, 25 35 } 26 36 27 37 impl Default for FireConfig { ··· 39 49 api_socket: None, 40 50 mac: None, 41 51 }, 52 + etcd: None, 42 53 } 43 54 } 44 55 }
+8 -8
crates/firecracker-prepare/src/downloader.rs
··· 180 180 181 181 pub fn download_fedora_rootfs(_arch: &str) -> Result<()> { 182 182 let app_dir = crate::config::get_config_dir()?; 183 - let output = format!("{}/fedora-rootfs.img", app_dir); 183 + let output = format!("{}/fedora-rootfs.squashfs", app_dir); 184 184 download_file("https://public.rocksky.app/fedora-rootfs.img", &output)?; 185 185 Ok(()) 186 186 } 187 187 188 188 pub fn download_gentoo_rootfs(_arch: &str) -> Result<()> { 189 189 let app_dir = crate::config::get_config_dir()?; 190 - let output = format!("{}/gentoo-rootfs.img", app_dir); 190 + let output = format!("{}/gentoo-rootfs.squashfs", app_dir); 191 191 download_file("https://public.rocksky.app/gentoo-rootfs.img", &output)?; 192 192 Ok(()) 193 193 } 194 194 195 195 pub fn download_slackware_rootfs(_arch: &str) -> Result<()> { 196 196 let app_dir = crate::config::get_config_dir()?; 197 - let output = format!("{}/slackware-rootfs.img", app_dir); 197 + let output = format!("{}/slackware-rootfs.squashfs", app_dir); 198 198 download_file("https://public.rocksky.app/slackware-rootfs.img", &output)?; 199 199 Ok(()) 200 200 } 201 201 202 202 pub fn download_opensuse_rootfs(_arch: &str) -> Result<()> { 203 203 let app_dir = crate::config::get_config_dir()?; 204 - let output = format!("{}/opensuse-rootfs.img", app_dir); 204 + let output = format!("{}/opensuse-rootfs.squashfs", app_dir); 205 205 download_file("https://public.rocksky.app/opensuse-rootfs.img", &output)?; 206 206 Ok(()) 207 207 } 208 208 209 209 pub fn download_opensuse_tumbleweed_rootfs(_arch: &str) -> Result<()> { 210 210 let app_dir = crate::config::get_config_dir()?; 211 - let output = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); 211 + let output = format!("{}/opensuse-tumbleweed-rootfs.squashfs", app_dir); 212 212 download_file( 213 213 "https://public.rocksky.app/opensuse-tumbleweed-rootfs.img", 214 214 &output, ··· 218 218 219 219 pub fn download_almalinux_rootfs(_arch: &str) -> Result<()> { 220 220 let app_dir = crate::config::get_config_dir()?; 221 - let output = format!("{}/almalinux-rootfs.img", app_dir); 221 + let output = format!("{}/almalinux-rootfs.squashfs", app_dir); 222 222 download_file("https://public.rocksky.app/almalinux-rootfs.img", &output)?; 223 223 Ok(()) 224 224 } 225 225 226 226 pub fn download_rockylinux_rootfs(_arch: &str) -> Result<()> { 227 227 let app_dir = crate::config::get_config_dir()?; 228 - let output = format!("{}/rockylinux-rootfs.img", app_dir); 228 + let output = format!("{}/rockylinux-rootfs.squashfs", app_dir); 229 229 download_file("https://public.rocksky.app/rockylinux-rootfs.img", &output)?; 230 230 Ok(()) 231 231 } 232 232 233 233 pub fn download_archlinux_rootfs(_arch: &str) -> Result<()> { 234 234 let app_dir = crate::config::get_config_dir()?; 235 - let output = format!("{}/archlinux-rootfs.img", app_dir); 235 + let output = format!("{}/archlinux-rootfs.squashfs", app_dir); 236 236 download_file("https://public.rocksky.app/archlinux-rootfs.img", &output)?; 237 237 Ok(()) 238 238 }
+48
crates/firecracker-prepare/src/lib.rs
··· 413 413 downloader::download_fedora_rootfs(arch)?; 414 414 rootfs::extract_squashfs(&squashfs_file, &fedora_rootfs)?; 415 415 416 + run_command( 417 + "chroot", 418 + &[&fedora_rootfs, "systemctl", "enable", "sshd"], 419 + true, 420 + )?; 421 + 416 422 let ssh_key_name = "id_rsa"; 417 423 ssh::generate_and_copy_ssh_key(&ssh_key_name, &fedora_rootfs)?; 418 424 ··· 450 456 downloader::download_gentoo_rootfs(arch)?; 451 457 rootfs::extract_squashfs(&squashfs_file, &gentoo_rootfs)?; 452 458 459 + // Enable sshd service 460 + run_command( 461 + "chroot", 462 + &[&gentoo_rootfs, "systemctl", "enable", "sshd"], 463 + true, 464 + )?; 465 + 453 466 let ssh_key_name = "id_rsa"; 454 467 ssh::generate_and_copy_ssh_key(&ssh_key_name, &gentoo_rootfs)?; 455 468 ··· 481 494 downloader::download_slackware_rootfs(arch)?; 482 495 rootfs::extract_squashfs(&squashfs_file, &slackware_rootfs)?; 483 496 497 + run_command( 498 + "chroot", 499 + &[ 500 + &slackware_rootfs, 501 + "ln", 502 + "-sf", 503 + "/etc/rc.d/rc.sshd", 504 + "/etc/rc.d/rc3.d/S50sshd", 505 + ], 506 + true, 507 + )?; 508 + 484 509 let ssh_key_name = "id_rsa"; 485 510 ssh::generate_and_copy_ssh_key(&ssh_key_name, &slackware_rootfs)?; 486 511 ··· 511 536 512 537 downloader::download_opensuse_rootfs(arch)?; 513 538 rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 539 + 540 + run_command( 541 + "chroot", 542 + &[&opensuse_rootfs, "systemctl", "enable", "sshd"], 543 + true, 544 + )?; 514 545 515 546 let ssh_key_name = "id_rsa"; 516 547 ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?; ··· 604 635 downloader::download_archlinux_rootfs(arch)?; 605 636 rootfs::extract_squashfs(&squashfs_file, &archlinux_rootfs)?; 606 637 638 + run_command( 639 + "chroot", 640 + &[&archlinux_rootfs, "systemctl", "enable", "sshd"], 641 + true, 642 + )?; 643 + run_command( 644 + "chroot", 645 + &[&archlinux_rootfs, "systemctl", "mask", "systemd-firstboot"], 646 + true, 647 + )?; 648 + 607 649 let ssh_key_name = "id_rsa"; 608 650 ssh::generate_and_copy_ssh_key(&ssh_key_name, &archlinux_rootfs)?; 609 651 ··· 634 676 635 677 downloader::download_opensuse_tumbleweed_rootfs(arch)?; 636 678 rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 679 + 680 + run_command( 681 + "chroot", 682 + &[&opensuse_rootfs, "systemctl", "enable", "sshd"], 683 + true, 684 + )?; 637 685 638 686 let ssh_key_name = "id_rsa"; 639 687 ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?;
+2 -2
crates/firecracker-prepare/src/rootfs.rs
··· 1 1 use anyhow::Result; 2 2 3 - use crate::command::run_command; 3 + use crate::command::{run_command, run_command_with_stdout_inherit}; 4 4 5 5 pub fn extract_squashfs(squashfs_file: &str, output_dir: &str) -> Result<()> { 6 6 if std::path::Path::new(output_dir).exists() { ··· 35 35 ); 36 36 return Ok(()); 37 37 } 38 - run_command("mksquashfs", &[squashfs_dir, output_file], true)?; 38 + run_command_with_stdout_inherit("mksquashfs", &[squashfs_dir, output_file], true)?; 39 39 Ok(()) 40 40 } 41 41
+5
crates/firecracker-prepare/src/ssh.rs
··· 12 12 ); 13 13 let pub_key_path = format!("{}/{}.pub", app_dir, key_name); 14 14 let auth_keys_path = format!("{}/root/.ssh/authorized_keys", squashfs_root_dir); 15 + run_command( 16 + "mkdir", 17 + &["-p", &format!("{}/root/.ssh", squashfs_root_dir)], 18 + true, 19 + )?; 15 20 run_command("cp", &[&pub_key_path, &auth_keys_path], true)?; 16 21 return Ok(()); 17 22 }
+5
crates/firecracker-up/src/cmd/start.rs
··· 7 7 use crate::cmd::up::up; 8 8 9 9 pub async fn start(name: &str) -> Result<(), Error> { 10 + let etcd = match fire_config::read_config() { 11 + Ok(config) => config.etcd, 12 + Err(_) => None, 13 + }; 10 14 let pool = firecracker_state::create_connection_pool().await?; 11 15 let vm = repo::virtual_machine::find(&pool, name).await?; 12 16 if vm.is_none() { ··· 38 42 tap: vm.tap, 39 43 api_socket: vm.api_socket, 40 44 mac_address: vm.mac_address, 45 + etcd, 41 46 }) 42 47 .await?; 43 48
+11 -6
crates/firecracker-up/src/main.rs
··· 59 59 .arg(arg!(--debian "Prepare Debian MicroVM").default_value("false")) 60 60 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 61 61 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 62 - .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 63 62 .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 64 63 .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 65 64 .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 66 65 .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 67 66 .arg( 68 - arg!(--opensuse-tumbleweed "Prepare OpenSUSE Tumbleweed MicroVM") 69 - .default_value("false"), 67 + Arg::new("opensuse-tumbleweed") 68 + .help("Prepare OpenSUSE Tumbleweed MicroVM") 69 + .action(clap::ArgAction::SetTrue), 70 70 ) 71 71 .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 72 72 .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 73 73 .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 74 + .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 74 75 .arg(arg!(--vcpu <n> "Number of vCPUs")) 75 76 .arg(arg!(--memory <m> "Memory size in MiB")) 76 77 .arg(arg!(--vmlinux <path> "Path to the kernel image")) ··· 126 127 .arg(arg!(--debian "Prepare Debian MicroVM").default_value("false")) 127 128 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 128 129 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 129 - .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 130 130 .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 131 131 .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 132 132 .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 133 133 .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 134 134 .arg( 135 - arg!(--opensuse-tumbleweed "Prepare OpenSUSE Tumbleweed MicroVM") 136 - .default_value("false"), 135 + Arg::new("opensuse-tumbleweed") 136 + .long("opensuse-tumbleweed") 137 + .help("Prepare OpenSUSE Tumbleweed MicroVM") 138 + .action(clap::ArgAction::SetTrue), 137 139 ) 138 140 .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 139 141 .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 140 142 .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 143 + .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 141 144 .arg(arg!(--vcpu <n> "Number of vCPUs")) 142 145 .arg(arg!(--memory <m> "Memory size in MiB")) 143 146 .arg(arg!(--vmlinux <path> "Path to the kernel image")) ··· 236 239 tap, 237 240 api_socket, 238 241 mac_address, 242 + etcd: None, 239 243 }; 240 244 up(options).await? 241 245 } ··· 339 343 tap, 340 344 api_socket, 341 345 mac_address, 346 + etcd: None, 342 347 }; 343 348 up(options).await? 344 349 }
+27 -5
crates/firecracker-vm/src/coredns.rs
··· 49 49 )?; 50 50 restart_coredns()?; 51 51 52 + let etcd_args = match config.etcd.clone() { 53 + Some(etcd) => { 54 + let mut args = vec![]; 55 + if let Some(endpoints) = &etcd.endpoints { 56 + args.push(format!("--endpoints={}", endpoints.join(","))); 57 + } 58 + if let Some(user) = &etcd.user { 59 + args.push(format!("--user={}", user)); 60 + } 61 + if let Some(password) = &etcd.password { 62 + args.push(format!("--password={}", password)); 63 + } 64 + if let Some(cacert) = &etcd.cacert { 65 + args.push(format!("--cacert={}", cacert)); 66 + } 67 + if let Some(cert) = &etcd.cert { 68 + args.push(format!("--cert={}", cert)); 69 + } 70 + args 71 + } 72 + None => vec![], 73 + }; 74 + 52 75 thread::spawn(move || { 53 76 let runtime = tokio::runtime::Runtime::new().unwrap(); 54 77 match runtime.block_on(async { ··· 75 98 76 99 let etcd_key = format!("/skydns/firecracker/{}", name); 77 100 let etcd_value = format!("{{\"host\":\"{}\"}}", ip_addr); 78 - run_command( 79 - "etcdctl", 80 - &["put", &etcd_key, &etcd_value], 81 - true, 82 - )?; 101 + let mut args = vec!["put", &etcd_key, &etcd_value]; 102 + args.extend(etcd_args.iter().map(String::as_str)); 103 + 104 + run_command("etcdctl", &args, false)?; 83 105 84 106 Ok::<(), Error>(()) 85 107 }) {
+1 -1
crates/firecracker-vm/src/firecracker.rs
··· 57 57 fn setup_boot_source(kernel: &str, arch: &str, options: &VmOptions) -> Result<String> { 58 58 println!("[+] Setting boot source..."); 59 59 let mut boot_args = 60 - "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp init=/sbin/overlay-init overlay_root=ram" 60 + "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp selinux=0 enforcing=0 init=/sbin/overlay-init overlay_root=ram" 61 61 .to_string(); 62 62 if arch == "aarch64" { 63 63 boot_args = format!("keep_bootcon {}", boot_args);
+5 -3
crates/firecracker-vm/src/types.rs
··· 1 - use fire_config::FireConfig; 1 + use fire_config::{EtcdConfig, FireConfig}; 2 2 use firecracker_prepare::Distro; 3 3 4 4 use crate::constants::{BRIDGE_DEV, FC_MAC, FIRECRACKER_SOCKET}; ··· 26 26 pub tap: String, 27 27 pub api_socket: String, 28 28 pub mac_address: String, 29 + pub etcd: Option<EtcdConfig>, 29 30 } 30 31 31 32 impl From<FireConfig> for VmOptions { ··· 53 54 tap: vm.tap.unwrap_or("".into()), 54 55 api_socket: vm.api_socket.unwrap_or(FIRECRACKER_SOCKET.into()), 55 56 mac_address: vm.mac.unwrap_or(FC_MAC.into()), 57 + etcd: config.etcd.clone(), 56 58 } 57 59 } 58 60 } ··· 65 67 Distro::Alpine 66 68 } else if self.nixos.unwrap_or(false) { 67 69 Distro::NixOS 68 - } else if self.ubuntu.unwrap_or(true) { 69 - Distro::Ubuntu 70 70 } else if self.fedora.unwrap_or(false) { 71 71 Distro::Fedora 72 72 } else if self.gentoo.unwrap_or(false) { ··· 83 83 Distro::RockyLinux 84 84 } else if self.archlinux.unwrap_or(false) { 85 85 Distro::Archlinux 86 + } else if self.ubuntu.unwrap_or(true) { 87 + Distro::Ubuntu 86 88 } else { 87 89 panic!("No valid distribution option provided."); 88 90 }