Compare commits
235 Commits
Author | SHA1 | Date |
---|---|---|
|
b213606120 | |
|
c1744babf0 | |
|
a8851850a4 | |
|
3272b14a92 | |
|
cb9bef3e72 | |
|
0bc67e36ef | |
|
edbb3fdcd2 | |
|
46a74315e0 | |
|
06f6eb1ce4 | |
|
f0abbc9b86 | |
|
ff78b7d276 | |
|
6b95c01981 | |
|
9c65909097 | |
|
f32844c664 | |
|
595d94962c | |
|
e069699438 | |
|
4340739be3 | |
|
d0c87b3032 | |
|
dafe1d1198 | |
|
a2cf546d2b | |
|
e7503b63ac | |
|
148ea2b622 | |
|
422e370fd0 | |
|
6351459ad0 | |
|
4b450db228 | |
|
a802098a94 | |
|
ef0a2b11db | |
|
7b96f6e81b | |
|
055b7fe620 | |
|
842fb71e2f | |
|
414f199dfa | |
|
350e2ba6d8 | |
|
1db728146f | |
|
14e9d73e85 | |
|
2a332101d1 | |
|
ce5e21cf3a | |
|
685995d83b | |
|
9c2bbec37d | |
|
d5d738df41 | |
|
faf99bbaf7 | |
|
9c6598f096 | |
|
5942f27d50 | |
|
fa835148f6 | |
|
add90de0b8 | |
|
15d04a6592 | |
|
d2c8a66e09 | |
|
cee7a045fd | |
|
85a8e735ba | |
|
fe799ab7ac | |
|
4d4b49dbb3 | |
|
c2125d6b21 | |
|
e49a9a8560 | |
|
a11ed870ba | |
|
ed3060da6c | |
|
3139d4727b | |
|
3f91463462 | |
|
f28f927db9 | |
|
7aa4b1728c | |
|
9e862dda38 | |
|
55c1fa867e | |
|
c41930a493 | |
|
417950e371 | |
|
a66506e8d4 | |
|
0fd79ecacc | |
|
4a5897a1c5 | |
|
e7572785dc | |
|
75cab98728 | |
|
e1521a2e1f | |
|
e6e43cec4c | |
|
9476038bc2 | |
|
305631aa65 | |
|
6bd181883d | |
|
93f71ad3b3 | |
|
138b168e33 | |
|
59113d11cb | |
|
8b061ce3a5 | |
|
6c31744b47 | |
|
2f79d6b6d1 | |
|
259dad6404 | |
|
6117d18b00 | |
|
a057d6960c | |
|
a8655b5c95 | |
|
c88ee40852 | |
|
1885a53749 | |
|
fab504a344 | |
|
fc5d916371 | |
|
3d65c4d471 | |
|
d9b31eeffe | |
|
ab63bf10ec | |
|
58da5ed7f2 | |
|
84e98863ef | |
|
7b026358b9 | |
|
132cc4c52a | |
|
c0888c6ffb | |
|
de388af2a4 | |
|
ad26d7fe95 | |
|
a9118ee63c | |
|
19cb2349a6 | |
|
b40201ba72 | |
|
db18060978 | |
|
f09ac901ff | |
|
646be3e47b | |
|
dd263a2bc4 | |
|
fe90b56767 | |
|
aa31281168 | |
|
adb9f38b43 | |
|
75ed7d972c | |
|
93ee592c46 | |
|
2a7585eb2d | |
|
326e575f56 | |
|
b3eb424766 | |
|
32d4db4bd7 | |
|
efa0d3629b | |
|
3fb7b4f49d | |
|
eabb1a6590 | |
|
c4ea481006 | |
|
c1cd5b29e7 | |
|
e65f60e62b | |
|
999eda2c92 | |
|
2ed49a082b | |
|
2adee838cc | |
|
9e6e91238f | |
|
b2d1e65960 | |
|
4bf73da5cb | |
|
2bfbc8319e | |
|
58557aa6e9 | |
|
454703095c | |
|
829e121a58 | |
|
91f5609c19 | |
|
2a78e5f0fe | |
|
a1b65cea0d | |
|
650e8fd78f | |
|
569fb5d468 | |
|
f9e8caa299 | |
|
6ea51361d7 | |
|
44a8d0eae2 | |
|
e2d53986b7 | |
|
183c3d2682 | |
|
d6834a960b | |
|
cd82042b27 | |
|
f977d7ac6f | |
|
f2b85be13a | |
|
52eab23681 | |
|
c8366a28e7 | |
|
8f788cbe1c | |
|
8aed4b02cc | |
|
54d921467d | |
|
29246880af | |
|
a0c4f425c1 | |
|
fa28a88d23 | |
|
b4d342a4f5 | |
|
4854b26886 | |
|
6bdeaa4cf1 | |
|
57021b72fd | |
|
55756dfa7f | |
|
167a5c955d | |
|
0afc66b56d | |
|
53ef61e843 | |
|
9d709ae9ec | |
|
5611aa5f78 | |
|
a31962fabe | |
|
cb4286e770 | |
|
fd6e7c644b | |
|
01eb9bd485 | |
|
530fc22d44 | |
|
e2750031d0 | |
|
c3b949f4a9 | |
|
9fbf17928e | |
|
00d705ed8e | |
|
253489ba30 | |
|
6a3437f1fe | |
|
8c4e505d94 | |
|
f3c37f3d68 | |
|
b2fd56055b | |
|
977faaee0e | |
|
20b1326108 | |
|
aeb0a17328 | |
|
7ef08f27aa | |
|
3a724ec6bc | |
|
ee504a43a2 | |
|
b349ec0f57 | |
|
4c2c9673b8 | |
|
3d99ddce2f | |
|
a75b65aafc | |
|
8148bc7580 | |
|
ccbea36ea4 | |
|
0b5e757037 | |
|
3151250389 | |
|
1197087348 | |
|
4fd5f0d856 | |
|
20253890fc | |
|
fafc72bbdd | |
|
c2fdb09e93 | |
|
9647a59164 | |
|
27d58182f0 | |
|
6cce28974b | |
|
c5af553e4e | |
|
640ddd6df9 | |
|
34b7b1bb83 | |
|
bed19c087b | |
|
8b4dcf0c52 | |
|
565ec60b4c | |
|
1efbd7d8b0 | |
|
46628d817d | |
|
0ef45a885a | |
|
d1d54e61c4 | |
|
0fbb8d4741 | |
|
93f277fe1d | |
|
bc246172f6 | |
|
2a8c00cc70 | |
|
2eb1059ff6 | |
|
a9d2204a54 | |
|
4074473885 | |
|
6952740be8 | |
|
f7098dd51a | |
|
2064469f01 | |
|
ec38f3b423 | |
|
7c32dd9bcb | |
|
f17c1fe88b | |
|
4115843e8f | |
|
adb8e2c302 | |
|
fd17070d5d | |
|
407bd2f415 | |
|
bb3309afe3 | |
|
ce88cd119d | |
|
488e53ef8a | |
|
4da92f3736 | |
|
99d74d5acd | |
|
3b35d0610b | |
|
4dbd7bc986 | |
|
ef5a4c0057 | |
|
1c173e4595 | |
|
066501d3f8 | |
|
caa191342a | |
|
6dffba3255 |
|
@ -1,2 +0,0 @@
|
|||
[profile.default]
|
||||
slow-timeout = { period = "60s", terminate-after = 2 }
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
/*
|
||||
Configuration Reference:
|
||||
https://docs.renovatebot.com/configuration-options/
|
||||
|
||||
Monitoring Dashboard:
|
||||
https://app.renovatebot.com/dashboard#github/containers
|
||||
|
||||
Configuration Update/Change Procedure:
|
||||
1. Make changes
|
||||
2. Manually validate changes (from repo-root):
|
||||
```bash
|
||||
$ podman run -it \
|
||||
-v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \
|
||||
ghcr.io/renovatebot/renovate:latest \
|
||||
renovate-config-validator
|
||||
```
|
||||
3. Commit.
|
||||
*/
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>containers/automation//renovate/defaults.json5"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
name: Rust ${{ matrix.rust }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [ stable, "1.74" ]
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set Rust version
|
||||
run: rustup default ${{ matrix.rust }}
|
||||
|
||||
- name: Install cargo-fmt and cargo-clippy
|
||||
run: rustup component add rustfmt clippy
|
||||
|
||||
- name: Lint
|
||||
run: tests/lint.sh
|
||||
|
||||
minimal-dependency-versions:
|
||||
name: Validate minimal dependency versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set Rust version
|
||||
run: rustup default 1.74
|
||||
|
||||
- name: Install cargo-hack and cargo-minimal-versions
|
||||
run: |
|
||||
rustup toolchain add nightly &&
|
||||
cargo +nightly install cargo-hack cargo-minimal-versions --locked
|
||||
|
||||
- name: Validate minimal dependency versions
|
||||
run: cargo minimal-versions check --workspace
|
|
@ -58,7 +58,7 @@ jobs:
|
|||
|
||||
- name: Release
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: ${{ github.event.inputs.name }}
|
||||
tag_name: ${{ github.event.inputs.name }}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
/out
|
||||
/target
|
||||
/bin/
|
||||
/targets/
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
.test:
|
||||
only:
|
||||
- branches
|
||||
- merge_requests
|
||||
tags:
|
||||
- saas-linux-medium-amd64
|
||||
|
||||
rust-stable:
|
||||
extends: .test
|
||||
image: rust:latest
|
||||
before_script:
|
||||
- rustup component add clippy rustfmt
|
||||
script:
|
||||
- ./lint.sh
|
||||
|
||||
rust-1.74:
|
||||
extends: rust-stable
|
||||
image: rust:1.74
|
||||
|
||||
minimum-dependency-versions:
|
||||
extends: .test
|
||||
image: rust:1.74
|
||||
before_script:
|
||||
- rustup toolchain add nightly
|
||||
- cargo +nightly install cargo-hack cargo-minimal-versions --locked
|
||||
script:
|
||||
- cargo minimal-versions check --workspace
|
67
.packit.yaml
67
.packit.yaml
|
@ -1,60 +1,25 @@
|
|||
---
|
||||
# See the documentation for more information:
|
||||
# https://packit.dev/docs/configuration/
|
||||
|
||||
specfile_path: rpm/crun-vm.spec
|
||||
|
||||
srpm_build_deps:
|
||||
- cargo
|
||||
- make
|
||||
- openssl-devel
|
||||
|
||||
jobs:
|
||||
- job: copr_build
|
||||
trigger: pull_request
|
||||
notifications:
|
||||
failure_comment:
|
||||
message: "Ephemeral COPR build failed. @containers/packit-build please check."
|
||||
targets:
|
||||
fedora-all-x86_64: {}
|
||||
fedora-all-aarch64: {}
|
||||
fedora-eln-x86_64:
|
||||
additional_repos:
|
||||
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/x86_64/"
|
||||
fedora-eln-aarch64:
|
||||
additional_repos:
|
||||
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/aarch64/"
|
||||
additional_repos:
|
||||
- "copr://rhcontainerbot/podman-next"
|
||||
enable_net: true
|
||||
|
||||
# Run on commit to main branch
|
||||
- job: copr_build
|
||||
trigger: commit
|
||||
notifications:
|
||||
failure_comment:
|
||||
message: "podman-next COPR build failed. @containers/packit-build please check."
|
||||
branch: main
|
||||
owner: rhcontainerbot
|
||||
project: podman-next
|
||||
enable_net: true
|
||||
|
||||
# Unit tests
|
||||
- job: tests
|
||||
- &tests
|
||||
job: tests
|
||||
trigger: pull_request
|
||||
skip_build: true
|
||||
enable_net: true
|
||||
targets:
|
||||
- fedora-all-aarch64
|
||||
- fedora-all-x86_64
|
||||
identifier: unit_test
|
||||
tmt_plan: "/plans/unit_test"
|
||||
# TODO: Replace these three with fedora-all-x86_64 once Fedora 38 is gone.
|
||||
- fedora-rawhide-x86_64
|
||||
- fedora-40-x86_64
|
||||
- fedora-39-x86_64
|
||||
identifier: podman
|
||||
tmt_plan: /tests/podman
|
||||
|
||||
# Validate test
|
||||
- job: tests
|
||||
trigger: pull_request
|
||||
skip_build: true
|
||||
targets:
|
||||
# Only need to test on one target
|
||||
- fedora-latest-stable-x86_64
|
||||
identifier: validate_test
|
||||
tmt_plan: "/plans/validate_test"
|
||||
- <<: *tests
|
||||
identifier: rootful-podman
|
||||
tmt_plan: /tests/rootful-podman
|
||||
|
||||
- <<: *tests
|
||||
identifier: docker
|
||||
tmt_plan: /tests/docker
|
||||
|
|
|
@ -4,36 +4,36 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.5"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
|
@ -46,15 +46,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.6"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
|
||||
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -63,10 +63,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.18"
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -74,32 +80,52 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.18"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
|
@ -112,7 +138,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crun-vm"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
|
@ -129,9 +155,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"test-case",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
|
@ -147,9 +171,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
|
@ -157,58 +181,58 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.4"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
"strsim 0.10.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.4"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.12.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.12.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.12.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -243,27 +267,16 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getset"
|
||||
version = "0.1.2"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
|
||||
checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -274,15 +287,15 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
|||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.4"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
|
||||
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
|
@ -301,9 +314,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.2"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
|
@ -311,36 +324,36 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "liboci-cli"
|
||||
version = "0.3.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fb8daab7180d25db1086b0fb73a0de935f3cb55d56d60e946ed84bec2a3ffd"
|
||||
checksum = "731e7d86b6f06717b9b365895f707b229fc755e45c0122b8ac1de9c0f0cf1547"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "minidom"
|
||||
|
@ -353,20 +366,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
|
@ -374,64 +388,66 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "oci-spec"
|
||||
version = "0.6.4"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8384f8eff13954bafafba991f1910779020456f9694de25e81a13da5b7de6309"
|
||||
checksum = "57e9beda9d92fac7bf4904c34c83340ef1024159faee67179a04e0277523da33"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"derive_builder",
|
||||
"getset",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.3"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -441,9 +457,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -452,15 +468,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.8.1"
|
||||
version = "8.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
|
||||
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
|
@ -469,27 +485,33 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "6.8.1"
|
||||
version = "8.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
|
||||
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.48",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "7.8.1"
|
||||
version = "8.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
|
||||
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "rxml"
|
||||
version = "0.9.1"
|
||||
|
@ -509,9 +531,9 @@ checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530"
|
|||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
|
@ -524,40 +546,41 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.196"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.113"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.31"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
|
@ -601,78 +624,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
|
||||
dependencies = [
|
||||
"test-case-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case-core"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case-macros"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -688,10 +692,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.10"
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
|
@ -699,15 +709,6 @@ version = "2.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -716,51 +717,23 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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.52.0"
|
||||
|
@ -772,13 +745,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
|
@ -787,48 +761,54 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.19"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
||||
checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7"
|
||||
|
|
43
Cargo.toml
43
Cargo.toml
|
@ -1,80 +1,71 @@
|
|||
[package]
|
||||
name = "crun-vm"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.74"
|
||||
license = "GPL-2.0-or-later"
|
||||
|
||||
[dependencies.anyhow]
|
||||
version = "1.0.46"
|
||||
version = "1.0.98"
|
||||
|
||||
[dependencies.camino]
|
||||
version = "1.1"
|
||||
version = "1.1.10"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4.0"
|
||||
version = "4.5.40"
|
||||
default-features = false
|
||||
features = ["cargo", "derive", "std"]
|
||||
|
||||
[dependencies.home]
|
||||
version = "0.5.0"
|
||||
version = "0.5.9"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.1"
|
||||
version = "1.5.0"
|
||||
default-features = false
|
||||
|
||||
[dependencies.liboci-cli]
|
||||
version = "0.3.0"
|
||||
version = "0.5.3"
|
||||
default-features = false
|
||||
|
||||
[dependencies.minidom]
|
||||
version = "0.15.2"
|
||||
|
||||
[dependencies.nix]
|
||||
version = "0.27.0"
|
||||
version = "0.30.1"
|
||||
default-features = false
|
||||
features = ["fs", "mount"]
|
||||
|
||||
[dependencies.num_cpus]
|
||||
version = "1.0"
|
||||
version = "1.17.0"
|
||||
default-features = false
|
||||
|
||||
[dependencies.oci-spec]
|
||||
version = "0.6.0"
|
||||
version = "0.8.1"
|
||||
default-features = false
|
||||
features = ["runtime"]
|
||||
|
||||
[dependencies.regex]
|
||||
version = "1.0"
|
||||
version = "1.10.6"
|
||||
default-features = false
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.rust-embed]
|
||||
version = "6.0"
|
||||
version = "8.7.2"
|
||||
default-features = false
|
||||
features = ["debug-embed"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
version = "1.0.219"
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0"
|
||||
version = "1.0.140"
|
||||
|
||||
[dependencies.serde_yaml]
|
||||
version = "0.9.0"
|
||||
version = "0.9.34"
|
||||
|
||||
[dependencies.urlencoding]
|
||||
version = "2.0"
|
||||
version = "2.1.3"
|
||||
|
||||
[dependencies.xml-rs]
|
||||
version = "0.8.0"
|
||||
version = "0.8.27"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.test-case]
|
||||
version = "3.0"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.uuid]
|
||||
version = "1.0"
|
||||
default-features = false
|
||||
features = ["std", "v4"]
|
||||
|
|
92
Makefile
92
Makefile
|
@ -1,80 +1,48 @@
|
|||
# This Makefile is intended for developer convenience. For the most part
|
||||
# all the targets here simply wrap calls to the `cargo` tool. Therefore,
|
||||
# most targets must be marked 'PHONY' to prevent `make` getting in the way
|
||||
#
|
||||
#prog :=xnixperms
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CARGO ?= cargo
|
||||
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
||||
# Get crate version by parsing the line that starts with version.
|
||||
CRATE_VERSION ?= $(shell grep ^version Cargo.toml | awk '{print $$3}')
|
||||
GIT_TAG ?= $(shell git describe --tags)
|
||||
|
||||
# Set this to any non-empty string to enable unoptimized
|
||||
# build w/ debugging features.
|
||||
debug ?=
|
||||
binpath := $(DESTDIR)/$(PREFIX)/bin/crun-vm
|
||||
manpath := $(DESTDIR)/$(PREFIX)/share/man/man1/crun-vm.1.gz
|
||||
|
||||
# Set path to cargo executable
|
||||
CARGO ?= cargo
|
||||
all: out/crun-vm out/crun-vm.1.gz
|
||||
|
||||
# All complication artifacts, including dependencies and intermediates
|
||||
# will be stored here, for all architectures. Use a non-default name
|
||||
# since the (default) 'target' is used/referenced ambiguously in many
|
||||
# places in the tool-chain (including 'make' itself).
|
||||
CARGO_TARGET_DIR ?= targets
|
||||
export CARGO_TARGET_DIR # 'cargo' is sensitive to this env. var. value.
|
||||
.PHONY: out/crun-vm
|
||||
out/crun-vm:
|
||||
mkdir -p $(@D)
|
||||
$(CARGO) build --release
|
||||
cp target/release/crun-vm $@
|
||||
|
||||
ifdef debug
|
||||
$(info debug is $(debug))
|
||||
# These affect both $(CARGO_TARGET_DIR) layout and contents
|
||||
# Ref: https://doc.rust-lang.org/cargo/guide/build-cache.html
|
||||
release :=
|
||||
profile :=debug
|
||||
else
|
||||
release :=--release
|
||||
profile :=release
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
bin:
|
||||
mkdir -p $@
|
||||
|
||||
$(CARGO_TARGET_DIR):
|
||||
mkdir -p $@
|
||||
|
||||
.PHONY: build
|
||||
build: bin $(CARGO_TARGET_DIR)
|
||||
$(CARGO) build $(release)
|
||||
cp $(CARGO_TARGET_DIR)/$(profile)/crun-vm bin/crun-vm$(if $(debug),.debug,)
|
||||
out/crun-vm.1.gz: docs/5-crun-vm.1.ronn
|
||||
mkdir -p $(@D)
|
||||
ronn --pipe --roff $< | gzip > $@
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf bin
|
||||
if [ "$(CARGO_TARGET_DIR)" = "targets" ]; then rm -rf targets; fi
|
||||
rm -fr out target
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install ${SELINUXOPT} -D -m0755 bin/crun-vm $(DESTDIR)/$(BINDIR)/crun-vm
|
||||
install: out/crun-vm install-man
|
||||
install ${SELINUXOPT} -D -m 0755 $< $(binpath)
|
||||
|
||||
.PHONY: install-man
|
||||
install-man: out/crun-vm.1.gz
|
||||
install ${SELINUXOPT} -D -m 0644 $< $(manpath)
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)/$(BINDIR)/crun-vm
|
||||
rm -f $(binpath) $(manpath)
|
||||
|
||||
#.PHONY: unit
|
||||
unit: $(CARGO_TARGET_DIR)
|
||||
$(SHELL) test.sh podman
|
||||
.PHONY: lint
|
||||
lint:
|
||||
tests/lint.sh
|
||||
|
||||
#.PHONY: code_coverage
|
||||
code_coverage: $(CARGO_TARGET_DIR)
|
||||
# Downloads tarpaulin only if same version is not present on local
|
||||
$(CARGO) install cargo-tarpaulin
|
||||
$(CARGO) tarpaulin -v
|
||||
|
||||
.PHONY: validate
|
||||
validate: $(CARGO_TARGET_DIR)
|
||||
$(SHELL) lint.sh
|
||||
.PHONY: test
|
||||
test:
|
||||
tests/env.sh build
|
||||
tests/env.sh start
|
||||
tests/env.sh run all all
|
||||
|
|
98
README.md
98
README.md
|
@ -1,47 +1,107 @@
|
|||
# The crun-vm OCI Runtime
|
||||
|
||||
**crun-vm** is an [OCI Runtime] that enables [Podman], [Docker], and
|
||||
[Kubernetes] to run QEMU-compatible Virtual Machine (VM) images.
|
||||
[Kubernetes] to run QEMU-compatible Virtual Machine (VM) images. This means you
|
||||
can:
|
||||
|
||||
- Run **VMs** as easily as you run **containers**.
|
||||
- Manage containers and VMs **together** using the **same** standard tooling.
|
||||
- **No need** for in-depth knowledge on virtualization technologies like libvirt
|
||||
or KubeVirt.
|
||||
- Run **VMs** as easily as you run **containers**.
|
||||
- Manage containers and VMs **together** using the **same** standard tooling.
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/example.gif" width="680" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="450" valign="top">
|
||||
|
||||
### Quick start
|
||||
|
||||
Install crun-vm:
|
||||
|
||||
```console
|
||||
$ dnf install crun-vm
|
||||
```
|
||||
|
||||
Launch a VM from a disk image packaged in a container:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it \
|
||||
quay.io/containerdisks/fedora:40
|
||||
```
|
||||
|
||||
Launch a VM from a disk image under `my-image-dir/`:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it \
|
||||
--rootfs my-image-dir/
|
||||
```
|
||||
|
||||
Launch a VM from a [bootable container]:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it \
|
||||
quay.io/crun-vm/example-fedora-bootc:40
|
||||
```
|
||||
|
||||
Set the password for a VM's default user:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it \
|
||||
quay.io/containerdisks/fedora:40 \
|
||||
--password pass # for user "fedora"
|
||||
```
|
||||
|
||||
Exec (ssh) into a VM:
|
||||
|
||||
```console
|
||||
$ podman exec -it --latest -- --as fedora
|
||||
```
|
||||
|
||||
<p></p>
|
||||
</td>
|
||||
<td valign="top">
|
||||
|
||||
### Major features
|
||||
|
||||
- Use it with **Podman**, **Docker**, or **Kubernetes**.
|
||||
- Launch VMs from VM **image files** present on the host or packaged into
|
||||
**container images**.
|
||||
- Control VM **CPU** and **memory** allocation.
|
||||
- Provide **cloud-init** and **Ignition** configurations to VMs.
|
||||
- **Mount directories** into VMs.
|
||||
- Pass **cloud-init** or **Ignition** configs to VMs.
|
||||
- Mount **directories** into VMs.
|
||||
- Pass **block devices** through to VMs.
|
||||
- Expose **qcow2 files** and other **disk images** to VMs as block devices.
|
||||
- Pass **vfio-pci** and **mediated vfio-pci** devices through to VMs.
|
||||
- Expose additional **disk images** to VMs.
|
||||
- **Forward ports** from the host to VMs.
|
||||
- **`podman exec`**/**`docker exec`**/**`kubectl exec`** into VMs.
|
||||
- **`podman|docker|kubectl exec`** into VMs.
|
||||
|
||||
---
|
||||
|
||||
### Documentation
|
||||
|
||||
1. [Installing crun-vm](docs/1-installing.md)
|
||||
2. [Using crun-vm as a Podman or Docker runtime](docs/2-podman-docker.md)
|
||||
3. [Using crun-vm as a Kubernetes runtime](docs/3-kubernetes.md)
|
||||
2. [Running VMs with **Podman** or **Docker**](docs/2-podman-docker.md)
|
||||
3. [Running VMs as **systemd** services](docs/3-systemd.md)
|
||||
4. [Running VMs in **Kubernetes**](docs/4-kubernetes.md)
|
||||
5. [**crun-vm(1)** man page](docs/5-crun-vm.1.ronn)
|
||||
|
||||
> [!TIP]
|
||||
> See also how you can [combine **crun-vm** and **Podman Quadlet** to easily
|
||||
> manage both containers and VMs through **systemd**](/examples/quadlet).
|
||||
---
|
||||
|
||||
### License
|
||||
|
||||
This project is released under the GPL 2.0 (or later) license. See
|
||||
[LICENSE](LICENSE).
|
||||
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[bootable container]: https://containers.github.io/bootable
|
||||
[Docker]: https://www.docker.com/
|
||||
[Kubernetes]: https://kubernetes.io/
|
||||
[Podman]: https://podman.io/
|
||||
[KubeVirt]: https://kubevirt.io/
|
||||
[OCI Runtime]: https://github.com/opencontainers/runtime-spec/blob/v1.1.0/spec.md
|
||||
[Podman]: https://podman.io/
|
||||
[systemd]: https://systemd.io/
|
||||
|
|
|
@ -1,66 +1,156 @@
|
|||
# 1. Installing crun-vm
|
||||
|
||||
## Build and install from source (on Fedora)
|
||||
There are two steps to setting up crun-vm on a system:
|
||||
|
||||
1. Install crun-vm's runtime dependencies:
|
||||
- Installing the actual `crun-vm` binary;
|
||||
- Configuring Podman, Docker, and/or Kubernetes (whichever you intend to use
|
||||
crun-vm with) to recognize crun-vm as a runtime.
|
||||
|
||||
These steps are detailed in the sections below.
|
||||
|
||||
<details open>
|
||||
<summary><b>Navigation</b></summary>
|
||||
|
||||
1. **Installing crun-vm**
|
||||
- [**Installing the `crun-vm` binary**](#installing-the-crun-vm-binary)
|
||||
- [On Fedora](#on-fedora)
|
||||
- [From source](#from-source)
|
||||
- [**Making crun-vm available as a runtime**](#making-crun-vm-available-as-a-runtime)
|
||||
- [For Podman](#for-podman)
|
||||
- [For Docker](#for-docker)
|
||||
- [For Kubernetes](#for-kubernetes)
|
||||
2. [Running VMs with **Podman** or **Docker**](2-podman-docker.md)
|
||||
3. [Running VMs as **systemd** services](3-systemd.md)
|
||||
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
|
||||
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
|
||||
|
||||
</details>
|
||||
|
||||
## Installing the `crun-vm` binary
|
||||
|
||||
### On Fedora
|
||||
|
||||
```console
|
||||
$ dnf install crun-vm
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
1. Install crun-vm's build dependencies:
|
||||
|
||||
- [cargo](https://doc.rust-lang.org/stable/cargo/getting-started/installation.html)
|
||||
- [gzip](https://www.gzip.org/)
|
||||
- [libselinux](https://github.com/SELinuxProject/selinux/tree/main/libselinux),
|
||||
including development headers
|
||||
- [ronn-ng](https://github.com/apjanke/ronn-ng)
|
||||
|
||||
2. Install crun-vm's runtime dependencies:
|
||||
|
||||
- bash
|
||||
- [coreutils](https://www.gnu.org/software/coreutils/)
|
||||
- [crun](https://github.com/containers/crun)
|
||||
- [crun-krun](https://github.com/containers/crun/blob/main/krun.1.md)
|
||||
- [genisoimage](https://github.com/Distrotech/cdrkit)
|
||||
- grep
|
||||
- [libselinux](https://github.com/SELinuxProject/selinux/tree/main/libselinux)
|
||||
- [libvirtd](https://gitlab.com/libvirt/libvirt) or
|
||||
[virtqemud](https://gitlab.com/libvirt/libvirt)
|
||||
- [passt](https://passt.top/)
|
||||
- [qemu-img](https://gitlab.com/qemu-project/qemu)
|
||||
- qemu-system-x86_64, qemu-system-aarch64, and/or other [QEMU system
|
||||
emulators](https://gitlab.com/qemu-project/qemu) for the VM architectures
|
||||
you want to support
|
||||
- [skopeo](https://github.com/containers/skopeo)
|
||||
- ssh
|
||||
- [util-linux](https://github.com/util-linux/util-linux)
|
||||
- [virsh](https://gitlab.com/libvirt/libvirt)
|
||||
- [virtiofsd](https://gitlab.com/virtio-fs/virtiofsd)
|
||||
- [virtlogd](https://gitlab.com/libvirt/libvirt)
|
||||
|
||||
3. Install crun-vm's binary and man page:
|
||||
|
||||
```console
|
||||
$ dnf install bash coreutils crun genisoimage grep libselinux-devel libvirt-client libvirt-daemon-driver-qemu libvirt-daemon-log openssh-clients qemu-img qemu-system-x86-core shadow-utils util-linux virtiofsd
|
||||
$ make install
|
||||
```
|
||||
|
||||
2. Install Rust and Cargo if you do not already have Rust tooling available:
|
||||
## Making crun-vm available as a runtime
|
||||
|
||||
```console
|
||||
$ dnf install cargo
|
||||
```
|
||||
### For Podman
|
||||
|
||||
3. Build crun-vm:
|
||||
Nothing to do here, since Podman automatically recognizes crun-vm as a runtime.
|
||||
Commands like `podman create` and `podman run` can be made to use the crun-vm
|
||||
runtime by passing them the `--runtime crun-vm` option.
|
||||
|
||||
```console
|
||||
$ cargo build
|
||||
```
|
||||
<!--
|
||||
Paths search by Podman:
|
||||
- `/usr/bin/crun-vm`
|
||||
- `/usr/local/bin/crun-vm`
|
||||
- `/usr/local/sbin/crun-vm`
|
||||
- `/sbin/crun-vm`
|
||||
- `/bin/crun-vm`
|
||||
- `/run/current-system/sw/bin/crun-vm`
|
||||
-->
|
||||
|
||||
4. Copy the `target/debug/crun-vm` binary to wherever you prefer:
|
||||
See [2. Running VMs with **Podman** or **Docker**](2-podman-docker.md) to get
|
||||
started.
|
||||
|
||||
```console
|
||||
$ cp target/debug/crun-vm /usr/local/bin/
|
||||
```
|
||||
### For Docker
|
||||
|
||||
5. If you're using Podman:
|
||||
1. Merge the following configuration into `/etc/docker/daemon.json` (creating
|
||||
that directory and file if necessary):
|
||||
|
||||
- Merge the following configuration into
|
||||
`/etc/containers/containers.conf`:
|
||||
|
||||
> For rootless Podman, you can instead use
|
||||
> `${XDG_CONFIG_PATH}/containers/containers.conf`, where
|
||||
> `$XDG_CONFIG_PATH` defaults to `${HOME}/.config`.
|
||||
|
||||
```toml
|
||||
[engine.runtimes]
|
||||
crun-vm = ["/usr/local/bin/crun-vm"]
|
||||
```
|
||||
|
||||
If you're using Docker:
|
||||
|
||||
- Merge the following configuration into `/etc/docker/daemon.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtimes": {
|
||||
"crun-vm": {
|
||||
"path": "/usr/local/bin/crun-vm"
|
||||
}
|
||||
}
|
||||
```json
|
||||
{
|
||||
"runtimes": {
|
||||
"crun-vm": {
|
||||
"path": "/usr/bin/crun-vm"
|
||||
}
|
||||
```
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Reload the `docker` service for the new configuration to take effect:
|
||||
2. Reload the `docker` service for the new configuration to take effect:
|
||||
|
||||
```console
|
||||
$ service docker reload
|
||||
```
|
||||
```console
|
||||
$ service docker reload
|
||||
```
|
||||
|
||||
With Podman, it is possible to use crun-vm without installing it, *i.e.*,
|
||||
performing only steps 1–3 above. In this case, instead of setting the runtime
|
||||
with `--runtime crun-vm`, specify an absolute path to the runtime binary:
|
||||
`--runtime "$PWD"/target/debug/crun-vm`.
|
||||
Commands like `docker create` and `docker run` can then be made to use the
|
||||
crun-vm runtime by passing them the `--runtime crun-vm` option.
|
||||
|
||||
See [2. Using crun-vm and **Podman** or **Docker** to run a
|
||||
VM](2-podman-docker.md) to get started.
|
||||
|
||||
### For Kubernetes
|
||||
|
||||
For crun-vm to be usable as a runtime in a Kubernetes cluster, the latter must
|
||||
be use the [CRI-O] runtime. See the Kubernetes docs on [runtimes] for more
|
||||
information.
|
||||
|
||||
1. Install crun-vm on all cluster nodes where pods may be scheduled, using any
|
||||
of the methods [described above](#installing-the-crun-vm-binary).
|
||||
|
||||
2. Append the following to `/etc/crio/crio.conf`:
|
||||
|
||||
```toml
|
||||
[crio.runtime.runtimes.crun-vm]
|
||||
runtime_path = "/usr/bin/crun-vm"
|
||||
```
|
||||
|
||||
3. Create a `RuntimeClass` object in the cluster that references crun-vm:
|
||||
|
||||
```yaml
|
||||
apiVersion: node.k8s.io/v1
|
||||
kind: RuntimeClass
|
||||
metadata:
|
||||
name: crun-vm # a name of your choice
|
||||
handler: crun-vm
|
||||
```
|
||||
|
||||
Pods can then be configured to use this `RuntimeClass` by specifying its name
|
||||
under `Pod.spec.runtimeClassName`.
|
||||
|
||||
See [4. Running VMs in **Kubernetes**](4-kubernetes.md) to get started.
|
||||
|
||||
[runtimes]: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
|
||||
[CRI-O]: https://cri-o.io/
|
||||
|
|
|
@ -1,39 +1,66 @@
|
|||
# 2. Using crun-vm as a Podman or Docker runtime
|
||||
# 2. Running VMs with **Podman** or **Docker**
|
||||
|
||||
Here we overview some of the major features provided by crun-vm. The commands
|
||||
below use `podman`, but unless otherwise stated you can simply replace it with
|
||||
`docker`.
|
||||
Here, we outline how to use crun-vm to run VMs using the Podman or Docker
|
||||
container engines. See [crun-vm(1)] for a full reference and additional details
|
||||
on crun-vm specific options.
|
||||
|
||||
We use `podman` in the example commands below. Unless otherwise noted, the same
|
||||
commands also with `docker`.
|
||||
|
||||
<details open>
|
||||
<summary><b>Navigation</b></summary>
|
||||
|
||||
1. [Installing crun-vm](1-installing.md)
|
||||
2. **Running VMs with **Podman** or **Docker****
|
||||
- [**Booting VMs**](#booting-vms)
|
||||
- [From containerdisks](#from-containerdisks)
|
||||
- [From VM image files](#from-vm-image-files)
|
||||
- [From bootable containers](#from-bootable-containers)
|
||||
- [**Configuring VMs on first boot**](#configuring-vms-on-first-boot)
|
||||
- [Default user password](#default-user-password)
|
||||
- [cloud-init](#cloud-init)
|
||||
- [Ignition](#ignition)
|
||||
- [**Interacting with VMs**](#interacting-with-vms)
|
||||
- [Exec'ing into VMs](#execing-into-vms)
|
||||
- [Port forwarding](#port-forwarding)
|
||||
- [**Sharing resources with VMs**](#sharing-resources-with-vms)
|
||||
- [Files](#files)
|
||||
- [Directories](#directories)
|
||||
- [Block devices](#block-devices)
|
||||
3. [Running VMs as **systemd** services](3-systemd.md)
|
||||
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
|
||||
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
|
||||
|
||||
</details>
|
||||
|
||||
## Booting VMs
|
||||
|
||||
### From regular VM image files
|
||||
### From containerdisks
|
||||
|
||||
First, obtain a QEMU-compatible VM image and place it in a directory by itself:
|
||||
A "containerdisk" is a container image that packages a sole VM image file under
|
||||
`/` or `/disk/`. This is how you would boot a VM from the
|
||||
quay.io/containerdisks/fedora:40 containerdisk using crun-vm:
|
||||
|
||||
```console
|
||||
$ mkdir my-vm-image
|
||||
$ curl -LO --output-dir my-vm-image https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2
|
||||
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40
|
||||
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
|
||||
[...]
|
||||
```
|
||||
|
||||
Then run:
|
||||
> Several regularly-updated containerdisks may be found at
|
||||
> https://quay.io/organization/containerdisks. You can also easily build your
|
||||
> own:
|
||||
>
|
||||
> ```dockerfile
|
||||
> FROM scratch
|
||||
> COPY my-vm-image.qcow2 /
|
||||
> ENTRYPOINT ["no-entrypoint"]
|
||||
> ```
|
||||
|
||||
> This example does not work with Docker, as docker-run does not support the
|
||||
> `--rootfs` flag; see the next section for a Docker-compatible way of running
|
||||
> VM images.
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
--rootfs my-vm-image \
|
||||
"" # unused, but must specify command
|
||||
```
|
||||
|
||||
The VM console should take over your terminal. At this point, the
|
||||
qcow2 image does not have any ssh keys, root password, or alternative
|
||||
users installed, so although you can interact with the VM's login
|
||||
screen, you will be unable to access a command prompt until more
|
||||
options are used in later sections. To abort the VM, press `ctrl-]`.
|
||||
The VM console should take over your terminal. This VM image has no users that
|
||||
you may log in as using a password, so although you can interact with the VM's
|
||||
login screen, you will be unable to access a command prompt for now. To abort
|
||||
the VM, press `ctrl-]`.
|
||||
|
||||
You can also detach from the VM without terminating it by pressing `ctrl-p,
|
||||
ctrl-q`. Afterwards, reattach by running:
|
||||
|
@ -50,162 +77,174 @@ podman-run, in which case you won't be able to interact with the VM but can
|
|||
still observe its console. Note that pressing `ctrl-]` will have no effect, but
|
||||
you can always use the following command to terminate the VM:
|
||||
|
||||
> For this command to work with Docker, you must replace the `--latest` flag
|
||||
> with the container's name or ID.
|
||||
|
||||
```container
|
||||
$ podman stop --latest
|
||||
```
|
||||
|
||||
Changes made by the VM to its image are by default not persisted in the original
|
||||
image file. This can be changed by passing in the non-standard option
|
||||
`--persistent` *after* the `--rootfs` option:
|
||||
### From VM image files
|
||||
|
||||
> This feature is only supported with Podman.
|
||||
|
||||
It is also possible to boot VMs directly from disk image files by using Podman's
|
||||
`--rootfs` option to point at a directory containing a sole image file. For
|
||||
instance, these commands download and boot a Fedora 40 VM:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
--rootfs my-vm-image \
|
||||
--persistent
|
||||
$ mkdir my-vm-image/ && curl -LO --output-dir my-vm-image/ https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic.x86_64-40-1.14.qcow2
|
||||
|
||||
$ podman run --runtime crun-vm -it --rootfs my-vm-image/
|
||||
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
|
||||
[...]
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> When using `--persistent`, make sure that the image file is never
|
||||
> simultaneously used by another process or VM, otherwise **data corruption may
|
||||
> occur**.
|
||||
### From bootable containers
|
||||
|
||||
### From VM image files packaged into container images
|
||||
|
||||
crun-vm also works with container images that contain a VM image file with
|
||||
any name under `/` or under `/disk/`. No other files may exist in those
|
||||
directories. Containers built for use as [KubeVirt `containerDisk`s] follow this
|
||||
convention, so you can use those here:
|
||||
crun-vm can also launch VMs from [bootc bootable container images], which are
|
||||
containers that package a full operating system:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
"" # unused, but must specify command because container image does not
|
||||
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
|
||||
Converting quay.io/crun-vm/example-fedora-bootc:40 into a VM image...
|
||||
[...]
|
||||
Caching VM image as a containerdisk...
|
||||
[...]
|
||||
Booting VM...
|
||||
[...]
|
||||
```
|
||||
|
||||
You can also use `util/package-vm-image.sh` to easily package a VM image into a
|
||||
container image, and `util/extract-vm-image.sh` to extract a VM image contained
|
||||
in a container image.
|
||||
crun-vm generates a VM image from the bootable container and then boots it. The
|
||||
generated VM image is packaged as a containerdisk and cached in the host's
|
||||
container storage, so that subsequent runs will boot faster:
|
||||
|
||||
Note that flag `--persistent` has no effect when running VMs from container
|
||||
images.
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
|
||||
Retrieving cached VM image...
|
||||
[...]
|
||||
Booting VM...
|
||||
[...]
|
||||
```
|
||||
|
||||
## First-boot customization
|
||||
## Configuring VMs on first boot
|
||||
|
||||
### Default user password
|
||||
|
||||
In the examples above, you were able to boot a VM but not to log in. An easy way
|
||||
to fix this when a VM has [cloud-init] installed is to use the [`--password`]
|
||||
option, which sets the password for the VM's "default" user (as determined in
|
||||
the image's cloud-init configuration). For quay.io/containerdisks/fedora:40,
|
||||
that is the `fedora` user:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 --password pass
|
||||
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
|
||||
[...]
|
||||
3b0232a04046 login: fedora
|
||||
Password: pass
|
||||
[fedora@3b0232a04046 ~]$
|
||||
```
|
||||
|
||||
Like all crun-vm specific options, [`--password`] must be passed in *after* the
|
||||
image specification.
|
||||
|
||||
### cloud-init
|
||||
|
||||
In the examples above, you were able to boot the VM but not to log in. To fix
|
||||
this and do other first-boot customization, you can provide a [cloud-init]
|
||||
NoCloud configuration to the VM by passing in the non-standard option
|
||||
`--cloud-init` *after* the image specification:
|
||||
|
||||
> For this command to work with Docker, you must provide an absolute path to
|
||||
> `--cloud-init`.
|
||||
You can provide a full [cloud-init] NoCloud configuration to a VM by passing in
|
||||
the crun-vm specific option [`--cloud-init`] *after* the image specification:
|
||||
|
||||
```console
|
||||
$ ls examples/cloud-init/config/
|
||||
meta-data user-data vendor-data
|
||||
$ ls my-cloud-init-config/
|
||||
meta-data user-data
|
||||
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--cloud-init examples/cloud-init/config
|
||||
```
|
||||
$ cat my-cloud-init-config/meta-data # empty
|
||||
|
||||
You should now be able to log in with the default `fedora` username and password
|
||||
`pass`.
|
||||
$ cat my-cloud-init-config/user-data
|
||||
#cloud-config
|
||||
write_files:
|
||||
- path: $home/file
|
||||
content: |
|
||||
hello
|
||||
|
||||
Alternatively, you can set the default user's password with the `--password`
|
||||
option:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--password pass
|
||||
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 \
|
||||
--cloud-init $PWD/my-cloud-init-config/ # path must be absolute
|
||||
```
|
||||
|
||||
### Ignition
|
||||
|
||||
Similarly, you can provide an [Ignition] configuration to the VM by passing in
|
||||
the `--ignition` option:
|
||||
|
||||
> For this command to work with Docker, you must provide an absolute path to
|
||||
> `--ignition`.
|
||||
You can also provide an [Ignition] configuration to a VM using the crun-vm
|
||||
specific [`--ignition`] option:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/crun-vm/example-fedora-coreos:39 \
|
||||
--ignition examples/ignition/config.ign
|
||||
$ cat my-ignition-config.ign
|
||||
{
|
||||
"ignition": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"passwd": {
|
||||
"users": [
|
||||
{
|
||||
"name": "core",
|
||||
"passwordHash": "$y$j9T$USdd8CBvFNVU1xKwQUnsU/$aE.3arHcRxD0ZT3vkvsSpEsteUj6vC4ZdRHY8eOj1f4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-coreos:40 \
|
||||
--ignition $PWD/my-ignition-config.ign # path must be absolute
|
||||
```
|
||||
|
||||
You should now be able to log in with the default `core` username and password
|
||||
`pass`.
|
||||
## Interacting with VMs
|
||||
|
||||
Note that the `--password` option requires cloud-init support and doesn't work
|
||||
if the VM uses Ignition.
|
||||
### Exec'ing into VMs
|
||||
|
||||
## SSH'ing into the VM
|
||||
|
||||
Assuming the VM supports cloud-init or Ignition and exposes an SSH server on
|
||||
port 22, you can `ssh` into it using podman-exec as the VMs default user:
|
||||
|
||||
> For this command to work with Docker, you must replace the `--latest` flag
|
||||
> with the container's name or ID.
|
||||
Assuming a VM supports cloud-init or Ignition and exposes an SSH server on port
|
||||
22, you can `ssh` into it as root using podman-exec:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
--detach --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
""
|
||||
$ podman run --runtime crun-vm --detach quay.io/containerdisks/fedora:40
|
||||
8068a2c180e0f4bf494f5e0baa37d9f13a9810f76b361c0771b73666e47ec383
|
||||
|
||||
$ podman exec --latest fedora whoami
|
||||
fedora
|
||||
$ podman exec --latest whoami
|
||||
Please login as the user "fedora" rather than the user "root".
|
||||
```
|
||||
|
||||
$ podman exec -it --latest fedora
|
||||
This particular VM image does not allow logging in as root. To `ssh` into the VM
|
||||
as a different user, specify its username using the [`--as`] option immediately
|
||||
before the command (if any). You may need to pass in `--` before this option to
|
||||
prevent podman-exec from trying to interpret it:
|
||||
|
||||
```console
|
||||
$ podman exec --latest -- --as fedora whoami
|
||||
fedora
|
||||
```
|
||||
|
||||
If you just want a login shell, pass in an empty string as the command. The
|
||||
following would be the output if this VM image allowed logging in as root:
|
||||
|
||||
```console
|
||||
$ podman exec -it --latest ""
|
||||
[root@8068a2c180e0 ~]$
|
||||
```
|
||||
|
||||
You may also log in as a specific user:
|
||||
|
||||
```console
|
||||
$ podman exec -it --latest -- --as fedora
|
||||
[fedora@8068a2c180e0 ~]$
|
||||
```
|
||||
|
||||
With cloud-init, the default user can vary between VM images. With Ignition,
|
||||
`core` is considered to be the default user. In both cases, if the SSH server
|
||||
allows password authentication, you should also be able to log in as any other
|
||||
user.
|
||||
When a VM supports cloud-init, `authorized_keys` is automatically set up to
|
||||
allow SSH access by podman-exec for users `root` and the default user as set in
|
||||
the image's cloud-init configuration. With Ignition, this is set up for users
|
||||
`root` and `core`.
|
||||
|
||||
The `fedora` argument to podman-exec above, which would typically correspond to
|
||||
the command to be executed, determines instead the name of the user to `ssh`
|
||||
into the VM as. A command can optionally be specified with further arguments. If
|
||||
no command is specified, a login shell is initiated. In this case, you probably
|
||||
also want to pass flags `-it` to podman-exec.
|
||||
### Port forwarding
|
||||
|
||||
If you actually just want to exec into the container in which the VM is running
|
||||
(probably to debug some problem with crun-vm itself), pass in `-` as the
|
||||
username.
|
||||
|
||||
## Port forwarding
|
||||
|
||||
You can use podman-run's standard `-p`/`--publish` option to set up TCP and/or
|
||||
You can use podman-run's standard `-p`/`--publish` option to enable TCP and/or
|
||||
UDP port forwarding:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
--detach --rm \
|
||||
-p 8000:80 \
|
||||
quay.io/crun-vm/example-http-server:latest \
|
||||
""
|
||||
$ podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
|
||||
36c8705482589cfc4336a03d3802e7699f5fb228123d18e693488ac7b80116d1
|
||||
|
||||
$ curl localhost:8000
|
||||
|
@ -219,145 +258,73 @@ $ curl localhost:8000
|
|||
[...]
|
||||
```
|
||||
|
||||
## Passing things through to the VM
|
||||
## Sharing resources with VMs
|
||||
|
||||
### Files
|
||||
|
||||
You can bind mount regular files into a VM:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm -it \
|
||||
-v ./README.md:/home/fedora/README.md:z \
|
||||
quay.io/containerdisks/fedora:40
|
||||
```
|
||||
|
||||
Regular files currently appear as block devices in the VM, but this is subject
|
||||
to change.
|
||||
|
||||
### Directories
|
||||
|
||||
Bind mounting directories into the VM is supported:
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> This example recursively modifies the SELinux context of all files under the
|
||||
> path being mounted, in this case `./util`, which in the worst case **may cause
|
||||
> you to lose access to your files**. This is due to the `:z` volume mount
|
||||
> modifier, which instructs Podman to relabel the volume so that the VM can
|
||||
> access it.
|
||||
>
|
||||
> Alternatively, you may remove this modifier from the command below and add
|
||||
> `--security-opt label=disable` instead to disable SELinux enforcement.
|
||||
It is also possible to bind mount directories into a VM:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
$ podman run --runtime crun-vm -it \
|
||||
-v ./util:/home/fedora/util:z \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--password pass
|
||||
quay.io/containerdisks/fedora:40
|
||||
```
|
||||
|
||||
If the VM supports cloud-init or Ignition, the volume will automatically be
|
||||
mounted at the given destination path. Otherwise, you can mount it manually with
|
||||
the following command, where `<index>` must be the 0-based index of the volume
|
||||
the following command, where `<index>` is the 0-based index of the volume
|
||||
according to the order the `-v`/`--volume` or `--mount` flags where given in:
|
||||
|
||||
```console
|
||||
$ mount -t virtiofs virtiofs-<index> /home/fedora/util
|
||||
```
|
||||
|
||||
### Regular files
|
||||
|
||||
Similarly to directories, you can bind mount regular files into the VM:
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> The warning about SELinux relabeling on the command above also applies here.
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
-v ./README.md:/home/fedora/README.md:z \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--password pass
|
||||
```
|
||||
|
||||
Regular files currently appear as block devices in the VM, but this is subject
|
||||
to change.
|
||||
|
||||
### Block devices
|
||||
|
||||
If cloud-init or Ignition are supported by the VM, it is possible to pass block
|
||||
devices through to it at a specific path using podman-run's `--device` flag
|
||||
(this example assumes `/dev/ram0` to exist and to be accessible by the current
|
||||
user):
|
||||
If cloud-init or Ignition are supported by a VM, it is possible to pass block
|
||||
devices through to it and make them appear at a specific path using podman-run's
|
||||
`--device` flag. For instance, assuming `/dev/ram0` exists on the host and is
|
||||
accessible by the current user:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
$ podman run --runtime crun-vm -it \
|
||||
--device /dev/ram0:/home/fedora/my-disk \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--password pass
|
||||
quay.io/containerdisks/fedora:40
|
||||
```
|
||||
|
||||
You can also use the more powerful `--blockdev
|
||||
source=<path>,target=<path>,format=<fmt>` custom option to this effect. This
|
||||
option also allows you specify a regular file as the source, and the source may
|
||||
be in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`,
|
||||
raw format is assumed):
|
||||
|
||||
> For this command to work with Docker, you must provide absolute paths to
|
||||
> `--blockdev`.
|
||||
You can also use the more powerful, crun-vm specific [`--blockdev`]
|
||||
`source=<path>,target=<path>,format=<fmt>` option to this effect. This option
|
||||
also allows you to specify a regular file as the source, and the source may be
|
||||
in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`, raw
|
||||
format is assumed):
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--password pass \
|
||||
--blockdev source=my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2
|
||||
$ podman run --runtime crun-vm -it \
|
||||
quay.io/containerdisks/fedora:40 \
|
||||
--blockdev source=$PWD/my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2 # paths must be absolute
|
||||
```
|
||||
|
||||
## Advanced options
|
||||
|
||||
### PCI device assignment
|
||||
|
||||
vfio-pci devices can be passed through to the VM by specifying the non-standard
|
||||
`--vfio-pci` option with a path to the device's sysfs directory (this example
|
||||
assumes that the corresponding VFIO device under `/dev/vfio/` is accessible to
|
||||
the current user):
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--vfio-pci /sys/bus/pci/devices/0000:00:01.0
|
||||
```
|
||||
|
||||
In turn, mediated (mdev) vfio-pci devices (such as vGPUs) can be passed through
|
||||
with the `--vfio-pci-mdev` option, specifying a path to the mdev's sysfs
|
||||
directory:
|
||||
|
||||
```console
|
||||
$ podman run \
|
||||
--runtime crun-vm \
|
||||
-it --rm \
|
||||
quay.io/containerdisks/fedora:39 \
|
||||
--vfio-pci-mdev /sys/bus/pci/devices/0000:00:02.0/5fa530b9-9fdf-4cde-8eb7-af73fcdeeaae
|
||||
```
|
||||
|
||||
### Inspecting and customizing the libvirt domain XML
|
||||
|
||||
crun-vm internally uses [libvirt] to launch a VM, generating a [domain XML
|
||||
definition] from the options provided to podman-run. This XML definition can be
|
||||
printed by adding the non-standard `--print-libvirt-xml` flag to your podman-run
|
||||
invocation.
|
||||
|
||||
The generated XML definition can also be customized by specifying an XML file to
|
||||
be merged with it using the non-standard option `--merge-libvirt-xml <file>`.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> While `--merge-libvirt-xml` gives you maximum flexibility, it thwarts
|
||||
> crun-vm's premise of isolating the user from such details as libvirt domain
|
||||
> definitions, and you have instead to take care that your XML is valid *and*
|
||||
> that the customized definition is compatible with what crun-vm expects.
|
||||
>
|
||||
> Before using this flag, consider if you would be better served using libvirt
|
||||
> directly to manage your VM.
|
||||
|
||||
[`--as`]: 5-crun-vm.1.ronn#exec-options
|
||||
[`--blockdev`]: 5-crun-vm.1.ronn#createrun-options
|
||||
[`--cloud-init`]: 5-crun-vm.1.ronn#createrun-options
|
||||
[`--ignition`]: 5-crun-vm.1.ronn#createrun-options
|
||||
[`--password`]: 5-crun-vm.1.ronn#createrun-options
|
||||
[bootc bootable container images]: https://containers.github.io/bootable/
|
||||
[cloud-init]: https://cloud-init.io/
|
||||
[crun-vm(1)]: 5-crun-vm.1.ronn
|
||||
[domain XML definition]: https://libvirt.org/formatdomain.html
|
||||
[Ignition]: https://coreos.github.io/ignition/
|
||||
[KubeVirt `containerDisk`s]: https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
# 3. Using crun-vm as a Kubernetes runtime
|
||||
|
||||
It is possible to use crun-vm as a [Kubernetes] runtime, allowing you to run
|
||||
VMs as regular pods.
|
||||
|
||||
## Preparation
|
||||
|
||||
To enable crun-vm on a Kubernetes cluster, follow these steps:
|
||||
|
||||
1. Ensure that the cluster is using the [CRI-O] container runtime. Refer to the
|
||||
Kubernetes docs on [container runtimes].
|
||||
|
||||
2. Install crun-vm on all cluster nodes where pods may be scheduled. Refer to
|
||||
the [installation instructions].
|
||||
|
||||
3. Append the following to `/etc/crio/crio.conf` (adjust the `runtime_path` if
|
||||
necessary):
|
||||
|
||||
```toml
|
||||
[crio.runtime.runtimes.crun-vm]
|
||||
runtime_path = "/usr/local/bin/crun-vm"
|
||||
```
|
||||
|
||||
4. Create a `RuntimeClass` that references crun-vm:
|
||||
|
||||
```yaml
|
||||
apiVersion: node.k8s.io/v1
|
||||
kind: RuntimeClass
|
||||
metadata:
|
||||
name: crun-vm
|
||||
handler: crun-vm
|
||||
```
|
||||
|
||||
## Using the runtime
|
||||
|
||||
> Under [examples/minikube] you can find a script that sets up a local minikube
|
||||
> Kubernetes cluster with crun-vm available as a runtime. You can use it to
|
||||
> easily try out the examples below.
|
||||
|
||||
From then on, you can run VM images packaged in container images by creating
|
||||
pods that use this `RuntimeClass`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-vm
|
||||
spec:
|
||||
containers:
|
||||
- name: my-vm
|
||||
image: quay.io/crun-vm/example-http-server:latest
|
||||
args:
|
||||
- "" # unused, but must specify command because container image does not
|
||||
ports:
|
||||
- containerPort: 80
|
||||
runtimeClassName: crun-vm
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
The VM's console output is logged:
|
||||
|
||||
```console
|
||||
$ kubectl logs my-vm
|
||||
```
|
||||
|
||||
### SSH'ing into the pod/VM
|
||||
|
||||
Assuming the VM supports cloud-init or Ignition, you can also SSH into it using
|
||||
`kubectl exec`, with the caveat that the user to SSH as is passed in place of
|
||||
the command (this is the same behavior as with `podman exec` or `docker exec`;
|
||||
see [SSH'ing into the VM]):
|
||||
|
||||
```console
|
||||
$ kubectl exec my-vm -- fedora whoami
|
||||
fedora
|
||||
|
||||
$ kubectl exec -it my-vm -- fedora
|
||||
[fedora@my-vm ~]$
|
||||
```
|
||||
|
||||
### Port forwarding
|
||||
|
||||
The pod/VM defined above actually exposes an HTTP server on port 80. To talk to
|
||||
it, we must first forward a local port to the pod/VM:
|
||||
|
||||
```console
|
||||
$ kubectl port-forward my-vm 8000:80
|
||||
Forwarding from 127.0.0.1:8000 -> 80
|
||||
Forwarding from [::1]:8000 -> 80
|
||||
```
|
||||
|
||||
With this command running, navigate to [`localhost:8000`] on your browser, or
|
||||
run the following on a second terminal:
|
||||
|
||||
```console
|
||||
$ curl localhost:8000
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Directory listing for /</title>
|
||||
</head>
|
||||
<body>
|
||||
[...]
|
||||
```
|
||||
|
||||
### cloud-init and Ignition
|
||||
|
||||
When using crun-vm as a Kubernetes runtime, paths given to `--cloud-init` and
|
||||
`--ignition` are interpreted in the context of the container/VM, instead of the
|
||||
host. This means that config files can be retrieved from mounted volumes. For
|
||||
instance, you could store your cloud-init config in a `ConfigMap`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: my-cloud-init-config
|
||||
data:
|
||||
meta-data: ""
|
||||
user-data: |
|
||||
#cloud-config
|
||||
runcmd:
|
||||
- echo 'Hello, world!' > /home/fedora/hello-world
|
||||
```
|
||||
|
||||
And pass it to your VMs like so:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-other-vm
|
||||
spec:
|
||||
containers:
|
||||
- name: my-other-vm
|
||||
image: quay.io/containerdisks/fedora:39
|
||||
args:
|
||||
- --cloud-init=/etc/cloud-init
|
||||
volumeMounts:
|
||||
- name: cloud-init-vol
|
||||
mountPath: /etc/cloud-init
|
||||
volumes:
|
||||
- name: cloud-init-vol
|
||||
configMap:
|
||||
name: my-cloud-init-config
|
||||
runtimeClassName: crun-vm
|
||||
```
|
||||
|
||||
[container runtimes]: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
|
||||
[CRI-O]: https://cri-o.io/
|
||||
[examples/minikube]: /examples/minikube
|
||||
[installation instructions]: 1-installing.md
|
||||
[Kubernetes]: https://kubernetes.io/
|
||||
[`localhost:8000`]: http://localhost:8000/
|
||||
[SSH'ing into the VM]: 2-podman-docker.md#sshing-into-the-vm
|
|
@ -0,0 +1,130 @@
|
|||
# 3. Running VMs as **systemd** services
|
||||
|
||||
crun-vm also enables you to define a [systemd] service corresponding to a VM,
|
||||
and thus manage it through systemd. This relies on Podman's [Quadlet] feature,
|
||||
through which you can define systemd unit files for containers.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> This means that system **containers** and **VMs** can both be deployed and
|
||||
> managed **using the same tooling**, *i.e.*, systemd!
|
||||
|
||||
Here, we overview how you can create Quadlet-powered systemd services to manage
|
||||
VMs. Make sure you have installed both crun-vm and Podman (see [1. Installing
|
||||
crun-vm]).
|
||||
|
||||
<details open>
|
||||
<summary><b>Navigation</b></summary>
|
||||
|
||||
1. [Installing crun-vm](1-installing.md)
|
||||
2. [Running VMs with **Podman** or **Docker**](2-podman-docker.md)
|
||||
3. **Running VMs as **systemd** services**
|
||||
- [**Creating a systemd service for a VM**](#creating-a-systemd-service-for-a-vm)
|
||||
- [**Further information**](#further-information)
|
||||
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
|
||||
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
|
||||
|
||||
</details>
|
||||
|
||||
## Creating a systemd service for a VM
|
||||
|
||||
The easiest way to do this is using [Podlet], a tool that can generate a systemd
|
||||
unit file corresponding to a given podman-run command. (Follow the instructions
|
||||
at https://github.com/containers/podlet to install Podlet.) This means we can
|
||||
apply it to the podman-run commands we use to launch VMs.
|
||||
|
||||
For instance, say you're using this command to launch a VM that runs a web
|
||||
service (see [2. Running VMs with **Podman** or **Docker**] to learn how crun-vm
|
||||
can be used with Podman):
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
|
||||
```
|
||||
|
||||
To convert this invocation into an equivalent systemd container unit definition,
|
||||
you would run:
|
||||
|
||||
```console
|
||||
$ podlet \
|
||||
--install \
|
||||
--wanted-by default.target \
|
||||
podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
|
||||
#example-http-server.container
|
||||
[Container]
|
||||
Image=quay.io/crun-vm/example-http-server:latest
|
||||
PublishPort=8000:80
|
||||
GlobalArgs=--runtime crun-vm
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
The `--install`, `--wanted-by default.target` options configure the service to
|
||||
run automatically on boot.
|
||||
|
||||
Finally, to actually install this unit definition, you would instead run (using
|
||||
`sudo` to become root):
|
||||
|
||||
```console
|
||||
$ sudo podlet \
|
||||
--name my-web-service \
|
||||
--unit-directory \
|
||||
--install \
|
||||
--wanted-by default.target \
|
||||
podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
|
||||
Wrote to file: /etc/containers/systemd/my-web-service.container
|
||||
|
||||
$ systemctl daemon-reload # load the new service
|
||||
```
|
||||
|
||||
With this, your web server VM becomes a systemd service:
|
||||
|
||||
```console
|
||||
$ sudo systemctl status my-web-service
|
||||
○ my-web-service.service
|
||||
Loaded: loaded (/etc/containers/systemd/my-web-service.container; generated)
|
||||
Drop-In: /usr/lib/systemd/system/service.d
|
||||
└─10-timeout-abort.conf
|
||||
Active: inactive (dead)
|
||||
|
||||
$ sudo systemctl start my-web-service # start the service without having to reboot
|
||||
|
||||
$ sudo systemctl status my-web-service
|
||||
● my-web-service.service
|
||||
Loaded: loaded (/etc/containers/systemd/my-web-service.container; generated)
|
||||
Drop-In: /usr/lib/systemd/system/service.d
|
||||
└─10-timeout-abort.conf
|
||||
Active: active (running) since Tue 2024-04-30 21:14:36 WEST; 4s ago
|
||||
Main PID: 1531707 (conmon)
|
||||
Tasks: 48 (limit: 76805)
|
||||
Memory: 1.1G (peak: 1.1G)
|
||||
CPU: 11.768s
|
||||
[...]
|
||||
|
||||
$ curl localhost:8000
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Directory listing for /</title>
|
||||
</head>
|
||||
<body>
|
||||
[...]
|
||||
```
|
||||
|
||||
## Further information
|
||||
|
||||
See [this article] for additional information on Podman Quadlet, and the
|
||||
[podman-systemd.unit(5)] man page for the reference format of container unit
|
||||
files.
|
||||
|
||||
The `podlet` commands provides several options to further customize the
|
||||
generated container unit file. Run `podlet -h` to know more.
|
||||
|
||||
[1. Installing crun-vm]: 1-installing.md
|
||||
[2. Running VMs with **Podman** or **Docker**]: 2-podman-docker.md
|
||||
[Podlet]: https://github.com/containers/podlet
|
||||
[podman-systemd.unit(5)]: https://docs.podman.io/en/stable/markdown/podman-systemd.unit.5.html
|
||||
[Quadlet]: https://docs.podman.io/en/stable/markdown/podman-systemd.unit.5.html
|
||||
[systemd]: https://systemd.io/
|
||||
[this article]: https://www.redhat.com/sysadmin/quadlet-podman
|
|
@ -0,0 +1,182 @@
|
|||
# 4. Running VMs in **Kubernetes**
|
||||
|
||||
crun-vm can also be used as a [Kubernetes] runtime, allowing you to run VMs as
|
||||
regular pods.
|
||||
|
||||
Note that there is already a very featureful project, [KubeVirt], that enables
|
||||
using Kubernetes to run VMs with great configurability. However, for simple use
|
||||
cases, crun-vm may be a good fit.
|
||||
|
||||
<details open>
|
||||
<summary><b>Navigation</b></summary>
|
||||
|
||||
1. [Installing crun-vm](1-installing.md)
|
||||
2. [Running VMs with **Podman** or **Docker**](2-podman-docker.md)
|
||||
3. [Running VMs as **systemd** services](3-systemd.md)
|
||||
4. **Running VMs in **Kubernetes****
|
||||
- [**Setting up**](#setting-up)
|
||||
- [**Creating VMs**](#creating-vms)
|
||||
- [**Interacting with VMs**](#interacting-with-vms)
|
||||
- [Exec'ing into VMs](#execing-into-vms)
|
||||
- [Port forwarding](#port-forwarding)
|
||||
- [**First-boot configuration**](#first-boot-configuration)
|
||||
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
|
||||
|
||||
</details>
|
||||
|
||||
## Setting up
|
||||
|
||||
You can use the [util/minikube-start.sh] script to set up a local [minikube]
|
||||
Kubernetes cluster with crun-vm available as a runtime, and use it to easily try
|
||||
out the examples below (note that this also points `kubectl` at the minikube
|
||||
cluster):
|
||||
|
||||
```console
|
||||
$ ./minikube-start.sh
|
||||
Compiling crun-vm v0.2.0 (/home/afaria/crun-vm)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.70s
|
||||
😄 [crun-vm-example] minikube v1.32.0 on Fedora 40
|
||||
[...]
|
||||
🏄 Done! kubectl is now configured to use "crun-vm-example" cluster and "default" namespace by default
|
||||
runtimeclass.node.k8s.io/crun-vm created
|
||||
```
|
||||
|
||||
Once you're done, you can delete the cluster with:
|
||||
|
||||
```console
|
||||
$ minikube -p crun-vm-example delete
|
||||
```
|
||||
|
||||
To enable crun-vm on a real Kubernetes cluster, follow the instructions in [1.
|
||||
Installing crun-vm].
|
||||
|
||||
## Creating VMs
|
||||
|
||||
To run a VM in your cluster, simply create a pod that references the
|
||||
`RuntimeClass` corresponding to crun-vm (here we assume it is named `crun-vm`):
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-vm
|
||||
spec:
|
||||
containers:
|
||||
- name: my-vm
|
||||
image: quay.io/crun-vm/example-http-server:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
runtimeClassName: crun-vm
|
||||
```
|
||||
|
||||
All the image formats supported by crun-vm when using Podman or Docker are also
|
||||
supported here. See [2. Running VMs with **Podman** or
|
||||
**Docker**](2-podman-docker.md#booting-vms) to know more.
|
||||
|
||||
You can inspect the VM's console output with the standard [`kubectl logs`]
|
||||
command:
|
||||
|
||||
```console
|
||||
$ kubectl logs my-vm
|
||||
```
|
||||
|
||||
## Interacting with VMs
|
||||
|
||||
### Exec'ing into VMs
|
||||
|
||||
Assuming a VM supports cloud-init or Ignition, you can `ssh` into it using
|
||||
`kubectl exec`:
|
||||
|
||||
```console
|
||||
$ kubectl exec my-vm -- --as fedora whoami
|
||||
fedora
|
||||
|
||||
$ kubectl exec -it my-vm -- --as fedora bash
|
||||
[fedora@my-vm ~]$
|
||||
```
|
||||
|
||||
The supported crun-vm specific options like `--as fedora` are the same as when
|
||||
using crun-vm with Podman or Docker. See [2. Running VMs with **Podman** or
|
||||
**Docker**](2-podman-docker.md#execing-into-vms) to know more.
|
||||
|
||||
### Port forwarding
|
||||
|
||||
The VM pod defined above actually exposes an HTTP server on port 80. To talk to
|
||||
it, we must first forward a local port to the VM:
|
||||
|
||||
```console
|
||||
$ kubectl port-forward my-vm 8000:80
|
||||
Forwarding from 127.0.0.1:8000 -> 80
|
||||
Forwarding from [::1]:8000 -> 80
|
||||
```
|
||||
|
||||
With this command running, navigate to [`localhost:8000`] on your browser, or
|
||||
run the following on a second terminal:
|
||||
|
||||
```console
|
||||
$ curl localhost:8000
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Directory listing for /</title>
|
||||
</head>
|
||||
<body>
|
||||
[...]
|
||||
```
|
||||
|
||||
## First-boot configuration
|
||||
|
||||
Options supported when using crun-vm with Podman or Docker, like `--password`,
|
||||
`--cloud-init`, and `--ignition`, are also supported here (see [2. Running VMs
|
||||
with **Podman** or **Docker**](2-podman-docker.md#configuring-vms-on-first-boot)
|
||||
for more information).
|
||||
|
||||
However, paths given to `--cloud-init` and `--ignition` are interpreted in the
|
||||
context of the VM, instead of the host. This means that first-boot configuration
|
||||
files can be retrieved from mounted volumes. For instance, you could store your
|
||||
cloud-init configuration in a `ConfigMap`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: my-cloud-init-config
|
||||
data:
|
||||
meta-data: ""
|
||||
user-data: |
|
||||
#cloud-config
|
||||
runcmd:
|
||||
- echo 'Hello, world!' > /home/fedora/hello-world
|
||||
```
|
||||
|
||||
And apply it to your VMs like so:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-other-vm
|
||||
spec:
|
||||
containers:
|
||||
- name: my-other-vm
|
||||
image: quay.io/containerdisks/fedora:40
|
||||
args:
|
||||
- --cloud-init=/etc/cloud-init
|
||||
volumeMounts:
|
||||
- name: cloud-init-vol
|
||||
mountPath: /etc/cloud-init
|
||||
volumes:
|
||||
- name: cloud-init-vol
|
||||
configMap:
|
||||
name: my-cloud-init-config
|
||||
runtimeClassName: crun-vm
|
||||
```
|
||||
|
||||
[`kubectl logs`]: https://kubernetes.io/docs/reference/kubectl/
|
||||
[`localhost:8000`]: http://localhost:8000/
|
||||
[1. Installing crun-vm]: 1-installing.md
|
||||
[Kubernetes]: https://kubernetes.io/
|
||||
[KubeVirt]: https://kubevirt.io/
|
||||
[minikube]: https://minikube.sigs.k8s.io/
|
||||
[util/minikube-start.sh]: ../util/minikube-start.sh
|
|
@ -0,0 +1,186 @@
|
|||
<!-- ENSURE THIS RENDERS WELL BOTH AS MAN PAGE AND GITHUB MARKDOWN -->
|
||||
|
||||
# 5. crun-vm(1) -- an OCI Runtime that runs VM images
|
||||
|
||||
<details open>
|
||||
<summary><b>Navigation</b></summary>
|
||||
|
||||
1. [Installing crun-vm](1-installing.md)
|
||||
2. [Running VMs with **Podman** or **Docker**](2-podman-docker.md)
|
||||
3. [Running VMs as **systemd** services](3-systemd.md)
|
||||
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
|
||||
5. ****crun-vm(1)** man page**
|
||||
- [**SYNOPSIS**](#synopsis)
|
||||
- [**DESCRIPTION**](#description)
|
||||
- [**QUICK START**](#quick-start)
|
||||
- [**CREATE/RUN USAGE**](#createrun-usage)
|
||||
- [**CREATE/RUN OPTIONS**](#createrun-options)
|
||||
- [**EXEC USAGE**](#exec-usage)
|
||||
- [**EXEC OPTIONS**](#exec-options)
|
||||
- [**ENVIRONMENT**](#environment)
|
||||
|
||||
</details>
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`podman|docker` `create|run` --runtime crun-vm [<engine_opts>...] <image_ref> [""] [<crun_vm_opts>...]<br>
|
||||
`podman|docker` `exec` [<engine_opts>...] [<crun_vm_opts>...] <cmd_and_args>...`|`""
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**crun-vm** is an OCI Runtime that enables Podman, Docker, and Kubernetes to run
|
||||
QEMU-compatible Virtual Machine (VM) images.
|
||||
|
||||
This man page enumerates the crun-vm specific options that may be used with
|
||||
Podman/Docker engine commands like `podman create` and `docker run`, and how
|
||||
they can be specified alongside standard engine options. Visit
|
||||
<https://github.com/containers/crun-vm> for additional guides.
|
||||
|
||||
## QUICK START
|
||||
|
||||
Launch a VM from a "containerdisk" (a disk image packaged in a container):
|
||||
|
||||
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40
|
||||
|
||||
Launch a VM from a disk image under *my-image-dir/*:
|
||||
|
||||
$ podman run --runtime crun-vm -it --rootfs my-image-dir/
|
||||
|
||||
Launch a VM from a bootable container (<https://containers.github.io/bootable/>):
|
||||
|
||||
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
|
||||
|
||||
Set the password for a VM's default user:
|
||||
|
||||
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 \
|
||||
--password pass # for user "fedora"
|
||||
|
||||
Exec (ssh) into a VM:
|
||||
|
||||
$ podman exec -it --latest -- --as fedora
|
||||
|
||||
## CREATE/RUN USAGE
|
||||
|
||||
`podman|docker` `create|run` --runtime crun-vm [<engine_opts>...] <image_ref> [""] [<crun_vm_opts>...]
|
||||
|
||||
Standard engine options are specified in <engine_opts>.
|
||||
|
||||
<image_ref> identifies a container image of one the following kinds:
|
||||
|
||||
1. A "containerdisk", i.e., a container image with a sole VM image file
|
||||
residing under `/` or `/disk/`, such as *quay.io/containerdisks/fedora:40*;
|
||||
|
||||
2. The Podman-specific `--rootfs` option pointing at a directory containing a
|
||||
sole VM image file;
|
||||
|
||||
3. A bootc bootable container, such as *quay.io/crun-vm/example-fedora-bootc:40*
|
||||
(see <https://containers.github.io/bootable/> for more information).
|
||||
|
||||
crun-vm specific options are specified in <crun_vm_opts>, i.e., where the
|
||||
command to execute in the container would usually by specified. Since this
|
||||
notion does not apply to VMs as it does to regular containers, no command should
|
||||
be specified here. However, if you're using Docker and the container you
|
||||
specified defines no default entrypoint, you have to also pass in an empty `""`
|
||||
argument to satisfy Docker's syntax.
|
||||
|
||||
## CREATE/RUN OPTIONS
|
||||
|
||||
All crun-vm specific options that may be passed to `podman|docker` `create|run`
|
||||
commands are listed here.
|
||||
|
||||
These options configure the VM's environment:
|
||||
|
||||
* `--blockdev` source=<src_path>,target=<tgt_path>,format=<fmt_name>:
|
||||
Expose the file or block device <src_path> at path <tgt_path> in the VM.
|
||||
<src_path> and <tgt_path> must be absolute paths. <fmt_name> specifies the
|
||||
QEMU-compatible image format of <src_path>, such as *raw* or *qcow2*. When
|
||||
*format=raw*, the same effect can be achieved with the standard
|
||||
`-m`/`--mount`/`-v`/`--volume` Podman/Docker options.
|
||||
|
||||
* `--persistent`:
|
||||
When using `podman|docker` `create|run` with the standard `--rootfs` option,
|
||||
this flag causes writes made by the VM to its disk to be persisted in the
|
||||
user's image file. Make sure that the image is never simultaneously used by
|
||||
another process or VM, otherwise **data corruption may occur**.
|
||||
|
||||
* `--bootc-disk-size` <disk_size>[KMGT]:
|
||||
Set the disk size of the VM image generated from a bootc bootable container.
|
||||
The default is twice the size of the container image.
|
||||
|
||||
* `--emulated`:
|
||||
Emulate the VM in software rather than using KVM for hardware-assisted
|
||||
virtualization. It's not currently possible to use this flag when the
|
||||
container image is a bootc bootable container.
|
||||
|
||||
These options control the VM's first-boot customization:
|
||||
|
||||
* `--password` <plaintext>:
|
||||
Set the password of the VM image's default user. Only works when the VM
|
||||
supports cloud-init.
|
||||
|
||||
* `--cloud-init` <config_dir>:
|
||||
Expose the given cloud-init configuration to the VM. <config_dir> must be an
|
||||
absolute path.
|
||||
|
||||
* `--ignition` <config_file>:
|
||||
Expose the given Ignition configuration to the VM. <config_dir> must be an
|
||||
absolute path.
|
||||
|
||||
* `--random-ssh-key-pair`:
|
||||
By default, when using podman-exec, the host user's ssh key pair (if any) is
|
||||
used to ssh into the VM. This is useful when the VM isn't easily
|
||||
customizable at first boot but already authorizes the user's public key.
|
||||
Otherwise, or if this flag is specified, a new random key pair is always
|
||||
used.
|
||||
|
||||
These options are mostly helpful when debugging crun-vm itself:
|
||||
|
||||
* `--merge-libvirt-xml` <xml_path>:
|
||||
Merge the given XML file into the libvirt domain XML generated by crun-vm,
|
||||
prior to using it to launch the VM. <xml_path> must be an absolute path.
|
||||
|
||||
* `--print-libvirt-xml`:
|
||||
Print the libvirt domain XML that would be used to launch the VM, and exit
|
||||
without launching the VM.
|
||||
|
||||
* `--print-config-json`:
|
||||
Print the OCI Runtime config.json file with crun-vm's modifications that was
|
||||
passed to *crun(1)*, and exit without launching the VM.
|
||||
|
||||
## EXEC USAGE
|
||||
|
||||
`podman|docker` `exec` [<engine_opts>...] [--] [<crun_vm_opts>...] <cmd_and_args>...`|`""
|
||||
|
||||
Standard engine arguments and options are specified in <engine_opts>.
|
||||
|
||||
crun-vm specific options are specified in <crun_vm_opts>, i.e., as a prefix to
|
||||
the command to be executed. To launch a login shell, pass `""` as the command.
|
||||
|
||||
When using Podman's `--latest` flag instead of providing a container name or ID,
|
||||
you may need pass in `--` to delimit standard options from crun-vm specific
|
||||
options.
|
||||
|
||||
## EXEC OPTIONS
|
||||
|
||||
Options that may be used with `podman|docker` `exec` as a prefix to the actual
|
||||
command to run, if any:
|
||||
|
||||
* `--as` <user_name>:
|
||||
The user to ssh into the VM as. Defaults to *root*.
|
||||
|
||||
* `--timeout` <max_secs>:
|
||||
The timeout, in seconds, to apply to a `podman|docker` `run` command.
|
||||
Defaults to the value of the `CRUN_VM_EXEC_TIMEOUT` environment variable if
|
||||
set, or *0* otherwise, which means no timeout.
|
||||
|
||||
These options are mostly helpful when debugging crun-vm itself:
|
||||
|
||||
* `--container`:
|
||||
Exec into the container where the VM hypervisor is running, instead of
|
||||
ssh'ing into the actual VM. Incompatible with `--as`.
|
||||
|
||||
## ENVIRONMENT
|
||||
|
||||
* `CRUN_VM_EXEC_TIMEOUT`:
|
||||
The timeout, in seconds, to apply to a `podman|docker` `exec` command.
|
||||
Defaults to *0*, which means no timeout. Is overridden by `--timeout`.
|
BIN
docs/example.gif
BIN
docs/example.gif
Binary file not shown.
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 365 KiB |
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"ociVersion": "1.0.0",
|
||||
"process": {
|
||||
"terminal": true,
|
||||
"user": { "uid": 0, "gid": 0 },
|
||||
"args": ["/output/entrypoint.sh", "<IMAGE_NAME>"],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/",
|
||||
"capabilities": {
|
||||
"bounding": [],
|
||||
"effective": [],
|
||||
"inheritable": [],
|
||||
"permitted": [],
|
||||
"ambient": []
|
||||
},
|
||||
"rlimits": [
|
||||
{
|
||||
"type": "RLIMIT_NOFILE",
|
||||
"hard": 262144,
|
||||
"soft": 262144
|
||||
}
|
||||
],
|
||||
"noNewPrivileges": true
|
||||
},
|
||||
"root": {
|
||||
"path": "<ORIGINAL_ROOT>",
|
||||
"readonly": false
|
||||
},
|
||||
"hostname": "bootc-install",
|
||||
"mounts": [
|
||||
{
|
||||
"type": "bind",
|
||||
"source": "<PRIV_DIR>/root/crun-vm/bootc",
|
||||
"destination": "/output",
|
||||
"options": ["bind", "rprivate", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/proc",
|
||||
"type": "proc",
|
||||
"source": "proc"
|
||||
},
|
||||
{
|
||||
"destination": "/dev/pts",
|
||||
"type": "devpts",
|
||||
"source": "devpts",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"newinstance",
|
||||
"ptmxmode=0666",
|
||||
"mode=0620",
|
||||
"gid=5"
|
||||
]
|
||||
}
|
||||
],
|
||||
"linux": {
|
||||
"devices": [
|
||||
{
|
||||
"path": "/dev/kvm",
|
||||
"type": "c",
|
||||
"major": 10,
|
||||
"minor": 232,
|
||||
"fileMode": 438,
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
}
|
||||
],
|
||||
"namespaces": [
|
||||
{ "type": "pid" },
|
||||
{ "type": "network" },
|
||||
{ "type": "ipc" },
|
||||
{ "type": "uts" },
|
||||
{ "type": "cgroup" },
|
||||
{ "type": "mount" }
|
||||
],
|
||||
"maskedPaths": [
|
||||
"/proc/acpi",
|
||||
"/proc/asound",
|
||||
"/proc/kcore",
|
||||
"/proc/keys",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/sys/firmware",
|
||||
"/proc/scsi"
|
||||
],
|
||||
"readonlyPaths": [
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -e
|
||||
|
||||
image_name=$1
|
||||
|
||||
# monkey-patch loopdev partition detection, given we're not running systemd
|
||||
# (bootc runs `udevadm settle` as a way to wait until loopdev partitions are
|
||||
# detected; we hijack that call and use partx to set up the partition devices)
|
||||
|
||||
original_udevadm=$( which udevadm )
|
||||
|
||||
mkdir -p /output/bin
|
||||
|
||||
cat >/output/bin/udevadm <<EOF
|
||||
#!/bin/sh
|
||||
${original_udevadm@Q} "\$@" && partx --add /dev/loop0
|
||||
EOF
|
||||
|
||||
chmod +x /output/bin/udevadm
|
||||
|
||||
# default to an xfs root file system if there is no bootc config (some images
|
||||
# don't currently provide any, for instance quay.io/fedora/fedora-bootc:40)
|
||||
|
||||
if ! find /usr/lib/bootc/install -mindepth 1 -maxdepth 1 | read; then
|
||||
# /usr/lib/bootc/install is empty
|
||||
|
||||
cat >/usr/lib/bootc/install/00-crun-vm.toml <<-EOF
|
||||
[install.filesystem.root]
|
||||
type = "xfs"
|
||||
EOF
|
||||
|
||||
fi
|
||||
|
||||
# build disk image using bootc-install
|
||||
|
||||
# TODO: `bootc install to-disk` currently fails when using docker-archive. Fix
|
||||
# the underlying issue to avoid this skopeo-copy command.
|
||||
skopeo copy --quiet \
|
||||
docker-archive:/output/image.docker-archive \
|
||||
oci-archive:/output/image.oci-archive
|
||||
|
||||
rm /output/image.docker-archive
|
||||
|
||||
PATH=/output/bin:$PATH bootc install to-disk \
|
||||
--source-imgref oci-archive:/output/image.oci-archive \
|
||||
--target-imgref "$image_name" \
|
||||
--skip-fetch-check \
|
||||
--generic-image \
|
||||
--via-loopback \
|
||||
--karg console=tty0 \
|
||||
--karg console=ttyS0 \
|
||||
/output/image.raw
|
||||
|
||||
# communicate success by creating a file, since krun always exits successfully
|
||||
|
||||
touch /output/bootc-install-success
|
|
@ -0,0 +1,139 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -o errexit -o pipefail -o nounset
|
||||
|
||||
engine=$1
|
||||
container_id=$2
|
||||
original_root=$3
|
||||
priv_dir=$4
|
||||
disk_size=$5
|
||||
|
||||
__step() {
|
||||
printf "\033[36m%s\033[0m\n" "$*"
|
||||
}
|
||||
|
||||
bootc_dir=$priv_dir/root/crun-vm/bootc
|
||||
|
||||
mkfifo "$bootc_dir/progress"
|
||||
exec > "$bootc_dir/progress" 2>&1
|
||||
|
||||
# this blocks here until the named pipe above is opened by entrypoint.sh
|
||||
|
||||
# get info about the container *image*
|
||||
|
||||
image_info=$(
|
||||
"$engine" container inspect \
|
||||
--format '{{.Config.Image}}'$'\t''{{.Image}}' \
|
||||
"$container_id"
|
||||
)
|
||||
|
||||
image_name=$( cut -f1 <<< "$image_info" )
|
||||
# image_name=${image_name#sha256:}
|
||||
|
||||
image_id=$( cut -f2 <<< "$image_info" )
|
||||
|
||||
# determine disk size
|
||||
|
||||
if [[ -z "$disk_size" ]]; then
|
||||
container_image_size=$(
|
||||
"$engine" image inspect --format '{{.VirtualSize}}' "$image_id"
|
||||
)
|
||||
|
||||
# use double the container image size to allow for in-place updates
|
||||
disk_size=$(( container_image_size * 2 ))
|
||||
|
||||
# round up to 1 MiB
|
||||
alignment=$(( 2**20 ))
|
||||
disk_size=$(( (disk_size + alignment - 1) / alignment * alignment ))
|
||||
fi
|
||||
|
||||
truncate --size "$disk_size" "$bootc_dir/image.raw"
|
||||
disk_size=$( stat --format %s "$bootc_dir/image.raw" )
|
||||
|
||||
# check if VM image is cached
|
||||
|
||||
container_name=crun-vm-$container_id
|
||||
|
||||
cache_image_labels=(
|
||||
"crun-vm.from=$image_id"
|
||||
"crun-vm.size=$disk_size"
|
||||
)
|
||||
|
||||
cache_image_id=$(
|
||||
"$engine" images \
|
||||
"${cache_image_labels[@]/#/--filter=label=}" \
|
||||
--format '{{.ID}}' --no-trunc
|
||||
)
|
||||
|
||||
if [[ -n "$cache_image_id" ]]; then
|
||||
|
||||
# retrieve VM image from cached containerdisk
|
||||
|
||||
__step "Retrieving cached VM image..."
|
||||
|
||||
trap '"$engine" rm --force "$container_name" >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
"$engine" create --quiet --name "$container_name" "$cache_image_id" </dev/null >/dev/null
|
||||
"$engine" export "$container_name" | tar -C "$bootc_dir" -x image.qcow2
|
||||
"$engine" rm "$container_name" >/dev/null 2>&1
|
||||
|
||||
trap '' EXIT
|
||||
|
||||
else
|
||||
|
||||
__step "Converting $image_name into a VM image..."
|
||||
|
||||
# save container *image* as an archive
|
||||
|
||||
echo -n 'Preparing container image...'
|
||||
|
||||
"$engine" save --output "$bootc_dir/image.docker-archive" "$image_id" </dev/null 2>&1 \
|
||||
| sed -u 's/.*/./' \
|
||||
| stdbuf -o0 tr -d '\n'
|
||||
|
||||
echo
|
||||
|
||||
# adjust krun config
|
||||
|
||||
__sed() {
|
||||
sed -i "s|$1|$2|" "$bootc_dir/config.json"
|
||||
}
|
||||
|
||||
__sed "<IMAGE_NAME>" "$image_name"
|
||||
__sed "<ORIGINAL_ROOT>" "$original_root"
|
||||
__sed "<PRIV_DIR>" "$priv_dir"
|
||||
|
||||
# run bootc-install under krun
|
||||
|
||||
trap 'krun delete --force "$container_name" >/dev/null 2>&1 || true' EXIT
|
||||
krun run --config "$bootc_dir/config.json" "$container_name" </dev/ptmx
|
||||
trap '' EXIT
|
||||
|
||||
[[ -e "$bootc_dir/bootc-install-success" ]]
|
||||
|
||||
# convert image to qcow2 to get a lower file size
|
||||
|
||||
qemu-img convert -f raw -O qcow2 "$bootc_dir/image.raw" "$bootc_dir/image.qcow2"
|
||||
|
||||
# cache VM image file as containerdisk
|
||||
|
||||
__step "Caching VM image as a containerdisk..."
|
||||
|
||||
id=$(
|
||||
"$engine" build --quiet --file - "${cache_image_labels[@]/#/--label=}" "$bootc_dir" <<-'EOF'
|
||||
FROM scratch
|
||||
COPY image.qcow2 /
|
||||
ENTRYPOINT ["no-entrypoint"]
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Stored as untagged container image with ID $id"
|
||||
|
||||
fi
|
||||
|
||||
rm "$bootc_dir/image.raw"
|
||||
|
||||
__step "Booting VM..."
|
||||
|
||||
touch "$bootc_dir/success"
|
|
@ -5,6 +5,11 @@ trap 'exit 143' SIGTERM
|
|||
|
||||
set -o errexit -o pipefail -o nounset
|
||||
|
||||
is_bootc_container=$1
|
||||
|
||||
# clean up locks that may have been left around from the container being killed
|
||||
rm -fr /var/lock
|
||||
|
||||
mkdir -p \
|
||||
/etc/libvirt \
|
||||
/tmp \
|
||||
|
@ -50,6 +55,22 @@ virsh --connect "qemu+unix:///session?socket=$socket" "\$@"
|
|||
EOF
|
||||
chmod +x /crun-vm/virsh
|
||||
|
||||
# wait until VM image is generated from bootable container (if applicable)
|
||||
|
||||
if (( is_bootc_container == 1 )) && [[ ! -e /crun-vm/image/image ]]; then
|
||||
|
||||
fifo=/crun-vm/bootc/progress
|
||||
while [[ ! -e "$fifo" ]]; do sleep 0.2; done
|
||||
cat "$fifo"
|
||||
rm "$fifo"
|
||||
|
||||
[[ -e /crun-vm/bootc/success ]]
|
||||
|
||||
mkdir -p /crun-vm/image
|
||||
mv /crun-vm/bootc/image.qcow2 /crun-vm/image/image
|
||||
|
||||
fi
|
||||
|
||||
# launch VM
|
||||
|
||||
function __bg_ensure_tty() {
|
||||
|
@ -64,12 +85,9 @@ function __bg_ensure_tty() {
|
|||
|
||||
virsh=( virsh --connect "qemu+unix:///session?socket=$socket" --quiet )
|
||||
|
||||
# If our container was stopped and is being restarted, the domain may still be
|
||||
# defined from the previous run, which would cause `virsh define` below to fail,
|
||||
# so we first undefine it.
|
||||
"${virsh[@]}" undefine domain &>/dev/null || true
|
||||
|
||||
"${virsh[@]}" define /crun-vm/domain.xml
|
||||
if [[ -z "$( "${virsh[@]}" list --all --name )" ]]; then
|
||||
"${virsh[@]}" define /crun-vm/domain.xml
|
||||
fi
|
||||
|
||||
# trigger graceful shutdown and wait for VM to terminate
|
||||
function __shutdown() {
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -e
|
||||
|
||||
timeout=$1
|
||||
user=$2
|
||||
command=( "${@:3}" )
|
||||
|
||||
__ssh() {
|
||||
ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-l "$user" \
|
||||
localhost \
|
||||
"$@"
|
||||
}
|
||||
|
||||
if [[ ! -e /crun-vm/ssh-successful ]]; then
|
||||
|
||||
# retry ssh for some time, ignoring some common errors
|
||||
|
||||
ignore_errors=(
|
||||
"Connection closed by remote host"
|
||||
"Connection refused"
|
||||
"Connection reset by peer"
|
||||
"System is booting up"
|
||||
)
|
||||
|
||||
ignore_pattern=$( printf "|%s" "${ignore_errors[@]}" )
|
||||
ignore_pattern=${ignore_pattern:1}
|
||||
|
||||
start_time=$( date +%s )
|
||||
end_time=$(( start_time + timeout ))
|
||||
|
||||
while true; do
|
||||
|
||||
if (( timeout > 0 && $( date +%s ) >= end_time )); then
|
||||
>&2 echo "exec timed out while attempting ssh"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
set +e
|
||||
output=$( __ssh -o BatchMode=yes </dev/null 2>&1 )
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
sleep 1
|
||||
|
||||
if (( exit_code != 255 )) || ! grep -iqE "$ignore_pattern" <<< "$output"; then
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
if (( exit_code != 0 )) && ! grep -iqE "Permission denied" <<< "$output"; then
|
||||
>&2 printf '%s\n' "$output"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
# avoid these steps next time
|
||||
|
||||
touch /crun-vm/ssh-successful
|
||||
|
||||
fi
|
||||
|
||||
__ssh -o LogLevel=ERROR -- "${command[@]}"
|
|
@ -1,4 +0,0 @@
|
|||
# Example: cloud-init configuration
|
||||
|
||||
Under [config/](config/) you can find the cloud-init NoCloud configuration that
|
||||
is used as an example throughout the documentation.
|
|
@ -1 +0,0 @@
|
|||
local-hostname: my-vm
|
|
@ -1,4 +0,0 @@
|
|||
#cloud-config
|
||||
password: pass
|
||||
chpasswd:
|
||||
expire: False
|
|
@ -1,4 +0,0 @@
|
|||
# Example: Ignition configuration
|
||||
|
||||
At [config.ign](config.ign) you can find the Ignition configuration that is used
|
||||
as an example throughout the documentation.
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"ignition": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"passwd": {
|
||||
"users": [
|
||||
{
|
||||
"name": "core",
|
||||
"passwordHash": "$y$j9T$USdd8CBvFNVU1xKwQUnsU/$aE.3arHcRxD0ZT3vkvsSpEsteUj6vC4ZdRHY8eOj1f4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"files": [
|
||||
{
|
||||
"path": "/etc/hostname",
|
||||
"mode": 420,
|
||||
"overwrite": true,
|
||||
"contents": {
|
||||
"source": "data:,my-vm"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
# Example: Minikube cluster with crun-vm available as a runtime
|
||||
|
||||
You can use the `./minikube-start.sh` script in this directory to create a local
|
||||
[minikube] Kubernetes cluster with crun-vm available as a runtime (note that
|
||||
this also points `kubectl` at the minikube cluster):
|
||||
|
||||
```console
|
||||
$ ./minikube-start.sh
|
||||
Compiling crun-vm v0.1.3 (/home/afaria/repos/crun-vm)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.38s
|
||||
😄 [crun-vm-example] minikube v1.32.0 on Fedora 39
|
||||
[...]
|
||||
🏄 Done! kubectl is now configured to use "crun-vm-example" cluster and "default" namespace by default
|
||||
runtimeclass.node.k8s.io/crun-vm created
|
||||
```
|
||||
|
||||
Try going through the examples at [Using crun-vm as a Kubernetes runtime].
|
||||
|
||||
Once you're done, you can delete the cluster with:
|
||||
|
||||
```console
|
||||
$ minikube -p crun-vm-example delete
|
||||
```
|
||||
|
||||
[minikube]: https://minikube.sigs.k8s.io/
|
||||
[Using crun-vm as a Kubernetes runtime]: /docs/3-kubernetes.md
|
|
@ -1,59 +0,0 @@
|
|||
# Example: Manage VMs with systemd using Podman Quadlet and crun-vm
|
||||
|
||||
[Podman Quadlet] is a feature that allows you to define systemd unit files for
|
||||
containers, such that they can be managed like any other systemd service.
|
||||
|
||||
crun-vm is fully compatible with Podman Quadlet, meaning that you can also
|
||||
launch and manage your VMs through systemd. Here's an example of how to do so:
|
||||
|
||||
1. Create file `$HOME/.config/containers/systemd/my-vm.container` with the
|
||||
following contents:
|
||||
|
||||
```toml
|
||||
[Unit]
|
||||
Description=My VM
|
||||
After=local-fs.target
|
||||
|
||||
[Container]
|
||||
PodmanArgs=--runtime crun-vm # make Podman use crun-vm as the runtime
|
||||
Image=quay.io/containerdisks/fedora:39 # the container image containing our VM image
|
||||
Exec=--password pass # optional crun-vm arguments
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target # start on boot by default
|
||||
```
|
||||
|
||||
The options under `[Container]` in this unit file will effectively translate
|
||||
into the following podman invocation:
|
||||
|
||||
```console
|
||||
$ podman run --runtime crun-vm quay.io/containerdisks/fedora:39 --password pass
|
||||
```
|
||||
|
||||
2. Inform systemd of the new unit file:
|
||||
|
||||
```console
|
||||
$ systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
This will creates a my-vm.service file based on my-vm.container above.
|
||||
|
||||
3. Manage my-vm.service as you would any other service. For instance, we can
|
||||
start it up:
|
||||
|
||||
```console
|
||||
$ systemctl --user start my-vm.service
|
||||
```
|
||||
|
||||
And then check its status:
|
||||
|
||||
```console
|
||||
$ systemctl --user status my-vm.service
|
||||
```
|
||||
|
||||
See [this article] for additional information on using Podman Quadlet, and
|
||||
[podman-systemd.unit(5)] for the reference format for Quadlet systemd units.
|
||||
|
||||
[Podman Quadlet]: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
|
||||
[podman-systemd.unit(5)]: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
|
||||
[this article]: https://www.redhat.com/sysadmin/quadlet-podman
|
|
@ -1,23 +0,0 @@
|
|||
prepare:
|
||||
- name: Install dependencies
|
||||
how: install
|
||||
package:
|
||||
- cargo
|
||||
- clippy
|
||||
- make
|
||||
- podman
|
||||
- rustfmt
|
||||
|
||||
/unit_test:
|
||||
summary: Run unit tests
|
||||
execute:
|
||||
how: tmt
|
||||
script: |
|
||||
cargo install nextest
|
||||
PATH=$PATH:/root/.cargo/bin make unit
|
||||
|
||||
/validate_test:
|
||||
summary: Run validate test
|
||||
execute:
|
||||
how: tmt
|
||||
script: make validate
|
|
@ -0,0 +1,61 @@
|
|||
provision:
|
||||
hardware:
|
||||
virtualization:
|
||||
is-supported: true
|
||||
|
||||
prepare:
|
||||
- name: Install dependencies
|
||||
how: install
|
||||
package:
|
||||
- bash
|
||||
- cargo
|
||||
- coreutils
|
||||
- crun
|
||||
- crun-krun
|
||||
- docker
|
||||
- genisoimage
|
||||
- grep
|
||||
- guestfs-tools
|
||||
- libselinux-devel
|
||||
- libvirt-client
|
||||
- libvirt-daemon-driver-qemu
|
||||
- openssh
|
||||
- openssh-clients
|
||||
- passt
|
||||
- podman
|
||||
- qemu-img
|
||||
- qemu-system-aarch64-core
|
||||
- qemu-system-x86-core
|
||||
- sed
|
||||
- util-linux
|
||||
- virtiofsd
|
||||
|
||||
execute:
|
||||
how: tmt
|
||||
script: |
|
||||
set -ex
|
||||
export PATH=$PATH:/root/.cargo/bin LIBGUESTFS_BACKEND=direct
|
||||
|
||||
# set the test VM's Fedora version to the host's to run the tests under the
|
||||
# requested environment
|
||||
tag=$( awk -F= '/^VERSION_ID=/ {print tolower($2)}' /etc/os-release )
|
||||
export CRUN_VM_TEST_ENV_BASE_IMAGE=quay.io/containerdisks/fedora:$tag
|
||||
|
||||
tests/env.sh build
|
||||
tests/env.sh start
|
||||
tests/env.sh run "$ENGINE" all
|
||||
|
||||
/podman:
|
||||
summary: Run all tests under Podman
|
||||
environment:
|
||||
ENGINE: podman
|
||||
|
||||
/rootful-podman:
|
||||
summary: Run all tests under Rootful Podman
|
||||
environment:
|
||||
ENGINE: rootful-podman
|
||||
|
||||
/docker:
|
||||
summary: Run all tests under Docker
|
||||
environment:
|
||||
ENGINE: docker
|
|
@ -1,85 +0,0 @@
|
|||
# trust-dns-{client,server} not available
|
||||
# using vendored deps
|
||||
|
||||
# RHEL doesn't include the package rust-packaging which provides %%__cargo macro, but EPEL
|
||||
# does. So we set it separately here and skip rust-packaging dependency for RHEL.
|
||||
# Buildability without EPEL is essential for packit builds.
|
||||
# ELN doesn't need this.
|
||||
%if %{defined rhel} && 0%{?rhel} < 10
|
||||
%define __cargo %{_bindir}/env CARGO_HOME=.cargo RUSTC_BOOTSTRAP=1 RUSTFLAGS='-Copt-level=3 -Cdebuginfo=2 -Ccodegen-units=1 -Clink-arg=-Wl,-z,relro -Clink-arg=-Wl,-z,now --cap-lints=warn' %{_bindir}/cargo
|
||||
%endif
|
||||
|
||||
%if %{defined copr_username}
|
||||
%define copr_build 1
|
||||
%endif
|
||||
|
||||
%global with_debug 1
|
||||
|
||||
%if 0%{?with_debug}
|
||||
%global _find_debuginfo_dwz_opts %{nil}
|
||||
%global _dwz_low_mem_die_limit 0
|
||||
%else
|
||||
%global debug_package %{nil}
|
||||
%endif
|
||||
|
||||
Name: crun-vm
|
||||
%if %{defined copr_build}
|
||||
Epoch: 102
|
||||
%endif
|
||||
# DO NOT TOUCH the Version string!
|
||||
# The TRUE source of this specfile is:
|
||||
# https://github.com/containers/crun-vm/blob/main/rpm/crun-vm.spec
|
||||
# If that's what you're reading, Version must be 0, and will be updated by Packit for
|
||||
# copr and koji builds.
|
||||
# If you're reading this on dist-git, the version is automatically filled in by Packit.
|
||||
Version: 0
|
||||
# The `AND` needs to be uppercase in the License for SPDX compatibility
|
||||
License: (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND (MIT OR Unlicense) AND Apache-2.0 AND GPL-2.0-or-later AND MIT AND MPL-2.0 AND MPL-2.0+ AND Unicode-DFS-2016
|
||||
Release: %autorelease
|
||||
%if %{defined golang_arches_future}
|
||||
ExclusiveArch: %{golang_arches_future}
|
||||
%else
|
||||
ExclusiveArch: aarch64 ppc64le s390x x86_64
|
||||
%endif
|
||||
Summary: OCI Runtime to run QEMU-compatible VM images
|
||||
URL: https://github.com/containers/%{name}
|
||||
# Tarballs fetched from upstream's release page
|
||||
Source0: %{url}/archive/%{version}.tar.gz
|
||||
BuildRequires: cargo
|
||||
BuildRequires: git-core
|
||||
BuildRequires: libselinux-devel
|
||||
BuildRequires: make
|
||||
%if %{defined rhel}
|
||||
# rust-toolset requires the `local` repo enabled on non-koji ELN build environments
|
||||
BuildRequires: rust-toolset
|
||||
%else
|
||||
BuildRequires: rust-packaging
|
||||
BuildRequires: rust-srpm-macros
|
||||
%endif
|
||||
|
||||
%description
|
||||
%{summary}
|
||||
|
||||
%prep
|
||||
%autosetup -Sgit -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
%{__make} CARGO="%{__cargo}" build
|
||||
|
||||
%install
|
||||
%{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} install
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%{_bindir}/%{name}
|
||||
|
||||
%changelog
|
||||
%if %{defined autochangelog}
|
||||
%autochangelog
|
||||
%else
|
||||
# NOTE: This changelog will be visible on CentOS Stream 8/9 builds
|
||||
# Other envs are capable of handling autochangelog
|
||||
* Mon Jan 16 2024 RH Container Bot <rhcontainerbot@fedoraproject.org>
|
||||
- Placeholder changelog for envs that are not autochangelog-ready
|
||||
- Contact upstream if you need to report an issue with the build.
|
||||
%endif
|
|
@ -1,46 +0,0 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -e
|
||||
|
||||
__ssh() {
|
||||
ssh \
|
||||
-o LogLevel=ERROR \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-l "$1" \
|
||||
localhost \
|
||||
"${@:2}"
|
||||
}
|
||||
|
||||
if [[ ! -e /crun-vm/ssh-successful ]]; then
|
||||
|
||||
# retry ssh for some time, ignoring some common errors
|
||||
|
||||
for (( i = 0; i < 60; ++i )); do
|
||||
|
||||
set +e
|
||||
output=$( __ssh "$1" </dev/null 2>&1 )
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
sleep 1
|
||||
|
||||
if (( exit_code != 255 )) ||
|
||||
! grep -iqE "Connection refused|Connection reset by peer|Connection closed by remote host" <<< "$output"; then
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
if (( exit_code != 0 )); then
|
||||
>&2 printf '%s\n' "$output"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
# avoid these steps next time
|
||||
|
||||
touch /crun-vm/ssh-successful
|
||||
|
||||
fi
|
||||
|
||||
__ssh "$1" -- "${@:2}"
|
|
@ -9,7 +9,7 @@ use clap::Parser;
|
|||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::commands::create::runtime_env::RuntimeEnv;
|
||||
use crate::commands::create::engine::Engine;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Blockdev {
|
||||
|
@ -41,252 +41,145 @@ impl FromStr for Blockdev {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VfioPciAddress {
|
||||
pub domain: u16,
|
||||
pub bus: u8,
|
||||
pub slot: u8,
|
||||
pub function: u8,
|
||||
}
|
||||
|
||||
impl VfioPciAddress {
|
||||
fn from_path(path: impl AsRef<Utf8Path>) -> Result<Self> {
|
||||
lazy_static! {
|
||||
static ref PATTERN: Regex = {
|
||||
let h = r"[0-9a-fA-F]".to_string();
|
||||
let db = format!(r"{h}{{4}}:{h}{{2}}");
|
||||
let dbsf = format!(r"{h}{{4}}:{h}{{2}}:{h}{{2}}.{h}{{1}}");
|
||||
|
||||
let pattern = format!(
|
||||
r"^/sys/devices/pci{db}(/{dbsf})*/({h}{{4}}):({h}{{2}}):({h}{{2}}).({h}{{1}})$"
|
||||
);
|
||||
|
||||
Regex::new(&pattern).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let path = path.as_ref().canonicalize_utf8()?;
|
||||
|
||||
let capture = PATTERN
|
||||
.captures(path.as_str())
|
||||
.ok_or_else(|| anyhow!("not a valid vfio-pci device sysfs path"))?;
|
||||
|
||||
let address = VfioPciAddress {
|
||||
domain: u16::from_str_radix(&capture[2], 16).unwrap(),
|
||||
bus: u8::from_str_radix(&capture[3], 16).unwrap(),
|
||||
slot: u8::from_str_radix(&capture[4], 16).unwrap(),
|
||||
function: u8::from_str_radix(&capture[5], 16).unwrap(),
|
||||
};
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VfioPciMdevUuid(pub String);
|
||||
|
||||
impl VfioPciMdevUuid {
|
||||
fn from_path(path: impl AsRef<Utf8Path>) -> Result<Self> {
|
||||
lazy_static! {
|
||||
static ref PATTERN: Regex = {
|
||||
let h = r"[0-9a-zA-Z]".to_string();
|
||||
let db = format!(r"{h}{{4}}:{h}{{2}}");
|
||||
let dbsf = format!(r"{h}{{4}}:{h}{{2}}:{h}{{2}}.{h}{{1}}");
|
||||
let uuid = format!(r"{h}{{8}}-{h}{{4}}-{h}{{4}}-{h}{{4}}-{h}{{12}}");
|
||||
|
||||
let pattern = format!(r"^/sys/devices/pci{db}(/{dbsf})+/({uuid})$");
|
||||
|
||||
Regex::new(&pattern).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let path = path.as_ref().canonicalize_utf8()?;
|
||||
|
||||
let capture = PATTERN
|
||||
.captures(path.as_str())
|
||||
.ok_or_else(|| anyhow!("not a valid vfio-pci mediated device sysfs path"))?;
|
||||
|
||||
Ok(VfioPciMdevUuid(capture[2].to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomOptions {
|
||||
pub blockdev: Vec<Blockdev>,
|
||||
pub persistent: bool,
|
||||
pub cloud_init: Option<Utf8PathBuf>,
|
||||
pub ignition: Option<Utf8PathBuf>,
|
||||
pub vfio_pci: Vec<VfioPciAddress>,
|
||||
pub vfio_pci_mdev: Vec<VfioPciMdevUuid>,
|
||||
pub password: Option<String>,
|
||||
pub merge_libvirt_xml: Vec<Utf8PathBuf>,
|
||||
pub print_libvirt_xml: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<CustomOptionsRaw> for CustomOptions {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(opts: CustomOptionsRaw) -> Result<Self> {
|
||||
Ok(Self {
|
||||
blockdev: opts.blockdev,
|
||||
persistent: opts.persistent,
|
||||
cloud_init: opts.cloud_init,
|
||||
ignition: opts.ignition,
|
||||
vfio_pci: opts
|
||||
.vfio_pci
|
||||
.iter()
|
||||
.map(VfioPciAddress::from_path)
|
||||
.collect::<Result<_>>()?,
|
||||
vfio_pci_mdev: opts
|
||||
.vfio_pci_mdev
|
||||
.iter()
|
||||
.map(VfioPciMdevUuid::from_path)
|
||||
.collect::<Result<_>>()?,
|
||||
password: opts.password,
|
||||
merge_libvirt_xml: opts.merge_libvirt_xml,
|
||||
print_libvirt_xml: opts.print_libvirt_xml,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::Parser, Debug)]
|
||||
struct CustomOptionsRaw {
|
||||
pub struct CustomOptions {
|
||||
#[clap(long)]
|
||||
blockdev: Vec<Blockdev>,
|
||||
pub blockdev: Vec<Blockdev>,
|
||||
|
||||
#[clap(long)]
|
||||
persistent: bool,
|
||||
pub persistent: bool,
|
||||
|
||||
#[clap(long)]
|
||||
cloud_init: Option<Utf8PathBuf>,
|
||||
pub random_ssh_key_pair: bool,
|
||||
|
||||
#[clap(long, help = "Use system emulation rather than KVM")]
|
||||
pub emulated: bool,
|
||||
|
||||
#[clap(long)]
|
||||
ignition: Option<Utf8PathBuf>,
|
||||
pub bootc_disk_size: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
vfio_pci: Vec<Utf8PathBuf>,
|
||||
pub cloud_init: Option<Utf8PathBuf>,
|
||||
|
||||
#[clap(long)]
|
||||
vfio_pci_mdev: Vec<Utf8PathBuf>,
|
||||
pub ignition: Option<Utf8PathBuf>,
|
||||
|
||||
#[clap(long)]
|
||||
password: Option<String>,
|
||||
pub password: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
merge_libvirt_xml: Vec<Utf8PathBuf>,
|
||||
pub merge_libvirt_xml: Vec<Utf8PathBuf>,
|
||||
|
||||
#[clap(long)]
|
||||
print_libvirt_xml: bool,
|
||||
pub print_libvirt_xml: bool,
|
||||
|
||||
#[clap(long, conflicts_with = "print_libvirt_xml")]
|
||||
pub print_config_json: bool,
|
||||
}
|
||||
|
||||
impl CustomOptions {
|
||||
pub fn from_spec(spec: &oci_spec::runtime::Spec, env: RuntimeEnv) -> Result<Self> {
|
||||
let args = spec
|
||||
pub fn from_spec(spec: &oci_spec::runtime::Spec, engine: Engine) -> Result<Self> {
|
||||
let mut args: Vec<&String> = spec
|
||||
.process()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.args()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|arg| !arg.trim().is_empty());
|
||||
.filter(|a| !a.trim().is_empty())
|
||||
.collect();
|
||||
|
||||
// TODO: We currently assume that no entrypoint is given (either by being set by in the
|
||||
// container image or through --entrypoint). Must somehow find whether the first arg is the
|
||||
// entrypoint and ignore it in that case.
|
||||
let mut options = CustomOptionsRaw::parse_from(
|
||||
if let Some(&first_arg) = args.first() {
|
||||
let ignore = [
|
||||
"no-entrypoint",
|
||||
"/sbin/init",
|
||||
"/usr/sbin/init",
|
||||
"/usr/local/sbin/init",
|
||||
];
|
||||
|
||||
if ignore.contains(&first_arg.as_str()) {
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(&first_arg) = args.first() {
|
||||
ensure!(
|
||||
first_arg.starts_with('-'),
|
||||
"unexpected entrypoint '{first_arg}' found; use an image without an entrypoint or with entrypoint \"no-entrypoint\", and/or pass in an empty \"\" entrypoint on the command line"
|
||||
);
|
||||
}
|
||||
|
||||
let mut options = CustomOptions::parse_from(
|
||||
iter::once(&"podman run [<podman-opts>] <image>".to_string()).chain(args),
|
||||
);
|
||||
|
||||
ensure!(
|
||||
!spec.root().as_ref().unwrap().readonly().unwrap_or(false) || !options.persistent,
|
||||
"--persistent was set but the container's root file system was mounted as read-only"
|
||||
);
|
||||
|
||||
fn all_are_absolute(iter: impl IntoIterator<Item = impl AsRef<Utf8Path>>) -> bool {
|
||||
iter.into_iter().all(|p| p.as_ref().is_absolute())
|
||||
}
|
||||
|
||||
fn path_in_container_into_path_in_host(
|
||||
spec: &oci_spec::runtime::Spec,
|
||||
path: impl AsRef<Utf8Path>,
|
||||
path: &Utf8Path,
|
||||
) -> Result<Utf8PathBuf> {
|
||||
let mount = spec
|
||||
.mounts()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|m| m.source().is_some())
|
||||
.filter(|m| path.as_ref().starts_with(m.destination()))
|
||||
.filter(|m| path.starts_with(m.destination()))
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("can't find {}", path.as_ref()))?;
|
||||
.ok_or_else(|| anyhow!("can't find {}", path))?;
|
||||
|
||||
let mount_source: &Utf8Path = mount.source().as_deref().unwrap().try_into()?;
|
||||
|
||||
let relative_path = path.as_ref().strip_prefix(mount.destination()).unwrap();
|
||||
let relative_path = path.strip_prefix(mount.destination()).unwrap();
|
||||
let path_in_host = mount_source.join(relative_path);
|
||||
|
||||
ensure!(path_in_host.try_exists()?, "can't find {}", path.as_ref());
|
||||
ensure!(path_in_host.try_exists()?, "can't find {}", path);
|
||||
|
||||
Ok(path_in_host)
|
||||
}
|
||||
|
||||
match env {
|
||||
RuntimeEnv::Docker => {
|
||||
// Docker doesn't run the runtime with the same working directory as the process
|
||||
// that launched `docker-run`, so we require custom option paths to be absolute.
|
||||
//
|
||||
// TODO: There must be a better way...
|
||||
ensure!(
|
||||
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
|
||||
&& all_are_absolute(&options.cloud_init)
|
||||
&& all_are_absolute(&options.ignition)
|
||||
&& all_are_absolute(&options.vfio_pci)
|
||||
&& all_are_absolute(&options.vfio_pci_mdev)
|
||||
&& all_are_absolute(&options.merge_libvirt_xml),
|
||||
concat!(
|
||||
"paths specified using --blockdev, --cloud-init, --ignition, --vfio-pci,",
|
||||
" --vfio-pci-mdev, or --merge-libvirt-xml must be absolute when using",
|
||||
" crun-vm as a Docker runtime",
|
||||
),
|
||||
);
|
||||
// Docker doesn't run the runtime with the same working directory as the process
|
||||
// that launched `docker-run`. Similarly, custom option paths in Kubernetes refer to
|
||||
// paths in the container/VM, and there isn't a reasonable notion of what the
|
||||
// current directory is. We thus simply always require custom option paths to be
|
||||
// absolute.
|
||||
ensure!(
|
||||
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
|
||||
&& all_are_absolute(&options.cloud_init)
|
||||
&& all_are_absolute(&options.ignition)
|
||||
&& all_are_absolute(&options.merge_libvirt_xml),
|
||||
concat!(
|
||||
"paths specified using --blockdev, --cloud-init, --ignition, or",
|
||||
" --merge-libvirt-xml must be absolute",
|
||||
),
|
||||
);
|
||||
|
||||
if engine == Engine::Kubernetes {
|
||||
for blockdev in &mut options.blockdev {
|
||||
blockdev.source = path_in_container_into_path_in_host(spec, &blockdev.source)?;
|
||||
blockdev.target = path_in_container_into_path_in_host(spec, &blockdev.target)?;
|
||||
}
|
||||
RuntimeEnv::Kubernetes => {
|
||||
// Custom option paths in Kubernetes refer to paths in the container/VM, and there
|
||||
// isn't a reasonable notion of what the current directory is.
|
||||
ensure!(
|
||||
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
|
||||
&& all_are_absolute(&options.cloud_init)
|
||||
&& all_are_absolute(&options.ignition)
|
||||
&& all_are_absolute(&options.merge_libvirt_xml),
|
||||
concat!(
|
||||
"paths specified using --blockdev, --cloud-init, --ignition, or",
|
||||
" --merge-libvirt-xml must be absolute when using crun-vm as a",
|
||||
" Kubernetes runtime",
|
||||
),
|
||||
);
|
||||
|
||||
ensure!(
|
||||
options.vfio_pci.is_empty() && options.vfio_pci_mdev.is_empty(),
|
||||
concat!(
|
||||
"options --vfio-pci and --vfio-pci-mdev are not allowed when using",
|
||||
" crun-vm as a Kubernetes runtime",
|
||||
)
|
||||
);
|
||||
|
||||
for blockdev in &mut options.blockdev {
|
||||
blockdev.source = path_in_container_into_path_in_host(spec, &blockdev.source)?;
|
||||
blockdev.target = path_in_container_into_path_in_host(spec, &blockdev.target)?;
|
||||
}
|
||||
|
||||
if let Some(path) = &mut options.cloud_init {
|
||||
*path = path_in_container_into_path_in_host(spec, &path)?;
|
||||
}
|
||||
|
||||
if let Some(path) = &mut options.ignition {
|
||||
*path = path_in_container_into_path_in_host(spec, &path)?;
|
||||
}
|
||||
|
||||
for path in &mut options.merge_libvirt_xml {
|
||||
*path = path_in_container_into_path_in_host(spec, &path)?;
|
||||
}
|
||||
if let Some(path) = &mut options.cloud_init {
|
||||
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||
}
|
||||
|
||||
if let Some(path) = &mut options.ignition {
|
||||
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||
}
|
||||
|
||||
for path in &mut options.merge_libvirt_xml {
|
||||
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||
}
|
||||
RuntimeEnv::Other => {}
|
||||
}
|
||||
|
||||
options.try_into()
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Write};
|
||||
|
||||
|
@ -7,7 +8,7 @@ use anyhow::{ensure, Result};
|
|||
use camino::Utf8Path;
|
||||
use xml::writer::XmlEvent;
|
||||
|
||||
use crate::commands::create::custom_opts::{CustomOptions, VfioPciMdevUuid};
|
||||
use crate::commands::create::custom_opts::CustomOptions;
|
||||
use crate::commands::create::Mounts;
|
||||
use crate::util::{SpecExt, VmImageInfo};
|
||||
|
||||
|
@ -36,10 +37,16 @@ fn generate(
|
|||
.perform_indent(true)
|
||||
.create_writer(File::create(path.as_ref())?);
|
||||
|
||||
s(&mut w, "domain", &[("type", "kvm")], |w| {
|
||||
let domain_type = match custom_options.emulated {
|
||||
true => "qemu",
|
||||
false => "kvm",
|
||||
};
|
||||
|
||||
s(&mut w, "domain", &[("type", domain_type)], |w| {
|
||||
st(w, "name", &[], "domain")?;
|
||||
|
||||
se(w, "cpu", &[("mode", "host-model")])?;
|
||||
se(w, "cpu", &[("mode", "maximum")])?;
|
||||
|
||||
let vcpus = get_vcpu_count(spec).to_string();
|
||||
if let Some(cpu_set) = get_cpu_set(spec) {
|
||||
st(w, "vcpu", &[("cpuset", cpu_set.as_str())], vcpus.as_str())?;
|
||||
|
@ -50,8 +57,18 @@ fn generate(
|
|||
let memory = get_memory_size(spec).to_string();
|
||||
st(w, "memory", &[("unit", "b")], memory.as_str())?;
|
||||
|
||||
s(w, "os", &[], |w| {
|
||||
st(w, "type", &[("machine", "q35")], "hvm")
|
||||
s(w, "os", &[("firmware", "efi")], |w| {
|
||||
let attrs = match ["x86", "x86_64"].contains(&env::consts::ARCH) {
|
||||
true => [("machine", "q35")].as_slice(),
|
||||
false => [].as_slice(), // use libvirt's default
|
||||
};
|
||||
st(w, "type", attrs, "hvm")?;
|
||||
|
||||
s(w, "firmware", &[], |w| {
|
||||
se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")])
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// fw_cfg requires ACPI
|
||||
|
@ -158,41 +175,6 @@ fn generate(
|
|||
})?;
|
||||
}
|
||||
|
||||
for address in &custom_options.vfio_pci {
|
||||
s(
|
||||
w,
|
||||
"hostdev",
|
||||
&[("mode", "subsystem"), ("type", "pci")],
|
||||
|w| {
|
||||
s(w, "source", &[], |w| {
|
||||
se(
|
||||
w,
|
||||
"address",
|
||||
&[
|
||||
("domain", &format!("0x{:04x}", address.domain)),
|
||||
("bus", &format!("0x{:02x}", address.bus)),
|
||||
("slot", &format!("0x{:02x}", address.slot)),
|
||||
("function", &format!("0x{:01x}", address.function)),
|
||||
],
|
||||
)
|
||||
})
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
for VfioPciMdevUuid(uuid) in &custom_options.vfio_pci_mdev {
|
||||
s(
|
||||
w,
|
||||
"hostdev",
|
||||
&[
|
||||
("mode", "subsystem"),
|
||||
("type", "mdev"),
|
||||
("model", "vfio-pci"),
|
||||
],
|
||||
|w| s(w, "source", &[], |w| se(w, "address", &[("uuid", uuid)])),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use camino::Utf8Path;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Engine {
|
||||
Podman,
|
||||
Docker,
|
||||
Kubernetes,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn command(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Engine::Podman => Some("podman"),
|
||||
Engine::Docker => Some("docker"),
|
||||
Engine::Kubernetes => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect(
|
||||
container_id: &str,
|
||||
bundle_path: &Utf8Path,
|
||||
spec: &oci_spec::runtime::Spec,
|
||||
original_root_path: impl AsRef<Utf8Path>,
|
||||
) -> Result<Engine> {
|
||||
// TODO: Make this absolutely robust and secure. Probably require engine config to pass us
|
||||
// an option specifying what engine is running crun-vm.
|
||||
|
||||
// check if we're under CRI-O under Kubernetes
|
||||
|
||||
{
|
||||
let has_kubernetes_secrets_dir = spec.mounts().iter().flatten().any(|m| {
|
||||
m.destination()
|
||||
.starts_with("/var/run/secrets/kubernetes.io")
|
||||
});
|
||||
|
||||
let has_kubernetes_managed_etc_hosts = spec
|
||||
.mounts()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|m| m.destination() == Utf8Path::new("/etc/hosts"))
|
||||
.flat_map(|m| m.source())
|
||||
.next()
|
||||
.map(fs::read_to_string)
|
||||
.transpose()?
|
||||
.and_then(|hosts| hosts.lines().next().map(|line| line.to_string()))
|
||||
.map(|line| line.contains("Kubernetes-managed hosts file"))
|
||||
.unwrap_or(false);
|
||||
|
||||
if has_kubernetes_secrets_dir || has_kubernetes_managed_etc_hosts {
|
||||
return Ok(Engine::Kubernetes);
|
||||
}
|
||||
}
|
||||
|
||||
// check if we're under Docker
|
||||
|
||||
{
|
||||
let has_dot_dockerenv_file = original_root_path
|
||||
.as_ref()
|
||||
.join(".dockerenv")
|
||||
.try_exists()?;
|
||||
|
||||
if has_dot_dockerenv_file {
|
||||
return Ok(Engine::Docker);
|
||||
}
|
||||
}
|
||||
|
||||
// check if we're under Podman
|
||||
|
||||
{
|
||||
let has_mount_on = |p| {
|
||||
spec.mounts()
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|m| m.destination() == Path::new(p))
|
||||
};
|
||||
|
||||
let has_dot_containerenv_file =
|
||||
has_mount_on("/run/.containerenv") || has_mount_on("/var/run/.containerenv");
|
||||
|
||||
lazy_static! {
|
||||
static ref BUNDLE_PATH_PATTERN: Regex =
|
||||
Regex::new(r"/overlay-containers/([^/]+)/userdata$").unwrap();
|
||||
}
|
||||
|
||||
let is_podman_bundle_path = match BUNDLE_PATH_PATTERN.captures(bundle_path.as_str()) {
|
||||
Some(captures) => &captures[1] == container_id,
|
||||
None => false,
|
||||
};
|
||||
|
||||
if has_dot_containerenv_file && is_podman_bundle_path {
|
||||
return Ok(Engine::Podman);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown engine
|
||||
|
||||
bail!("could not identify container engine; crun-vm current only supports Podman, Docker, and Kubernetes");
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ pub struct FirstBootConfig<'a> {
|
|||
}
|
||||
|
||||
impl FirstBootConfig<'_> {
|
||||
/// Returns `true` if a cloud-init config should be passed to the VM.
|
||||
pub fn apply_to_cloud_init_config(
|
||||
&self,
|
||||
in_config_dir_path: Option<impl AsRef<Utf8Path>>,
|
||||
|
@ -132,11 +131,38 @@ impl FirstBootConfig<'_> {
|
|||
|
||||
ssh_authorized_keys.push(self.container_public_key.into());
|
||||
|
||||
let write_files = match user_data_mapping
|
||||
.entry("write_files".into())
|
||||
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
|
||||
{
|
||||
serde_yaml::Value::Sequence(v) => v,
|
||||
_ => bail!("invalid user-data file"),
|
||||
};
|
||||
|
||||
write_files.push({
|
||||
let mut m = serde_yaml::Mapping::new();
|
||||
m.insert("path".into(), "/root/.ssh/authorized_keys".into());
|
||||
m.insert(
|
||||
"content".into(),
|
||||
("\n".to_string() + self.container_public_key).into(),
|
||||
);
|
||||
m.insert("append".into(), true.into());
|
||||
m.into()
|
||||
});
|
||||
|
||||
// create block device symlinks and udev rules
|
||||
|
||||
let block_device_symlinks = self.get_block_device_symlinks();
|
||||
let block_device_udev_rules = self.get_block_device_udev_rules();
|
||||
|
||||
if let Some(rules) = &block_device_udev_rules {
|
||||
let mut mapping = serde_yaml::Mapping::new();
|
||||
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
|
||||
mapping.insert("content".into(), rules.to_string().into());
|
||||
|
||||
write_files.push(mapping.into());
|
||||
}
|
||||
|
||||
if !block_device_symlinks.is_empty() || block_device_udev_rules.is_some() {
|
||||
let runcmd = match user_data_mapping
|
||||
.entry("runcmd".into())
|
||||
|
@ -166,22 +192,6 @@ impl FirstBootConfig<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(rules) = block_device_udev_rules {
|
||||
let write_files = match user_data_mapping
|
||||
.entry("write_files".into())
|
||||
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
|
||||
{
|
||||
serde_yaml::Value::Sequence(v) => v,
|
||||
_ => bail!("invalid user-data file"),
|
||||
};
|
||||
|
||||
let mut mapping = serde_yaml::Mapping::new();
|
||||
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
|
||||
mapping.insert("content".into(), rules.into());
|
||||
|
||||
write_files.push(mapping.into());
|
||||
}
|
||||
|
||||
// generate iso
|
||||
|
||||
{
|
||||
|
@ -263,15 +273,17 @@ impl FirstBootConfig<'_> {
|
|||
_ => bail!("invalid config file"),
|
||||
};
|
||||
|
||||
let users_contains_core = users.iter().any(|u| match u {
|
||||
serde_json::Value::Object(m) => m.get("name") == Some(&"core".into()),
|
||||
_ => false,
|
||||
});
|
||||
for user in ["root", "core"] {
|
||||
let user_exists = users.iter().any(|u| match u {
|
||||
serde_json::Value::Object(m) => m.get("name") == Some(&user.into()),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if !users_contains_core {
|
||||
users.push(serde_json::json!({
|
||||
"name": "core",
|
||||
}));
|
||||
if !user_exists {
|
||||
users.push(serde_json::json!({
|
||||
"name": user,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for user in users {
|
||||
|
@ -280,7 +292,9 @@ impl FirstBootConfig<'_> {
|
|||
_ => bail!("invalid config file"),
|
||||
};
|
||||
|
||||
if map.get("name") == Some(&"core".into()) {
|
||||
let name = map.get("name");
|
||||
|
||||
if name == Some(&"root".into()) || name == Some(&"core".into()) {
|
||||
let keys = match map
|
||||
.entry("sshAuthorizedKeys")
|
||||
.or_insert_with(|| serde_json::json!([]))
|
||||
|
@ -290,8 +304,6 @@ impl FirstBootConfig<'_> {
|
|||
};
|
||||
|
||||
keys.push(self.container_public_key.into());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,115 +2,273 @@
|
|||
|
||||
mod custom_opts;
|
||||
mod domain;
|
||||
mod engine;
|
||||
mod first_boot;
|
||||
mod runtime_env;
|
||||
|
||||
use std::fs::{self, Permissions};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File, Permissions};
|
||||
use std::io::ErrorKind;
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use lazy_static::lazy_static;
|
||||
use nix::sys::stat::{major, makedev, minor, mknod, Mode, SFlag};
|
||||
use regex::Regex;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use crate::commands::create::custom_opts::CustomOptions;
|
||||
use crate::commands::create::domain::set_up_libvirt_domain_xml;
|
||||
use crate::commands::create::engine::Engine;
|
||||
use crate::commands::create::first_boot::FirstBootConfig;
|
||||
use crate::commands::create::runtime_env::RuntimeEnv;
|
||||
use crate::crun::crun_create;
|
||||
use crate::util::{
|
||||
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image,
|
||||
find_single_file_in_dirs, set_file_context, SpecExt, VmImageInfo,
|
||||
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image, crun,
|
||||
find_single_file_in_dirs, fix_selinux_label, is_mountpoint, set_file_context, SpecExt,
|
||||
VmImageInfo,
|
||||
};
|
||||
|
||||
pub fn create(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Create) -> Result<()> {
|
||||
pub fn create(args: &liboci_cli::Create, raw_args: &[impl AsRef<OsStr>]) -> Result<()> {
|
||||
let bundle_path: &Utf8Path = args.bundle.as_path().try_into()?;
|
||||
let config_path = bundle_path.join("config.json");
|
||||
|
||||
let mut spec = oci_spec::runtime::Spec::load(&config_path)?;
|
||||
let original_root_path = spec.root_path()?.to_path_buf();
|
||||
ensure_unprivileged(&spec)?;
|
||||
|
||||
let runtime_env = RuntimeEnv::current(&spec, &original_root_path)?;
|
||||
let custom_options = CustomOptions::from_spec(&spec, runtime_env)?;
|
||||
let original_root_path: Utf8PathBuf = spec.root_path()?.canonicalize()?.try_into()?; // ensure absolute
|
||||
|
||||
set_up_container_root(&mut spec, bundle_path, &custom_options)?;
|
||||
let base_vm_image_info =
|
||||
set_up_vm_image(&spec, bundle_path, &original_root_path, &custom_options)?;
|
||||
let engine = Engine::detect(&args.container_id, bundle_path, &spec, &original_root_path)?;
|
||||
let custom_options = CustomOptions::from_spec(&spec, engine)?;
|
||||
let is_bootc_container = is_bootc_container(&original_root_path, &custom_options, engine)?;
|
||||
|
||||
// We include container_id in our paths to ensure no overlap with the user container's contents.
|
||||
let priv_dir_path = original_root_path.join(format!("crun-vm-{}", args.container_id));
|
||||
fs::create_dir_all(&priv_dir_path)?;
|
||||
|
||||
if let Some(context) = spec.mount_label() {
|
||||
// the directory we're using as the root for the container is not the one that podman
|
||||
// prepared for us, so we need to set its context ourselves to prevent SELinux from getting
|
||||
// angry at us
|
||||
set_file_context(&priv_dir_path, context)?;
|
||||
}
|
||||
|
||||
set_up_container_root(
|
||||
&mut spec,
|
||||
&priv_dir_path,
|
||||
&custom_options,
|
||||
is_bootc_container,
|
||||
)?;
|
||||
|
||||
let is_first_create = is_first_create(&spec)?;
|
||||
|
||||
let base_vm_image_info = set_up_vm_image(
|
||||
&spec,
|
||||
&original_root_path,
|
||||
&priv_dir_path,
|
||||
&custom_options,
|
||||
is_first_create,
|
||||
is_bootc_container,
|
||||
)?;
|
||||
|
||||
let mut mounts = Mounts::default();
|
||||
set_up_mounts(&mut spec, &mut mounts)?;
|
||||
set_up_devices(&mut spec, &mut mounts)?;
|
||||
set_up_blockdevs(&mut spec, &mut mounts, &custom_options)?;
|
||||
|
||||
set_up_extra_container_mounts_and_devices(&mut spec)?;
|
||||
set_up_extra_container_mounts_and_devices(&mut spec, &custom_options)?;
|
||||
set_up_security(&mut spec);
|
||||
|
||||
set_up_first_boot_config(&spec, &mounts, &custom_options)?;
|
||||
set_up_libvirt_domain_xml(&spec, &base_vm_image_info, &mounts, &custom_options)?;
|
||||
let ssh_pub_key = set_up_ssh_key_pair(
|
||||
&mut spec,
|
||||
&custom_options,
|
||||
engine,
|
||||
&priv_dir_path,
|
||||
is_first_create,
|
||||
)?;
|
||||
|
||||
if is_first_create {
|
||||
set_up_first_boot_config(&spec, &mounts, &custom_options, &ssh_pub_key)?;
|
||||
set_up_libvirt_domain_xml(&spec, &base_vm_image_info, &mounts, &custom_options)?;
|
||||
}
|
||||
|
||||
adjust_container_rlimits_and_resources(&mut spec);
|
||||
|
||||
spec.save(&config_path)?;
|
||||
spec.save(spec.root_path()?.join("crun-vm/config.json"))?; // to aid debugging
|
||||
|
||||
crun_create(global_args, args)?; // actually create container
|
||||
crun(raw_args)?; // actually create container
|
||||
|
||||
if is_first_create && is_bootc_container {
|
||||
// We want to ask podman what our image name is, so we can give it to bootc-install, but we
|
||||
// can't wait synchronously for a response since podman hangs until this create command
|
||||
// completes. We then want to run bootc-install under krun, which already isolates the
|
||||
// workload and so can be run outside of our container. We thus launch a process that
|
||||
// asynchronously performs these steps, and share its progress and output with out
|
||||
// container's entrypoint through a named pipe.
|
||||
//
|
||||
// Note that this process blocks until our container's entrypoint actually starts running,
|
||||
// thus after the "start" OCI runtime command is called.
|
||||
|
||||
let bootc_dir = priv_dir_path.join("root/crun-vm/bootc");
|
||||
fs::create_dir_all(&bootc_dir)?;
|
||||
|
||||
std::process::Command::new(bootc_dir.join("prepare.sh"))
|
||||
.arg(engine.command().unwrap())
|
||||
.arg(&args.container_id)
|
||||
.arg(&original_root_path)
|
||||
.arg(&priv_dir_path)
|
||||
.arg(custom_options.bootc_disk_size.unwrap_or_default())
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_unprivileged(spec: &oci_spec::runtime::Spec) -> Result<()> {
|
||||
if let Some(process) = spec.process().as_ref() {
|
||||
if let Some(capabilities) = process.capabilities().as_ref() {
|
||||
fn any_is_cap_sys_admin(caps: &Option<oci_spec::runtime::Capabilities>) -> bool {
|
||||
caps.as_ref()
|
||||
.is_some_and(|set| set.contains(&oci_spec::runtime::Capability::SysAdmin))
|
||||
}
|
||||
|
||||
ensure!(
|
||||
!any_is_cap_sys_admin(capabilities.bounding())
|
||||
&& !any_is_cap_sys_admin(capabilities.effective())
|
||||
&& !any_is_cap_sys_admin(capabilities.inheritable())
|
||||
&& !any_is_cap_sys_admin(capabilities.permitted())
|
||||
&& !any_is_cap_sys_admin(capabilities.ambient()),
|
||||
"crun-vm is incompatible with privileged containers"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_bootc_container(
|
||||
original_root_path: &Utf8Path,
|
||||
custom_options: &CustomOptions,
|
||||
engine: Engine,
|
||||
) -> Result<bool> {
|
||||
let is_bootc_container = original_root_path.join("usr/lib/bootc/install").is_dir();
|
||||
|
||||
ensure!(
|
||||
!is_bootc_container || engine == Engine::Podman || engine == Engine::Docker,
|
||||
"bootc containers are only supported with Podman and Docker"
|
||||
);
|
||||
|
||||
ensure!(
|
||||
!is_bootc_container || !custom_options.emulated,
|
||||
"--emulated is incompatible with bootable containers"
|
||||
);
|
||||
|
||||
if let Some(size) = &custom_options.bootc_disk_size {
|
||||
lazy_static! {
|
||||
static ref SIZE_PATTERN: Regex = Regex::new(r"^[0-9]+[KMGT]?$").unwrap();
|
||||
}
|
||||
|
||||
ensure!(
|
||||
SIZE_PATTERN.is_match(size),
|
||||
concat!(
|
||||
"--bootc-disk-size value must be a number followed by an optional suffix K",
|
||||
" (kilobyte, 1024), M (megabyte, 1024k), G (gigabyte, 1024M), or T (terabyte,",
|
||||
" 1024G)",
|
||||
)
|
||||
);
|
||||
|
||||
ensure!(
|
||||
is_bootc_container,
|
||||
"--bootc-disk-size only applies to bootable containers"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(is_bootc_container)
|
||||
}
|
||||
|
||||
fn is_first_create(spec: &oci_spec::runtime::Spec) -> Result<bool> {
|
||||
let path = spec.root_path()?.join("crun-vm/create-ran");
|
||||
|
||||
let error = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path)
|
||||
.err();
|
||||
|
||||
match error {
|
||||
Some(e) if e.kind() == ErrorKind::AlreadyExists => Ok(false),
|
||||
Some(e) => Err(e.into()),
|
||||
None => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_up_container_root(
|
||||
spec: &mut oci_spec::runtime::Spec,
|
||||
bundle_path: &Utf8Path,
|
||||
priv_dir_path: &Utf8Path,
|
||||
custom_options: &CustomOptions,
|
||||
is_bootc_container: bool,
|
||||
) -> Result<()> {
|
||||
let new_root_path = priv_dir_path.join("root");
|
||||
fs::create_dir_all(&new_root_path)?;
|
||||
|
||||
match fs::remove_file(new_root_path.join("crun-vm/ssh-successful")) {
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {}
|
||||
r => r?,
|
||||
};
|
||||
|
||||
// create root directory
|
||||
|
||||
spec.set_root(Some(
|
||||
oci_spec::runtime::RootBuilder::default()
|
||||
.path(bundle_path.join("crun-vm-root"))
|
||||
.path(&new_root_path)
|
||||
.readonly(false)
|
||||
.build()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
fs::create_dir_all(spec.root_path()?)?;
|
||||
|
||||
if let Some(context) = spec.mount_label() {
|
||||
// the directory we're using as the root for the container is not the one that podman
|
||||
// prepared for us, so we need to set its context ourselves to prevent SELinux from getting
|
||||
// angry at us
|
||||
set_file_context(spec.root_path()?, context)?;
|
||||
}
|
||||
|
||||
// set up container scripts
|
||||
// set up container files
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "scripts/"]
|
||||
struct Scripts;
|
||||
#[folder = "embed/"]
|
||||
struct Embed;
|
||||
|
||||
for path in Scripts::iter() {
|
||||
let path_in_host = spec.root_path()?.join("crun-vm").join(path.as_ref());
|
||||
for path in Embed::iter() {
|
||||
let path_in_host = new_root_path.join("crun-vm").join(path.as_ref());
|
||||
fs::create_dir_all(path_in_host.parent().unwrap())?;
|
||||
|
||||
let file = Scripts::get(&path).unwrap();
|
||||
let file = Embed::get(&path).unwrap();
|
||||
fs::write(&path_in_host, file.data)?;
|
||||
fs::set_permissions(&path_in_host, Permissions::from_mode(0o555))?;
|
||||
|
||||
let is_script = path.as_ref().ends_with(".sh");
|
||||
let mode = if is_script { 0o755 } else { 0o644 };
|
||||
fs::set_permissions(&path_in_host, Permissions::from_mode(mode))?;
|
||||
}
|
||||
|
||||
// configure container entrypoint
|
||||
|
||||
let command = match custom_options.print_libvirt_xml {
|
||||
true => vec!["cat", "/crun-vm/domain.xml"],
|
||||
false => vec!["/crun-vm/entrypoint.sh"],
|
||||
let command = if custom_options.print_libvirt_xml {
|
||||
vec!["cat", "/crun-vm/domain.xml"]
|
||||
} else if custom_options.print_config_json {
|
||||
vec!["cat", "/crun-vm/config.json"]
|
||||
} else {
|
||||
let arg = if is_bootc_container { "1" } else { "0" };
|
||||
vec!["/crun-vm/entrypoint.sh", arg]
|
||||
};
|
||||
|
||||
spec.set_process({
|
||||
let mut process = spec.process().clone().unwrap();
|
||||
|
||||
process.set_cwd(".".into());
|
||||
process.set_command_line(None);
|
||||
process.set_args(Some(command.into_iter().map(String::from).collect()));
|
||||
|
||||
fix_selinux_label(&mut process);
|
||||
|
||||
Some(process)
|
||||
});
|
||||
|
||||
|
@ -119,10 +277,24 @@ fn set_up_container_root(
|
|||
|
||||
fn set_up_vm_image(
|
||||
spec: &oci_spec::runtime::Spec,
|
||||
bundle_path: &Utf8Path,
|
||||
original_root_path: &Utf8Path,
|
||||
priv_dir_path: &Utf8Path,
|
||||
custom_options: &CustomOptions,
|
||||
is_first_create: bool,
|
||||
is_bootc_container: bool,
|
||||
) -> Result<VmImageInfo> {
|
||||
let mirror_vm_image_path_in_container = Utf8PathBuf::from("/crun-vm/image/image");
|
||||
let mirror_vm_image_path_in_host = spec.root_path()?.join("crun-vm/image/image");
|
||||
|
||||
if is_bootc_container {
|
||||
// the image will be generated later
|
||||
return Ok(VmImageInfo {
|
||||
path: mirror_vm_image_path_in_container,
|
||||
size: 0,
|
||||
format: "qcow2".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// where inside the container to look for the VM image
|
||||
const VM_IMAGE_SEARCH_PATHS: [&str; 2] = ["./", "disk/"];
|
||||
|
||||
|
@ -136,54 +308,57 @@ fn set_up_vm_image(
|
|||
|
||||
// mount user-provided VM image file into container
|
||||
|
||||
let mirror_vm_image_path_in_container =
|
||||
Utf8Path::new("crun-vm/image").join(vm_image_path_in_host.file_name().unwrap());
|
||||
let mirror_vm_image_path_in_host = spec.root_path()?.join(&mirror_vm_image_path_in_container);
|
||||
let mirror_vm_image_path_in_container =
|
||||
Utf8Path::new("/").join(mirror_vm_image_path_in_container);
|
||||
// Make VM image file available in a subtree that doesn't overlap our internal container root so
|
||||
// overlayfs works.
|
||||
|
||||
let private_dir = if custom_options.persistent {
|
||||
let vm_image_dir_path = vm_image_path_in_host.parent().unwrap();
|
||||
let vm_image_dir_name = vm_image_dir_path.file_name().unwrap();
|
||||
let image_dir_path = priv_dir_path.join("image");
|
||||
fs::create_dir_all(&image_dir_path)?;
|
||||
|
||||
let overlay_private_dir_name = format!(".crun-vm.{}.tmp", vm_image_dir_name);
|
||||
let overlay_private_dir_path = vm_image_dir_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(overlay_private_dir_name);
|
||||
|
||||
overlay_private_dir_path
|
||||
} else {
|
||||
bundle_path.join("crun-vm-vm-image-overlayfs")
|
||||
};
|
||||
|
||||
// We may need to change the VM image context to actually be able to access it, but we don't
|
||||
// want to change the user's original image file and also don't want to do a full data copy, so
|
||||
// we use an overlayfs mount, which allows us to expose the same file with a different context.
|
||||
//
|
||||
// TODO: Clean up `private_dir` when VM is terminated (would be best-effort, but better than
|
||||
// nothing).
|
||||
bind_mount_dir_with_different_context(
|
||||
vm_image_path_in_host.parent().unwrap(),
|
||||
mirror_vm_image_path_in_host.parent().unwrap(),
|
||||
spec.mount_label(),
|
||||
custom_options.persistent,
|
||||
private_dir,
|
||||
)?;
|
||||
|
||||
let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
|
||||
if !image_dir_path.join("image").try_exists()? {
|
||||
fs::hard_link(vm_image_path_in_host, image_dir_path.join("image"))?;
|
||||
}
|
||||
|
||||
if custom_options.persistent {
|
||||
// We want to propagate writes but not removal, so that the user's file isn't deleted by
|
||||
// Podman on cleanup, so we bind mount it on top of itself.
|
||||
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
|
||||
// can always access it, using the file's parent as the upperdir so that writes still
|
||||
// propagate to it.
|
||||
|
||||
if !is_mountpoint(mirror_vm_image_path_in_host.parent().unwrap())? {
|
||||
bind_mount_dir_with_different_context(
|
||||
image_dir_path,
|
||||
mirror_vm_image_path_in_host.parent().unwrap(),
|
||||
priv_dir_path.join("scratch-image"),
|
||||
spec.mount_label(),
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Prevent the container engine from deleting the user's actual VM image file by mounting it
|
||||
// on top of itself under our overlayfs mount.
|
||||
|
||||
bind_mount_file(&mirror_vm_image_path_in_host, &mirror_vm_image_path_in_host)?;
|
||||
|
||||
let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
|
||||
vm_image_info.path = mirror_vm_image_path_in_container;
|
||||
|
||||
Ok(vm_image_info)
|
||||
} else {
|
||||
// The overlayfs mount already isolates the user's original image file from writes, but to
|
||||
// ensure that we get copy-on-write and page cache sharing even when the underlying file
|
||||
// system doesn't support reflinks, we create a qcow2 overlay and use that as the image.
|
||||
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
|
||||
// can always access it.
|
||||
|
||||
if !is_mountpoint(mirror_vm_image_path_in_host.parent().unwrap())? {
|
||||
bind_mount_dir_with_different_context(
|
||||
image_dir_path,
|
||||
mirror_vm_image_path_in_host.parent().unwrap(),
|
||||
priv_dir_path.join("scratch-image"),
|
||||
spec.mount_label(),
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
// The overlayfs mount forbids writes to the VM image file, and also we want to get
|
||||
// copy-on-write and page cache sharing even when the underlying file system doesn't support
|
||||
// reflinks, so we create a qcow2 overlay image.
|
||||
|
||||
let overlay_vm_image_path_in_container = Utf8PathBuf::from("crun-vm/image-overlay.qcow2");
|
||||
let overlay_vm_image_path_in_host =
|
||||
|
@ -191,13 +366,19 @@ fn set_up_vm_image(
|
|||
let overlay_vm_image_path_in_container =
|
||||
Utf8Path::new("/").join(overlay_vm_image_path_in_container);
|
||||
|
||||
vm_image_info.path = mirror_vm_image_path_in_container;
|
||||
create_overlay_vm_image(&overlay_vm_image_path_in_host, &vm_image_info)?;
|
||||
let mut base_vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
|
||||
base_vm_image_info.path = mirror_vm_image_path_in_container;
|
||||
|
||||
vm_image_info.path = overlay_vm_image_path_in_container;
|
||||
if is_first_create {
|
||||
create_overlay_vm_image(&overlay_vm_image_path_in_host, &base_vm_image_info)?;
|
||||
}
|
||||
|
||||
Ok(VmImageInfo {
|
||||
path: Utf8Path::new("/").join(overlay_vm_image_path_in_container),
|
||||
size: base_vm_image_info.size,
|
||||
format: "qcow2".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(vm_image_info)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -406,7 +587,10 @@ fn set_up_blockdevs(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn set_up_extra_container_mounts_and_devices(spec: &mut oci_spec::runtime::Spec) -> Result<()> {
|
||||
fn set_up_extra_container_mounts_and_devices(
|
||||
spec: &mut oci_spec::runtime::Spec,
|
||||
custom_options: &CustomOptions,
|
||||
) -> Result<()> {
|
||||
fn add_bind_mount(spec: &mut oci_spec::runtime::Spec, path: impl AsRef<Path>) {
|
||||
spec.mounts_push(
|
||||
oci_spec::runtime::MountBuilder::default()
|
||||
|
@ -457,9 +641,17 @@ fn set_up_extra_container_mounts_and_devices(spec: &mut oci_spec::runtime::Spec)
|
|||
.unwrap(),
|
||||
);
|
||||
|
||||
add_bind_mount(spec, "/dev/kvm");
|
||||
add_char_dev(spec, "/dev/kvm")?;
|
||||
if !custom_options.emulated {
|
||||
ensure!(
|
||||
Path::new("/dev/kvm").try_exists()?,
|
||||
"/dev/kvm not found; is host KVM support enabled?"
|
||||
);
|
||||
|
||||
add_bind_mount(spec, "/dev/kvm");
|
||||
add_char_dev(spec, "/dev/kvm")?;
|
||||
}
|
||||
|
||||
// in case user sets up VFIO passthrough by overriding the libvirt XML
|
||||
for entry in fs::read_dir("/dev/vfio")? {
|
||||
let entry = entry?;
|
||||
if entry.metadata()?.file_type().is_char_device() {
|
||||
|
@ -485,7 +677,7 @@ fn set_up_security(spec: &mut oci_spec::runtime::Spec) {
|
|||
// TODO: This doesn't seem reasonable at all. Should we just force users to use a different
|
||||
// seccomp profile? Should passt provide the option to bypass a lot of the isolation that it
|
||||
// does, given we're already in a container *and* under a seccomp profile?
|
||||
spec.linux_seccomp_syscalls_push(
|
||||
spec.linux_seccomp_syscalls_push_front(
|
||||
oci_spec::runtime::LinuxSyscallBuilder::default()
|
||||
.names(["mount", "pivot_root", "umount2", "unshare"].map(String::from))
|
||||
.action(oci_spec::runtime::LinuxSeccompAction::ScmpActAllow)
|
||||
|
@ -499,12 +691,11 @@ fn set_up_first_boot_config(
|
|||
spec: &oci_spec::runtime::Spec,
|
||||
mounts: &Mounts,
|
||||
custom_options: &CustomOptions,
|
||||
container_public_key: &str,
|
||||
) -> Result<()> {
|
||||
let container_public_key = gen_container_ssh_key_pair(spec)?;
|
||||
|
||||
let config = FirstBootConfig {
|
||||
hostname: spec.hostname().as_deref(),
|
||||
container_public_key: &container_public_key,
|
||||
container_public_key,
|
||||
password: custom_options.password.as_deref(),
|
||||
mounts,
|
||||
};
|
||||
|
@ -528,25 +719,64 @@ fn set_up_first_boot_config(
|
|||
}
|
||||
|
||||
/// Returns the public key for the container.
|
||||
fn gen_container_ssh_key_pair(spec: &oci_spec::runtime::Spec) -> Result<String> {
|
||||
let ssh_path = spec.root_path()?.join("root/.ssh");
|
||||
///
|
||||
/// This first attempts to use the current user's key pair, just in case the VM does not support
|
||||
/// cloud-init but the user injected their public key into it themselves.
|
||||
fn set_up_ssh_key_pair(
|
||||
spec: &mut oci_spec::runtime::Spec,
|
||||
custom_options: &CustomOptions,
|
||||
engine: Engine,
|
||||
priv_dir_path: &Utf8Path,
|
||||
is_first_create: bool,
|
||||
) -> Result<String> {
|
||||
let user_home: Utf8PathBuf = home::home_dir()
|
||||
.ok_or_else(|| anyhow!("could not determine user home"))?
|
||||
.try_into()?;
|
||||
|
||||
if !ssh_path.join("id_rsa.pub").exists() {
|
||||
fs::create_dir_all(&ssh_path)?;
|
||||
let user_ssh_dir = user_home.join(".ssh");
|
||||
let container_ssh_dir = spec.root_path()?.join("root/.ssh");
|
||||
|
||||
// Use the host user's key pair if:
|
||||
// - The user didn't set the --random-ssh-key-pair flag; and
|
||||
// - We're not running under Docker (otherwise we'd probably not be running as the user that
|
||||
// invoked the engine); and
|
||||
// - We're not running under Kubernetes (where there isn't a "host user"); and
|
||||
// - They have a key pair.
|
||||
let use_user_key_pair = !custom_options.random_ssh_key_pair
|
||||
&& engine == Engine::Podman
|
||||
&& user_ssh_dir.join("id_rsa.pub").is_file()
|
||||
&& user_ssh_dir.join("id_rsa").is_file();
|
||||
|
||||
if use_user_key_pair {
|
||||
// use host user's key pair
|
||||
|
||||
bind_mount_dir_with_different_context(
|
||||
&user_ssh_dir,
|
||||
&container_ssh_dir,
|
||||
priv_dir_path.join("scratch-ssh"),
|
||||
spec.mount_label(),
|
||||
true,
|
||||
)?;
|
||||
} else if is_first_create {
|
||||
// use new key pair
|
||||
|
||||
fs::create_dir_all(&container_ssh_dir)?;
|
||||
|
||||
let status = Command::new("ssh-keygen")
|
||||
.arg("-q")
|
||||
.arg("-f")
|
||||
.arg(ssh_path.join("id_rsa"))
|
||||
.arg(container_ssh_dir.join("id_rsa"))
|
||||
.arg("-N")
|
||||
.arg("")
|
||||
.arg("-C")
|
||||
.arg("")
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
ensure!(status.success(), "ssh-keygen failed");
|
||||
}
|
||||
|
||||
Ok(fs::read_to_string(ssh_path.join("id_rsa.pub"))?)
|
||||
Ok(fs::read_to_string(container_ssh_dir.join("id_rsa.pub"))?)
|
||||
}
|
||||
|
||||
fn adjust_container_rlimits_and_resources(spec: &mut oci_spec::runtime::Spec) {
|
||||
|
@ -557,10 +787,10 @@ fn adjust_container_rlimits_and_resources(spec: &mut oci_spec::runtime::Spec) {
|
|||
|
||||
// Forwarding all UDP and TCP traffic requires passt to open many sockets. Ensure that
|
||||
// the container's RLIMIT_NOFILE is large enough.
|
||||
rlimits.retain(|rl| rl.typ() != oci_spec::runtime::LinuxRlimitType::RlimitNofile);
|
||||
rlimits.retain(|rl| rl.typ() != oci_spec::runtime::PosixRlimitType::RlimitNofile);
|
||||
rlimits.push(
|
||||
oci_spec::runtime::LinuxRlimitBuilder::default()
|
||||
.typ(oci_spec::runtime::LinuxRlimitType::RlimitNofile)
|
||||
oci_spec::runtime::PosixRlimitBuilder::default()
|
||||
.typ(oci_spec::runtime::PosixRlimitType::RlimitNofile)
|
||||
.hard(262144u64)
|
||||
.soft(262144u64)
|
||||
.build()
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::fs;
|
||||
|
||||
use anyhow::Result;
|
||||
use camino::Utf8Path;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RuntimeEnv {
|
||||
Docker,
|
||||
Kubernetes,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl RuntimeEnv {
|
||||
pub fn current(
|
||||
spec: &oci_spec::runtime::Spec,
|
||||
original_root_path: impl AsRef<Utf8Path>,
|
||||
) -> Result<RuntimeEnv> {
|
||||
let has_kubernetes_secrets_dir = spec.mounts().iter().flatten().any(|m| {
|
||||
m.destination()
|
||||
.starts_with("/var/run/secrets/kubernetes.io")
|
||||
});
|
||||
|
||||
let has_kubernetes_managed_etc_hosts = spec
|
||||
.mounts()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|m| m.destination() == Utf8Path::new("/etc/hosts"))
|
||||
.flat_map(|m| m.source())
|
||||
.next()
|
||||
.map(fs::read_to_string)
|
||||
.transpose()?
|
||||
.and_then(|hosts| hosts.lines().next().map(|line| line.to_string()))
|
||||
.map(|line| line.contains("Kubernetes-managed hosts file"))
|
||||
.unwrap_or(false);
|
||||
|
||||
let has_dockerenv_dot_file = original_root_path
|
||||
.as_ref()
|
||||
.join(".dockerenv")
|
||||
.try_exists()?;
|
||||
|
||||
if has_kubernetes_secrets_dir || has_kubernetes_managed_etc_hosts {
|
||||
Ok(RuntimeEnv::Kubernetes)
|
||||
} else if has_dockerenv_dot_file {
|
||||
Ok(RuntimeEnv::Docker)
|
||||
} else {
|
||||
Ok(RuntimeEnv::Other)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use camino::Utf8PathBuf;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::util::{crun, ensure_unmounted};
|
||||
|
||||
pub fn delete(args: &liboci_cli::Delete, raw_args: &[impl AsRef<OsStr>]) -> Result<()> {
|
||||
// get container root path
|
||||
|
||||
// the container might not exist because creation failed midway through, so we ignore errors
|
||||
let root_path = get_root_path(&args.container_id).ok();
|
||||
|
||||
// actually delete the container
|
||||
|
||||
crun(raw_args)?;
|
||||
|
||||
// clean up crun-vm mounts so that user doesn't have to deal with them when they decide to
|
||||
// delete crun-vm's state/private directory
|
||||
|
||||
if let Some(root_path) = root_path {
|
||||
let private_dir_path: Utf8PathBuf = root_path
|
||||
.canonicalize()?
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_path_buf()
|
||||
.try_into()?;
|
||||
|
||||
let ssh_dir_path = private_dir_path.join("root/root/.ssh");
|
||||
ensure_unmounted(ssh_dir_path)?;
|
||||
|
||||
let image_dir_path = private_dir_path.join("root/crun-vm/image");
|
||||
let image_file_path = image_dir_path.join("image");
|
||||
ensure_unmounted(image_file_path)?;
|
||||
ensure_unmounted(image_dir_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_root_path(container_id: &str) -> Result<Utf8PathBuf> {
|
||||
let output = Command::new("crun")
|
||||
.arg("state")
|
||||
.arg(container_id)
|
||||
.stderr(Stdio::null())
|
||||
.output()?;
|
||||
|
||||
ensure!(output.status.success());
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ContainerState {
|
||||
rootfs: PathBuf,
|
||||
}
|
||||
|
||||
let state: ContainerState = serde_json::from_slice(&output.stdout)?;
|
||||
|
||||
Ok(state.rootfs.try_into()?)
|
||||
}
|
|
@ -1,47 +1,103 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter},
|
||||
};
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use crate::crun::crun_exec;
|
||||
use crate::util::{crun, fix_selinux_label};
|
||||
|
||||
pub fn exec(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Exec) -> Result<()> {
|
||||
pub fn exec(args: &liboci_cli::Exec, raw_args: &[impl AsRef<OsStr>]) -> Result<()> {
|
||||
assert!(args.command.is_empty());
|
||||
|
||||
// load exec process config
|
||||
|
||||
let process_config_path = args.process.as_ref().expect("process config");
|
||||
let mut process: oci_spec::runtime::Process =
|
||||
serde_json::from_reader(File::open(process_config_path).map(BufReader::new)?)?;
|
||||
|
||||
let command = process.args().as_ref().expect("command specified");
|
||||
|
||||
let ssh_user = command
|
||||
.first()
|
||||
.expect("first command argument is user to ssh as into the vm");
|
||||
|
||||
let mut new_command = vec![];
|
||||
|
||||
if ssh_user != "-" {
|
||||
new_command.extend(["/crun-vm/exec.sh".to_string(), ssh_user.clone()]);
|
||||
}
|
||||
|
||||
new_command.extend(command.iter().skip(1).cloned());
|
||||
|
||||
if ssh_user == "-" && new_command.is_empty() {
|
||||
new_command.push("/bin/bash".to_string());
|
||||
}
|
||||
|
||||
let new_command = build_command(command)?;
|
||||
process.set_args(Some(new_command));
|
||||
|
||||
fix_selinux_label(&mut process);
|
||||
|
||||
// store modified exec process config
|
||||
|
||||
serde_json::to_writer(
|
||||
File::create(process_config_path).map(BufWriter::new)?,
|
||||
&process,
|
||||
)?;
|
||||
|
||||
crun_exec(global_args, args)?;
|
||||
// actually exec
|
||||
|
||||
crun(raw_args)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(no_binary_name = true, disable_help_flag = true)]
|
||||
struct ExecArgs {
|
||||
#[clap(long = "as", default_value = "root")]
|
||||
user: String,
|
||||
|
||||
#[clap(long, conflicts_with = "user")]
|
||||
container: bool,
|
||||
|
||||
#[clap(long = "timeout")]
|
||||
timeout: Option<u32>,
|
||||
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
fn build_command(original_command: &Vec<String>) -> Result<Vec<String>> {
|
||||
let cmd = if original_command.first() == Some(&"--".to_string()) {
|
||||
// Podman gobbles up a -- before the command in some cases, but not in others. Gobble it up
|
||||
// ourselves to avoid user confusion; see https://github.com/containers/crun-vm/issues/117.
|
||||
&original_command[1..]
|
||||
} else {
|
||||
original_command
|
||||
};
|
||||
|
||||
let mut args: ExecArgs = ExecArgs::parse_from(cmd);
|
||||
|
||||
let timeout = if let Some(t) = args.timeout {
|
||||
t
|
||||
} else if let Some(t) = env::var_os("CRUN_VM_EXEC_TIMEOUT") {
|
||||
match t.to_str().and_then(|t| t.parse().ok()) {
|
||||
Some(t) => t,
|
||||
None => bail!("env var CRUN_VM_EXEC_TIMEOUT has invalid value"),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if args.command.starts_with(&["".to_string()]) {
|
||||
args.command.remove(0);
|
||||
}
|
||||
|
||||
let command = if args.container {
|
||||
if args.command.is_empty() {
|
||||
vec!["/bin/bash".to_string()]
|
||||
} else {
|
||||
args.command
|
||||
}
|
||||
} else {
|
||||
[
|
||||
"/crun-vm/exec.sh".to_string(),
|
||||
timeout.to_string(),
|
||||
args.user,
|
||||
]
|
||||
.into_iter()
|
||||
.chain(args.command)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(command)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod exec;
|
||||
|
|
198
src/crun.rs
198
src/crun.rs
|
@ -1,198 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
/// Run `crun`.
|
||||
///
|
||||
/// `crun` will inherit this process' standard streams.
|
||||
///
|
||||
/// TODO: It may be better to use libcrun directly, although its public API purportedly isn't in
|
||||
/// great shape: https://github.com/containers/crun/issues/1018
|
||||
pub fn crun(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
|
||||
let status = Command::new("crun").args(args).spawn()?.wait()?;
|
||||
ensure!(status.success(), "crun failed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn crun_create(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Create) -> Result<()> {
|
||||
let mut a = Vec::<OsString>::new();
|
||||
|
||||
fn add(list: &mut Vec<OsString>, arg: impl AsRef<OsStr>) {
|
||||
list.push(arg.as_ref().to_os_string());
|
||||
}
|
||||
|
||||
// build crun argument list
|
||||
|
||||
if global_args.debug {
|
||||
add(&mut a, "--debug");
|
||||
}
|
||||
|
||||
if let Some(path) = &global_args.log {
|
||||
add(&mut a, "--log");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if let Some(format) = &global_args.log_format {
|
||||
add(&mut a, "--log-format");
|
||||
add(&mut a, format);
|
||||
}
|
||||
|
||||
if args.no_pivot {
|
||||
add(&mut a, "--no-pivot");
|
||||
}
|
||||
|
||||
if let Some(path) = &global_args.root {
|
||||
add(&mut a, "--root");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if global_args.systemd_cgroup {
|
||||
add(&mut a, "--systemd-cgroup");
|
||||
}
|
||||
|
||||
add(&mut a, "create");
|
||||
|
||||
add(&mut a, "--bundle");
|
||||
add(&mut a, &args.bundle);
|
||||
|
||||
if let Some(path) = &args.console_socket {
|
||||
add(&mut a, "--console-socket");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if args.no_new_keyring {
|
||||
add(&mut a, "--no-new-keyring");
|
||||
}
|
||||
|
||||
add(&mut a, "--preserve-fds");
|
||||
add(&mut a, args.preserve_fds.to_string());
|
||||
|
||||
if let Some(path) = &args.pid_file {
|
||||
add(&mut a, "--pid-file");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
add(&mut a, &args.container_id);
|
||||
|
||||
// run crun
|
||||
|
||||
crun(a)
|
||||
}
|
||||
|
||||
pub fn crun_exec(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Exec) -> Result<()> {
|
||||
let mut a = Vec::<OsString>::new();
|
||||
|
||||
fn add(list: &mut Vec<OsString>, arg: impl AsRef<OsStr>) {
|
||||
list.push(arg.as_ref().to_os_string());
|
||||
}
|
||||
|
||||
// build crun argument list
|
||||
|
||||
if global_args.debug {
|
||||
add(&mut a, "--debug");
|
||||
}
|
||||
|
||||
if let Some(path) = &global_args.log {
|
||||
add(&mut a, "--log");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if let Some(format) = &global_args.log_format {
|
||||
add(&mut a, "--log-format");
|
||||
add(&mut a, format);
|
||||
}
|
||||
|
||||
if let Some(path) = &global_args.root {
|
||||
add(&mut a, "--root");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if global_args.systemd_cgroup {
|
||||
add(&mut a, "--systemd-cgroup");
|
||||
}
|
||||
|
||||
add(&mut a, "exec");
|
||||
|
||||
if let Some(profile) = &args.apparmor {
|
||||
add(&mut a, "--apparmor");
|
||||
add(&mut a, profile);
|
||||
}
|
||||
|
||||
if let Some(path) = &args.console_socket {
|
||||
add(&mut a, "--console-socket");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if let Some(cwd) = &args.cwd {
|
||||
add(&mut a, "--cwd");
|
||||
add(&mut a, cwd);
|
||||
}
|
||||
|
||||
for cap in &args.cap {
|
||||
add(&mut a, "--cap");
|
||||
add(&mut a, cap);
|
||||
}
|
||||
|
||||
if args.detach {
|
||||
add(&mut a, "--detach");
|
||||
}
|
||||
|
||||
if let Some(path) = &args.cgroup {
|
||||
add(&mut a, "--cgroup");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
for (name, value) in &args.env {
|
||||
add(&mut a, "--env");
|
||||
add(&mut a, format!("{name}={value}"));
|
||||
}
|
||||
|
||||
if args.no_new_privs {
|
||||
add(&mut a, "--no-new-privs");
|
||||
}
|
||||
|
||||
add(&mut a, "--preserve-fds");
|
||||
add(&mut a, args.preserve_fds.to_string());
|
||||
|
||||
if let Some(path) = &args.process {
|
||||
add(&mut a, "--process");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if let Some(label) = &args.process_label {
|
||||
add(&mut a, "--process-label");
|
||||
add(&mut a, label);
|
||||
}
|
||||
|
||||
if let Some(path) = &args.pid_file {
|
||||
add(&mut a, "--pid-file");
|
||||
add(&mut a, path);
|
||||
}
|
||||
|
||||
if args.tty {
|
||||
add(&mut a, "--tty");
|
||||
}
|
||||
|
||||
if let Some((uid, gid)) = &args.user {
|
||||
add(&mut a, "--user");
|
||||
add(
|
||||
&mut a,
|
||||
match gid {
|
||||
Some(gid) => format!("{uid}:{gid}"),
|
||||
None => format!("{uid}"),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
add(&mut a, &args.container_id);
|
||||
|
||||
a.extend(args.command.iter().map(Into::into));
|
||||
|
||||
// run crun
|
||||
|
||||
crun(a)
|
||||
}
|
45
src/lib.rs
45
src/lib.rs
|
@ -1,18 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
mod commands;
|
||||
mod crun;
|
||||
mod util;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use crun::crun;
|
||||
use util::crun;
|
||||
|
||||
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(no_binary_name = true)]
|
||||
struct Args {
|
||||
#[clap(flatten)]
|
||||
global: liboci_cli::GlobalOpts,
|
||||
|
@ -22,7 +21,7 @@ struct Args {
|
|||
}
|
||||
|
||||
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
||||
#[derive(clap::Parser, Debug)]
|
||||
#[derive(Parser, Debug)]
|
||||
enum Command {
|
||||
#[clap(flatten)]
|
||||
Standard(Box<liboci_cli::StandardCmd>),
|
||||
|
@ -32,27 +31,43 @@ enum Command {
|
|||
}
|
||||
|
||||
pub fn main(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
|
||||
let args = args
|
||||
let raw_args = args
|
||||
.into_iter()
|
||||
.map(|a| a.as_ref().to_os_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let parsed_args =
|
||||
Args::parse_from(iter::once(&OsStr::new("crun-vm").to_os_string()).chain(&args));
|
||||
let parsed_args = Args::parse_from(&raw_args);
|
||||
|
||||
match parsed_args.command {
|
||||
Command::Standard(cmd) => {
|
||||
if let liboci_cli::StandardCmd::Create(create_args) = *cmd {
|
||||
return commands::create::create(&parsed_args.global, &create_args);
|
||||
match *cmd {
|
||||
liboci_cli::StandardCmd::Create(args) => commands::create::create(&args, &raw_args),
|
||||
liboci_cli::StandardCmd::Delete(args) => commands::delete::delete(&args, &raw_args),
|
||||
liboci_cli::StandardCmd::Start(_)
|
||||
| liboci_cli::StandardCmd::State(_)
|
||||
| liboci_cli::StandardCmd::Kill(_) => {
|
||||
// not a command we implement ourselves, pass it on to crun
|
||||
crun(&raw_args)
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Common(cmd) => {
|
||||
if let liboci_cli::CommonCmd::Exec(exec_args) = *cmd {
|
||||
return commands::exec::exec(&parsed_args.global, &exec_args);
|
||||
match *cmd {
|
||||
liboci_cli::CommonCmd::Exec(args) => commands::exec::exec(&args, &raw_args),
|
||||
liboci_cli::CommonCmd::Checkpointt(_)
|
||||
| liboci_cli::CommonCmd::Events(_)
|
||||
| liboci_cli::CommonCmd::Features(_)
|
||||
| liboci_cli::CommonCmd::List(_)
|
||||
| liboci_cli::CommonCmd::Pause(_)
|
||||
| liboci_cli::CommonCmd::Ps(_)
|
||||
| liboci_cli::CommonCmd::Resume(_)
|
||||
| liboci_cli::CommonCmd::Run(_)
|
||||
| liboci_cli::CommonCmd::Update(_)
|
||||
| liboci_cli::CommonCmd::Spec(_) => {
|
||||
// not a command we support
|
||||
bail!("Unknown command")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not a command we implement ourselves, just pass it on to crun
|
||||
crun(&args)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,14 @@ use std::process;
|
|||
fn main() {
|
||||
if let Err(e) = crun_vm::main(env::args_os().skip(1)) {
|
||||
eprintln!("{:#}", e);
|
||||
|
||||
let rust_backtrace = env::var_os("RUST_BACKTRACE").unwrap_or_default();
|
||||
let rust_lib_backtrace = env::var_os("RUST_LIB_BACKTRACE").unwrap_or_default();
|
||||
|
||||
if (rust_backtrace == "1" || rust_lib_backtrace == "1") && rust_lib_backtrace != "0" {
|
||||
eprintln!("\n{}\n", e.backtrace());
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
168
src/util.rs
168
src/util.rs
|
@ -1,32 +1,68 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::ffi::{c_char, CString, OsStr};
|
||||
use std::fs::{self, OpenOptions, Permissions};
|
||||
use std::io;
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str;
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use nix::mount::MsFlags;
|
||||
use nix::mount::{MntFlags, MsFlags};
|
||||
use serde::Deserialize;
|
||||
|
||||
// When the container image's entrypoint is /sbin/init or similar, Podman gives the entrypoint (and
|
||||
// exec entrypoint) process an SELinux label of, for instance:
|
||||
//
|
||||
// system_u:system_r:container_init_t:s0:c276,c638
|
||||
//
|
||||
// However, we are going to change our entrypoint to something else, so we need to use the
|
||||
// "standard" label that Podman otherwise gives, which in this case would be:
|
||||
//
|
||||
// system_u:system_r:container_t:s0:c276,c638
|
||||
//
|
||||
// This function performs that mapping.
|
||||
pub fn fix_selinux_label(process: &mut oci_spec::runtime::Process) {
|
||||
if let Some(label) = process.selinux_label() {
|
||||
let new_label = label.replace("container_init_t", "container_t");
|
||||
process.set_selinux_label(Some(new_label));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_file_context(path: impl AsRef<Utf8Path>, context: &str) -> Result<()> {
|
||||
extern "C" {
|
||||
fn setfilecon(path: *const c_char, con: *const c_char) -> i32;
|
||||
fn lsetfilecon(path: *const c_char, con: *const c_char) -> i32;
|
||||
}
|
||||
|
||||
let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
|
||||
let context = CString::new(context.as_bytes())?;
|
||||
|
||||
if unsafe { setfilecon(path.as_ptr(), context.as_ptr()) } != 0 {
|
||||
if unsafe { lsetfilecon(path.as_ptr(), context.as_ptr()) } != 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_mountpoint(path: impl AsRef<Utf8Path>) -> Result<bool> {
|
||||
let parent = path
|
||||
.as_ref()
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("path does not have a parent"))?;
|
||||
|
||||
let path_dev = match fs::symlink_metadata(path.as_ref()) {
|
||||
Ok(meta) => meta.dev(),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(false),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let parent_dev = fs::symlink_metadata(parent)?.dev();
|
||||
|
||||
Ok(path_dev != parent_dev)
|
||||
}
|
||||
|
||||
pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) -> Result<()> {
|
||||
// ensure target exists
|
||||
|
||||
|
@ -59,52 +95,59 @@ pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) ->
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn escape_path(mount_option: impl AsRef<Utf8Path>) -> String {
|
||||
mount_option
|
||||
.as_ref()
|
||||
.as_str()
|
||||
.replace('\\', "\\\\")
|
||||
.replace(',', "\\,")
|
||||
}
|
||||
|
||||
fn escape_context(mount_option: &str) -> String {
|
||||
assert!(!mount_option.contains('"'));
|
||||
format!("\"{}\"", mount_option)
|
||||
}
|
||||
|
||||
/// Expose directory `from` at `to` with the given SELinux `context`, if any, recursively applied.
|
||||
///
|
||||
/// This does *not* modify the SELinux context of `from` nor of files under `from`.
|
||||
///
|
||||
/// If `propagate_changes` is true, `private_dir` must belong to the same file system as `from` and
|
||||
/// be a separate subtree.
|
||||
/// If `read_only` is false, `scratch_dir` must belong to the same file system as `from` and be a
|
||||
/// separate subtree.
|
||||
///
|
||||
/// TODO: Is this a neat relabeling trick or simply a bad hack?
|
||||
pub fn bind_mount_dir_with_different_context(
|
||||
from: impl AsRef<Utf8Path>,
|
||||
to: impl AsRef<Utf8Path>,
|
||||
scratch_dir: impl AsRef<Utf8Path>,
|
||||
context: Option<&str>,
|
||||
propagate_changes: bool,
|
||||
private_dir: impl AsRef<Utf8Path>,
|
||||
read_only: bool,
|
||||
) -> Result<()> {
|
||||
let layer_dir = private_dir.as_ref().join("layer");
|
||||
let work_dir = private_dir.as_ref().join("work");
|
||||
|
||||
fs::create_dir_all(&layer_dir)?;
|
||||
fs::create_dir_all(&work_dir)?;
|
||||
fs::create_dir_all(to.as_ref())?;
|
||||
|
||||
fn escape_path(mount_option: &Utf8Path) -> String {
|
||||
mount_option
|
||||
.as_str()
|
||||
.replace('\\', "\\\\")
|
||||
.replace(',', "\\,")
|
||||
}
|
||||
let mut options = if read_only {
|
||||
fs::create_dir_all(scratch_dir.as_ref())?;
|
||||
|
||||
fn escape_context(mount_option: &str) -> String {
|
||||
assert!(!mount_option.contains('"'));
|
||||
format!("\"{}\"", mount_option)
|
||||
}
|
||||
format!(
|
||||
"lowerdir={}:{}",
|
||||
escape_path(scratch_dir.as_ref()),
|
||||
escape_path(from)
|
||||
)
|
||||
} else {
|
||||
let layer_dir = scratch_dir.as_ref().join("layer");
|
||||
let work_dir = scratch_dir.as_ref().join("work");
|
||||
|
||||
let (lower_dir, upper_dir) = match propagate_changes {
|
||||
true => (layer_dir.as_path(), from.as_ref()),
|
||||
false => (from.as_ref(), layer_dir.as_path()),
|
||||
fs::create_dir_all(&layer_dir)?;
|
||||
fs::create_dir_all(&work_dir)?;
|
||||
|
||||
format!(
|
||||
"lowerdir={},upperdir={},workdir={}",
|
||||
escape_path(layer_dir),
|
||||
escape_path(from),
|
||||
escape_path(&work_dir),
|
||||
)
|
||||
};
|
||||
|
||||
let mut options = format!(
|
||||
"lowerdir={},upperdir={},workdir={}",
|
||||
escape_path(lower_dir),
|
||||
escape_path(upper_dir),
|
||||
escape_path(&work_dir),
|
||||
);
|
||||
|
||||
if let Some(context) = context {
|
||||
options = format!("{},context={}", options, escape_context(context));
|
||||
}
|
||||
|
@ -124,9 +167,22 @@ pub fn bind_mount_dir_with_different_context(
|
|||
);
|
||||
}
|
||||
|
||||
// Make any necessary manual cleanup a bit easier by ensuring the workdir is accessible to the
|
||||
// user that Podman is running under.
|
||||
fs::set_permissions(work_dir.join("work"), Permissions::from_mode(0o700))?;
|
||||
if !read_only {
|
||||
// Make any necessary manual cleanup a bit easier by ensuring the workdir is accessible to
|
||||
// the user that Podman is running under.
|
||||
fs::set_permissions(
|
||||
scratch_dir.as_ref().join("work/work"),
|
||||
Permissions::from_mode(0o700),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_unmounted(path: impl AsRef<Utf8Path>) -> Result<()> {
|
||||
while is_mountpoint(&path)? {
|
||||
nix::mount::umount2(path.as_ref().as_std_path(), MntFlags::MNT_DETACH)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -142,7 +198,7 @@ pub trait SpecExt {
|
|||
linux_device_cgroup: oci_spec::runtime::LinuxDeviceCgroup,
|
||||
);
|
||||
fn process_capabilities_insert_beip(&mut self, capability: oci_spec::runtime::Capability);
|
||||
fn linux_seccomp_syscalls_push(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall);
|
||||
fn linux_seccomp_syscalls_push_front(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall);
|
||||
}
|
||||
|
||||
impl SpecExt for oci_spec::runtime::Spec {
|
||||
|
@ -220,7 +276,10 @@ impl SpecExt for oci_spec::runtime::Spec {
|
|||
});
|
||||
}
|
||||
|
||||
fn linux_seccomp_syscalls_push(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall) {
|
||||
fn linux_seccomp_syscalls_push_front(
|
||||
&mut self,
|
||||
linux_syscall: oci_spec::runtime::LinuxSyscall,
|
||||
) {
|
||||
self.set_linux({
|
||||
let mut linux = self.linux().clone().expect("linux config");
|
||||
linux.set_seccomp({
|
||||
|
@ -228,7 +287,7 @@ impl SpecExt for oci_spec::runtime::Spec {
|
|||
if let Some(seccomp) = &mut seccomp {
|
||||
seccomp.set_syscalls({
|
||||
let mut syscalls = seccomp.syscalls().clone().unwrap_or_default();
|
||||
syscalls.push(linux_syscall);
|
||||
syscalls.insert(0, linux_syscall);
|
||||
Some(syscalls)
|
||||
});
|
||||
}
|
||||
|
@ -294,7 +353,11 @@ impl VmImageInfo {
|
|||
.stdout(Stdio::piped())
|
||||
.output()?;
|
||||
|
||||
ensure!(output.status.success(), "`qemu-img info` failed");
|
||||
ensure!(
|
||||
output.status.success(),
|
||||
"`qemu-img info` failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?;
|
||||
info.path = vm_image_path;
|
||||
|
@ -307,7 +370,7 @@ pub fn create_overlay_vm_image(
|
|||
overlay_vm_image_path: &Utf8Path,
|
||||
base_vm_image_info: &VmImageInfo,
|
||||
) -> Result<()> {
|
||||
let status = Command::new("qemu-img")
|
||||
let output = Command::new("qemu-img")
|
||||
.arg("create")
|
||||
.arg("-q")
|
||||
.arg("-f")
|
||||
|
@ -319,10 +382,23 @@ pub fn create_overlay_vm_image(
|
|||
.arg(&base_vm_image_info.path)
|
||||
.arg(overlay_vm_image_path)
|
||||
.arg(base_vm_image_info.size.to_string())
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
.output()?;
|
||||
|
||||
ensure!(status.success(), "`qemu-img create` failed");
|
||||
ensure!(
|
||||
output.status.success(),
|
||||
"`qemu-img create` failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `crun`.
|
||||
///
|
||||
/// `crun` will inherit this process' standard streams.
|
||||
pub fn crun(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
|
||||
let status = Command::new("crun").args(args).spawn()?.wait()?;
|
||||
ensure!(status.success(), "crun failed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
53
test.sh
53
test.sh
|
@ -1,53 +0,0 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -o errexit -o pipefail -o nounset
|
||||
|
||||
if (( $# == 0 )); then
|
||||
>&2 echo -n "\
|
||||
Usage: $0 <engine...>
|
||||
|
||||
Examples:
|
||||
$ $0 podman # rootless Podman
|
||||
$ sudo $0 podman # rootful Podman
|
||||
$ $0 docker # Docker
|
||||
$ $0 podman docker # rootless Podman and Docker
|
||||
"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
function __log_and_run() {
|
||||
printf '\033[0;33m%s\033[0m\n' "$*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
export CARGO_TERM_COLOR=always
|
||||
|
||||
script_dir="$( dirname "$0" )"
|
||||
if [[ "${script_dir}" != . ]]; then
|
||||
__log_and_run cd "${script_dir}"
|
||||
fi
|
||||
|
||||
images=(
|
||||
quay.io/containerdisks/fedora:39
|
||||
quay.io/crun-vm/example-fedora-coreos:39
|
||||
)
|
||||
|
||||
# ensure that tests don't timeout because they're pulling images
|
||||
for engine in "$@"; do
|
||||
for image in "${images[@]}"; do
|
||||
__log_and_run "${engine}" pull "${image}"
|
||||
done
|
||||
done
|
||||
|
||||
nextest_run=(
|
||||
nextest run \
|
||||
--all-targets --all-features --test-threads $(( $(nproc) / 2 )) \
|
||||
-- "${@/#/test_run::engine_}"
|
||||
)
|
||||
|
||||
if command -v cargo-nextest &> /dev/null; then
|
||||
__log_and_run cargo "${nextest_run[@]}"
|
||||
else
|
||||
__log_and_run "${nextest_run[@]}"
|
||||
fi
|
|
@ -0,0 +1,447 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -o errexit -o pipefail -o nounset
|
||||
|
||||
start_time="$( date +%s%N )"
|
||||
|
||||
env_image_base=${CRUN_VM_TEST_ENV_BASE_IMAGE:-"quay.io/containerdisks/fedora:40"}
|
||||
env_image=quay.io/crun-vm/test-env:latest
|
||||
container_name=crun-vm-test-env
|
||||
|
||||
declare -A TEST_IMAGES
|
||||
TEST_IMAGES=(
|
||||
[fedora]=quay.io/containerdisks/fedora:40 # uses cloud-init
|
||||
[coreos]=quay.io/crun-vm/example-fedora-coreos:40 # uses Ignition
|
||||
[fedora-bootc]=quay.io/crun-vm/example-fedora-bootc:40 # bootable container
|
||||
)
|
||||
|
||||
declare -A TEST_IMAGES_DEFAULT_USER
|
||||
TEST_IMAGES_DEFAULT_USER=(
|
||||
[fedora]=fedora
|
||||
[coreos]=core
|
||||
[fedora-bootc]=fedora
|
||||
)
|
||||
|
||||
declare -A TEST_IMAGES_DEFAULT_USER_HOME
|
||||
TEST_IMAGES_DEFAULT_USER_HOME=(
|
||||
[fedora]=/home/fedora
|
||||
[coreos]=/var/home/core
|
||||
[fedora-bootc]=/var/home/fedora
|
||||
)
|
||||
|
||||
__bad_usage() {
|
||||
>&2 echo -n "\
|
||||
Usage: $0 <command> [<args...>]
|
||||
|
||||
build
|
||||
$0 run <engine> <test_scripts...>
|
||||
$0 start
|
||||
$0 stop
|
||||
|
||||
COMMANDS
|
||||
|
||||
build
|
||||
Build the test env VM container image.
|
||||
|
||||
start
|
||||
Start the test env VM.
|
||||
|
||||
restart
|
||||
Stop the test env VM if running, then start it.
|
||||
|
||||
stop
|
||||
Stop the test env VM if running.
|
||||
|
||||
run <engine> <test_script>
|
||||
run <engine> all
|
||||
Run a test script in the test env VM under the given engine. <engine> must
|
||||
be one of 'podman', 'rootful-podman', 'docker', or 'all'.
|
||||
|
||||
ssh
|
||||
SSH into the test env VM for debugging.
|
||||
"
|
||||
exit 2
|
||||
}
|
||||
|
||||
# Usage: __elapsed
|
||||
__elapsed() {
|
||||
local delta
|
||||
delta=$(( $( date +%s%N ) - start_time ))
|
||||
printf '%d.%09d' "$(( delta / 10**9 ))" "$(( delta % 10**9 ))"
|
||||
}
|
||||
|
||||
# Usage: __small_log_without_time <color> <format> <args...>
|
||||
__small_log_without_time() {
|
||||
# shellcheck disable=SC2059
|
||||
>&2 printf "\033[%sm--- %s\033[0m\n" \
|
||||
"$1" "$( printf "${@:2}" )"
|
||||
}
|
||||
|
||||
# Usage: __log <color> <format> <args...>
|
||||
__small_log() {
|
||||
# shellcheck disable=SC2059
|
||||
>&2 printf "\033[%sm--- [%6.1f] %s\033[0m\n" \
|
||||
"$1" "$( __elapsed )" "$( printf "${@:2}" )"
|
||||
}
|
||||
|
||||
# Usage: __big_log <color> <format> <args...>
|
||||
__big_log() {
|
||||
local text term_cols sep_len
|
||||
text="$( printf "${@:2}" )"
|
||||
term_cols="$( tput cols 2> /dev/null )" || term_cols=80
|
||||
sep_len="$(( term_cols - ${#text} - 16 ))"
|
||||
>&2 printf "\033[%sm--- [%6.1f] %s " "$1" "$( __elapsed )" "${text}"
|
||||
>&2 printf '%*s\033[0m\n' "$(( sep_len < 0 ? 0 : sep_len ))" '' | tr ' ' -
|
||||
}
|
||||
|
||||
__log_without_time_and_run() {
|
||||
__small_log_without_time 36 '$ %s' "$*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
__log_and_run() {
|
||||
__small_log 36 '$ %s' "$*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
__rel() {
|
||||
realpath -s --relative-to=. "$1"
|
||||
}
|
||||
|
||||
__build_runtime() {
|
||||
__big_log 33 'Building crun-vm...'
|
||||
__log_and_run make -C "$repo_root"
|
||||
runtime=$repo_root/out/crun-vm
|
||||
}
|
||||
|
||||
__extra_cleanup() { :; }
|
||||
|
||||
repo_root=$( readlink -e "$( dirname "$0" )/.." )
|
||||
cd "$repo_root"
|
||||
|
||||
temp_dir=$( mktemp -d )
|
||||
trap '__extra_cleanup; rm -fr "$temp_dir"' EXIT
|
||||
|
||||
export RUST_BACKTRACE=1 RUST_LIB_BACKTRACE=1
|
||||
|
||||
case "${1:-}" in
|
||||
build)
|
||||
if (( $# != 1 )); then
|
||||
__bad_usage
|
||||
fi
|
||||
|
||||
__build_runtime
|
||||
|
||||
__big_log 33 'Building test env image...'
|
||||
|
||||
# extract base image file
|
||||
|
||||
__log_and_run "$( __rel "$repo_root/util/extract-vm-image.sh" )" \
|
||||
"$env_image_base" \
|
||||
"$temp_dir/image"
|
||||
|
||||
# expand base image
|
||||
|
||||
__log_and_run qemu-img create -f qcow2 "$temp_dir/image.qcow2" 50G
|
||||
__log_and_run virt-resize \
|
||||
--quiet \
|
||||
--expand /dev/sda4 \
|
||||
"$temp_dir/image" \
|
||||
"$temp_dir/image.qcow2"
|
||||
|
||||
rm "$temp_dir/image"
|
||||
|
||||
# launch VM from base image file
|
||||
|
||||
__log_and_run podman run \
|
||||
--name "$container_name-build" \
|
||||
--runtime "$runtime" \
|
||||
--memory 8g \
|
||||
--rm -dit \
|
||||
--rootfs "$temp_dir" \
|
||||
--persistent
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
__extra_cleanup() {
|
||||
__log_and_run podman stop --time 0 "$container_name-build"
|
||||
}
|
||||
|
||||
# customize VM
|
||||
|
||||
__exec() {
|
||||
__log_and_run podman exec "$container_name-build" --as fedora "$@"
|
||||
}
|
||||
|
||||
# generate an ssh keypair for users fedora and root so crun-vm containers
|
||||
# get a predictable keypair
|
||||
__exec 'ssh-keygen -q -f .ssh/id_rsa -N "" && sudo cp -r .ssh /root/'
|
||||
|
||||
__exec sudo dnf update -y
|
||||
__exec sudo dnf install -y \
|
||||
bash \
|
||||
coreutils \
|
||||
crun \
|
||||
crun-krun \
|
||||
docker \
|
||||
genisoimage \
|
||||
grep \
|
||||
htop \
|
||||
libselinux-devel \
|
||||
libvirt-client \
|
||||
libvirt-daemon-driver-qemu \
|
||||
libvirt-daemon-log \
|
||||
lsof \
|
||||
openssh-clients \
|
||||
podman \
|
||||
qemu-img \
|
||||
qemu-system-x86-core \
|
||||
shadow-utils \
|
||||
util-linux \
|
||||
virtiofsd
|
||||
__exec sudo dnf clean all
|
||||
|
||||
daemon_json='{ "runtimes": { "crun-vm": { "path": "/home/fedora/bin/crun-vm" } } }'
|
||||
__exec sudo mkdir -p /etc/docker
|
||||
__exec "echo ${daemon_json@Q} | sudo tee /etc/docker/daemon.json"
|
||||
|
||||
__exec sudo cloud-init clean --logs # run cloud-init again on next boot
|
||||
|
||||
__exec sudo poweroff || true
|
||||
|
||||
# sparsify image file
|
||||
|
||||
__log_and_run podman wait --ignore "$container_name-build"
|
||||
__extra_cleanup() { :; }
|
||||
|
||||
__log_and_run virt-sparsify --quiet --in-place "$temp_dir/image.qcow2"
|
||||
|
||||
# package new image file
|
||||
|
||||
__log_and_run "$( __rel "$repo_root/util/package-vm-image.sh" )" \
|
||||
"$temp_dir/image.qcow2" \
|
||||
"$env_image"
|
||||
|
||||
__big_log 33 'Done.'
|
||||
;;
|
||||
|
||||
start)
|
||||
if (( $# != 1 )); then
|
||||
__bad_usage
|
||||
fi
|
||||
|
||||
if podman container exists "$container_name"; then
|
||||
>&2 echo "Already started."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
__build_runtime
|
||||
|
||||
# launch VM
|
||||
|
||||
__log_and_run podman run \
|
||||
--name "$container_name" \
|
||||
--pull never \
|
||||
--runtime "$runtime" \
|
||||
--memory 8g \
|
||||
--rm -dit \
|
||||
-v "$temp_dir":/home/fedora/images:z \
|
||||
-v "$repo_root/out":/home/fedora/bin:z \
|
||||
"$env_image"
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
__extra_cleanup() {
|
||||
__log_and_run podman stop --time 0 "$container_name"
|
||||
}
|
||||
|
||||
# load test images onto VM
|
||||
|
||||
__exec() {
|
||||
__log_and_run podman exec "$container_name" --as fedora "$@"
|
||||
}
|
||||
|
||||
chmod a+rx "$temp_dir" # so user "fedora" in guest can access it
|
||||
|
||||
for image in "${TEST_IMAGES[@]}"; do
|
||||
__log_and_run podman pull "$image"
|
||||
__log_and_run podman save "$image" -o "$temp_dir/image.tar"
|
||||
|
||||
__exec cp /home/fedora/images/image.tar image.tar
|
||||
__exec podman load -i image.tar
|
||||
__exec sudo podman load -i image.tar
|
||||
__exec sudo docker load -i image.tar
|
||||
__exec rm image.tar
|
||||
|
||||
rm "$temp_dir/image.tar"
|
||||
done
|
||||
|
||||
__extra_cleanup() { :; }
|
||||
;;
|
||||
|
||||
restart)
|
||||
"$0" stop
|
||||
"$0" start
|
||||
;;
|
||||
|
||||
stop)
|
||||
if (( $# != 1 )); then
|
||||
__bad_usage
|
||||
fi
|
||||
|
||||
__log_and_run podman stop --ignore "$container_name"
|
||||
__log_and_run podman wait --ignore "$container_name"
|
||||
;;
|
||||
|
||||
run)
|
||||
if (( $# < 3 )); then
|
||||
__bad_usage
|
||||
fi
|
||||
|
||||
case "$2" in
|
||||
podman|rootful-podman|docker)
|
||||
engines=( "$2" )
|
||||
;;
|
||||
all)
|
||||
engines=( podman rootful-podman docker )
|
||||
;;
|
||||
*)
|
||||
__bad_usage
|
||||
;;
|
||||
esac
|
||||
|
||||
if (( $# == 3 )) && [[ "$3" == all ]]; then
|
||||
mapfile -d '' -t tests < <( find "$repo_root/tests/t" -type f -print0 | sort -z )
|
||||
else
|
||||
tests=( "${@:3}" )
|
||||
fi
|
||||
|
||||
if ! podman container exists "$container_name"; then
|
||||
>&2 echo "The test environment VM isn't running. Start it with:"
|
||||
>&2 echo " \$ $0 start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
__build_runtime
|
||||
|
||||
for t in "${tests[@]}"; do
|
||||
for engine in "${engines[@]}"; do
|
||||
|
||||
__big_log 33 'Running test %s under %s...' "$( __rel "$t" )" "$engine"
|
||||
|
||||
case "$engine" in
|
||||
podman)
|
||||
engine_cmd=( podman )
|
||||
runtime_in_env=/home/fedora/bin/crun-vm
|
||||
;;
|
||||
rootful-podman)
|
||||
engine_cmd=( sudo podman )
|
||||
runtime_in_env=/home/fedora/bin/crun-vm
|
||||
;;
|
||||
docker)
|
||||
engine_cmd=( sudo docker )
|
||||
runtime_in_env=crun-vm
|
||||
;;
|
||||
esac
|
||||
|
||||
# generate random label for containers created by test script
|
||||
label=$( mktemp --dry-run | xargs basename )
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
__engine() {
|
||||
if [[ "$1" == run ]]; then
|
||||
__log_and_run "${engine_cmd[@]}" run \
|
||||
--runtime "$runtime_in_env" \
|
||||
--pull never \
|
||||
--label "$label" \
|
||||
"${@:2}"
|
||||
else
|
||||
__log_and_run "${engine_cmd[@]}" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
__exec() {
|
||||
podman exec -i "$container_name" --as fedora "$@"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2088
|
||||
__exec mkdir "$label.temp" "$label.util"
|
||||
|
||||
# copy util scripts
|
||||
|
||||
for file in $repo_root/util/*; do
|
||||
contents=$( cat "$file" )
|
||||
path_in_vm=$label.util/$( basename "$file" )
|
||||
__exec "echo ${contents@Q} > $path_in_vm && chmod +x $path_in_vm"
|
||||
done
|
||||
|
||||
# run test
|
||||
|
||||
full_script="\
|
||||
set -o errexit -o pipefail -o nounset
|
||||
$(
|
||||
declare -p \
|
||||
TEST_IMAGES TEST_IMAGES_DEFAULT_USER TEST_IMAGES_DEFAULT_USER_HOME \
|
||||
engine_cmd runtime_in_env label start_time
|
||||
)
|
||||
$( declare -f __elapsed __engine __log_and_run __small_log )
|
||||
__skip() {
|
||||
exit 0
|
||||
}
|
||||
__log() {
|
||||
__small_log 36 \"\$@\"
|
||||
}
|
||||
TEMP_DIR=~/$label.temp
|
||||
UTIL_DIR=~/$label.util
|
||||
TEST_ID=$label
|
||||
ENGINE=$engine
|
||||
export RUST_BACKTRACE=1 RUST_LIB_BACKTRACE=1
|
||||
$( cat "$t" )\
|
||||
"
|
||||
|
||||
exit_code=0
|
||||
__exec <<< "$full_script" || exit_code=$?
|
||||
|
||||
# remove any leftover containers
|
||||
|
||||
__small_log 36 'Cleaning up...'
|
||||
|
||||
full_script="\
|
||||
set -o errexit -o pipefail -o nounset
|
||||
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' |
|
||||
xargs --no-run-if-empty ${engine_cmd[*]} stop --time 0 \
|
||||
>/dev/null 2>&1
|
||||
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' --all |
|
||||
xargs --no-run-if-empty ${engine_cmd[*]} rm --force \
|
||||
>/dev/null 2>&1 \
|
||||
|| true # avoid 'removal already in progress' docker errors
|
||||
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' --all |
|
||||
xargs --no-run-if-empty false # fail if containers still exist
|
||||
sudo rm -fr $label.temp $label.util
|
||||
"
|
||||
|
||||
__exec <<< "$full_script"
|
||||
|
||||
# report test result
|
||||
|
||||
if (( exit_code == 0 )); then
|
||||
__small_log 36 'Test succeeded.'
|
||||
else
|
||||
__small_log 36 'Test failed.'
|
||||
__big_log 31 'A test failed.'
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
done
|
||||
done
|
||||
|
||||
__big_log 32 'All tests succeeded.'
|
||||
;;
|
||||
|
||||
ssh)
|
||||
__log_and_run podman exec -it "$container_name" --as fedora "${@:2}"
|
||||
;;
|
||||
|
||||
*)
|
||||
__bad_usage
|
||||
;;
|
||||
esac
|
184
tests/run.rs
184
tests/run.rs
|
@ -1,184 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#![allow(clippy::items_after_test_module)]
|
||||
|
||||
use std::env;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use camino::Utf8Path;
|
||||
use test_case::test_matrix;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn simple_test_case(image: &str, home_dir: &str) -> TestCase {
|
||||
TestCase {
|
||||
run_args: vec![image.to_string(), "".to_string()],
|
||||
exec_user: Utf8Path::new(home_dir).file_name().unwrap().to_string(),
|
||||
test_script: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn complex_test_case(
|
||||
image: &str,
|
||||
home_dir: &str,
|
||||
cloud_init_and_ignition_prefix: &str,
|
||||
) -> TestCase {
|
||||
let cloud_init_and_ignition_prefix = match cloud_init_and_ignition_prefix {
|
||||
"" => "".to_string(),
|
||||
prefix => format!("{prefix}/"),
|
||||
};
|
||||
|
||||
TestCase {
|
||||
run_args: vec![
|
||||
"-h=my-test-vm".to_string(),
|
||||
format!("-v=./util:{home_dir}/util"),
|
||||
format!("-v=./README.md:{home_dir}/README.md:z,ro"), // "ro" is so qemu uses shared lock
|
||||
format!("--mount=type=tmpfs,dst={home_dir}/tmp"),
|
||||
image.to_string(),
|
||||
format!("--cloud-init={cloud_init_and_ignition_prefix}examples/cloud-init/config"),
|
||||
format!("--ignition={cloud_init_and_ignition_prefix}examples/ignition/config.ign"),
|
||||
],
|
||||
exec_user: Utf8Path::new(home_dir).file_name().unwrap().to_string(),
|
||||
test_script: format!(
|
||||
"
|
||||
mount -l | grep '^virtiofs-0 on {home_dir}/util type virtiofs'
|
||||
mount -l | grep '^tmpfs on {home_dir}/tmp type tmpfs'
|
||||
[[ -b ~/README.md ]]
|
||||
sudo grep 'This project is released under' ~/README.md
|
||||
"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test_matrix(
|
||||
// engines
|
||||
[
|
||||
Engine::Podman,
|
||||
Engine::Docker,
|
||||
],
|
||||
|
||||
// cases
|
||||
[
|
||||
simple_test_case("quay.io/containerdisks/fedora:39", "/home/fedora"),
|
||||
simple_test_case("quay.io/crun-vm/example-fedora-coreos:39", "/var/home/core"),
|
||||
|
||||
complex_test_case("quay.io/containerdisks/fedora:39", "/home/fedora", REPO_PATH),
|
||||
complex_test_case("quay.io/crun-vm/example-fedora-coreos:39", "/var/home/core", REPO_PATH),
|
||||
]
|
||||
)]
|
||||
#[test_matrix(
|
||||
// engines
|
||||
[
|
||||
Engine::Podman,
|
||||
],
|
||||
|
||||
// cases
|
||||
[
|
||||
complex_test_case("quay.io/containerdisks/fedora:39", "/home/fedora", ""),
|
||||
complex_test_case("quay.io/crun-vm/example-fedora-coreos:39", "/var/home/core", ""),
|
||||
]
|
||||
)]
|
||||
fn test_run(engine: Engine, case: TestCase) {
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
|
||||
let container_name = get_random_container_name();
|
||||
|
||||
// launch VM
|
||||
|
||||
let status = engine
|
||||
.command("run")
|
||||
.arg(format!("--name={}", container_name))
|
||||
.arg("--rm")
|
||||
.arg("--detach")
|
||||
.args(&case.run_args)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
assert!(status.success());
|
||||
|
||||
// run the test script
|
||||
|
||||
let result = (|| -> Result<()> {
|
||||
let mut exec_child = engine
|
||||
.command("exec")
|
||||
.arg("-i")
|
||||
.arg(&container_name)
|
||||
.arg(&case.exec_user)
|
||||
.arg("bash")
|
||||
.arg("-s")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
{
|
||||
let mut writer = BufWriter::new(exec_child.stdin.take().unwrap());
|
||||
writer.write_all("set -ex\n".as_bytes())?;
|
||||
writer.write_all("! command -v cloud-init || cloud-init status --wait\n".as_bytes())?;
|
||||
writer.write_all(case.test_script.as_bytes())?;
|
||||
writer.write_all("\n".as_bytes())?;
|
||||
writer.flush()?;
|
||||
// stdin is closed when writer is dropped
|
||||
}
|
||||
|
||||
match exec_child.wait()?.code().unwrap() {
|
||||
0 => Ok(()),
|
||||
n => Err(anyhow!("test script failed with exit code {n}")),
|
||||
}
|
||||
})();
|
||||
|
||||
// terminate the VM
|
||||
|
||||
let status = engine
|
||||
.command("stop")
|
||||
.arg(&container_name)
|
||||
.stdin(Stdio::null())
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
assert!(status.success());
|
||||
|
||||
result.unwrap();
|
||||
}
|
||||
|
||||
const BINARY_PATH: &str = env!("CARGO_BIN_EXE_crun-vm");
|
||||
const REPO_PATH: &str = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
struct TestCase {
|
||||
run_args: Vec<String>,
|
||||
exec_user: String,
|
||||
test_script: String,
|
||||
}
|
||||
|
||||
fn get_random_container_name() -> String {
|
||||
format!("crun-vm-test-{}", Uuid::new_v4())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum Engine {
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn command(self, subcommand: &str) -> Command {
|
||||
let engine = match self {
|
||||
Engine::Podman => "podman",
|
||||
Engine::Docker => "docker",
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(engine);
|
||||
cmd.arg(subcommand);
|
||||
|
||||
if subcommand == "run" {
|
||||
match self {
|
||||
Engine::Podman => cmd.arg(format!("--runtime={}", BINARY_PATH)),
|
||||
Engine::Docker => cmd.arg("--runtime=crun-vm"),
|
||||
};
|
||||
}
|
||||
|
||||
cmd.env("RUST_BACKTRACE", "1");
|
||||
cmd
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
image="${TEST_IMAGES[fedora-bootc]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[fedora-bootc]}"
|
||||
|
||||
__run() {
|
||||
__engine run --detach --name "$TEST_ID" "$image" --bootc-disk-size "$1"
|
||||
}
|
||||
|
||||
__run 1M
|
||||
! __engine exec "$TEST_ID" --as "$user"
|
||||
__engine rm --force "$TEST_ID"
|
||||
|
||||
__run 4G
|
||||
__engine exec "$TEST_ID" --as "$user"
|
||||
__engine rm --force "$TEST_ID"
|
|
@ -0,0 +1,36 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
for os in fedora fedora-bootc; do
|
||||
|
||||
image="${TEST_IMAGES[$os]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[$os]}"
|
||||
home="${TEST_IMAGES_DEFAULT_USER_HOME[$os]}"
|
||||
|
||||
cat >"$TEMP_DIR/user-data" <<-EOF
|
||||
#cloud-config
|
||||
write_files:
|
||||
- path: $home/file
|
||||
content: |
|
||||
hello
|
||||
EOF
|
||||
|
||||
cat >"$TEMP_DIR/meta-data" <<-EOF
|
||||
EOF
|
||||
|
||||
__engine run \
|
||||
--rm --detach \
|
||||
--name "$TEST_ID" \
|
||||
"$image" \
|
||||
--cloud-init "$TEMP_DIR"
|
||||
|
||||
__test() {
|
||||
__engine exec "$TEST_ID" --as "$user" "cmp $home/file <<< hello"
|
||||
}
|
||||
|
||||
__test
|
||||
__engine restart "$TEST_ID"
|
||||
__test
|
||||
|
||||
__engine stop "$TEST_ID"
|
||||
|
||||
done
|
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__engine run --detach --name "$TEST_ID" "${TEST_IMAGES[fedora]}" --emulated
|
||||
__engine exec "$TEST_ID" --as fedora
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
for os in "${!TEST_IMAGES[@]}"; do
|
||||
|
||||
image="${TEST_IMAGES[$os]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[$os]}"
|
||||
|
||||
# default hostname
|
||||
|
||||
id=$( __engine run --rm --detach --name "$TEST_ID-$os-default" "$image" )
|
||||
|
||||
__test() {
|
||||
__engine exec "$TEST_ID-$os-default" --as "$user" \
|
||||
"set -x && [[ \$( hostname ) == ${id::12} ]]"
|
||||
}
|
||||
|
||||
__test
|
||||
__engine restart "$TEST_ID-$os-default"
|
||||
__test
|
||||
|
||||
__engine stop --time 0 "$TEST_ID-$os-default"
|
||||
|
||||
# custom hostname
|
||||
|
||||
__engine run \
|
||||
--rm --detach \
|
||||
--name "$TEST_ID-$os-custom" \
|
||||
--hostname my-test-vm \
|
||||
"$image"
|
||||
|
||||
__test() {
|
||||
__engine exec "$TEST_ID-$os-custom" --as "$user" \
|
||||
"set -x && [[ \$( hostname ) == my-test-vm ]]"
|
||||
}
|
||||
|
||||
__test
|
||||
__engine restart "$TEST_ID-$os-custom"
|
||||
__test
|
||||
|
||||
__engine stop --time 0 "$TEST_ID-$os-custom"
|
||||
|
||||
done
|
|
@ -0,0 +1,39 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
image="${TEST_IMAGES[coreos]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[coreos]}"
|
||||
home="${TEST_IMAGES_DEFAULT_USER_HOME[coreos]}"
|
||||
|
||||
cat >"$TEMP_DIR/config.ign" <<EOF
|
||||
{
|
||||
"ignition": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"storage": {
|
||||
"files": [
|
||||
{
|
||||
"path": "$home/file",
|
||||
"mode": 420,
|
||||
"overwrite": true,
|
||||
"contents": {
|
||||
"source": "data:,hello%0A"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
__engine run \
|
||||
--rm --detach \
|
||||
--name "$TEST_ID" \
|
||||
"$image" \
|
||||
--ignition "$TEMP_DIR/config.ign"
|
||||
|
||||
__test() {
|
||||
__engine exec "$TEST_ID" --as "$user" "cmp $home/file <<< hello"
|
||||
}
|
||||
|
||||
__test
|
||||
__engine restart "$TEST_ID"
|
||||
__test
|
|
@ -0,0 +1,46 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
for os in "${!TEST_IMAGES[@]}"; do
|
||||
|
||||
image="${TEST_IMAGES[$os]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[$os]}"
|
||||
home="${TEST_IMAGES_DEFAULT_USER_HOME[$os]}"
|
||||
|
||||
echo hello > "$TEMP_DIR/file"
|
||||
|
||||
__engine run \
|
||||
--rm --detach \
|
||||
--name "$TEST_ID-$os" \
|
||||
--volume "$TEMP_DIR/file:$home/file:z" \
|
||||
--volume "$TEMP_DIR:$home/dir:z" \
|
||||
--mount "type=tmpfs,dst=$home/tmp" \
|
||||
"$image"
|
||||
|
||||
__test() {
|
||||
__engine exec "$TEST_ID-$os" --as "$user"
|
||||
|
||||
__engine exec "$TEST_ID-$os" --as "$user" "
|
||||
set -e
|
||||
[[ -b $home/file ]]
|
||||
sudo cmp -n 6 $home/file <<< hello
|
||||
"
|
||||
|
||||
__engine exec "$TEST_ID-$os" --as "$user" "
|
||||
set -e
|
||||
mount -l | grep '^virtiofs-0 on $home/dir type virtiofs'
|
||||
[[ -d $home/dir ]]
|
||||
sudo cmp $home/dir/file <<< hello
|
||||
"
|
||||
|
||||
__engine exec "$TEST_ID-$os" --as "$user" "
|
||||
mount -l | grep '^tmpfs on $home/tmp type tmpfs'
|
||||
"
|
||||
}
|
||||
|
||||
__test
|
||||
__engine restart "$TEST_ID-$os"
|
||||
__test
|
||||
|
||||
__engine stop --time 0 "$TEST_ID-$os"
|
||||
|
||||
done
|
|
@ -0,0 +1,37 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
if [[ "$ENGINE" == docker ]]; then
|
||||
# docker doesn't support --rootfs
|
||||
__skip
|
||||
fi
|
||||
|
||||
"$UTIL_DIR/extract-vm-image.sh" "${TEST_IMAGES[fedora]}" "$TEMP_DIR/image"
|
||||
|
||||
# Usage: __run <crun_vm_option> [<extra_podman_options...>]
|
||||
__run() {
|
||||
__engine run --rm --detach --name "$TEST_ID" "${@:2}" --rootfs "$TEMP_DIR" "$1"
|
||||
}
|
||||
|
||||
# Usage: __test <crun_vm_option> <condition>
|
||||
__test() {
|
||||
id=$( __run "$1" )
|
||||
__engine exec "$TEST_ID" --as fedora "$2"
|
||||
__engine stop "$TEST_ID"
|
||||
|
||||
if [[ "$ENGINE" != rootful-podman ]]; then
|
||||
# ensure user that invoked `engine run` can delete crun-vm state
|
||||
rm -r "$TEMP_DIR/crun-vm-$id"
|
||||
fi
|
||||
}
|
||||
|
||||
__test "" '[[ ! -e i-was-here ]] && touch i-was-here'
|
||||
__test --persistent '[[ ! -e i-was-here ]] && touch i-was-here'
|
||||
__test --persistent '[[ -e i-was-here ]]'
|
||||
__test "" '[[ -e i-was-here ]]'
|
||||
|
||||
# ensure --persistent is rejected iff the rootfs is configured as read-only
|
||||
|
||||
! RUST_LIB_BACKTRACE=0 __run --persistent --read-only
|
||||
|
||||
__run "" --read-only
|
||||
__engine exec "$TEST_ID" --as fedora
|
|
@ -0,0 +1,33 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
trap '__engine stop "$TEST_ID"' EXIT
|
||||
|
||||
for os in fedora fedora-bootc; do
|
||||
|
||||
image="${TEST_IMAGES[$os]}"
|
||||
user="${TEST_IMAGES_DEFAULT_USER[$os]}"
|
||||
|
||||
__engine run --rm --detach --name "$TEST_ID" --publish 127.0.0.1::8000 "$image"
|
||||
|
||||
endpoint=$( __engine port "$TEST_ID" | tee /dev/stderr | cut -d' ' -f3 )
|
||||
|
||||
__engine exec "$TEST_ID" --as "$user"
|
||||
|
||||
__log 'Ensuring curl fails...'
|
||||
! curl "$endpoint" 2>/dev/null
|
||||
|
||||
__engine exec "$TEST_ID" --as "$user" python -m http.server &
|
||||
|
||||
__log 'Ensuring curl succeeds...'
|
||||
|
||||
i=0
|
||||
max_tries=30
|
||||
|
||||
until [[ "$( curl "$endpoint" 2>/dev/null )" == '<!DOCTYPE HTML>'* ]]; do
|
||||
(( ++i < max_tries ))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
__engine stop "$TEST_ID"
|
||||
|
||||
done
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__engine run \
|
||||
--detach \
|
||||
--name "$TEST_ID" \
|
||||
"${TEST_IMAGES[fedora]}" \
|
||||
--random-ssh-key-pair
|
||||
|
||||
__engine exec "$TEST_ID" --as fedora
|
||||
__engine restart "$TEST_ID"
|
||||
__engine exec "$TEST_ID" --as fedora
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__engine run --detach --name "$TEST_ID" "${TEST_IMAGES[fedora]}"
|
||||
|
||||
__engine exec "$TEST_ID" --as fedora '[[ ! -e i-was-here ]] && touch i-was-here'
|
||||
|
||||
for (( i = 0; i < 2; ++i )); do
|
||||
|
||||
__engine stop "$TEST_ID"
|
||||
__engine start "$TEST_ID"
|
||||
|
||||
__engine exec "$TEST_ID" --as fedora '[[ -e i-was-here ]]'
|
||||
|
||||
done
|
|
@ -29,6 +29,8 @@ elif (( ${#candidates[@]} > 1 )); then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
>&2 echo "Extracting ${candidates[0]} to ${output_vm_image_file}"
|
||||
|
||||
tar -C "${temp_dir}" -xf "${temp_dir}/root.tar" "${candidates[0]}"
|
||||
chmod +w "${temp_dir}/${candidates[0]}"
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
set -o errexit -o pipefail -o nounset
|
||||
|
||||
script_dir="$( dirname "$0" | xargs readlink -e )"
|
||||
repo_root=$script_dir/../..
|
||||
|
||||
__minikube() {
|
||||
minikube -p=crun-vm-example "$@"
|
||||
|
@ -25,7 +26,7 @@ __apt_get() {
|
|||
|
||||
# build runtime
|
||||
|
||||
cargo build
|
||||
make -C "$repo_root"
|
||||
|
||||
# create minikube cluster
|
||||
|
||||
|
@ -57,7 +58,7 @@ __ssh 'tee --append /etc/crio/crio.conf <<EOF
|
|||
runtime_path = "/usr/local/bin/crun-vm"
|
||||
EOF'
|
||||
|
||||
__cp "${script_dir}/../../target/debug/crun-vm" /usr/local/bin/crun-vm
|
||||
__cp "$repo_root/out/crun-vm" /usr/local/bin/crun-vm
|
||||
__ssh chmod +x /usr/local/bin/crun-vm
|
||||
|
||||
# reload cluster so that the new runtime is picked up
|
|
@ -12,9 +12,11 @@ fi
|
|||
vm_image_file=$1
|
||||
container_image_tag=$2
|
||||
|
||||
image_path_in_container=/$( basename "${vm_image_file}" )
|
||||
abs_vm_image_file=$( readlink -e "${vm_image_file}" )
|
||||
image_path_in_container=/disk/$( basename "${vm_image_file}" )
|
||||
|
||||
podman image build --file=- --tag="${container_image_tag}" . <<EOF
|
||||
podman image build --file=- --tag="${container_image_tag}" / <<EOF
|
||||
FROM scratch
|
||||
COPY ${vm_image_file@Q} ${image_path_in_container@Q}
|
||||
COPY ${abs_vm_image_file@Q} ${image_path_in_container@Q}
|
||||
ENTRYPOINT ["no-entrypoint"]
|
||||
EOF
|
||||
|
|
Loading…
Reference in New Issue