Compare commits

...

25 Commits
v0.2.3 ... main

Author SHA1 Message Date
Sergio López 21b9cb4760
Merge pull request #68 from stephen-fox/macos-only-map-volumes-if-required
start: Only map volumes if required on macOS
2025-06-16 16:07:10 +01:00
stephen-fox 28ad60fb7b start: Only map volumes if required on macOS
In issue #67, it was reported that VM creation on macOS always fails
with the message: "Error setting VM mapped volumes". [^1] It appears
krun always calls `krun_set_mapped_volumes` even if the user has not
configured any volumes. That krun library function was disabled in
Feb 2024 by commit 186083df9. [^2] Based on the commit message, it
sounds like virtio-fs devices will be the preferred way to implement
mapped volumes going forward.

For now, we can at least avoid calling the deprecated function if
the user has not configured any mapped volumes. That will obviously
not fix the krun feature, but it makes krunvm usable on macOS.

References

1. https://github.com/containers/krunvm/issues/67
2. libkrun commit 186083df9faf704e2b431d90b1ed147363aca004

Signed-off-by: Stephen Fox <stephen.j.fox.jr@gmail.com>
2025-06-13 21:00:53 +00:00
Sergio López 2d9e1d6c6d
Merge pull request #66 from KevSlashNull/inspect
Add inspect command
2025-01-08 17:20:15 +01:00
Kev 🐶 713d418480 Add inspect command
This adds a `krunvm inspect` command that runs `buildah inspect` under
the hood, so users can find information about the VM, such as
the image metadata (e.g. image name and digest) or mount point.

Signed-off-by: Kev <kevslashnull@gmail.com>
2025-01-08 17:06:56 +01:00
Sergio López 765614c783
Merge pull request #58 from EduM22/feat/set-env
Pass environment variable(s) to microVM
2024-06-10 10:38:06 +02:00
Sergio López 212e203f88
Merge pull request #64 from slp/forbid-empty-names
create: forbid empty names
2024-05-30 19:06:09 +02:00
Sergio Lopez b001dca718 create: forbid empty names
Fixes #63

Signed-off-by: Sergio Lopez <slp@redhat.com>
2024-05-30 19:03:00 +02:00
EduM22 57cf8e61b6 add docs + change variable name
Signed-off-by: EduM22 <38257387+EduM22@users.noreply.github.com>
2024-02-06 20:09:29 +01:00
EduM22 a2b60aee8b set env in VM from start args
Signed-off-by: EduM22 <38257387+EduM22@users.noreply.github.com>
2024-02-05 18:13:59 +01:00
Sergio López 5494d84a66
Merge pull request #52 from mtjhrc/refactor-clap
Refactor argument parsing using newer clap derive
2023-11-29 18:35:51 +01:00
Sergio López 27a6dfb4f4
Merge pull request #55 from mtjhrc/fix-aarch64
Fix aarch64 i8/u8 mismatch and improve pipeline
2023-11-29 18:28:27 +01:00
Matej Hrica 90a8299743 Move commands to a module
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-11-02 19:36:02 +01:00
Matej Hrica 5f8693c693 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 <mhrica@redhat.com>
2023-11-02 19:35:50 +01:00
Matej Hrica e467ff9746 Refactor argument parsing using new clap 4.x version
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-11-02 19:35:50 +01:00
Matej Hrica cbf34eb064 Resolve clippy warning for macOS target
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-11-02 12:42:33 +01:00
Matej Hrica 38e1e5cffc Fix compilation for macOS target
We need #[allow(unused_mut)], to disable warnings on other targets.

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-11-02 12:42:33 +01:00
Matej Hrica ced22ac583 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 <mhrica@redhat.com>
2023-11-02 12:42:31 +01:00
Matej Hrica 6b6ea50458 Fix compilation on aarch64 due to i8/u8 mismatch
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-11-02 11:33:52 +01:00
Sergio López 101a402871
Merge pull request #47 from mtjhrc/resolve-warnings
Resolve clippy warnings and use correct types in bindings
2023-10-25 12:16:39 +02:00
Matej Hrica acf6f437a1 Resolve clippy warnings and use correct types in bindings.
bindings were using i8 instead of c_char for C string pointers, but
the types are defined as c_char in libkrun.

There were a lot of unecessary casts from i8 to c_char according to clippy
because on x86_64 they are the same type.
(The casts were necessary on ARM, because c_char is unsigned on ARM).

Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-10-10 12:06:09 +02:00
Sergio López 647ec27c24
Merge pull request #46 from mtjhrc/add-dependancy-to-readme
README: Add asciidoctor dependency to building from source instructions
2023-10-09 11:24:22 +02:00
Sergio López b5c38223f8
Merge pull request #45 from mtjhrc/unnecessary-var-makefile
Makefile: Remove INIT_BINARY because there is no such thing
2023-10-09 11:12:06 +02:00
Matej Hrica 59e3704368 Resolve warnings about unnecessary mut
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-10-04 11:51:30 +02:00
Matej Hrica 7a8a1c6d5e README: Add asciidoctor dependency to building from source instructions
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-10-04 10:54:39 +02:00
Matej Hrica a0e2da39b0 Makefile: Remove INIT_BINARY because there is no such thing
Signed-off-by: Matej Hrica <mhrica@redhat.com>
2023-10-04 10:38:21 +02:00
22 changed files with 1002 additions and 838 deletions

View File

@ -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

260
Cargo.lock generated
View File

@ -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",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
"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",
]
[[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 = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
@ -264,21 +342,23 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "syn"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "text_io"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb170b4f47dc48835fbc56259c12d8963e542b05a24be2e3a1f5a6c320fd2d4"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "toml"
version = "0.5.8"
@ -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"

View File

@ -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"]}

View File

@ -1,7 +1,6 @@
OS = $(shell uname -s)
KRUNVM_RELEASE = target/release/krunvm
KRUNVM_DEBUG = target/debug/krunvm
INIT_BINARY = init/init
ifeq ($(PREFIX),)
PREFIX := /usr/local

View File

@ -46,6 +46,7 @@ dnf install -y krunvm
* Rust Toolchain
* [libkrun](https://github.com/containers/libkrun)
* [buildah](https://github.com/containers/buildah)
* [asciidoctor](https://github.com/asciidoctor/asciidoctor)
#### Building

View File

@ -33,6 +33,8 @@ OPTIONS
*--mem* _NUM_::
Override amount of RAM, in MiB, configured for this microVM.
*--env* _KEY=VALUE_::
Set environment variable to be passed to the microVM.
SEE ALSO
--------

View File

@ -1,22 +1,24 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use libc::{c_char, c_int};
#[link(name = "krun")]
extern "C" {
pub fn krun_set_log_level(level: u32) -> i32;
pub fn krun_create_ctx() -> i32;
pub fn krun_free_ctx(ctx: u32) -> i32;
pub fn krun_set_vm_config(ctx: u32, num_vcpus: u8, ram_mib: u32) -> i32;
pub fn krun_set_root(ctx: u32, root_path: *const i8) -> i32;
pub fn krun_set_mapped_volumes(ctx: u32, mapped_volumes: *const *const i8) -> i32;
pub fn krun_set_port_map(ctx: u32, port_map: *const *const i8) -> i32;
pub fn krun_set_workdir(ctx: u32, workdir_path: *const i8) -> i32;
pub fn krun_set_root(ctx: u32, root_path: *const c_char) -> i32;
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_exec(
ctx: u32,
exec_path: *const i8,
argv: *const *const i8,
envp: *const *const i8,
exec_path: *const c_char,
argv: *const *const c_char,
envp: *const *const c_char,
) -> i32;
pub fn krun_set_env(ctx: u32, envp: *const *const i8) -> i32;
pub fn krun_set_env(ctx: u32, envp: *const *const c_char) -> i32;
pub fn krun_start_enter(ctx: u32) -> i32;
}

View File

@ -1,119 +0,0 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use crate::{ArgMatches, 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;
let name = matches.value_of("NAME").unwrap();
let mut vmcfg = if let Some(new_name) = matches.value_of("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) {
None => {
println!("No VM found with name {}", 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(name) {
None => {
println!("No VM found with name {}", name);
std::process::exit(-1);
}
Some(vmcfg) => vmcfg,
}
};
if let Some(cpus_str) = matches.value_of("cpus") {
match cpus_str.parse::<u32>() {
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(mem_str) = matches.value_of("mem") {
match mem_str.parse::<u32>() {
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 matches.is_present("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);
if !mapped_volumes.is_empty() {
vmcfg.mapped_volumes = mapped_volumes;
cfg_changed = true;
}
}
if matches.is_present("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);
if !mapped_ports.is_empty() {
vmcfg.mapped_ports = mapped_ports;
cfg_changed = true;
}
}
if let Some(workdir) = matches.value_of("workdir") {
vmcfg.workdir = workdir.to_string();
cfg_changed = true;
}
println!();
printvm(vmcfg);
println!();
if cfg_changed {
confy::store(APP_NAME, &cfg).unwrap();
}
}

141
src/commands/changevm.rs Normal file
View File

@ -0,0 +1,141 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use std::collections::HashMap;
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
#[derive(Args, Debug)]
pub struct ChangeVmCmd {
/// Name of the VM to be modified
name: String,
/// Assign a new name to the VM
#[arg(long)]
new_name: Option<String>,
/// Number of vCPUs
#[arg(long)]
cpus: Option<u32>,
/// Amount of RAM in MiB
#[arg(long)]
mem: Option<u32>,
/// Working directory inside the microVM
#[arg(short, long)]
workdir: Option<String>,
/// Remove all volume mappings
#[arg(long)]
remove_volumes: bool,
/// Volume(s) in form "host_path:guest_path" to be exposed to the guest
#[arg(short, long = "volume")]
volumes: Vec<PathPair>,
/// 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<PortPair>,
}
impl ChangeVmCmd {
pub fn run(self, cfg: &mut KrunvmConfig) {
let mut cfg_changed = false;
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);
}
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,
}
};
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;
}
}
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 self.remove_volumes {
vmcfg.mapped_volumes = HashMap::new();
cfg_changed = true;
} else {
let mapped_volumes = path_pairs_to_hash_map(self.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 self.remove_ports {
vmcfg.mapped_ports = HashMap::new();
cfg_changed = true;
} else {
let mapped_ports = port_pairs_to_hash_map(self.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) = self.workdir {
vmcfg.workdir = workdir.to_string();
cfg_changed = true;
}
println!();
printvm(vmcfg);
println!();
if cfg_changed {
confy::store(APP_NAME, &cfg).unwrap();
}
}
}

68
src/commands/config.rs Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{KrunvmConfig, APP_NAME};
use clap::Args;
/// Configure global values
#[derive(Args, Debug)]
pub struct ConfigCmd {
// Default number of vCPUs for newly created VMs
#[arg(long)]
cpus: Option<u32>,
///Default amount of RAM in MiB for newly created VMs
#[arg(long)]
mem: Option<u32>,
/// DNS server to use in the microVM
#[arg(long)]
dns: Option<String>,
}
impl ConfigCmd {
pub fn run(self, cfg: &mut KrunvmConfig) {
let mut cfg_changed = false;
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(dns) = self.dns {
cfg.default_dns = dns;
cfg_changed = true;
}
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
);
}
}

233
src/commands/create.rs Normal file
View File

@ -0,0 +1,233 @@
// 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 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";
/// Create a new microVM
#[derive(Args, Debug)]
pub struct CreateCmd {
/// OCI image to use as template
image: String,
/// Assign a name to the VM
#[arg(long)]
name: Option<String>,
/// Number of vCPUs
#[arg(long)]
cpus: Option<u32>,
/// Amount of RAM in MiB
#[arg(long)]
mem: Option<u32>,
/// DNS server to use in the microVM
#[arg(long)]
dns: Option<String>,
/// 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<PathPair>,
/// Port(s) in format "host_port:guest_port" to be exposed to the host
#[arg(long = "port")]
ports: Vec<PortPair>,
/// Create a x86_64 microVM even on an Aarch64 host
#[arg(short, long)]
#[cfg(target_os = "macos")]
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 name.is_empty() {
println!("Invalid name for VM");
std::process::exit(-1);
}
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)?;
let resolvconf = format!("{}/etc/resolv.conf", rootfs);
let mut file = fs::File::create(resolvconf)?;
file.write_all(b"options use-vc\nnameserver ")?;
file.write_all(dns.as_bytes())?;
file.write_all(b"\n")?;
Ok(())
}
fn export_container_config(
cfg: &KrunvmConfig,
rootfs: &str,
image: &str,
) -> Result<(), std::io::Error> {
let mut args = get_buildah_args(cfg, BuildahCommand::Inspect);
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 mut file = fs::File::create(format!("{}/.krun_config.json", rootfs))?;
file.write_all(&output.stdout)?;
Ok(())
}

31
src/commands/delete.rs Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{KrunvmConfig, APP_NAME};
use clap::Args;
use crate::utils::{remove_container, umount_container};
/// Delete an existing microVM
#[derive(Args, Debug)]
pub struct DeleteCmd {
/// Name of the microVM to be deleted
name: String,
}
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();
confy::store(APP_NAME, &cfg).unwrap();
}
}

52
src/commands/inspect.rs Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use std::process::Command;
use crate::{
utils::{get_buildah_args, BuildahCommand},
KrunvmConfig,
};
use clap::Args;
/// Run `buildah inspect` on an existing microVM
#[derive(Args, Debug)]
pub struct InspectCmd {
/// Name of the microVM to be inspected
name: String,
}
impl InspectCmd {
pub fn run(self, cfg: &mut KrunvmConfig) {
let vmcfg = match cfg.vmconfig_map.get(&self.name) {
None => {
println!("No VM found with that name");
std::process::exit(-1);
}
Some(vmcfg) => vmcfg,
};
let mut args = get_buildah_args(cfg, BuildahCommand::Inspect);
args.push(vmcfg.container.clone());
let output = Command::new("buildah")
.args(&args)
.stderr(std::process::Stdio::inherit())
.output();
if output.is_err() {
println!("Failed to inspect VM");
std::process::exit(1);
}
let output = match String::from_utf8(output.unwrap().stdout) {
Err(err) => {
println!("Failed to parse `buildah inspect` output: #{err}.");
std::process::exit(1);
}
Ok(output) => output,
};
println!("{output}");
}
}

38
src/commands/list.rs Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{KrunvmConfig, VmConfig};
use clap::Args;
/// List microVMs
#[derive(Args, Debug)]
pub struct ListCmd {
/// Print debug information verbosely
#[arg(short)]
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) {
println!("{}", vm.name);
println!(" CPUs: {}", vm.cpus);
println!(" RAM (MiB): {}", vm.mem);
println!(" DNS server: {}", vm.dns);
println!(" Buildah container: {}", vm.container);
println!(" Workdir: {}", vm.workdir);
println!(" Mapped volumes: {:?}", vm.mapped_volumes);
println!(" Mapped ports: {:?}", vm.mapped_ports);
}

15
src/commands/mod.rs Normal file
View File

@ -0,0 +1,15 @@
mod changevm;
mod config;
mod create;
mod delete;
mod inspect;
mod list;
mod start;
pub use changevm::ChangeVmCmd;
pub use config::ConfigCmd;
pub use create::CreateCmd;
pub use delete::DeleteCmd;
pub use inspect::InspectCmd;
pub use list::ListCmd;
pub use start::StartCmd;

View File

@ -1,6 +1,8 @@
// 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;
#[cfg(target_os = "linux")]
@ -9,9 +11,76 @@ 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::{ArgMatches, KrunvmConfig, VmConfig};
use crate::bindings;
use crate::utils::{mount_container, umount_container};
use crate::{KrunvmConfig, VmConfig};
#[derive(Args, Debug)]
/// Start an existing microVM
pub struct StartCmd {
/// Name of the microVM
name: String,
/// Command to run inside the VM
command: Option<String>,
/// Arguments to be passed to the command executed in the VM
args: Vec<String>,
/// Number of vCPUs
#[arg(long)]
cpus: Option<u8>, // TODO: implement or remove this
/// Amount of RAM in MiB
#[arg(long)]
mem: Option<usize>, // TODO: implement or remove this
/// env(s) in format "key=value" to be exposed to the VM
#[arg(long = "env")]
envs: Option<Vec<String>>,
}
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<CString> = if self.command.is_some() {
self.args
.into_iter()
.map(|val| CString::new(val).unwrap())
.collect()
} else {
Vec::new()
};
let env_pairs: Vec<CString> = if self.envs.is_some() {
self.envs
.unwrap()
.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, env_pairs) };
umount_container(cfg, vmcfg).expect("Error unmounting container");
}
}
#[cfg(target_os = "linux")]
fn map_volumes(_ctx: u32, vmcfg: &VmConfig, rootfs: &str) {
@ -43,6 +112,9 @@ fn map_volumes(_ctx: u32, vmcfg: &VmConfig, rootfs: &str) {
#[cfg(target_os = "macos")]
fn map_volumes(ctx: u32, vmcfg: &VmConfig, rootfs: &str) {
if vmcfg.mapped_volumes.is_empty() {
return;
}
let mut volumes = Vec::new();
for (host_path, guest_path) in vmcfg.mapped_volumes.iter() {
let full_guest = format!("{}{}", &rootfs, guest_path);
@ -66,7 +138,13 @@ fn map_volumes(ctx: u32, vmcfg: &VmConfig, rootfs: &str) {
}
}
unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<CString>) {
unsafe fn exec_vm(
vmcfg: &VmConfig,
rootfs: &str,
cmd: Option<&str>,
args: Vec<CString>,
env_pairs: Vec<CString>,
) {
//bindings::krun_set_log_level(9);
let ctx = bindings::krun_create_ctx() as u32;
@ -78,7 +156,7 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<C
}
let c_rootfs = CString::new(rootfs).unwrap();
let ret = bindings::krun_set_root(ctx, c_rootfs.as_ptr() as *const i8);
let ret = bindings::krun_set_root(ctx, c_rootfs.as_ptr());
if ret < 0 {
println!("Error setting VM rootfs");
std::process::exit(-1);
@ -91,11 +169,12 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<C
let map = format!("{}:{}", host_port, guest_port);
ports.push(CString::new(map).unwrap());
}
let mut ps: Vec<*const i8> = Vec::new();
let mut ps: Vec<*const c_char> = Vec::new();
for port in ports.iter() {
ps.push(port.as_ptr() as *const i8);
ps.push(port.as_ptr());
}
ps.push(std::ptr::null());
let ret = bindings::krun_set_port_map(ctx, ps.as_ptr());
if ret < 0 {
println!("Error setting VM port map");
@ -104,7 +183,7 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<C
if !vmcfg.workdir.is_empty() {
let c_workdir = CString::new(vmcfg.workdir.clone()).unwrap();
let ret = bindings::krun_set_workdir(ctx, c_workdir.as_ptr() as *const i8);
let ret = bindings::krun_set_workdir(ctx, c_workdir.as_ptr());
if ret < 0 {
println!("Error setting VM workdir");
std::process::exit(-1);
@ -113,32 +192,30 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<C
let hostname = CString::new(format!("HOSTNAME={}", vmcfg.name)).unwrap();
let home = CString::new("HOME=/root").unwrap();
let env: [*const i8; 3] = [
hostname.as_ptr() as *const i8,
home.as_ptr() as *const i8,
std::ptr::null(),
];
let mut env: Vec<*const c_char> = Vec::new();
env.push(hostname.as_ptr());
env.push(home.as_ptr());
for value in env_pairs.iter() {
env.push(value.as_ptr());
}
env.push(std::ptr::null());
if let Some(cmd) = cmd {
let mut argv: Vec<*const i8> = Vec::new();
let mut argv: Vec<*const c_char> = Vec::new();
for a in args.iter() {
argv.push(a.as_ptr() as *const i8);
argv.push(a.as_ptr());
}
argv.push(std::ptr::null());
let c_cmd = CString::new(cmd).unwrap();
let ret = bindings::krun_set_exec(
ctx,
c_cmd.as_ptr() as *const i8,
argv.as_ptr() as *const *const i8,
env.as_ptr() as *const *const i8,
);
let ret = bindings::krun_set_exec(ctx, c_cmd.as_ptr(), argv.as_ptr(), env.as_ptr());
if ret < 0 {
println!("Error setting VM config");
std::process::exit(-1);
}
} else {
let ret = bindings::krun_set_env(ctx, env.as_ptr() as *const *const i8);
let ret = bindings::krun_set_env(ctx, env.as_ptr());
if ret < 0 {
println!("Error setting VM environment variables");
std::process::exit(-1);
@ -182,36 +259,3 @@ fn set_lock(rootfs: &str) -> 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) {
None => {
println!("No VM found with name {}", 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 args: Vec<CString> = if cmd.is_some() {
match matches.values_of("ARGS") {
Some(a) => a.map(|val| CString::new(val).unwrap()).collect(),
None => Vec::new(),
}
} else {
Vec::new()
};
set_rlimits();
let _file = set_lock(&rootfs);
unsafe { exec_vm(vmcfg, &rootfs, cmd, args) };
umount_container(cfg, vmcfg).expect("Error unmounting container");
}

View File

@ -1,59 +0,0 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{ArgMatches, KrunvmConfig, APP_NAME};
pub fn config(cfg: &mut KrunvmConfig, matches: &ArgMatches) {
let mut cfg_changed = false;
if let Some(cpus_str) = matches.value_of("cpus") {
match cpus_str.parse::<u32>() {
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(mem_str) = matches.value_of("mem") {
match mem_str.parse::<u32>() {
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(dns) = matches.value_of("dns") {
cfg.default_dns = dns.to_string();
cfg_changed = true;
}
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
);
}

View File

@ -1,220 +0,0 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
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};
#[cfg(target_os = "macos")]
const KRUNVM_ROSETTA_FILE: &str = ".krunvm-rosetta";
fn fix_resolv_conf(rootfs: &str, dns: &str) -> Result<(), std::io::Error> {
let resolvconf_dir = format!("{}/etc/", rootfs);
fs::create_dir_all(resolvconf_dir)?;
let resolvconf = format!("{}/etc/resolv.conf", rootfs);
let mut file = fs::File::create(resolvconf)?;
file.write_all(b"options use-vc\nnameserver ")?;
file.write_all(dns.as_bytes())?;
file.write_all(b"\n")?;
Ok(())
}
fn export_container_config(
cfg: &KrunvmConfig,
rootfs: &str,
image: &str,
) -> Result<(), std::io::Error> {
let mut args = get_buildah_args(cfg, BuildahCommand::Inspect);
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 mut file = fs::File::create(format!("{}/.krun_config.json", rootfs))?;
file.write_all(&output.stdout)?;
Ok(())
}
pub fn create(cfg: &mut KrunvmConfig, matches: &ArgMatches) {
let mut cpus = match matches.value_of("cpus") {
Some(c) => match c.parse::<u32>() {
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::<u32>() {
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 {
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 = matches.is_present("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);
}

View File

@ -1,23 +0,0 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{ArgMatches, KrunvmConfig, APP_NAME};
use super::utils::{remove_container, umount_container};
pub fn delete(cfg: &mut KrunvmConfig, matches: &ArgMatches) {
let name = matches.value_of("NAME").unwrap();
let vmcfg = match cfg.vmconfig_map.remove(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();
confy::store(APP_NAME, &cfg).unwrap();
}

View File

@ -1,27 +0,0 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::{ArgMatches, KrunvmConfig, VmConfig};
pub fn printvm(vm: &VmConfig) {
println!("{}", vm.name);
println!(" CPUs: {}", vm.cpus);
println!(" RAM (MiB): {}", vm.mem);
println!(" DNS server: {}", vm.dns);
println!(" Buildah container: {}", vm.container);
println!(" Workdir: {}", vm.workdir);
println!(" Mapped volumes: {:?}", vm.mapped_volumes);
println!(" Mapped ports: {:?}", vm.mapped_ports);
}
pub fn list(cfg: &KrunvmConfig, _matches: &ArgMatches) {
if cfg.vmconfig_map.is_empty() {
println!("No microVMs found");
} else {
for (_name, vm) in cfg.vmconfig_map.iter() {
println!();
printvm(vm);
}
println!();
}
}

View File

@ -7,19 +7,17 @@ use std::fs::File;
#[cfg(target_os = "macos")]
use std::io::{self, Read, Write};
use clap::{crate_version, App, Arg, ArgMatches};
use crate::commands::{
ChangeVmCmd, ConfigCmd, CreateCmd, DeleteCmd, InspectCmd, 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";
@ -149,236 +147,44 @@ fn check_unshare() {
}
}
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Cli {
/// Sets the level of verbosity
#[arg(short)]
verbosity: Option<u8>, //TODO: implement or remove this
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Start(StartCmd),
Create(CreateCmd),
Inspect(InspectCmd),
List(ListCmd),
Delete(DeleteCmd),
#[command(name = "changevm")]
ChangeVm(ChangeVmCmd),
Config(ConfigCmd),
}
fn main() {
let mut cfg: KrunvmConfig = confy::load(APP_NAME).unwrap();
let mut app = App::new("krunvm")
.version(crate_version!())
.author("Sergio Lopez <slp@redhat.com>")
.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"),
);
}
app = app.subcommand(create);
let matches = app.clone().get_matches();
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::Inspect(cmd) => cmd.run(&mut cfg),
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),
}
}

View File

@ -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<String>
args
}
pub fn parse_mapped_ports(port_matches: Vec<&str>) -> HashMap<String, String> {
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<Item = PortPair>,
) -> HashMap<String, String> {
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<Self, Self::Err> {
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(),
})
}
mapped_ports
}
pub fn parse_mapped_volumes(volume_matches: Vec<&str>) -> HashMap<String, String> {
let mut mapped_volumes = HashMap::new();
for volume in volume_matches.iter() {
let vtuple: Vec<&str> = volume.split(':').collect();
#[derive(Debug, Clone)]
pub struct PathPair {
pub host_path: String,
pub guest_path: String,
}
pub fn path_pairs_to_hash_map(
volume_pairs: impl IntoIterator<Item = PathPair>,
) -> HashMap<String, String> {
volume_pairs
.into_iter()
.map(|pair: PathPair| (pair.host_path, pair.guest_path))
.collect()
}
impl FromStr for PathPair {
type Err = &'static str;
fn from_str(input: &str) -> Result<Self, Self::Err> {
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")]
@ -198,7 +222,7 @@ pub fn mount_container(cfg: &KrunvmConfig, vmcfg: &VmConfig) -> Result<String, s
let rootfs = std::str::from_utf8(&output.stdout).unwrap().trim();
#[cfg(target_os = "macos")]
fix_root_mode(&rootfs);
fix_root_mode(rootfs);
Ok(rootfs.to_string())
}