diff --git a/Cargo.lock b/Cargo.lock index 8933ac6..50ff01a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,7 +206,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -223,6 +234,7 @@ dependencies = [ "confy", "libc", "nix", + "rand", "serde", "serde_derive", "text_io", @@ -261,6 +273,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -279,6 +297,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -291,7 +339,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ - "getrandom", + "getrandom 0.1.16", "redox_syscall", "rust-argon2", ] @@ -392,6 +440,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index dec77b6..0ec5d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ serde = "1.0.120" serde_derive = "1.0.120" text_io = "0.1.8" nix = {version = "0.27.1", features = ["socket", "fs"]} +rand="0.8.5" diff --git a/src/bindings.rs b/src/bindings.rs index 41dc52c..b8676e3 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -13,6 +13,7 @@ extern "C" { pub fn krun_set_mapped_volumes(ctx: u32, mapped_volumes: *const *const c_char) -> i32; pub fn krun_set_port_map(ctx: u32, port_map: *const *const c_char) -> i32; pub fn krun_set_workdir(ctx: u32, workdir_path: *const c_char) -> i32; + pub fn krun_set_passt_fd(ctx: u32, fd: c_int) -> i32; pub fn krun_set_exec( ctx: u32, exec_path: *const c_char, diff --git a/src/commands/changevm.rs b/src/commands/changevm.rs index 5cd365b..25d4880 100644 --- a/src/commands/changevm.rs +++ b/src/commands/changevm.rs @@ -4,12 +4,12 @@ use clap::Args; use std::collections::HashMap; +use crate::config::{KrunvmConfig, NetworkMode}; use crate::utils::{path_pairs_to_hash_map, port_pairs_to_hash_map, PathPair, PortPair}; -use crate::{KrunvmConfig, APP_NAME}; use super::list::printvm; -/// Change the configuration of a microVM +/// Change the config of a microVM #[derive(Args, Debug)] pub struct ChangeVmCmd { /// Name of the VM to be modified @@ -46,6 +46,9 @@ pub struct ChangeVmCmd { /// Port(s) in format "host_port:guest_port" to be exposed to the host #[arg(long = "port")] ports: Vec, + + #[arg(long)] + network_mode: Option, } impl ChangeVmCmd { @@ -130,12 +133,17 @@ impl ChangeVmCmd { cfg_changed = true; } + if let Some(network_mode) = self.network_mode { + vmcfg.network_mode = network_mode; + cfg_changed = true; + } + println!(); printvm(vmcfg); println!(); if cfg_changed { - confy::store(APP_NAME, &cfg).unwrap(); + crate::config::save(cfg).unwrap(); } } } diff --git a/src/commands/config.rs b/src/commands/config.rs index ab09d34..eabea49 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -1,7 +1,7 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{KrunvmConfig, APP_NAME}; +use crate::config::{KrunvmConfig, NetworkMode}; use clap::Args; /// Configure global values @@ -18,6 +18,10 @@ pub struct ConfigCmd { /// DNS server to use in the microVM #[arg(long)] dns: Option, + + /// Default network mode to use + #[arg(long)] + network_mode: Option, } impl ConfigCmd { @@ -47,11 +51,18 @@ impl ConfigCmd { cfg_changed = true; } + if let Some(network_mode) = self.network_mode { + if network_mode != cfg.default_network_mode { + cfg.default_network_mode = network_mode; + cfg_changed = true; + } + } + if cfg_changed { - confy::store(APP_NAME, &cfg).unwrap(); + crate::config::save(cfg).unwrap(); } - println!("Global configuration:"); + println!("Global config:"); println!( "Default number of CPUs for newly created VMs: {}", cfg.default_cpus diff --git a/src/commands/create.rs b/src/commands/create.rs index 4040983..7ad04aa 100644 --- a/src/commands/create.rs +++ b/src/commands/create.rs @@ -1,6 +1,8 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::config::{KrunvmConfig, NetworkMode, VmConfig}; +use crate::APP_NAME; use clap::Args; use std::fs; use std::io::Write; @@ -12,8 +14,6 @@ use crate::utils::{ get_buildah_args, mount_container, path_pairs_to_hash_map, port_pairs_to_hash_map, umount_container, BuildahCommand, PathPair, PortPair, }; -use crate::{KrunvmConfig, VmConfig, APP_NAME}; - #[cfg(target_os = "macos")] const KRUNVM_ROSETTA_FILE: &str = ".krunvm-rosetta"; @@ -51,6 +51,10 @@ pub struct CreateCmd { #[arg(long = "port")] ports: Vec, + /// Network mode to use + #[arg(long)] + network_mode: Option, + /// Create a x86_64 microVM even on an Aarch64 host #[arg(short, long)] #[cfg(target_os = "macos")] @@ -68,6 +72,9 @@ impl CreateCmd { let mapped_ports = port_pairs_to_hash_map(self.ports); let image = self.image; let name = self.name; + let network_mode = self + .network_mode + .unwrap_or_else(|| cfg.default_network_mode.clone()); if let Some(ref name) = name { if cfg.vmconfig_map.contains_key(name) { @@ -160,6 +167,7 @@ https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/ workdir: workdir.to_string(), mapped_volumes, mapped_ports, + network_mode, }; let rootfs = mount_container(cfg, &vmcfg).unwrap(); diff --git a/src/commands/delete.rs b/src/commands/delete.rs index 2f4c54a..d027a92 100644 --- a/src/commands/delete.rs +++ b/src/commands/delete.rs @@ -1,7 +1,8 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{KrunvmConfig, APP_NAME}; +use crate::config; +use crate::config::KrunvmConfig; use clap::Args; use crate::utils::{remove_container, umount_container}; @@ -26,6 +27,6 @@ impl DeleteCmd { umount_container(cfg, &vmcfg).unwrap(); remove_container(cfg, &vmcfg).unwrap(); - confy::store(APP_NAME, &cfg).unwrap(); + config::save(cfg).unwrap() } } diff --git a/src/commands/list.rs b/src/commands/list.rs index 791943c..7ca03cf 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -1,7 +1,7 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{KrunvmConfig, VmConfig}; +use crate::config::{KrunvmConfig, VmConfig}; use clap::Args; /// List microVMs @@ -33,6 +33,7 @@ pub fn printvm(vm: &VmConfig) { println!(" DNS server: {}", vm.dns); println!(" Buildah container: {}", vm.container); println!(" Workdir: {}", vm.workdir); + println!(" Network mode: {:?}", vm.network_mode); println!(" Mapped volumes: {:?}", vm.mapped_volumes); println!(" Mapped ports: {:?}", vm.mapped_ports); } diff --git a/src/commands/start.rs b/src/commands/start.rs index 2d67423..c441a43 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -2,18 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 use clap::Args; -use libc::c_char; +use libc::{c_char, c_int}; +use nix::errno::Errno; +use nix::sys::socket::{connect, socket, AddressFamily, SockFlag, SockType, UnixAddr}; +use nix::unistd::unlink; +use rand::distributions::{Alphanumeric, DistString}; use std::ffi::CString; + use std::fs::File; +use std::io::{stderr, Write}; #[cfg(target_os = "linux")] use std::io::{Error, ErrorKind}; + +use std::os::fd::OwnedFd; use std::os::unix::io::AsRawFd; + #[cfg(target_os = "macos")] use std::path::Path; +use std::thread; +use std::time::Duration; + use crate::bindings; +use crate::bindings::krun_set_passt_fd; +use crate::config::{KrunvmConfig, NetworkMode, VmConfig}; use crate::utils::{mount_container, umount_container}; -use crate::{KrunvmConfig, VmConfig}; #[derive(Args, Debug)] /// Start an existing microVM @@ -36,6 +49,59 @@ pub struct StartCmd { mem: Option, // TODO: implement or remove this } +fn start_passt() -> Result { + let path = format!( + "/tmp/krunvm-passt-{}.socket", + Alphanumeric.sample_string(&mut rand::thread_rng(), 32) + ); + + //TODO: port forwarding + // passt will fork itself and run in the background + match std::process::Command::new("passt") + .arg("--one-off") + .arg("--socket") + .arg(&path) + .output() + { + Ok(output) if output.status.success() => (), + Ok(output) => { + eprintln!("Failed to start passt: {}", output.status); + eprintln!("Passt stderr follows:"); + eprintln!("---"); + stderr().lock().write_all(&output.stderr).unwrap(); + eprintln!("---"); + return Err(()); + } + Err(e) => { + eprintln!("Failed to start passt: {e}"); + return Err(()); + } + } + + let sock_path = UnixAddr::new(path.as_str()).expect("Failed to create UnixAddr"); + let fd = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("Failed to create socket fd"); + + loop { + match connect(fd.as_raw_fd(), &sock_path) { + Ok(()) => { + let _ = unlink(path.as_str()); + return Ok(fd); + } + Err(Errno::ECONNREFUSED) => thread::sleep(Duration::from_millis(10)), + Err(e) => { + eprintln!("Failed to connect to passt socket: {e}"); + return Err(()); + } + } + } +} + impl StartCmd { pub fn run(self, cfg: &KrunvmConfig) { let vmcfg = match cfg.vmconfig_map.get(&self.name) { @@ -152,10 +218,29 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec { + let ret = bindings::krun_set_port_map(ctx, ps.as_ptr()); + if ret < 0 { + println!("Error setting VM port map"); + std::process::exit(-1); + } + } + NetworkMode::Passt => { + let Ok(passt_fd) = start_passt() else { + std::process::exit(-1); + }; + let ret = krun_set_passt_fd(ctx, passt_fd.as_raw_fd() as c_int); + if ret < 0 { + let errno = Errno::from_i32(-ret); + if errno == Errno::ENOTSUP { + println!("Failed to set passt fd: your libkrun build does not support virtio-net/passt mode."); + } else { + println!("Failed to set passt fd: {}", errno); + } + std::process::exit(-1); + } + } } if !vmcfg.workdir.is_empty() { diff --git a/src/config/migrate.rs b/src/config/migrate.rs new file mode 100644 index 0000000..91a5956 --- /dev/null +++ b/src/config/migrate.rs @@ -0,0 +1,138 @@ +use crate::config::{v1, v2}; +use confy::ConfyError; + +pub fn migrate_and_load_impl( + load_v2: impl FnOnce() -> Result, + load_v1: impl FnOnce() -> Result, + save_v2: impl FnOnce(&v2::KrunvmConfig) -> Result<(), ()>, +) -> Result { + fn check_version(got: u8, expected: u8) -> Result<(), ()> { + if expected != got { + eprintln!( + "Invalid config version number {} expected {}", + got, expected + ); + Err(()) + } else { + Ok(()) + } + } + + let v2_load_err = match load_v2() { + Ok(conf) => { + check_version(conf.version, 2)?; + return Ok(conf); + } + Err(e) => e, + }; + + let v1_load_err = match load_v1() { + Ok(cfg) => { + check_version(cfg.version, 1)?; + let v2_config = cfg.into(); + save_v2(&v2_config)?; + return Ok(v2_config); + } + Err(e) => e, + }; + + eprintln!("Failed to load config: "); + eprintln!("Tried to load as as v2 config, got error: {v2_load_err}"); + eprintln!("Tried to load as as v1 config, got error: {v1_load_err}"); + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::NetworkMode; + use std::collections::HashMap; + + #[test] + fn load_without_migrate() { + let cfg = v2::KrunvmConfig { + default_dns: "8.8.8.8".into(), + ..v2::KrunvmConfig::default() + }; + + let returned_cfg = migrate_and_load_impl( + || Ok(cfg.clone()), + || panic!("Loading v1 should not be attempted"), + |_| panic!("Migration should not occur"), + ) + .unwrap(); + assert_eq!(returned_cfg, cfg); + } + + #[test] + fn load_migrating_to_v2() { + let v1_vms = [( + "fedora".to_string(), + v1::VmConfig { + name: "fedora".to_string(), + mapped_ports: Default::default(), + cpus: 2, + dns: "1.1.1.1".to_string(), + mapped_volumes: Default::default(), + workdir: "/".to_string(), + container: "fedora".to_string(), + mem: 8192, + }, + )]; + + let v1_cfg = v1::KrunvmConfig { + default_dns: "8.8.8.8".into(), + vmconfig_map: HashMap::from(v1_vms), + ..v1::KrunvmConfig::default() + }; + + let result_v2_vms = [( + "fedora".to_string(), + v2::VmConfig { + name: "fedora".to_string(), + mapped_ports: Default::default(), + cpus: 2, + dns: "1.1.1.1".to_string(), + mapped_volumes: Default::default(), + workdir: "/".to_string(), + container: "fedora".to_string(), + mem: 8192, + network_mode: NetworkMode::Tsi, + }, + )]; + + let result_v2_cfg = v2::KrunvmConfig { + default_dns: "8.8.8.8".into(), + vmconfig_map: HashMap::from(result_v2_vms), + default_network_mode: NetworkMode::Tsi, + ..v2::KrunvmConfig::default() + }; + + let mut load_v2_called = false; + let mut load_v1_called = false; + let mut save_called = false; + + let returned_cfg = migrate_and_load_impl( + || { + load_v2_called = true; + Err(ConfyError::BadConfigDirectoryStr) + }, + || { + load_v1_called = true; + Ok(v1_cfg) + }, + |migrated| { + save_called = true; + assert_eq!(migrated, &result_v2_cfg); + Ok(()) + }, + ) + .unwrap(); + + assert!(save_called, "Save must be called"); + assert!(load_v2_called, "Load v2 must be called"); + assert!(save_called, "Load v1 must be called"); + + assert_eq!(returned_cfg, result_v2_cfg); + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..f50873c --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,20 @@ +mod migrate; +mod v1; +mod v2; + +use crate::APP_NAME; + +use crate::config::migrate::migrate_and_load_impl; +pub use v2::{KrunvmConfig, NetworkMode, VmConfig}; + +pub fn save(cfg: &KrunvmConfig) -> Result<(), ()> { + confy::store(APP_NAME, cfg).map_err(|e| eprintln!("Failed to load config: {e}")) +} + +pub fn load() -> Result { + migrate_and_load_impl( + || confy::load::(APP_NAME), + || confy::load::(APP_NAME), + save, + ) +} diff --git a/src/config/v1.rs b/src/config/v1.rs new file mode 100644 index 0000000..877f034 --- /dev/null +++ b/src/config/v1.rs @@ -0,0 +1,37 @@ +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Serialize, Deserialize)] +pub struct KrunvmConfig { + pub version: u8, + pub default_cpus: u32, + pub default_mem: u32, + pub default_dns: String, + pub storage_volume: String, + pub vmconfig_map: HashMap, +} + +impl Default for KrunvmConfig { + fn default() -> KrunvmConfig { + KrunvmConfig { + version: 1, + default_cpus: 2, + default_mem: 1024, + default_dns: "1.1.1.1".to_string(), + storage_volume: String::new(), + vmconfig_map: HashMap::new(), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VmConfig { + pub name: String, + pub cpus: u32, + pub mem: u32, + pub container: String, + pub workdir: String, + pub dns: String, + pub mapped_volumes: HashMap, + pub mapped_ports: HashMap, +} diff --git a/src/config/v2.rs b/src/config/v2.rs new file mode 100644 index 0000000..c48e3d7 --- /dev/null +++ b/src/config/v2.rs @@ -0,0 +1,98 @@ +use crate::config::v1; + +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::str::FromStr; + +#[derive(Clone, Serialize, Deserialize, Debug, Default, Eq, PartialEq)] +pub enum NetworkMode { + #[default] + Tsi, + Passt, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct KrunvmConfig { + pub version: u8, + pub default_cpus: u32, + pub default_mem: u32, + pub default_dns: String, + pub default_network_mode: NetworkMode, + pub storage_volume: String, + pub vmconfig_map: HashMap, +} + +impl Default for KrunvmConfig { + fn default() -> KrunvmConfig { + KrunvmConfig { + version: 2, + default_cpus: 2, + default_mem: 1024, + default_dns: "1.1.1.1".to_string(), + default_network_mode: NetworkMode::default(), + storage_volume: String::new(), + vmconfig_map: HashMap::new(), + } + } +} + +impl From for KrunvmConfig { + fn from(old: v1::KrunvmConfig) -> Self { + KrunvmConfig { + version: 2, + default_cpus: old.default_cpus, + default_mem: old.default_mem, + default_dns: old.default_dns, + default_network_mode: NetworkMode::default(), + storage_volume: old.storage_volume, + vmconfig_map: old + .vmconfig_map + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct VmConfig { + pub name: String, + pub cpus: u32, + pub mem: u32, + pub container: String, + pub workdir: String, + pub dns: String, + pub network_mode: NetworkMode, + pub mapped_volumes: HashMap, + pub mapped_ports: HashMap, +} + +impl From for VmConfig { + fn from(old: v1::VmConfig) -> Self { + VmConfig { + name: old.name, + cpus: old.cpus, + mem: old.mem, + container: old.container, + workdir: old.workdir, + dns: old.dns, + mapped_volumes: old.mapped_volumes, + mapped_ports: old.mapped_ports, + network_mode: NetworkMode::default(), + } + } +} + +impl FromStr for NetworkMode { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if s.eq_ignore_ascii_case("tsi") { + Ok(NetworkMode::Tsi) + } else if s.eq_ignore_ascii_case("passt") { + Ok(NetworkMode::Passt) + } else { + Err("Invalid network mode") + } + } +} diff --git a/src/main.rs b/src/main.rs index 0531136..c35d619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,60 +1,26 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; #[cfg(target_os = "macos")] use std::fs::File; #[cfg(target_os = "macos")] use std::io::{self, Read, Write}; use crate::commands::{ChangeVmCmd, ConfigCmd, CreateCmd, DeleteCmd, ListCmd, StartCmd}; +#[cfg(target_os = "macos")] +use crate::config::KrunvmConfig; use clap::{Parser, Subcommand}; -use serde_derive::{Deserialize, Serialize}; #[cfg(target_os = "macos")] use text_io::read; #[allow(unused)] mod bindings; mod commands; +mod config; mod utils; const APP_NAME: &str = "krunvm"; -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct VmConfig { - name: String, - cpus: u32, - mem: u32, - container: String, - workdir: String, - dns: String, - mapped_volumes: HashMap, - mapped_ports: HashMap, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct KrunvmConfig { - version: u8, - default_cpus: u32, - default_mem: u32, - default_dns: String, - storage_volume: String, - vmconfig_map: HashMap, -} - -impl Default for KrunvmConfig { - fn default() -> KrunvmConfig { - KrunvmConfig { - version: 1, - default_cpus: 2, - default_mem: 1024, - default_dns: "1.1.1.1".to_string(), - storage_volume: String::new(), - vmconfig_map: HashMap::new(), - } - } -} - #[cfg(target_os = "macos")] fn check_case_sensitivity(volume: &str) -> Result { let first_path = format!("{}/krunvm_test", volume); @@ -167,7 +133,7 @@ enum Command { } fn main() { - let mut cfg: KrunvmConfig = confy::load(APP_NAME).unwrap(); + let mut cfg = config::load().unwrap(); let cli_args = Cli::parse(); #[cfg(target_os = "macos")] diff --git a/src/utils.rs b/src/utils.rs index 8b3d5b2..003f621 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,13 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::APP_NAME; use std::collections::HashMap; use std::path::Path; use std::process::Command; use std::str::FromStr; -use crate::{KrunvmConfig, VmConfig, APP_NAME}; +use crate::config::{KrunvmConfig, VmConfig}; pub enum BuildahCommand { From,