From 6b6ea504583b944902d0a3d11260a3e8e94413fa Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 2 Nov 2023 11:10:55 +0100 Subject: [PATCH 1/8] Fix compilation on aarch64 due to i8/u8 mismatch Signed-off-by: Matej Hrica --- src/start.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/start.rs b/src/start.rs index 7dc5f02..05375f3 100644 --- a/src/start.rs +++ b/src/start.rs @@ -1,6 +1,7 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use libc::c_char; use std::ffi::CString; use std::fs::File; #[cfg(target_os = "linux")] @@ -91,7 +92,7 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec = Vec::new(); + let mut ps: Vec<*const c_char> = Vec::new(); for port in ports.iter() { ps.push(port.as_ptr()); } @@ -113,10 +114,10 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec = Vec::new(); + let mut argv: Vec<*const c_char> = Vec::new(); for a in args.iter() { argv.push(a.as_ptr()); } From ced22ac583f666b25fa4b9935d9386a160d0e6ae Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 2 Nov 2023 11:21:08 +0100 Subject: [PATCH 2/8] Run cargo clippy more times with each supported platform separately This seems to catch more problems, such as the u8/i8 mismatch on aarch64 platforms. Signed-off-by: Matej Hrica --- .github/workflows/code_quality.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 89739b3..80f6ad1 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -26,8 +26,17 @@ jobs: - name: Install asciidoctor run: sudo apt-get install -y asciidoctor + - name: Install additional Rust rust targets + run: rustup target add aarch64-unknown-linux-gnu aarch64-apple-darwin + - name: Formatting (rustfmt) run: cargo fmt -- --check - - name: Clippy (all features) - run: cargo clippy --all-targets --all-features + - name: Clippy x86_64-unknown-linux-gnu (all features) + run: cargo clippy --all-features --target x86_64-unknown-linux-gnu + + - name: Clippy aarch64-unknown-linux-gnu (all features) + run: cargo clippy --all-features --target aarch64-unknown-linux-gnu + + - name: Clippy aarch64-apple-darwin (all features) + run: cargo clippy --all-features --target aarch64-apple-darwin From 38e1e5cffc9bb8f94022c4d7a3f913190d54dc78 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 2 Nov 2023 11:41:10 +0100 Subject: [PATCH 3/8] Fix compilation for macOS target We need #[allow(unused_mut)], to disable warnings on other targets. Signed-off-by: Matej Hrica --- src/create.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/create.rs b/src/create.rs index 3b88f7e..17e9cd4 100644 --- a/src/create.rs +++ b/src/create.rs @@ -67,7 +67,8 @@ fn export_container_config( } pub fn create(cfg: &mut KrunvmConfig, matches: &ArgMatches) { - let cpus = match matches.value_of("cpus") { + #[allow(unused_mut)] + let mut cpus = match matches.value_of("cpus") { Some(c) => match c.parse::() { Err(_) => { println!("Invalid value for \"cpus\""); From cbf34eb064526cfa412265c6caa1738ff55d2273 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 2 Nov 2023 11:42:56 +0100 Subject: [PATCH 4/8] Resolve clippy warning for macOS target Signed-off-by: Matej Hrica --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 46bd2e4..220720e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -198,7 +198,7 @@ pub fn mount_container(cfg: &KrunvmConfig, vmcfg: &VmConfig) -> Result Date: Tue, 10 Oct 2023 13:58:53 +0200 Subject: [PATCH 5/8] Refactor argument parsing using new clap 4.x version Signed-off-by: Matej Hrica --- Cargo.lock | 258 ++++++++++++++++++++++++++++++++++++----------- Cargo.toml | 3 +- src/bindings.rs | 2 +- src/changevm.rs | 114 ++++++++++++--------- src/config.rs | 59 ++++++----- src/create.rs | 122 +++++++++++----------- src/delete.rs | 14 ++- src/list.rs | 13 ++- src/main.rs | 261 +++++++----------------------------------------- src/start.rs | 46 ++++++--- src/utils.rs | 92 ++++++++++------- 11 files changed, 516 insertions(+), 468 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 509f15c..8933ac6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,12 +3,51 @@ version = 3 [[package]] -name = "ansi_term" -version = "0.11.0" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", ] [[package]] @@ -23,17 +62,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.0.1" @@ -48,9 +76,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" -version = "1.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "blake2b_simd" @@ -77,19 +105,50 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.3" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ - "ansi_term", - "atty", - "bitflags", + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", "strsim", - "textwrap", - "unicode-width", - "vec_map", ] +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "confy" version = "0.4.0" @@ -151,13 +210,10 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.1.18" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "krunvm" @@ -166,6 +222,7 @@ dependencies = [ "clap", "confy", "libc", + "nix", "serde", "serde_derive", "text_io", @@ -179,24 +236,45 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.90" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "memoffset" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", + "memoffset", +] [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -244,14 +322,14 @@ checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.64", ] [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -265,19 +343,21 @@ dependencies = [ ] [[package]] -name = "text_io" -version = "0.1.8" +name = "syn" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb170b4f47dc48835fbc56259c12d8963e542b05a24be2e3a1f5a6c320fd2d4" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] -name = "textwrap" -version = "0.11.0" +name = "text_io" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "6cb170b4f47dc48835fbc56259c12d8963e542b05a24be2e3a1f5a6c320fd2d4" [[package]] name = "toml" @@ -289,10 +369,10 @@ dependencies = [ ] [[package]] -name = "unicode-width" -version = "0.1.8" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-xid" @@ -301,10 +381,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] -name = "vec_map" -version = "0.8.2" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "wasi" @@ -333,3 +413,69 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index a345f02..dec77b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,10 @@ edition = "2018" build = "build.rs" [dependencies] -clap = "2.33.3" +clap = {version = "4.4.6", features = ["derive"]} confy = "0.4.0" libc = "0.2.82" serde = "1.0.120" serde_derive = "1.0.120" text_io = "0.1.8" +nix = {version = "0.27.1", features = ["socket", "fs"]} diff --git a/src/bindings.rs b/src/bindings.rs index 16b0894..41dc52c 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -1,7 +1,7 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use libc::c_char; +use libc::{c_char, c_int}; #[link(name = "krun")] extern "C" { diff --git a/src/changevm.rs b/src/changevm.rs index 99499c2..4b93931 100644 --- a/src/changevm.rs +++ b/src/changevm.rs @@ -1,27 +1,65 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use clap::Args; use std::collections::HashMap; -use crate::{ArgMatches, KrunvmConfig, APP_NAME}; +use crate::utils::{path_pairs_to_hash_map, port_pairs_to_hash_map, PathPair, PortPair}; +use crate::{KrunvmConfig, APP_NAME}; use super::list::printvm; -use super::utils::{parse_mapped_ports, parse_mapped_volumes}; -pub fn changevm(cfg: &mut KrunvmConfig, matches: &ArgMatches) { - let mut cfg_changed = false; +/// Change the configuration of a microVM +#[derive(Args, Debug)] +pub struct ChangeVmCmdArgs { + /// Name of the VM to be modified + name: String, + + /// Assign a new name to the VM + #[arg(long)] + new_name: Option, + + /// Number of vCPUs + #[arg(long)] + cpus: Option, + + /// Amount of RAM in MiB + #[arg(long)] + mem: Option, + + /// Working directory inside the microVM + #[arg(short, long)] + workdir: Option, + + /// Remove all volume mappings + #[arg(long)] + remove_volumes: bool, - let name = matches.value_of("NAME").unwrap(); + /// Volume(s) in form "host_path:guest_path" to be exposed to the guest + #[arg(short, long = "volume")] + volumes: Vec, + + /// Remove all port mappings + #[arg(long)] + remove_ports: bool, + + /// Port(s) in format "host_port:guest_port" to be exposed to the host + #[arg(long = "port")] + ports: Vec, +} + +pub fn changevm(cfg: &mut KrunvmConfig, args: ChangeVmCmdArgs) { + let mut cfg_changed = false; - let vmcfg = if let Some(new_name) = matches.value_of("new-name") { + let vmcfg = if let Some(new_name) = &args.new_name { if cfg.vmconfig_map.contains_key(new_name) { println!("A VM with name {} already exists", new_name); std::process::exit(-1); } - let mut vmcfg = match cfg.vmconfig_map.remove(name) { + let mut vmcfg = match cfg.vmconfig_map.remove(&args.name) { None => { - println!("No VM found with name {}", name); + println!("No VM found with name {}", &args.name); std::process::exit(-1); } Some(vmcfg) => vmcfg, @@ -33,78 +71,60 @@ pub fn changevm(cfg: &mut KrunvmConfig, matches: &ArgMatches) { cfg.vmconfig_map.insert(name.clone(), vmcfg); cfg.vmconfig_map.get_mut(&name).unwrap() } else { - match cfg.vmconfig_map.get_mut(name) { + match cfg.vmconfig_map.get_mut(&args.name) { None => { - println!("No VM found with name {}", name); + println!("No VM found with name {}", args.name); std::process::exit(-1); } Some(vmcfg) => vmcfg, } }; - if let Some(cpus_str) = matches.value_of("cpus") { - match cpus_str.parse::() { - Err(_) => println!("Invalid value for \"cpus\""), - Ok(cpus) => { - if cpus > 8 { - println!("Error: the maximum number of CPUs supported is 8"); - } else { - vmcfg.cpus = cpus; - cfg_changed = true; - } - } + if let Some(cpus) = args.cpus { + if cpus > 8 { + println!("Error: the maximum number of CPUs supported is 8"); + } else { + vmcfg.cpus = cpus; + cfg_changed = true; } } - if let Some(mem_str) = matches.value_of("mem") { - match mem_str.parse::() { - Err(_) => println!("Invalid value for \"mem\""), - Ok(mem) => { - if mem > 16384 { - println!("Error: the maximum amount of RAM supported is 16384 MiB"); - } else { - vmcfg.mem = mem; - cfg_changed = true; - } - } + if let Some(mem) = args.mem { + if mem > 16384 { + println!("Error: the maximum amount of RAM supported is 16384 MiB"); + } else { + vmcfg.mem = mem; + cfg_changed = true; } } - if matches.is_present("remove-volumes") { + if args.remove_volumes { vmcfg.mapped_volumes = HashMap::new(); cfg_changed = true; } else { - let volume_matches = if matches.is_present("volume") { - matches.values_of("volume").unwrap().collect() - } else { - vec![] - }; - let mapped_volumes = parse_mapped_volumes(volume_matches); + let mapped_volumes = path_pairs_to_hash_map(args.volumes); if !mapped_volumes.is_empty() { vmcfg.mapped_volumes = mapped_volumes; cfg_changed = true; } } + // TODO: don't just silently ignore --volume args when --remove_volumes is specified - if matches.is_present("remove-ports") { + if args.remove_ports { vmcfg.mapped_ports = HashMap::new(); cfg_changed = true; } else { - let port_matches = if matches.is_present("port") { - matches.values_of("port").unwrap().collect() - } else { - vec![] - }; - let mapped_ports = parse_mapped_ports(port_matches); + let mapped_ports = port_pairs_to_hash_map(args.ports); if !mapped_ports.is_empty() { vmcfg.mapped_ports = mapped_ports; cfg_changed = true; } } + // TODO: don't just silently ignore --port args when --remove_ports is specified - if let Some(workdir) = matches.value_of("workdir") { + if let Some(workdir) = args.workdir { vmcfg.workdir = workdir.to_string(); cfg_changed = true; } diff --git a/src/config.rs b/src/config.rs index 1852f28..b711ca1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,41 +1,48 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ArgMatches, KrunvmConfig, APP_NAME}; +use crate::{KrunvmConfig, APP_NAME}; +use clap::Args; -pub fn config(cfg: &mut KrunvmConfig, matches: &ArgMatches) { +/// Configure global values +#[derive(Args, Debug)] +pub struct ConfigCmdArgs { + // Default number of vCPUs for newly created VMs + #[arg(long)] + cpus: Option, + + ///Default amount of RAM in MiB for newly created VMs + #[arg(long)] + mem: Option, + + /// DNS server to use in the microVM + #[arg(long)] + dns: Option, +} + +pub fn config(cfg: &mut KrunvmConfig, args: ConfigCmdArgs) { let mut cfg_changed = false; - if let Some(cpus_str) = matches.value_of("cpus") { - match cpus_str.parse::() { - Err(_) => println!("Invalid value for \"cpus\""), - Ok(cpus) => { - if cpus > 8 { - println!("Error: the maximum number of CPUs supported is 8"); - } else { - cfg.default_cpus = cpus; - cfg_changed = true; - } - } + if let Some(cpus) = args.cpus { + if cpus > 8 { + println!("Error: the maximum number of CPUs supported is 8"); + } else { + cfg.default_cpus = cpus; + cfg_changed = true; } } - if let Some(mem_str) = matches.value_of("mem") { - match mem_str.parse::() { - Err(_) => println!("Invalid value for \"mem\""), - Ok(mem) => { - if mem > 16384 { - println!("Error: the maximum amount of RAM supported is 16384 MiB"); - } else { - cfg.default_mem = mem; - cfg_changed = true; - } - } + if let Some(mem) = args.mem { + if mem > 16384 { + println!("Error: the maximum amount of RAM supported is 16384 MiB"); + } else { + cfg.default_mem = mem; + cfg_changed = true; } } - if let Some(dns) = matches.value_of("dns") { - cfg.default_dns = dns.to_string(); + if let Some(dns) = args.dns { + cfg.default_dns = dns; cfg_changed = true; } diff --git a/src/create.rs b/src/create.rs index 17e9cd4..c8491d6 100644 --- a/src/create.rs +++ b/src/create.rs @@ -1,21 +1,60 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use clap::Args; use std::fs; use std::io::Write; #[cfg(target_os = "macos")] use std::path::Path; use std::process::Command; -use super::utils::{ - get_buildah_args, mount_container, parse_mapped_ports, parse_mapped_volumes, umount_container, - BuildahCommand, -}; -use crate::{ArgMatches, KrunvmConfig, VmConfig, APP_NAME}; +use super::utils::{get_buildah_args, mount_container, umount_container, BuildahCommand}; +use crate::utils::{path_pairs_to_hash_map, port_pairs_to_hash_map, PathPair, PortPair}; +use crate::{KrunvmConfig, VmConfig, APP_NAME}; #[cfg(target_os = "macos")] const KRUNVM_ROSETTA_FILE: &str = ".krunvm-rosetta"; +/// Create a new microVM +#[derive(Args, Debug)] +pub struct CreateCmdArgs { + /// OCI image to use as template + image: String, + + /// Assign a name to the VM + #[arg(long)] + name: Option, + + /// Number of vCPUs + #[arg(long)] + cpus: Option, + + /// Amount of RAM in MiB + #[arg(long)] + mem: Option, + + /// DNS server to use in the microVM + #[arg(long)] + dns: Option, + + /// Working directory inside the microVM + #[arg(short, long, default_value = "")] + workdir: String, + + /// Volume(s) in form "host_path:guest_path" to be exposed to the guest + #[arg(short, long = "volume")] + volumes: Vec, + + /// Port(s) in format "host_port:guest_port" to be exposed to the host + #[arg(long = "port")] + ports: Vec, + + /// Create a x86_64 microVM even on an Aarch64 host + #[arg(short, long)] + #[cfg(target_os = "macos")] + x86: bool, +} + fn fix_resolv_conf(rootfs: &str, dns: &str) -> Result<(), std::io::Error> { let resolvconf_dir = format!("{}/etc/", rootfs); fs::create_dir_all(resolvconf_dir)?; @@ -66,63 +105,28 @@ fn export_container_config( Ok(()) } -pub fn create(cfg: &mut KrunvmConfig, matches: &ArgMatches) { +pub fn create(cfg: &mut KrunvmConfig, args: CreateCmdArgs) { #[allow(unused_mut)] - let mut cpus = match matches.value_of("cpus") { - Some(c) => match c.parse::() { - Err(_) => { - println!("Invalid value for \"cpus\""); - std::process::exit(-1); - } - Ok(cpus) => cpus, - }, - None => cfg.default_cpus, - }; - let mem = match matches.value_of("mem") { - Some(m) => match m.parse::() { - Err(_) => { - println!("Invalid value for \"mem\""); - std::process::exit(-1); - } - Ok(mem) => mem, - }, - None => cfg.default_mem, - }; - let dns = match matches.value_of("dns") { - Some(d) => d, - None => &cfg.default_dns, - }; - - let workdir = matches.value_of("workdir").unwrap(); - - let volume_matches = if matches.is_present("volume") { - matches.values_of("volume").unwrap().collect() - } else { - vec![] - }; - let mapped_volumes = parse_mapped_volumes(volume_matches); - - let port_matches = if matches.is_present("port") { - matches.values_of("port").unwrap().collect() - } else { - vec![] - }; - let mapped_ports = parse_mapped_ports(port_matches); - - let image = matches.value_of("IMAGE").unwrap(); - - let name = matches.value_of("name"); - if let Some(name) = name { + let mut cpus = args.cpus.unwrap_or(cfg.default_cpus); + let mem = args.mem.unwrap_or(cfg.default_mem); + let dns = args.dns.unwrap_or_else(|| cfg.default_dns.clone()); + let workdir = args.workdir; + let mapped_volumes = path_pairs_to_hash_map(args.volumes); + let mapped_ports = port_pairs_to_hash_map(args.ports); + let image = args.image; + let name = args.name; + + if let Some(ref name) = name { if cfg.vmconfig_map.contains_key(name) { println!("A VM with this name already exists"); std::process::exit(-1); } } - let mut args = get_buildah_args(cfg, BuildahCommand::From); + let mut buildah_args = get_buildah_args(cfg, BuildahCommand::From); #[cfg(target_os = "macos")] - let force_x86 = matches.is_present("x86"); + let force_x86 = args.x86; #[cfg(target_os = "macos")] if force_x86 { @@ -157,14 +161,14 @@ https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/ println!("x86 microVMs on Aarch64 are restricted to 1 CPU"); cpus = 1; } - args.push("--arch".to_string()); - args.push("x86_64".to_string()); + buildah_args.push("--arch".to_string()); + buildah_args.push("x86_64".to_string()); } - args.push(image.to_string()); + buildah_args.push(image.to_string()); let output = match Command::new("buildah") - .args(&args) + .args(&buildah_args) .stderr(std::process::Stdio::inherit()) .output() { @@ -206,8 +210,8 @@ https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/ }; let rootfs = mount_container(cfg, &vmcfg).unwrap(); - export_container_config(cfg, &rootfs, image).unwrap(); - fix_resolv_conf(&rootfs, dns).unwrap(); + export_container_config(cfg, &rootfs, &image).unwrap(); + fix_resolv_conf(&rootfs, &dns).unwrap(); #[cfg(target_os = "macos")] if force_x86 { _ = fs::create_dir(format!("{}/.rosetta", rootfs)); diff --git a/src/delete.rs b/src/delete.rs index 7a79932..0770d1d 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -1,14 +1,20 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ArgMatches, KrunvmConfig, APP_NAME}; +use crate::{KrunvmConfig, APP_NAME}; +use clap::Args; use super::utils::{remove_container, umount_container}; -pub fn delete(cfg: &mut KrunvmConfig, matches: &ArgMatches) { - let name = matches.value_of("NAME").unwrap(); +/// Delete an existing microVM +#[derive(Args, Debug)] +pub struct DeleteCmdArgs { + /// Name of the microVM to be deleted + name: String, +} - let vmcfg = match cfg.vmconfig_map.remove(name) { +pub fn delete(cfg: &mut KrunvmConfig, args: DeleteCmdArgs) { + let vmcfg = match cfg.vmconfig_map.remove(&args.name) { None => { println!("No VM found with that name"); std::process::exit(-1); diff --git a/src/list.rs b/src/list.rs index 41d68d3..bd0834c 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,7 +1,16 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ArgMatches, KrunvmConfig, VmConfig}; +use crate::{KrunvmConfig, VmConfig}; +use clap::Args; + +/// List microVMs +#[derive(Args, Debug)] +pub struct ListCmdArgs { + /// Print debug information verbosely + #[arg(short)] + debug: bool, //TODO: implement or remove this +} pub fn printvm(vm: &VmConfig) { println!("{}", vm.name); @@ -14,7 +23,7 @@ pub fn printvm(vm: &VmConfig) { println!(" Mapped ports: {:?}", vm.mapped_ports); } -pub fn list(cfg: &KrunvmConfig, _matches: &ArgMatches) { +pub fn list(cfg: &KrunvmConfig, _args: ListCmdArgs) { if cfg.vmconfig_map.is_empty() { println!("No microVMs found"); } else { diff --git a/src/main.rs b/src/main.rs index 4fa6a84..9e393eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,16 @@ use std::fs::File; #[cfg(target_os = "macos")] use std::io::{self, Read, Write}; -use clap::{crate_version, App, Arg, ArgMatches}; +use crate::changevm::ChangeVmCmdArgs; +use crate::config::ConfigCmdArgs; +use crate::create::CreateCmdArgs; +use crate::delete::DeleteCmdArgs; +use crate::list::ListCmdArgs; +use crate::start::StartCmdArgs; +use clap::{Parser, Subcommand}; use serde_derive::{Deserialize, Serialize}; #[cfg(target_os = "macos")] use text_io::read; - #[allow(unused)] mod bindings; mod changevm; @@ -149,236 +154,42 @@ fn check_unshare() { } } -fn main() { - let mut cfg: KrunvmConfig = confy::load(APP_NAME).unwrap(); - - let mut app = App::new("krunvm") - .version(crate_version!()) - .author("Sergio Lopez ") - .about("Manage microVMs created from OCI images") - .arg( - Arg::with_name("v") - .short("v") - .multiple(true) - .help("Sets the level of verbosity"), - ) - .subcommand( - App::new("changevm") - .about("Change the configuration of a microVM") - .arg( - Arg::with_name("cpus") - .long("cpus") - .help("Number of vCPUs") - .takes_value(true), - ) - .arg( - Arg::with_name("mem") - .long("mem") - .help("Amount of RAM in MiB") - .takes_value(true), - ) - .arg( - Arg::with_name("workdir") - .long("workdir") - .short("w") - .help("Working directory inside the microVM") - .takes_value(true), - ) - .arg( - Arg::with_name("remove-volumes") - .long("remove-volumes") - .help("Remove all volume mappings"), - ) - .arg( - Arg::with_name("volume") - .long("volume") - .short("v") - .help("Volume in form \"host_path:guest_path\" to be exposed to the guest") - .takes_value(true) - .multiple(true) - .number_of_values(1), - ) - .arg( - Arg::with_name("remove-ports") - .long("remove-ports") - .help("Remove all port mappings"), - ) - .arg( - Arg::with_name("port") - .long("port") - .short("p") - .help("Port in format \"host_port:guest_port\" to be exposed to the host") - .takes_value(true) - .multiple(true) - .number_of_values(1), - ) - .arg( - Arg::with_name("new-name") - .long("name") - .help("Assign a new name to the VM") - .takes_value(true), - ) - .arg( - Arg::with_name("NAME") - .help("Name of the VM to be modified") - .required(true), - ), - ) - .subcommand( - App::new("config") - .about("Configure global values") - .arg( - Arg::with_name("cpus") - .long("cpus") - .help("Default number of vCPUs for newly created VMs") - .takes_value(true), - ) - .arg( - Arg::with_name("mem") - .long("mem") - .help("Default amount of RAM in MiB for newly created VMs") - .takes_value(true), - ) - .arg( - Arg::with_name("dns") - .long("dns") - .help("DNS server to use in the microVM") - .takes_value(true), - ), - ) - .subcommand( - App::new("delete").about("Delete an existing microVM").arg( - Arg::with_name("NAME") - .help("Name of the microVM to be deleted") - .required(true) - .index(1), - ), - ) - .subcommand( - App::new("list").about("List microVMs").arg( - Arg::with_name("debug") - .short("d") - .help("print debug information verbosely"), - ), - ) - .subcommand( - App::new("start") - .about("Start an existing microVM") - .arg(Arg::with_name("cpus").long("cpus").help("Number of vCPUs")) - .arg( - Arg::with_name("mem") - .long("mem") - .help("Amount of RAM in MiB"), - ) - .arg( - Arg::with_name("NAME") - .help("Name of the microVM") - .required(true) - .index(1), - ) - .arg( - Arg::with_name("COMMAND") - .help("Command to run inside the VM") - .index(2), - ) - .arg( - Arg::with_name("ARGS") - .help("Arguments to be passed to the command executed in the VM") - .multiple(true) - .last(true), - ), - ); - - let mut create = App::new("create") - .about("Create a new microVM") - .arg( - Arg::with_name("cpus") - .long("cpus") - .help("Number of vCPUs") - .takes_value(true), - ) - .arg( - Arg::with_name("mem") - .long("mem") - .help("Amount of RAM in MiB") - .takes_value(true), - ) - .arg( - Arg::with_name("dns") - .long("dns") - .help("DNS server to use in the microVM") - .takes_value(true), - ) - .arg( - Arg::with_name("workdir") - .long("workdir") - .short("w") - .help("Working directory inside the microVM") - .takes_value(true) - .default_value(""), - ) - .arg( - Arg::with_name("volume") - .long("volume") - .short("v") - .help("Volume in form \"host_path:guest_path\" to be exposed to the guest") - .takes_value(true) - .multiple(true) - .number_of_values(1), - ) - .arg( - Arg::with_name("port") - .long("port") - .short("p") - .help("Port in format \"host_port:guest_port\" to be exposed to the host") - .takes_value(true) - .multiple(true) - .number_of_values(1), - ) - .arg( - Arg::with_name("name") - .long("name") - .help("Assign a name to the VM") - .takes_value(true), - ) - .arg( - Arg::with_name("IMAGE") - .help("OCI image to use as template") - .required(true), - ); - - if cfg!(target_os = "macos") { - create = create.arg( - Arg::with_name("x86") - .long("x86") - .short("x") - .help("Create a x86_64 microVM even on an Aarch64 host"), - ); - } +#[derive(Parser, Debug)] +#[command(author, version, about)] +struct Cli { + /// Sets the level of verbosity + #[arg(short)] + verbosity: Option, //TODO: implement or remove this + #[command(subcommand)] + command: Command, +} - app = app.subcommand(create); +#[derive(Subcommand, Debug)] +enum Command { + Start(StartCmdArgs), + Create(CreateCmdArgs), + List(ListCmdArgs), + Delete(DeleteCmdArgs), + #[command(name = "changevm")] + ChangeVm(ChangeVmCmdArgs), + Config(ConfigCmdArgs), +} - let matches = app.clone().get_matches(); +fn main() { + let mut cfg: KrunvmConfig = confy::load(APP_NAME).unwrap(); + let cli_args = Cli::parse(); #[cfg(target_os = "macos")] check_volume(&mut cfg); #[cfg(target_os = "linux")] check_unshare(); - if let Some(matches) = matches.subcommand_matches("changevm") { - changevm::changevm(&mut cfg, matches); - } else if let Some(matches) = matches.subcommand_matches("config") { - config::config(&mut cfg, matches); - } else if let Some(matches) = matches.subcommand_matches("create") { - create::create(&mut cfg, matches); - } else if let Some(matches) = matches.subcommand_matches("delete") { - delete::delete(&mut cfg, matches); - } else if let Some(matches) = matches.subcommand_matches("list") { - list::list(&cfg, matches); - } else if let Some(matches) = matches.subcommand_matches("start") { - start::start(&cfg, matches); - } else { - app.print_long_help().unwrap(); - println!(); + match cli_args.command { + Command::Start(args) => start::start(&cfg, args), + Command::Create(args) => create::create(&mut cfg, args), + Command::List(args) => list::list(&cfg, args), + Command::Delete(args) => delete::delete(&mut cfg, args), + Command::ChangeVm(args) => changevm::changevm(&mut cfg, args), + Command::Config(args) => config::config(&mut cfg, args), } } diff --git a/src/start.rs b/src/start.rs index 05375f3..648796b 100644 --- a/src/start.rs +++ b/src/start.rs @@ -1,6 +1,7 @@ // Copyright 2021 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use clap::Args; use libc::c_char; use std::ffi::CString; use std::fs::File; @@ -12,7 +13,28 @@ use std::path::Path; use super::bindings; use super::utils::{mount_container, umount_container}; -use crate::{ArgMatches, KrunvmConfig, VmConfig}; +use crate::{KrunvmConfig, VmConfig}; + +#[derive(Args, Debug)] +/// Start an existing microVM +pub struct StartCmdArgs { + /// Name of the microVM + name: String, + + /// Command to run inside the VM + command: Option, + + /// Arguments to be passed to the command executed in the VM + args: Vec, + + /// Number of vCPUs + #[arg(long)] + cpus: Option, // TODO: implement or remove this + + /// Amount of RAM in MiB + #[arg(long)] + mem: Option, // TODO: implement or remove this +} #[cfg(target_os = "linux")] fn map_volumes(_ctx: u32, vmcfg: &VmConfig, rootfs: &str) { @@ -97,6 +119,7 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec File { file } -pub fn start(cfg: &KrunvmConfig, matches: &ArgMatches) { - let cmd = matches.value_of("COMMAND"); - let name = matches.value_of("NAME").unwrap(); - - let vmcfg = match cfg.vmconfig_map.get(name) { +pub fn start(cfg: &KrunvmConfig, args: StartCmdArgs) { + let vmcfg = match cfg.vmconfig_map.get(&args.name) { None => { - println!("No VM found with name {}", name); + println!("No VM found with name {}", args.name); std::process::exit(-1); } Some(vmcfg) => vmcfg, @@ -190,11 +210,11 @@ pub fn start(cfg: &KrunvmConfig, matches: &ArgMatches) { umount_container(cfg, vmcfg).expect("Error unmounting container"); let rootfs = mount_container(cfg, vmcfg).expect("Error mounting container"); - let args: Vec = if cmd.is_some() { - match matches.values_of("ARGS") { - Some(a) => a.map(|val| CString::new(val).unwrap()).collect(), - None => Vec::new(), - } + let vm_args: Vec = if args.command.is_some() { + args.args + .into_iter() + .map(|val| CString::new(val).unwrap()) + .collect() } else { Vec::new() }; @@ -203,7 +223,7 @@ pub fn start(cfg: &KrunvmConfig, matches: &ArgMatches) { let _file = set_lock(&rootfs); - unsafe { exec_vm(vmcfg, &rootfs, cmd, args) }; + unsafe { exec_vm(vmcfg, &rootfs, args.command.as_deref(), vm_args) }; umount_container(cfg, vmcfg).expect("Error unmounting container"); } diff --git a/src/utils.rs b/src/utils.rs index 220720e..8b3d5b2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::path::Path; use std::process::Command; +use std::str::FromStr; use crate::{KrunvmConfig, VmConfig, APP_NAME}; @@ -71,70 +72,93 @@ pub fn get_buildah_args(cfg: &KrunvmConfig, cmd: BuildahCommand) -> Vec args } -pub fn parse_mapped_ports(port_matches: Vec<&str>) -> HashMap { - let mut mapped_ports = HashMap::new(); - for port in port_matches.iter() { - let vtuple: Vec<&str> = port.split(':').collect(); +#[derive(Debug, Clone)] +pub struct PortPair { + pub host_port: String, + pub guest_port: String, +} + +pub fn port_pairs_to_hash_map( + port_pairs: impl IntoIterator, +) -> HashMap { + port_pairs + .into_iter() + .map(|pair: PortPair| (pair.host_port, pair.guest_port)) + .collect() +} + +impl FromStr for PortPair { + type Err = &'static str; + + fn from_str(input: &str) -> Result { + let vtuple: Vec<&str> = input.split(':').collect(); if vtuple.len() != 2 { - println!("Invalid value for \"port\""); - std::process::exit(-1); + return Err("Too many ':' separators"); } let host_port: u16 = match vtuple[0].parse() { Ok(p) => p, Err(_) => { - println!("Invalid host port"); - std::process::exit(-1); + return Err("Invalid host port"); } }; let guest_port: u16 = match vtuple[1].parse() { Ok(p) => p, Err(_) => { - println!("Invalid guest port"); - std::process::exit(-1); + return Err("Invalid guest port"); } }; - - mapped_ports.insert(host_port.to_string(), guest_port.to_string()); + Ok(PortPair { + host_port: host_port.to_string(), + guest_port: guest_port.to_string(), + }) } +} + +#[derive(Debug, Clone)] +pub struct PathPair { + pub host_path: String, + pub guest_path: String, +} - mapped_ports +pub fn path_pairs_to_hash_map( + volume_pairs: impl IntoIterator, +) -> HashMap { + volume_pairs + .into_iter() + .map(|pair: PathPair| (pair.host_path, pair.guest_path)) + .collect() } -pub fn parse_mapped_volumes(volume_matches: Vec<&str>) -> HashMap { - let mut mapped_volumes = HashMap::new(); - for volume in volume_matches.iter() { - let vtuple: Vec<&str> = volume.split(':').collect(); +impl FromStr for PathPair { + type Err = &'static str; + + fn from_str(input: &str) -> Result { + let vtuple: Vec<&str> = input.split(':').collect(); if vtuple.len() != 2 { - println!("Invalid value for \"volume\""); - std::process::exit(-1); + return Err("Too many ':' separators"); } + let host_path = Path::new(vtuple[0]); if !host_path.is_absolute() { - println!("Invalid volume, host_path is not an absolute path"); - std::process::exit(-1); + return Err("Invalid volume, host_path is not an absolute path"); } if !host_path.exists() { - println!("Invalid volume, host_path does not exists"); - std::process::exit(-1); + return Err("Invalid volume, host_path does not exists"); } let guest_path = Path::new(vtuple[1]); if !guest_path.is_absolute() { - println!("Invalid volume, guest_path is not an absolute path"); - std::process::exit(-1); + return Err("Invalid volume, guest_path is not an absolute path"); } if guest_path.components().count() != 2 { - println!( - "Invalid volume, only single direct root children are supported as guest_path" + return Err( + "Invalid volume, only single direct root children are supported as guest_path", ); - std::process::exit(-1); } - mapped_volumes.insert( - host_path.to_str().unwrap().to_string(), - guest_path.to_str().unwrap().to_string(), - ); + Ok(Self { + host_path: vtuple[0].to_string(), + guest_path: vtuple[1].to_string(), + }) } - - mapped_volumes } #[cfg(target_os = "macos")] From 5f8693c69351441bded73fca4c318765e50c0e9e Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 19 Oct 2023 15:29:29 +0200 Subject: [PATCH 6/8] Instead of a free function for each command make it a method You could have a trait and uniform interface for the run method, but it isn't necessary for now. It also allows passing more arguments from main. Signed-off-by: Matej Hrica --- src/changevm.rs | 138 +++++++++++++-------------- src/config.rs | 74 ++++++++------- src/create.rs | 242 ++++++++++++++++++++++++------------------------ src/delete.rs | 26 +++--- src/list.rs | 30 +++--- src/main.rs | 36 +++---- src/start.rs | 64 ++++++------- 7 files changed, 311 insertions(+), 299 deletions(-) diff --git a/src/changevm.rs b/src/changevm.rs index 4b93931..5cd365b 100644 --- a/src/changevm.rs +++ b/src/changevm.rs @@ -11,7 +11,7 @@ use super::list::printvm; /// Change the configuration of a microVM #[derive(Args, Debug)] -pub struct ChangeVmCmdArgs { +pub struct ChangeVmCmd { /// Name of the VM to be modified name: String, @@ -48,92 +48,94 @@ pub struct ChangeVmCmdArgs { ports: Vec, } -pub fn changevm(cfg: &mut KrunvmConfig, args: ChangeVmCmdArgs) { - let mut cfg_changed = false; +impl ChangeVmCmd { + pub fn run(self, cfg: &mut KrunvmConfig) { + let mut cfg_changed = false; - let vmcfg = if let Some(new_name) = &args.new_name { - if cfg.vmconfig_map.contains_key(new_name) { - println!("A VM with name {} already exists", new_name); - std::process::exit(-1); - } - - let mut vmcfg = match cfg.vmconfig_map.remove(&args.name) { - None => { - println!("No VM found with name {}", &args.name); + let vmcfg = if let Some(new_name) = &self.new_name { + if cfg.vmconfig_map.contains_key(new_name) { + println!("A VM with name {} already exists", new_name); std::process::exit(-1); } - Some(vmcfg) => vmcfg, + + let mut vmcfg = match cfg.vmconfig_map.remove(&self.name) { + None => { + println!("No VM found with name {}", &self.name); + std::process::exit(-1); + } + Some(vmcfg) => vmcfg, + }; + + cfg_changed = true; + let name = new_name.to_string(); + vmcfg.name = name.clone(); + cfg.vmconfig_map.insert(name.clone(), vmcfg); + cfg.vmconfig_map.get_mut(&name).unwrap() + } else { + match cfg.vmconfig_map.get_mut(&self.name) { + None => { + println!("No VM found with name {}", self.name); + std::process::exit(-1); + } + Some(vmcfg) => vmcfg, + } }; - cfg_changed = true; - let name = new_name.to_string(); - vmcfg.name = name.clone(); - cfg.vmconfig_map.insert(name.clone(), vmcfg); - cfg.vmconfig_map.get_mut(&name).unwrap() - } else { - match cfg.vmconfig_map.get_mut(&args.name) { - None => { - println!("No VM found with name {}", args.name); - std::process::exit(-1); + if let Some(cpus) = self.cpus { + if cpus > 8 { + println!("Error: the maximum number of CPUs supported is 8"); + } else { + vmcfg.cpus = cpus; + cfg_changed = true; } - Some(vmcfg) => vmcfg, } - }; - if let Some(cpus) = args.cpus { - if cpus > 8 { - println!("Error: the maximum number of CPUs supported is 8"); - } else { - vmcfg.cpus = cpus; - cfg_changed = true; + if let Some(mem) = self.mem { + if mem > 16384 { + println!("Error: the maximum amount of RAM supported is 16384 MiB"); + } else { + vmcfg.mem = mem; + cfg_changed = true; + } } - } - if let Some(mem) = args.mem { - if mem > 16384 { - println!("Error: the maximum amount of RAM supported is 16384 MiB"); - } else { - vmcfg.mem = mem; + if self.remove_volumes { + vmcfg.mapped_volumes = HashMap::new(); cfg_changed = true; - } - } + } else { + let mapped_volumes = path_pairs_to_hash_map(self.volumes); - if args.remove_volumes { - vmcfg.mapped_volumes = HashMap::new(); - cfg_changed = true; - } else { - let mapped_volumes = path_pairs_to_hash_map(args.volumes); + if !mapped_volumes.is_empty() { + vmcfg.mapped_volumes = mapped_volumes; + cfg_changed = true; + } + } + // TODO: don't just silently ignore --volume args when --remove_volumes is specified - if !mapped_volumes.is_empty() { - vmcfg.mapped_volumes = mapped_volumes; + if self.remove_ports { + vmcfg.mapped_ports = HashMap::new(); cfg_changed = true; - } - } - // TODO: don't just silently ignore --volume args when --remove_volumes is specified + } else { + let mapped_ports = port_pairs_to_hash_map(self.ports); - if args.remove_ports { - vmcfg.mapped_ports = HashMap::new(); - cfg_changed = true; - } else { - let mapped_ports = port_pairs_to_hash_map(args.ports); + if !mapped_ports.is_empty() { + vmcfg.mapped_ports = mapped_ports; + cfg_changed = true; + } + } + // TODO: don't just silently ignore --port args when --remove_ports is specified - if !mapped_ports.is_empty() { - vmcfg.mapped_ports = mapped_ports; + if let Some(workdir) = self.workdir { + vmcfg.workdir = workdir.to_string(); cfg_changed = true; } - } - // TODO: don't just silently ignore --port args when --remove_ports is specified - if let Some(workdir) = args.workdir { - vmcfg.workdir = workdir.to_string(); - cfg_changed = true; - } - - println!(); - printvm(vmcfg); - println!(); + println!(); + printvm(vmcfg); + println!(); - if cfg_changed { - confy::store(APP_NAME, &cfg).unwrap(); + if cfg_changed { + confy::store(APP_NAME, &cfg).unwrap(); + } } } diff --git a/src/config.rs b/src/config.rs index b711ca1..ab09d34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use clap::Args; /// Configure global values #[derive(Args, Debug)] -pub struct ConfigCmdArgs { +pub struct ConfigCmd { // Default number of vCPUs for newly created VMs #[arg(long)] cpus: Option, @@ -20,47 +20,49 @@ pub struct ConfigCmdArgs { dns: Option, } -pub fn config(cfg: &mut KrunvmConfig, args: ConfigCmdArgs) { - let mut cfg_changed = false; +impl ConfigCmd { + pub fn run(self, cfg: &mut KrunvmConfig) { + let mut cfg_changed = false; - if let Some(cpus) = args.cpus { - if cpus > 8 { - println!("Error: the maximum number of CPUs supported is 8"); - } else { - cfg.default_cpus = cpus; - cfg_changed = true; + if let Some(cpus) = self.cpus { + if cpus > 8 { + println!("Error: the maximum number of CPUs supported is 8"); + } else { + cfg.default_cpus = cpus; + cfg_changed = true; + } + } + + if let Some(mem) = self.mem { + if mem > 16384 { + println!("Error: the maximum amount of RAM supported is 16384 MiB"); + } else { + cfg.default_mem = mem; + cfg_changed = true; + } } - } - if let Some(mem) = args.mem { - if mem > 16384 { - println!("Error: the maximum amount of RAM supported is 16384 MiB"); - } else { - cfg.default_mem = mem; + if let Some(dns) = self.dns { + cfg.default_dns = dns; cfg_changed = true; } - } - if let Some(dns) = args.dns { - cfg.default_dns = dns; - cfg_changed = true; - } + if cfg_changed { + confy::store(APP_NAME, &cfg).unwrap(); + } - if cfg_changed { - confy::store(APP_NAME, &cfg).unwrap(); + println!("Global configuration:"); + println!( + "Default number of CPUs for newly created VMs: {}", + cfg.default_cpus + ); + println!( + "Default amount of RAM (MiB) for newly created VMs: {}", + cfg.default_mem + ); + println!( + "Default DNS server for newly created VMs: {}", + cfg.default_dns + ); } - - println!("Global configuration:"); - println!( - "Default number of CPUs for newly created VMs: {}", - cfg.default_cpus - ); - println!( - "Default amount of RAM (MiB) for newly created VMs: {}", - cfg.default_mem - ); - println!( - "Default DNS server for newly created VMs: {}", - cfg.default_dns - ); } diff --git a/src/create.rs b/src/create.rs index c8491d6..bc1bf8b 100644 --- a/src/create.rs +++ b/src/create.rs @@ -17,7 +17,7 @@ const KRUNVM_ROSETTA_FILE: &str = ".krunvm-rosetta"; /// Create a new microVM #[derive(Args, Debug)] -pub struct CreateCmdArgs { +pub struct CreateCmd { /// OCI image to use as template image: String, @@ -55,6 +55,127 @@ pub struct CreateCmdArgs { x86: bool, } +impl CreateCmd { + pub fn run(self, cfg: &mut KrunvmConfig) { + #[allow(unused_mut)] + let mut cpus = self.cpus.unwrap_or(cfg.default_cpus); + let mem = self.mem.unwrap_or(cfg.default_mem); + let dns = self.dns.unwrap_or_else(|| cfg.default_dns.clone()); + let workdir = self.workdir; + let mapped_volumes = path_pairs_to_hash_map(self.volumes); + let mapped_ports = port_pairs_to_hash_map(self.ports); + let image = self.image; + let name = self.name; + + if let Some(ref name) = name { + if cfg.vmconfig_map.contains_key(name) { + println!("A VM with this name already exists"); + std::process::exit(-1); + } + } + + let mut args = get_buildah_args(cfg, BuildahCommand::From); + + #[cfg(target_os = "macos")] + let force_x86 = self.x86; + + #[cfg(target_os = "macos")] + if force_x86 { + let home = match std::env::var("HOME") { + Err(e) => { + println!("Error reading \"HOME\" enviroment variable: {}", e); + std::process::exit(-1); + } + Ok(home) => home, + }; + + let path = format!("{}/{}", home, KRUNVM_ROSETTA_FILE); + if !Path::new(&path).is_file() { + println!( + " +To use Rosetta for Linux you need to create the file... + +{} + +...with the contents that the \"rosetta\" binary expects to be served from +its specific ioctl. + +For more information, please refer to this post: +https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/ +", + path + ); + std::process::exit(-1); + } + + if cpus != 1 { + println!("x86 microVMs on Aarch64 are restricted to 1 CPU"); + cpus = 1; + } + args.push("--arch".to_string()); + args.push("x86_64".to_string()); + } + + args.push(image.to_string()); + + let output = match Command::new("buildah") + .args(&args) + .stderr(std::process::Stdio::inherit()) + .output() + { + Ok(output) => output, + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + println!("{} requires buildah to manage the OCI images, and it wasn't found on this system.", APP_NAME); + } else { + println!("Error executing buildah: {}", err); + } + std::process::exit(-1); + } + }; + + let exit_code = output.status.code().unwrap_or(-1); + if exit_code != 0 { + println!( + "buildah returned an error: {}", + std::str::from_utf8(&output.stdout).unwrap() + ); + std::process::exit(-1); + } + + let container = std::str::from_utf8(&output.stdout).unwrap().trim(); + let name = if let Some(name) = name { + name.to_string() + } else { + container.to_string() + }; + let vmcfg = VmConfig { + name: name.clone(), + cpus, + mem, + dns: dns.to_string(), + container: container.to_string(), + workdir: workdir.to_string(), + mapped_volumes, + mapped_ports, + }; + + let rootfs = mount_container(cfg, &vmcfg).unwrap(); + export_container_config(cfg, &rootfs, &image).unwrap(); + fix_resolv_conf(&rootfs, &dns).unwrap(); + #[cfg(target_os = "macos")] + if force_x86 { + _ = fs::create_dir(format!("{}/.rosetta", rootfs)); + } + umount_container(cfg, &vmcfg).unwrap(); + + cfg.vmconfig_map.insert(name.clone(), vmcfg); + confy::store(APP_NAME, cfg).unwrap(); + + println!("microVM created with name: {}", name); + } +} + fn fix_resolv_conf(rootfs: &str, dns: &str) -> Result<(), std::io::Error> { let resolvconf_dir = format!("{}/etc/", rootfs); fs::create_dir_all(resolvconf_dir)?; @@ -104,122 +225,3 @@ fn export_container_config( Ok(()) } - -pub fn create(cfg: &mut KrunvmConfig, args: CreateCmdArgs) { - #[allow(unused_mut)] - let mut cpus = args.cpus.unwrap_or(cfg.default_cpus); - let mem = args.mem.unwrap_or(cfg.default_mem); - let dns = args.dns.unwrap_or_else(|| cfg.default_dns.clone()); - let workdir = args.workdir; - let mapped_volumes = path_pairs_to_hash_map(args.volumes); - let mapped_ports = port_pairs_to_hash_map(args.ports); - let image = args.image; - let name = args.name; - - if let Some(ref name) = name { - if cfg.vmconfig_map.contains_key(name) { - println!("A VM with this name already exists"); - std::process::exit(-1); - } - } - - let mut buildah_args = get_buildah_args(cfg, BuildahCommand::From); - - #[cfg(target_os = "macos")] - let force_x86 = args.x86; - - #[cfg(target_os = "macos")] - if force_x86 { - let home = match std::env::var("HOME") { - Err(e) => { - println!("Error reading \"HOME\" enviroment variable: {}", e); - std::process::exit(-1); - } - Ok(home) => home, - }; - - let path = format!("{}/{}", home, KRUNVM_ROSETTA_FILE); - if !Path::new(&path).is_file() { - println!( - " -To use Rosetta for Linux you need to create the file... - -{} - -...with the contents that the \"rosetta\" binary expects to be served from -its specific ioctl. - -For more information, please refer to this post: -https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/ -", - path - ); - std::process::exit(-1); - } - - if cpus != 1 { - println!("x86 microVMs on Aarch64 are restricted to 1 CPU"); - cpus = 1; - } - buildah_args.push("--arch".to_string()); - buildah_args.push("x86_64".to_string()); - } - - buildah_args.push(image.to_string()); - - let output = match Command::new("buildah") - .args(&buildah_args) - .stderr(std::process::Stdio::inherit()) - .output() - { - Ok(output) => output, - Err(err) => { - if err.kind() == std::io::ErrorKind::NotFound { - println!("{} requires buildah to manage the OCI images, and it wasn't found on this system.", APP_NAME); - } else { - println!("Error executing buildah: {}", err); - } - std::process::exit(-1); - } - }; - - let exit_code = output.status.code().unwrap_or(-1); - if exit_code != 0 { - println!( - "buildah returned an error: {}", - std::str::from_utf8(&output.stdout).unwrap() - ); - std::process::exit(-1); - } - - let container = std::str::from_utf8(&output.stdout).unwrap().trim(); - let name = if let Some(name) = name { - name.to_string() - } else { - container.to_string() - }; - let vmcfg = VmConfig { - name: name.clone(), - cpus, - mem, - dns: dns.to_string(), - container: container.to_string(), - workdir: workdir.to_string(), - mapped_volumes, - mapped_ports, - }; - - let rootfs = mount_container(cfg, &vmcfg).unwrap(); - export_container_config(cfg, &rootfs, &image).unwrap(); - fix_resolv_conf(&rootfs, &dns).unwrap(); - #[cfg(target_os = "macos")] - if force_x86 { - _ = fs::create_dir(format!("{}/.rosetta", rootfs)); - } - umount_container(cfg, &vmcfg).unwrap(); - - cfg.vmconfig_map.insert(name.clone(), vmcfg); - confy::store(APP_NAME, cfg).unwrap(); - - println!("microVM created with name: {}", name); -} diff --git a/src/delete.rs b/src/delete.rs index 0770d1d..acb1b7f 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -8,22 +8,24 @@ use super::utils::{remove_container, umount_container}; /// Delete an existing microVM #[derive(Args, Debug)] -pub struct DeleteCmdArgs { +pub struct DeleteCmd { /// Name of the microVM to be deleted name: String, } -pub fn delete(cfg: &mut KrunvmConfig, args: DeleteCmdArgs) { - let vmcfg = match cfg.vmconfig_map.remove(&args.name) { - None => { - println!("No VM found with that name"); - std::process::exit(-1); - } - Some(vmcfg) => vmcfg, - }; +impl DeleteCmd { + pub fn run(self, cfg: &mut KrunvmConfig) { + let vmcfg = match cfg.vmconfig_map.remove(&self.name) { + None => { + println!("No VM found with that name"); + std::process::exit(-1); + } + Some(vmcfg) => vmcfg, + }; - umount_container(cfg, &vmcfg).unwrap(); - remove_container(cfg, &vmcfg).unwrap(); + umount_container(cfg, &vmcfg).unwrap(); + remove_container(cfg, &vmcfg).unwrap(); - confy::store(APP_NAME, &cfg).unwrap(); + confy::store(APP_NAME, &cfg).unwrap(); + } } diff --git a/src/list.rs b/src/list.rs index bd0834c..791943c 100644 --- a/src/list.rs +++ b/src/list.rs @@ -6,10 +6,24 @@ use clap::Args; /// List microVMs #[derive(Args, Debug)] -pub struct ListCmdArgs { +pub struct ListCmd { /// Print debug information verbosely #[arg(short)] - debug: bool, //TODO: implement or remove this + pub debug: bool, //TODO: implement or remove this +} + +impl ListCmd { + pub fn run(self, cfg: &KrunvmConfig) { + if cfg.vmconfig_map.is_empty() { + println!("No microVMs found"); + } else { + for (_name, vm) in cfg.vmconfig_map.iter() { + println!(); + printvm(vm); + } + println!(); + } + } } pub fn printvm(vm: &VmConfig) { @@ -22,15 +36,3 @@ pub fn printvm(vm: &VmConfig) { println!(" Mapped volumes: {:?}", vm.mapped_volumes); println!(" Mapped ports: {:?}", vm.mapped_ports); } - -pub fn list(cfg: &KrunvmConfig, _args: ListCmdArgs) { - if cfg.vmconfig_map.is_empty() { - println!("No microVMs found"); - } else { - for (_name, vm) in cfg.vmconfig_map.iter() { - println!(); - printvm(vm); - } - println!(); - } -} diff --git a/src/main.rs b/src/main.rs index 9e393eb..abf2b3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,12 +7,12 @@ use std::fs::File; #[cfg(target_os = "macos")] use std::io::{self, Read, Write}; -use crate::changevm::ChangeVmCmdArgs; -use crate::config::ConfigCmdArgs; -use crate::create::CreateCmdArgs; -use crate::delete::DeleteCmdArgs; -use crate::list::ListCmdArgs; -use crate::start::StartCmdArgs; +use crate::changevm::ChangeVmCmd; +use crate::config::ConfigCmd; +use crate::create::CreateCmd; +use crate::delete::DeleteCmd; +use crate::list::ListCmd; +use crate::start::StartCmd; use clap::{Parser, Subcommand}; use serde_derive::{Deserialize, Serialize}; #[cfg(target_os = "macos")] @@ -166,13 +166,13 @@ struct Cli { #[derive(Subcommand, Debug)] enum Command { - Start(StartCmdArgs), - Create(CreateCmdArgs), - List(ListCmdArgs), - Delete(DeleteCmdArgs), + Start(StartCmd), + Create(CreateCmd), + List(ListCmd), + Delete(DeleteCmd), #[command(name = "changevm")] - ChangeVm(ChangeVmCmdArgs), - Config(ConfigCmdArgs), + ChangeVm(ChangeVmCmd), + Config(ConfigCmd), } fn main() { @@ -185,11 +185,11 @@ fn main() { check_unshare(); match cli_args.command { - Command::Start(args) => start::start(&cfg, args), - Command::Create(args) => create::create(&mut cfg, args), - Command::List(args) => list::list(&cfg, args), - Command::Delete(args) => delete::delete(&mut cfg, args), - Command::ChangeVm(args) => changevm::changevm(&mut cfg, args), - Command::Config(args) => config::config(&mut cfg, args), + Command::Start(cmd) => cmd.run(&cfg), + Command::Create(cmd) => cmd.run(&mut cfg), + Command::List(cmd) => cmd.run(&cfg), + Command::Delete(cmd) => cmd.run(&mut cfg), + Command::ChangeVm(cmd) => cmd.run(&mut cfg), + Command::Config(cmd) => cmd.run(&mut cfg), } } diff --git a/src/start.rs b/src/start.rs index 648796b..0d7823b 100644 --- a/src/start.rs +++ b/src/start.rs @@ -17,7 +17,7 @@ use crate::{KrunvmConfig, VmConfig}; #[derive(Args, Debug)] /// Start an existing microVM -pub struct StartCmdArgs { +pub struct StartCmd { /// Name of the microVM name: String, @@ -36,6 +36,38 @@ pub struct StartCmdArgs { mem: Option, // TODO: implement or remove this } +impl StartCmd { + pub fn run(self, cfg: &KrunvmConfig) { + let vmcfg = match cfg.vmconfig_map.get(&self.name) { + None => { + println!("No VM found with name {}", self.name); + std::process::exit(-1); + } + Some(vmcfg) => vmcfg, + }; + + umount_container(cfg, vmcfg).expect("Error unmounting container"); + let rootfs = mount_container(cfg, vmcfg).expect("Error mounting container"); + + let vm_args: Vec = if self.command.is_some() { + self.args + .into_iter() + .map(|val| CString::new(val).unwrap()) + .collect() + } else { + Vec::new() + }; + + set_rlimits(); + + let _file = set_lock(&rootfs); + + unsafe { exec_vm(vmcfg, &rootfs, self.command.as_deref(), vm_args) }; + + umount_container(cfg, vmcfg).expect("Error unmounting container"); + } +} + #[cfg(target_os = "linux")] fn map_volumes(_ctx: u32, vmcfg: &VmConfig, rootfs: &str) { for (host_path, guest_path) in vmcfg.mapped_volumes.iter() { @@ -197,33 +229,3 @@ fn set_lock(rootfs: &str) -> File { file } - -pub fn start(cfg: &KrunvmConfig, args: StartCmdArgs) { - let vmcfg = match cfg.vmconfig_map.get(&args.name) { - None => { - println!("No VM found with name {}", args.name); - std::process::exit(-1); - } - Some(vmcfg) => vmcfg, - }; - - umount_container(cfg, vmcfg).expect("Error unmounting container"); - let rootfs = mount_container(cfg, vmcfg).expect("Error mounting container"); - - let vm_args: Vec = if args.command.is_some() { - args.args - .into_iter() - .map(|val| CString::new(val).unwrap()) - .collect() - } else { - Vec::new() - }; - - set_rlimits(); - - let _file = set_lock(&rootfs); - - unsafe { exec_vm(vmcfg, &rootfs, args.command.as_deref(), vm_args) }; - - umount_container(cfg, vmcfg).expect("Error unmounting container"); -} From 90a8299743bebb60a0a938d25274a1e60b84ec08 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 19 Oct 2023 15:39:52 +0200 Subject: [PATCH 7/8] Move commands to a module Signed-off-by: Matej Hrica --- src/{ => commands}/changevm.rs | 0 src/{ => commands}/config.rs | 0 src/{ => commands}/create.rs | 6 ++++-- src/{ => commands}/delete.rs | 2 +- src/{ => commands}/list.rs | 0 src/commands/mod.rs | 13 +++++++++++++ src/{ => commands}/start.rs | 4 ++-- src/main.rs | 15 +++------------ 8 files changed, 23 insertions(+), 17 deletions(-) rename src/{ => commands}/changevm.rs (100%) rename src/{ => commands}/config.rs (100%) rename src/{ => commands}/create.rs (97%) rename src/{ => commands}/delete.rs (92%) rename src/{ => commands}/list.rs (100%) create mode 100644 src/commands/mod.rs rename src/{ => commands}/start.rs (98%) diff --git a/src/changevm.rs b/src/commands/changevm.rs similarity index 100% rename from src/changevm.rs rename to src/commands/changevm.rs diff --git a/src/config.rs b/src/commands/config.rs similarity index 100% rename from src/config.rs rename to src/commands/config.rs diff --git a/src/create.rs b/src/commands/create.rs similarity index 97% rename from src/create.rs rename to src/commands/create.rs index bc1bf8b..4040983 100644 --- a/src/create.rs +++ b/src/commands/create.rs @@ -8,8 +8,10 @@ use std::io::Write; use std::path::Path; use std::process::Command; -use super::utils::{get_buildah_args, mount_container, umount_container, BuildahCommand}; -use crate::utils::{path_pairs_to_hash_map, port_pairs_to_hash_map, PathPair, PortPair}; +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")] diff --git a/src/delete.rs b/src/commands/delete.rs similarity index 92% rename from src/delete.rs rename to src/commands/delete.rs index acb1b7f..2f4c54a 100644 --- a/src/delete.rs +++ b/src/commands/delete.rs @@ -4,7 +4,7 @@ use crate::{KrunvmConfig, APP_NAME}; use clap::Args; -use super::utils::{remove_container, umount_container}; +use crate::utils::{remove_container, umount_container}; /// Delete an existing microVM #[derive(Args, Debug)] diff --git a/src/list.rs b/src/commands/list.rs similarity index 100% rename from src/list.rs rename to src/commands/list.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..256e206 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,13 @@ +mod changevm; +mod config; +mod create; +mod delete; +mod list; +mod start; + +pub use changevm::ChangeVmCmd; +pub use config::ConfigCmd; +pub use create::CreateCmd; +pub use delete::DeleteCmd; +pub use list::ListCmd; +pub use start::StartCmd; diff --git a/src/start.rs b/src/commands/start.rs similarity index 98% rename from src/start.rs rename to src/commands/start.rs index 0d7823b..2d67423 100644 --- a/src/start.rs +++ b/src/commands/start.rs @@ -11,8 +11,8 @@ use std::os::unix::io::AsRawFd; #[cfg(target_os = "macos")] use std::path::Path; -use super::bindings; -use super::utils::{mount_container, umount_container}; +use crate::bindings; +use crate::utils::{mount_container, umount_container}; use crate::{KrunvmConfig, VmConfig}; #[derive(Args, Debug)] diff --git a/src/main.rs b/src/main.rs index abf2b3a..0531136 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,24 +7,15 @@ use std::fs::File; #[cfg(target_os = "macos")] use std::io::{self, Read, Write}; -use crate::changevm::ChangeVmCmd; -use crate::config::ConfigCmd; -use crate::create::CreateCmd; -use crate::delete::DeleteCmd; -use crate::list::ListCmd; -use crate::start::StartCmd; +use crate::commands::{ChangeVmCmd, ConfigCmd, CreateCmd, DeleteCmd, ListCmd, StartCmd}; use clap::{Parser, Subcommand}; use serde_derive::{Deserialize, Serialize}; #[cfg(target_os = "macos")] use text_io::read; + #[allow(unused)] mod bindings; -mod changevm; -mod config; -mod create; -mod delete; -mod list; -mod start; +mod commands; mod utils; const APP_NAME: &str = "krunvm"; From 5fc42aa4dfde8608320e06192a7d5ec428834bb5 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 8 Nov 2023 11:42:06 +0100 Subject: [PATCH 8/8] Add support for starting VMs with passt network This required adding another field to the config. This is done by migrating the old config to a newer version. We are backwards compatible on configuration but not forwards compatible (older versions of krunvm will not be able to use the config from this version) If we want forward compatibility, I feel like we need to ditch the confy crate. Signed-off-by: Matej Hrica --- Cargo.toml | 2 +- docs/krunvm-changevm.1.txt | 2 + docs/krunvm-config.1.txt | 3 + docs/krunvm-create.1.txt | 2 + docs/krunvm.1.txt | 11 ++- src/bindings.rs | 1 + src/commands/changevm.rs | 13 +++- src/commands/config.rs | 17 ++++- src/commands/create.rs | 10 ++- src/commands/delete.rs | 5 +- src/commands/list.rs | 3 +- src/commands/start.rs | 85 +++++++++++++++++++++-- src/config/migrate.rs | 138 +++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 20 ++++++ src/config/v1.rs | 37 ++++++++++ src/config/v2.rs | 98 ++++++++++++++++++++++++++ src/main.rs | 42 ++--------- src/utils.rs | 3 +- 18 files changed, 433 insertions(+), 59 deletions(-) create mode 100644 src/config/migrate.rs create mode 100644 src/config/mod.rs create mode 100644 src/config/v1.rs create mode 100644 src/config/v2.rs diff --git a/Cargo.toml b/Cargo.toml index dec77b6..80451e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,4 @@ libc = "0.2.82" serde = "1.0.120" serde_derive = "1.0.120" text_io = "0.1.8" -nix = {version = "0.27.1", features = ["socket", "fs"]} +nix = {version = "0.27.1", features = ["socket", "fs"]} \ No newline at end of file diff --git a/docs/krunvm-changevm.1.txt b/docs/krunvm-changevm.1.txt index 4f14e90..e00838d 100644 --- a/docs/krunvm-changevm.1.txt +++ b/docs/krunvm-changevm.1.txt @@ -61,6 +61,8 @@ host visible in the guest. An empty string ("") tells krunvm to not set a working directory explicitly, letting libkrun decide which one should be set. +*--net* _NETWORK_MODE_:: + Configures the network connection mode. Supported modes are either PASST or TSI. SEE ALSO -------- diff --git a/docs/krunvm-config.1.txt b/docs/krunvm-config.1.txt index 168ed91..ae3b416 100644 --- a/docs/krunvm-config.1.txt +++ b/docs/krunvm-config.1.txt @@ -34,6 +34,9 @@ OPTIONS Sets the default mount of RAM, in MiB, that will be configured for newly created microVMs. +*--net* _NETWORK_MODE_:: + Sets the default network connection mode, that will be configured for + newly created microVMs. Supported modes are PASST or TSI. SEE ALSO -------- diff --git a/docs/krunvm-create.1.txt b/docs/krunvm-create.1.txt index d63f3f8..488bcb4 100644 --- a/docs/krunvm-create.1.txt +++ b/docs/krunvm-create.1.txt @@ -53,6 +53,8 @@ host visible in the guest. An empty string ("") tells krunvm to not set a working directory explicitly, letting libkrun decide which one should be set. +*--net* _NETWORK_MODE_:: + Set the network connection mode. Supported modes are either PASST or TSI. SEE ALSO -------- diff --git a/docs/krunvm.1.txt b/docs/krunvm.1.txt index 8a956e8..e04ea82 100644 --- a/docs/krunvm.1.txt +++ b/docs/krunvm.1.txt @@ -29,10 +29,15 @@ microVM and exposing ports from the guest to the host (and the networks connected to it). Networking to the guest running in the microVM is provided by -libkrun's TSI (Transparent Socket Impersonation), enabling a seamless -experience that doesn't require network bridges nor other explicit -network configuration. +either libkrun's TSI (Transparent Socket Impersonation) or PASST. +TSI enables a seamless experience that doesn't require network bridges nor other explicit +network configuration. It only supports impersonating AF_INET SOCK_DGRAM and SOCK_STREAM sockets. +This implies it's not possible to communicate outside the VM with raw sockets. + +PASST uses virtio-net guest device and sends all traffic to a passt subprocess. +Support of network protocols is therefore dependent on what passt supports. +Note that currently you need to run a DHCP client in the guest to get an IP address. GLOBAL OPTIONS -------------- 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..9b7e9d7 100644 --- a/src/commands/changevm.rs +++ b/src/commands/changevm.rs @@ -4,8 +4,8 @@ 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; @@ -46,6 +46,10 @@ pub struct ChangeVmCmd { /// Port(s) in format "host_port:guest_port" to be exposed to the host #[arg(long = "port")] ports: Vec, + + /// Set the network connection mode for the microVM + #[arg(long)] + net: Option, } impl ChangeVmCmd { @@ -130,12 +134,17 @@ impl ChangeVmCmd { cfg_changed = true; } + if let Some(network_mode) = self.net { + 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..1e3b372 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 connection mode to use + #[arg(long)] + net: Option, } impl ConfigCmd { @@ -47,11 +51,18 @@ impl ConfigCmd { cfg_changed = true; } + if let Some(network_mode) = self.net { + 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..60d7ae0 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 connection mode to use + #[arg(long)] + net: Option, + /// Create a x86_64 microVM even on an Aarch64 host #[arg(short, long)] #[cfg(target_os = "macos")] @@ -68,6 +72,7 @@ impl CreateCmd { let mapped_ports = port_pairs_to_hash_map(self.ports); let image = self.image; let name = self.name; + let network_mode = self.net.unwrap_or_else(|| cfg.default_network_mode.clone()); if let Some(ref name) = name { if cfg.vmconfig_map.contains_key(name) { @@ -160,6 +165,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..11641a5 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -2,18 +2,29 @@ // 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::{socketpair, AddressFamily, SockFlag, SockType}; +use std::collections::HashMap; use std::ffi::CString; + use std::fs::File; #[cfg(target_os = "linux")] use std::io::{Error, ErrorKind}; + +use std::os::fd::{IntoRawFd, OwnedFd}; use std::os::unix::io::AsRawFd; + #[cfg(target_os = "macos")] use std::path::Path; +use std::process::Stdio; + +use nix::fcntl::{fcntl, FcntlArg, FdFlag}; 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 +47,49 @@ pub struct StartCmd { mem: Option, // TODO: implement or remove this } +fn start_passt(mapped_ports: &HashMap) -> Result { + let (passt_fd, krun_fd) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .map_err(|e| { + eprint!("Failed to create socket pair for passt: {e}"); + })?; + + if let Err(e) = fcntl(krun_fd.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)) { + eprint!("Failed to set FD_CLOEXEC: {e}"); + } + + let mut cmd = std::process::Command::new("passt"); + cmd.arg("-q") + .arg("-f") + .arg("-F") + .arg(passt_fd.as_raw_fd().to_string()); + + if !mapped_ports.is_empty() { + let comma_separated_ports = mapped_ports + .iter() + .map(|(host_port, guest_port)| format!("{}:{}", host_port, guest_port)) + .collect::>() + .join(","); + + cmd.arg("-t").arg(comma_separated_ports); + } + + cmd.stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()); + + if let Err(e) = cmd.spawn() { + eprintln!("Failed to start passt: {e}"); + return Err(()); + } + + Ok(krun_fd) +} + impl StartCmd { pub fn run(self, cfg: &KrunvmConfig) { let vmcfg = match cfg.vmconfig_map.get(&self.name) { @@ -152,10 +206,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(&vmcfg.mapped_ports) else { + std::process::exit(-1); + }; + let ret = krun_set_passt_fd(ctx, passt_fd.into_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..f3d8d8b --- /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!(load_v2_called, "Load v2 must be called"); + assert!(load_v1_called, "Load v1 must be called"); + assert!(save_called, "Save 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,