diff --git a/Cargo.lock b/Cargo.lock index 02f1cda..9734d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -852,6 +852,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -923,8 +946,17 @@ version = "0.1.0" dependencies = [ "actix-web", "anyhow", + "env_logger", + "firecracker-prepare", + "firecracker-process", + "firecracker-state", + "firecracker-vm", + "names", "owo-colors", "serde", + "serde_json", + "sqlx", + "tokio-stream", "utoipa", "utoipa-actix-web", "utoipa-rapidoc", @@ -1199,9 +1231,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" @@ -1682,6 +1714,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2167,6 +2223,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -2752,14 +2823,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] diff --git a/crates/fire-server/Cargo.toml b/crates/fire-server/Cargo.toml index 0b8ce3b..6bebc3f 100644 --- a/crates/fire-server/Cargo.toml +++ b/crates/fire-server/Cargo.toml @@ -9,10 +9,26 @@ repository.workspace = true [dependencies] actix-web = "4.11.0" anyhow = "1.0.99" +env_logger = "0.11.8" owo-colors = "4.2.2" serde = { version = "1.0.225", features = ["serde_derive", "derive"] } +tokio-stream = "0.1.17" utoipa = { version = "5.4.0", features = ["actix_extras"] } utoipa-actix-web = "0.1.2" utoipa-rapidoc = { version = "6.0.0", features = ["actix-web"] } utoipa-redoc = { version = "6.0.0", features = ["actix-web"] } utoipa-swagger-ui = { version = "9", features = ["actix-web", "reqwest"] } +firecracker-state = { path = "../firecracker-state" } +firecracker-vm = { path = "../firecracker-vm" } +firecracker-prepare = { path = "../firecracker-prepare" } +firecracker-process = { path = "../firecracker-process" } +serde_json = "1.0.145" +sqlx = { version = "0.8.6", features = [ + "runtime-tokio", + "tls-rustls", + "sqlite", + "chrono", + "derive", + "macros", +] } +names = "0.14.0" diff --git a/crates/fire-server/src/api/microvm.rs b/crates/fire-server/src/api/microvm.rs index 168fd13..3523605 100644 --- a/crates/fire-server/src/api/microvm.rs +++ b/crates/fire-server/src/api/microvm.rs @@ -1,24 +1,22 @@ -use actix_web::{delete, get, post, Responder}; +use std::sync::Arc; + +use actix_web::{delete, get, post, web, HttpResponse, Responder}; +use firecracker_state::repo; use serde::{Deserialize, Serialize}; +use sqlx::{Pool, Sqlite}; +use tokio_stream::StreamExt; use utoipa::ToSchema; use utoipa_actix_web::service_config::ServiceConfig; -const MICRO_VM: &str = "MicroVM"; +use crate::{ + read_payload, services, + types::microvm::{CreateMicroVM, MicroVM}, +}; -#[derive(Serialize, Deserialize, Clone, ToSchema)] -pub(super) struct MicroVM { - pub id: String, - pub name: String, - pub status: String, - pub vcpus: u8, - pub memory_mb: u32, - pub kernel_image: String, - pub rootfs_image: String, - pub ssh_keys: Vec, -} +const MICRO_VM: &str = "MicroVM"; #[derive(Serialize, Deserialize, Clone, ToSchema)] -pub(super) enum ErrorResponse { +pub enum ErrorResponse { NotFound(String), Conflict(String), Unauthorized(String), @@ -32,8 +30,30 @@ pub(super) enum ErrorResponse { ) )] #[post("")] -async fn create_microvm() -> Result { - Ok("MicroVM created") +async fn create_microvm( + mut payload: web::Payload, + pool: web::Data>>, +) -> Result { + let body = read_payload!(payload); + let params = match body.is_empty() { + true => CreateMicroVM { + name: None, + vcpus: None, + memory: None, + image: None, + vmlinux: None, + rootfs: None, + boot_args: None, + ssh_keys: None, + start: None, + }, + false => serde_json::from_slice::(&body)?, + }; + let pool = pool.get_ref().clone(); + let vm = services::microvm::create_microvm(pool, params) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + Ok(HttpResponse::Created().json(vm)) } #[utoipa::path( @@ -47,8 +67,21 @@ async fn create_microvm() -> Result { ) )] #[delete("/{id}")] -async fn delete_microvm() -> Result { - Ok("MicroVM deleted") +async fn delete_microvm( + id: web::Path, + pool: web::Data>>, +) -> Result { + let id = id.into_inner(); + let pool = pool.get_ref().clone(); + let vm = services::microvm::delete_microvm(pool, &id) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + + if vm.is_none() { + return Ok(HttpResponse::NotFound().json(ErrorResponse::NotFound(id))); + } + + Ok(HttpResponse::Ok().json(vm)) } #[utoipa::path( @@ -62,8 +95,20 @@ async fn delete_microvm() -> Result { ) )] #[get("/{id}")] -async fn get_microvm() -> Result { - Ok("MicroVM details") +async fn get_microvm( + id: web::Path, + pool: web::Data>>, +) -> Result { + let id = id.into_inner(); + let pool = pool.get_ref().clone(); + let vm = repo::virtual_machine::find(&pool, &id) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + + Ok(match vm { + Some(vm) => HttpResponse::Ok().json(vm), + None => HttpResponse::NotFound().json(ErrorResponse::NotFound(id)), + }) } #[utoipa::path( @@ -73,8 +118,14 @@ async fn get_microvm() -> Result { ) )] #[get("")] -async fn list_microvms() -> Result { - Ok("List of MicroVMs") +async fn list_microvms( + pool: web::Data>>, +) -> Result { + let pool = pool.get_ref().clone(); + let results = repo::virtual_machine::all(&pool) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + Ok(HttpResponse::Ok().json(results)) } #[utoipa::path( @@ -88,8 +139,16 @@ async fn list_microvms() -> Result { ) )] #[post("/{id}/start")] -async fn start_microvm() -> Result { - Ok("MicroVM started") +async fn start_microvm( + id: web::Path, + pool: web::Data>>, +) -> Result { + let id = id.into_inner(); + let pool = pool.get_ref().clone(); + let vm = services::microvm::start_microvm(pool, &id) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + Ok(HttpResponse::Ok().json(vm)) } #[utoipa::path( @@ -103,8 +162,21 @@ async fn start_microvm() -> Result { ) )] #[post("/{id}/stop")] -async fn stop_microvm() -> Result { - Ok("MicroVM stopped") +async fn stop_microvm( + id: web::Path, + pool: web::Data>>, +) -> Result { + let id = id.into_inner(); + let pool = pool.get_ref().clone(); + let vm = services::microvm::stop_microvm(pool, &id) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + + if vm.is_none() { + return Ok(HttpResponse::NotFound().json(ErrorResponse::NotFound(id))); + } + + Ok(HttpResponse::Ok().json(vm)) } pub fn configure() -> impl FnOnce(&mut ServiceConfig) { diff --git a/crates/fire-server/src/api/mod.rs b/crates/fire-server/src/api/mod.rs index d27f2ac..ed5eb45 100644 --- a/crates/fire-server/src/api/mod.rs +++ b/crates/fire-server/src/api/mod.rs @@ -1 +1,15 @@ pub mod microvm; + +#[macro_export] +macro_rules! read_payload { + ($payload:expr) => {{ + let mut body = Vec::new(); + while let Some(chunk) = $payload.next().await { + match chunk { + Ok(bytes) => body.extend_from_slice(&bytes), + Err(err) => return Err(err.into()), + } + } + body + }}; +} diff --git a/crates/fire-server/src/handlers/microvm.rs b/crates/fire-server/src/handlers/microvm.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/fire-server/src/handlers/microvm.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/fire-server/src/lib.rs b/crates/fire-server/src/lib.rs index ca7be9b..9fa1ee4 100644 --- a/crates/fire-server/src/lib.rs +++ b/crates/fire-server/src/lib.rs @@ -1,3 +1,4 @@ pub mod api; -pub mod handlers; pub mod server; +pub mod services; +pub mod types; diff --git a/crates/fire-server/src/server.rs b/crates/fire-server/src/server.rs index 3f14896..6704b6c 100644 --- a/crates/fire-server/src/server.rs +++ b/crates/fire-server/src/server.rs @@ -1,7 +1,8 @@ -use std::env; +use std::{env, sync::Arc}; -use actix_web::{App, HttpServer}; +use actix_web::{middleware::Logger, web::Data, App, HttpServer}; use anyhow::Error; +use firecracker_process::command::{is_root, run_command}; use owo_colors::OwoColorize; use utoipa::OpenApi; use utoipa_actix_web::AppExt; @@ -13,12 +14,18 @@ use crate::api::microvm; #[derive(OpenApi)] #[openapi( tags( - (name = "fireup", description = "Firecracker microVM management API") + (name = "fireup", description = "Firecracker MicroVM management API") ), )] struct ApiDoc; pub async fn run() -> Result<(), Error> { + env_logger::init(); + + if !is_root() { + run_command("sudo", &["-v"], false)?; + } + let port = env::var("FIREUP_PORT").unwrap_or_else(|_| "9090".to_string()); let host = env::var("FIREUP_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let addr = format!("{}:{}", host, port); @@ -26,9 +33,14 @@ pub async fn run() -> Result<(), Error> { let url = format!("http://{}", addr); println!("Starting server at {}", url.green()); + let pool = firecracker_state::create_connection_pool().await?; + let pool = Arc::new(pool); + HttpServer::new(move || { App::new() + .app_data(Data::new(pool.clone())) .into_utoipa_app() + .map(|app| app.wrap(Logger::default())) .openapi(ApiDoc::openapi()) .service(utoipa_actix_web::scope("/v1/microvms").configure(microvm::configure())) .openapi_service(|api| { diff --git a/crates/fire-server/src/services/microvm.rs b/crates/fire-server/src/services/microvm.rs new file mode 100644 index 0000000..7980f8b --- /dev/null +++ b/crates/fire-server/src/services/microvm.rs @@ -0,0 +1,155 @@ +use std::{sync::Arc, thread}; + +use crate::types::microvm::CreateMicroVM; +use anyhow::Error; +use firecracker_state::{entity::virtual_machine::VirtualMachine, repo}; +use firecracker_vm::{constants::BRIDGE_DEV, types::VmOptions}; +use owo_colors::OwoColorize; +use sqlx::{Pool, Sqlite}; + +pub async fn create_microvm( + pool: Arc>, + params: CreateMicroVM, +) -> Result { + let mut options: VmOptions = params.into(); + + if options.api_socket.is_empty() { + let vm_name = names::Generator::default().next().unwrap(); + options.api_socket = format!("/tmp/firecracker-{}.sock", vm_name); + } + + options.bridge = BRIDGE_DEV.into(); + + let vm = start(pool, options, None).await?; + Ok(vm) +} + +pub async fn delete_microvm( + pool: Arc>, + id: &str, +) -> Result, Error> { + let vm = repo::virtual_machine::find(&pool, id).await?; + if vm.is_none() { + println!("[!] No virtual machine found with the name: {}", id); + return Ok(None); + } + + let mut vm = vm.unwrap(); + firecracker_process::stop(Some(vm.name.clone())).await?; + repo::virtual_machine::delete(&pool, id).await?; + vm.status = "DELETED".into(); + Ok(Some(vm)) +} + +pub async fn start_microvm(pool: Arc>, id: &str) -> Result { + let vm = repo::virtual_machine::find(&pool, id).await?; + if vm.is_none() { + println!("[!] No virtual machine found with the name: {}", id); + return Err(Error::msg(format!( + "No virtual machine found with the given id: {}", + id + ))); + } + + let vm = vm.unwrap(); + + let options = VmOptions { + debian: Some(vm.distro == "debian"), + alpine: Some(vm.distro == "alpine"), + ubuntu: Some(vm.distro == "ubuntu"), + nixos: Some(vm.distro == "nixos"), + fedora: Some(vm.distro == "fedora"), + gentoo: Some(vm.distro == "gentoo"), + slackware: Some(vm.distro == "slackware"), + opensuse: Some(vm.distro == "opensuse"), + opensuse_tumbleweed: Some(vm.distro == "opensuse-tumbleweed"), + almalinux: Some(vm.distro == "almalinux"), + rockylinux: Some(vm.distro == "rockylinux"), + archlinux: Some(vm.distro == "archlinux"), + vcpu: vm.vcpu, + memory: vm.memory, + vmlinux: vm.vmlinux.clone(), + rootfs: vm.rootfs.clone(), + bootargs: vm.bootargs.clone(), + bridge: vm.bridge.clone(), + tap: vm.tap.clone(), + api_socket: vm.api_socket.clone(), + mac_address: vm.mac_address.clone(), + etcd: None, + }; + + let vm = start(pool, options, Some(vm.id)).await?; + + Ok(vm) +} + +pub async fn stop_microvm( + pool: Arc>, + id: &str, +) -> Result, Error> { + let vm = repo::virtual_machine::find(&pool, id).await?; + if vm.is_none() { + println!("[!] No virtual machine found with the name: {}", id); + return Ok(None); + } + + let mut vm = vm.unwrap(); + firecracker_process::stop(Some(vm.name.clone())).await?; + repo::virtual_machine::update_status(&pool, id, "STOPPED").await?; + vm.status = "STOPPED".into(); + Ok(Some(vm)) +} + +async fn start( + pool: Arc>, + mut options: VmOptions, + vm_id: Option, +) -> Result { + let vms = repo::virtual_machine::all(&pool).await?; + if options.tap.is_empty() { + let vms = vms + .into_iter() + .filter(|vm| vm.tap.starts_with("tap")) + .collect::>(); + options.tap = format!("tap{}", vms.len()); + + while vms.iter().any(|vm| vm.tap == options.tap) { + let tap_num: u32 = options + .tap + .trim_start_matches("tap") + .parse::() + .unwrap_or(0) + .checked_add(1) + .unwrap_or(0); + options.tap = format!("tap{}", tap_num); + } + } else { + if vms + .iter() + .any(|vm| vm.tap == options.tap && vm.api_socket != options.api_socket) + { + println!( + "[!] Tap device name {} is already in use. Please choose a different name.", + options.tap.cyan() + ); + return Err(Error::msg("Tap device name already in use")); + } + } + + let pid = firecracker_process::start(&options).await?; + + loop { + thread::sleep(std::time::Duration::from_secs(1)); + if firecracker_process::is_running() { + println!("[+] Firecracker is running."); + break; + } + } + + firecracker_prepare::prepare(options.clone().into(), options.vmlinux.clone())?; + let vm_id = firecracker_vm::setup(&options, pid, vm_id).await?; + let vm = repo::virtual_machine::find(&pool, &vm_id) + .await? + .ok_or_else(|| Error::msg("Failed to retrieve the created VM"))?; + Ok(vm) +} diff --git a/crates/fire-server/src/handlers/mod.rs b/crates/fire-server/src/services/mod.rs similarity index 100% rename from crates/fire-server/src/handlers/mod.rs rename to crates/fire-server/src/services/mod.rs diff --git a/crates/fire-server/src/types/microvm.rs b/crates/fire-server/src/types/microvm.rs new file mode 100644 index 0000000..cd21acc --- /dev/null +++ b/crates/fire-server/src/types/microvm.rs @@ -0,0 +1,116 @@ +use firecracker_vm::{mac::generate_unique_mac, types::VmOptions}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Serialize, Deserialize, Clone, ToSchema)] +pub struct MicroVM { + pub id: String, + pub name: String, + pub image: String, + pub vcpu: u8, + pub memory: u32, + pub vmlinux: String, + pub rootfs: String, + pub boot_args: String, + pub status: String, + pub ssh_keys: Vec, +} + +#[derive(Serialize, Deserialize, Clone, ToSchema)] +pub struct CreateMicroVM { + pub name: Option, + pub vcpus: Option, + pub memory: Option, + pub image: Option, + pub vmlinux: Option, + pub rootfs: Option, + pub boot_args: Option, + pub ssh_keys: Option>, + pub start: Option, +} + +impl Into for CreateMicroVM { + fn into(self) -> VmOptions { + VmOptions { + debian: self + .image + .as_ref() + .map(|img| img == "debian") + .unwrap_or(false) + .then_some(true), + alpine: self + .image + .as_ref() + .map(|img| img == "alpine") + .unwrap_or(false) + .then_some(true), + ubuntu: self + .image + .as_ref() + .map(|img| img == "ubuntu") + .unwrap_or(false) + .then_some(true), + nixos: self + .image + .as_ref() + .map(|img| img == "nixos") + .unwrap_or(false) + .then_some(true), + fedora: self + .image + .as_ref() + .map(|img| img == "fedora") + .unwrap_or(false) + .then_some(true), + gentoo: self + .image + .as_ref() + .map(|img| img == "gentoo") + .unwrap_or(false) + .then_some(true), + slackware: self + .image + .as_ref() + .map(|img| img == "slackware") + .unwrap_or(false) + .then_some(true), + opensuse: self + .image + .as_ref() + .map(|img| img == "opensuse") + .unwrap_or(false) + .then_some(true), + opensuse_tumbleweed: self + .image + .as_ref() + .map(|img| img == "opensuse-tumbleweed") + .unwrap_or(false) + .then_some(true), + almalinux: self + .image + .as_ref() + .map(|img| img == "almalinux") + .unwrap_or(false) + .then_some(true), + rockylinux: self + .image + .as_ref() + .map(|img| img == "rockylinux") + .unwrap_or(false) + .then_some(true), + archlinux: self + .image + .as_ref() + .map(|img| img == "archlinux") + .unwrap_or(false) + .then_some(true), + vcpu: self.vcpus.unwrap_or(1) as u16, + memory: self.memory.unwrap_or(512), + vmlinux: self.vmlinux, + rootfs: self.rootfs, + bootargs: self.boot_args, + mac_address: generate_unique_mac(), + ..Default::default() + } + } +} diff --git a/crates/fire-server/src/types/mod.rs b/crates/fire-server/src/types/mod.rs new file mode 100644 index 0000000..d27f2ac --- /dev/null +++ b/crates/fire-server/src/types/mod.rs @@ -0,0 +1 @@ +pub mod microvm; diff --git a/crates/firecracker-process/src/lib.rs b/crates/firecracker-process/src/lib.rs index f546939..3484bbf 100644 --- a/crates/firecracker-process/src/lib.rs +++ b/crates/firecracker-process/src/lib.rs @@ -63,7 +63,9 @@ pub async fn stop(name: Option) -> Result<()> { let vm = vm.unwrap(); if let Some(pid) = vm.pid { - run_command("kill", &["-s", "KILL", &pid.to_string()], true)?; + if run_command("kill", &["-s", "KILL", &pid.to_string()], true).is_err() { + println!("[!] Failed to kill process with PID {}.", pid); + } } run_command("rm", &["-rf", &config.api_socket], true)?; @@ -82,9 +84,7 @@ pub async fn vm_is_running(name: &str) -> Result { if std::path::Path::new(&vm.api_socket).exists() { return Ok(true); } - if vm.status == "RUNNING" { - return Ok(true); - } + repo::virtual_machine::update_status(&pool, name, "STOPPED").await?; } Ok(false) diff --git a/crates/firecracker-state/src/repo/virtual_machine.rs b/crates/firecracker-state/src/repo/virtual_machine.rs index 118d9b1..a703e0d 100644 --- a/crates/firecracker-state/src/repo/virtual_machine.rs +++ b/crates/firecracker-state/src/repo/virtual_machine.rs @@ -16,6 +16,7 @@ pub async fn all(pool: &Pool) -> Result, Error> { pub async fn find(pool: &Pool, name: &str) -> Result, Error> { let result: Option = sqlx::query_as("SELECT * FROM virtual_machines WHERE name = ? OR id = ?") + .bind(name) .bind(name) .fetch_optional(pool) .await @@ -40,7 +41,7 @@ pub async fn find_by_project_dir( Ok(result) } -pub async fn create(pool: &Pool, vm: VirtualMachine) -> Result<(), Error> { +pub async fn create(pool: &Pool, vm: VirtualMachine) -> Result { let id = xid::new().to_string(); let project_dir = match Path::exists(Path::new("fire.toml")) { true => Some(std::env::current_dir()?.display().to_string()), @@ -85,7 +86,7 @@ pub async fn create(pool: &Pool, vm: VirtualMachine) -> Result<(), Error .execute(pool) .await .with_context(|| "Failed to create virtual machine")?; - Ok(()) + Ok(id) } pub async fn delete(pool: &Pool, name: &str) -> Result<(), Error> { diff --git a/crates/firecracker-vm/src/lib.rs b/crates/firecracker-vm/src/lib.rs index 0310ddb..db2254b 100644 --- a/crates/firecracker-vm/src/lib.rs +++ b/crates/firecracker-vm/src/lib.rs @@ -19,7 +19,7 @@ mod mqttc; mod network; pub mod types; -pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Result<()> { +pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Result { let distro: Distro = options.clone().into(); let app_dir = get_config_dir().with_context(|| "Failed to get configuration directory")?; @@ -144,7 +144,7 @@ pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Resu .display() .to_string(); - match vm_id { + let vm_id = match vm_id { Some(id) => { repo::virtual_machine::update( &pool, @@ -169,6 +169,7 @@ pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Resu }, ) .await?; + id } None => { repo::virtual_machine::create( @@ -192,14 +193,14 @@ pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Resu ..Default::default() }, ) - .await?; + .await? } - } + }; println!("[✓] MicroVM booted and network is configured 🎉"); println!("SSH into the VM using the following command:"); println!("{} {}", "fireup ssh".bright_green(), name.bright_green()); - Ok(()) + Ok(vm_id) }