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
|
- name: Release
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
name: ${{ github.event.inputs.name }}
|
name: ${{ github.event.inputs.name }}
|
||||||
tag_name: ${{ github.event.inputs.name }}
|
tag_name: ${{ github.event.inputs.name }}
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
|
/out
|
||||||
/target
|
/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:
|
# See the documentation for more information:
|
||||||
# https://packit.dev/docs/configuration/
|
# https://packit.dev/docs/configuration/
|
||||||
|
|
||||||
specfile_path: rpm/crun-vm.spec
|
|
||||||
|
|
||||||
srpm_build_deps:
|
|
||||||
- cargo
|
|
||||||
- make
|
|
||||||
- openssl-devel
|
|
||||||
|
|
||||||
jobs:
|
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
|
- &tests
|
||||||
- job: copr_build
|
job: tests
|
||||||
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
|
|
||||||
trigger: pull_request
|
trigger: pull_request
|
||||||
skip_build: true
|
skip_build: true
|
||||||
|
enable_net: true
|
||||||
targets:
|
targets:
|
||||||
- fedora-all-aarch64
|
# TODO: Replace these three with fedora-all-x86_64 once Fedora 38 is gone.
|
||||||
- fedora-all-x86_64
|
- fedora-rawhide-x86_64
|
||||||
identifier: unit_test
|
- fedora-40-x86_64
|
||||||
tmt_plan: "/plans/unit_test"
|
- fedora-39-x86_64
|
||||||
|
identifier: podman
|
||||||
|
tmt_plan: /tests/podman
|
||||||
|
|
||||||
# Validate test
|
- <<: *tests
|
||||||
- job: tests
|
identifier: rootful-podman
|
||||||
trigger: pull_request
|
tmt_plan: /tests/rootful-podman
|
||||||
skip_build: true
|
|
||||||
targets:
|
- <<: *tests
|
||||||
# Only need to test on one target
|
identifier: docker
|
||||||
- fedora-latest-stable-x86_64
|
tmt_plan: /tests/docker
|
||||||
identifier: validate_test
|
|
||||||
tmt_plan: "/plans/validate_test"
|
|
||||||
|
|
|
@ -4,36 +4,36 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.2"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.5"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.79"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.2"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
|
@ -46,15 +46,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "camino"
|
name = "camino"
|
||||||
version = "1.1.6"
|
version = "1.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
|
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -63,10 +63,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "cfg_aliases"
|
||||||
version = "4.4.18"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
@ -74,32 +80,52 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.4.18"
|
version = "4.5.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim 0.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.4.7"
|
version = "4.5.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.6.0"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
|
@ -112,7 +138,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crun-vm"
|
name = "crun-vm"
|
||||||
version = "0.1.3"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"camino",
|
"camino",
|
||||||
|
@ -129,9 +155,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"test-case",
|
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid",
|
|
||||||
"xml-rs",
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -147,9 +171,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.14.4"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
|
@ -157,58 +181,58 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.14.4"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim 0.10.0",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.14.4"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.12.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_macro",
|
"derive_builder_macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder_core"
|
name = "derive_builder_core"
|
||||||
version = "0.12.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder_macro"
|
name = "derive_builder_macro"
|
||||||
version = "0.12.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -243,27 +267,16 @@ dependencies = [
|
||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "getset"
|
name = "getset"
|
||||||
version = "0.1.2"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
|
checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error",
|
"proc-macro-error2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -274,15 +287,15 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.4"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
|
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
|
@ -301,9 +314,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.2"
|
version = "2.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -311,36 +324,36 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.10"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.153"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "liboci-cli"
|
name = "liboci-cli"
|
||||||
version = "0.3.2"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2fb8daab7180d25db1086b0fb73a0de935f3cb55d56d60e946ed84bec2a3ffd"
|
checksum = "731e7d86b6f06717b9b365895f707b229fc755e45c0122b8ac1de9c0f0cf1547"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.1"
|
version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minidom"
|
name = "minidom"
|
||||||
|
@ -353,20 +366,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.27.1"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -374,64 +388,66 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oci-spec"
|
name = "oci-spec"
|
||||||
version = "0.6.4"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8384f8eff13954bafafba991f1910779020456f9694de25e81a13da5b7de6309"
|
checksum = "57e9beda9d92fac7bf4904c34c83340ef1024159faee67179a04e0277523da33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"const_format",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"getset",
|
"getset",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error-attr2"
|
||||||
version = "1.0.4"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error-attr"
|
name = "proc-macro-error2"
|
||||||
version = "1.0.4"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"version_check",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.78"
|
version = "1.0.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.3"
|
version = "1.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -441,9 +457,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.5"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -452,15 +468,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed"
|
name = "rust-embed"
|
||||||
version = "6.8.1"
|
version = "8.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
|
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rust-embed-impl",
|
"rust-embed-impl",
|
||||||
"rust-embed-utils",
|
"rust-embed-utils",
|
||||||
|
@ -469,27 +485,33 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed-impl"
|
name = "rust-embed-impl"
|
||||||
version = "6.8.1"
|
version = "8.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
|
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rust-embed-utils",
|
"rust-embed-utils",
|
||||||
"syn 2.0.48",
|
"syn",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed-utils"
|
name = "rust-embed-utils"
|
||||||
version = "7.8.1"
|
version = "8.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
|
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rxml"
|
name = "rxml"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
@ -509,9 +531,9 @@ checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.16"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
|
@ -524,40 +546,41 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.196"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.196"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.113"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.9.31"
|
version = "0.9.34+deprecated"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
|
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
@ -601,78 +624,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "strsim"
|
||||||
version = "1.0.109"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"rustversion",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.48"
|
version = "2.0.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.56"
|
version = "2.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.56"
|
version = "2.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -688,10 +692,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unicode-xid"
|
||||||
version = "0.2.10"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "urlencoding"
|
name = "urlencoding"
|
||||||
|
@ -699,15 +709,6 @@ version = "2.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uuid"
|
|
||||||
version = "1.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -716,51 +717,23 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file",
|
"same-file",
|
||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -772,13 +745,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm",
|
||||||
|
@ -787,48 +761,54 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.0"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.8.19"
|
version = "0.8.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]
|
[package]
|
||||||
name = "crun-vm"
|
name = "crun-vm"
|
||||||
version = "0.1.3"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.74"
|
rust-version = "1.74"
|
||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
|
|
||||||
[dependencies.anyhow]
|
[dependencies.anyhow]
|
||||||
version = "1.0.46"
|
version = "1.0.98"
|
||||||
|
|
||||||
[dependencies.camino]
|
[dependencies.camino]
|
||||||
version = "1.1"
|
version = "1.1.10"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.0"
|
version = "4.5.40"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["cargo", "derive", "std"]
|
features = ["cargo", "derive", "std"]
|
||||||
|
|
||||||
[dependencies.home]
|
[dependencies.home]
|
||||||
version = "0.5.0"
|
version = "0.5.9"
|
||||||
|
|
||||||
[dependencies.lazy_static]
|
[dependencies.lazy_static]
|
||||||
version = "1.1"
|
version = "1.5.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.liboci-cli]
|
[dependencies.liboci-cli]
|
||||||
version = "0.3.0"
|
version = "0.5.3"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.minidom]
|
[dependencies.minidom]
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
|
|
||||||
[dependencies.nix]
|
[dependencies.nix]
|
||||||
version = "0.27.0"
|
version = "0.30.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["fs", "mount"]
|
features = ["fs", "mount"]
|
||||||
|
|
||||||
[dependencies.num_cpus]
|
[dependencies.num_cpus]
|
||||||
version = "1.0"
|
version = "1.17.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.oci-spec]
|
[dependencies.oci-spec]
|
||||||
version = "0.6.0"
|
version = "0.8.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["runtime"]
|
features = ["runtime"]
|
||||||
|
|
||||||
[dependencies.regex]
|
[dependencies.regex]
|
||||||
version = "1.0"
|
version = "1.10.6"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["std"]
|
features = ["std"]
|
||||||
|
|
||||||
[dependencies.rust-embed]
|
[dependencies.rust-embed]
|
||||||
version = "6.0"
|
version = "8.7.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["debug-embed"]
|
features = ["debug-embed"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0.219"
|
||||||
|
|
||||||
[dependencies.serde_json]
|
[dependencies.serde_json]
|
||||||
version = "1.0"
|
version = "1.0.140"
|
||||||
|
|
||||||
[dependencies.serde_yaml]
|
[dependencies.serde_yaml]
|
||||||
version = "0.9.0"
|
version = "0.9.34"
|
||||||
|
|
||||||
[dependencies.urlencoding]
|
[dependencies.urlencoding]
|
||||||
version = "2.0"
|
version = "2.1.3"
|
||||||
|
|
||||||
[dependencies.xml-rs]
|
[dependencies.xml-rs]
|
||||||
version = "0.8.0"
|
version = "0.8.27"
|
||||||
default-features = false
|
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
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
# 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
|
|
||||||
|
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BINDIR ?= $(PREFIX)/bin
|
|
||||||
|
|
||||||
|
CARGO ?= cargo
|
||||||
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
|
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
|
binpath := $(DESTDIR)/$(PREFIX)/bin/crun-vm
|
||||||
# build w/ debugging features.
|
manpath := $(DESTDIR)/$(PREFIX)/share/man/man1/crun-vm.1.gz
|
||||||
debug ?=
|
|
||||||
|
|
||||||
# Set path to cargo executable
|
all: out/crun-vm out/crun-vm.1.gz
|
||||||
CARGO ?= cargo
|
|
||||||
|
|
||||||
# All complication artifacts, including dependencies and intermediates
|
.PHONY: out/crun-vm
|
||||||
# will be stored here, for all architectures. Use a non-default name
|
out/crun-vm:
|
||||||
# since the (default) 'target' is used/referenced ambiguously in many
|
mkdir -p $(@D)
|
||||||
# places in the tool-chain (including 'make' itself).
|
$(CARGO) build --release
|
||||||
CARGO_TARGET_DIR ?= targets
|
cp target/release/crun-vm $@
|
||||||
export CARGO_TARGET_DIR # 'cargo' is sensitive to this env. var. value.
|
|
||||||
|
|
||||||
ifdef debug
|
out/crun-vm.1.gz: docs/5-crun-vm.1.ronn
|
||||||
$(info debug is $(debug))
|
mkdir -p $(@D)
|
||||||
# These affect both $(CARGO_TARGET_DIR) layout and contents
|
ronn --pipe --roff $< | gzip > $@
|
||||||
# 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,)
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin
|
rm -fr out target
|
||||||
if [ "$(CARGO_TARGET_DIR)" = "targets" ]; then rm -rf targets; fi
|
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install: out/crun-vm install-man
|
||||||
install ${SELINUXOPT} -D -m0755 bin/crun-vm $(DESTDIR)/$(BINDIR)/crun-vm
|
install ${SELINUXOPT} -D -m 0755 $< $(binpath)
|
||||||
|
|
||||||
|
.PHONY: install-man
|
||||||
|
install-man: out/crun-vm.1.gz
|
||||||
|
install ${SELINUXOPT} -D -m 0644 $< $(manpath)
|
||||||
|
|
||||||
.PHONY: uninstall
|
.PHONY: uninstall
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f $(DESTDIR)/$(BINDIR)/crun-vm
|
rm -f $(binpath) $(manpath)
|
||||||
|
|
||||||
#.PHONY: unit
|
.PHONY: lint
|
||||||
unit: $(CARGO_TARGET_DIR)
|
lint:
|
||||||
$(SHELL) test.sh podman
|
tests/lint.sh
|
||||||
|
|
||||||
#.PHONY: code_coverage
|
.PHONY: test
|
||||||
code_coverage: $(CARGO_TARGET_DIR)
|
test:
|
||||||
# Downloads tarpaulin only if same version is not present on local
|
tests/env.sh build
|
||||||
$(CARGO) install cargo-tarpaulin
|
tests/env.sh start
|
||||||
$(CARGO) tarpaulin -v
|
tests/env.sh run all all
|
||||||
|
|
||||||
.PHONY: validate
|
|
||||||
validate: $(CARGO_TARGET_DIR)
|
|
||||||
$(SHELL) lint.sh
|
|
||||||
|
|
98
README.md
98
README.md
|
@ -1,47 +1,107 @@
|
||||||
# The crun-vm OCI Runtime
|
# The crun-vm OCI Runtime
|
||||||
|
|
||||||
**crun-vm** is an [OCI Runtime] that enables [Podman], [Docker], and
|
**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**.
|
- Run **VMs** as easily as you run **containers**.
|
||||||
- Manage containers and VMs **together** using the **same** standard tooling.
|
- Manage containers and VMs **together** using the **same** standard tooling.
|
||||||
- **No need** for in-depth knowledge on virtualization technologies like libvirt
|
|
||||||
or KubeVirt.
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/example.gif" width="680" />
|
<img src="docs/example.gif" width="680" />
|
||||||
</p>
|
</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
|
### 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.
|
- Control VM **CPU** and **memory** allocation.
|
||||||
- Provide **cloud-init** and **Ignition** configurations to VMs.
|
- Pass **cloud-init** or **Ignition** configs to VMs.
|
||||||
- **Mount directories** into VMs.
|
- Mount **directories** into VMs.
|
||||||
- Pass **block devices** through to VMs.
|
- Pass **block devices** through to VMs.
|
||||||
- Expose **qcow2 files** and other **disk images** to VMs as block devices.
|
- Expose additional **disk images** to VMs.
|
||||||
- Pass **vfio-pci** and **mediated vfio-pci** devices through to VMs.
|
|
||||||
- **Forward ports** from the host to VMs.
|
- **Forward ports** from the host to VMs.
|
||||||
- **`podman exec`**/**`docker exec`**/**`kubectl exec`** into VMs.
|
- **`podman|docker|kubectl exec`** into VMs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
1. [Installing crun-vm](docs/1-installing.md)
|
1. [Installing crun-vm](docs/1-installing.md)
|
||||||
2. [Using crun-vm as a Podman or Docker runtime](docs/2-podman-docker.md)
|
2. [Running VMs with **Podman** or **Docker**](docs/2-podman-docker.md)
|
||||||
3. [Using crun-vm as a Kubernetes runtime](docs/3-kubernetes.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
|
### License
|
||||||
|
|
||||||
This project is released under the GPL 2.0 (or later) license. See
|
This project is released under the GPL 2.0 (or later) license. See
|
||||||
[LICENSE](LICENSE).
|
[LICENSE](LICENSE).
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
[bootable container]: https://containers.github.io/bootable
|
||||||
[Docker]: https://www.docker.com/
|
[Docker]: https://www.docker.com/
|
||||||
[Kubernetes]: https://kubernetes.io/
|
[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
|
[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
|
# 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
|
```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
|
### For Podman
|
||||||
$ dnf install cargo
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
### For Docker
|
||||||
$ cp target/debug/crun-vm /usr/local/bin/
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
```json
|
||||||
`/etc/containers/containers.conf`:
|
{
|
||||||
|
"runtimes": {
|
||||||
> For rootless Podman, you can instead use
|
"crun-vm": {
|
||||||
> `${XDG_CONFIG_PATH}/containers/containers.conf`, where
|
"path": "/usr/bin/crun-vm"
|
||||||
> `$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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Reload the `docker` service for the new configuration to take effect:
|
2. Reload the `docker` service for the new configuration to take effect:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ service docker reload
|
$ service docker reload
|
||||||
```
|
```
|
||||||
|
|
||||||
With Podman, it is possible to use crun-vm without installing it, *i.e.*,
|
Commands like `docker create` and `docker run` can then be made to use the
|
||||||
performing only steps 1–3 above. In this case, instead of setting the runtime
|
crun-vm runtime by passing them the `--runtime crun-vm` option.
|
||||||
with `--runtime crun-vm`, specify an absolute path to the runtime binary:
|
|
||||||
`--runtime "$PWD"/target/debug/crun-vm`.
|
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
|
Here, we outline how to use crun-vm to run VMs using the Podman or Docker
|
||||||
below use `podman`, but unless otherwise stated you can simply replace it with
|
container engines. See [crun-vm(1)] for a full reference and additional details
|
||||||
`docker`.
|
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
|
## 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
|
```console
|
||||||
$ mkdir my-vm-image
|
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40
|
||||||
$ 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
|
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
|
The VM console should take over your terminal. This VM image has no users that
|
||||||
> `--rootfs` flag; see the next section for a Docker-compatible way of running
|
you may log in as using a password, so although you can interact with the VM's
|
||||||
> VM images.
|
login screen, you will be unable to access a command prompt for now. To abort
|
||||||
|
the VM, press `ctrl-]`.
|
||||||
```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-]`.
|
|
||||||
|
|
||||||
You can also detach from the VM without terminating it by pressing `ctrl-p,
|
You can also detach from the VM without terminating it by pressing `ctrl-p,
|
||||||
ctrl-q`. Afterwards, reattach by running:
|
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
|
still observe its console. Note that pressing `ctrl-]` will have no effect, but
|
||||||
you can always use the following command to terminate the VM:
|
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
|
```container
|
||||||
$ podman stop --latest
|
$ podman stop --latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Changes made by the VM to its image are by default not persisted in the original
|
### From VM image files
|
||||||
image file. This can be changed by passing in the non-standard option
|
|
||||||
`--persistent` *after* the `--rootfs` option:
|
> 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
|
```console
|
||||||
$ podman run \
|
$ 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
|
||||||
--runtime crun-vm \
|
|
||||||
-it --rm \
|
$ podman run --runtime crun-vm -it --rootfs my-vm-image/
|
||||||
--rootfs my-vm-image \
|
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
|
||||||
--persistent
|
[...]
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
### From bootable containers
|
||||||
>
|
|
||||||
> When using `--persistent`, make sure that the image file is never
|
|
||||||
> simultaneously used by another process or VM, otherwise **data corruption may
|
|
||||||
> occur**.
|
|
||||||
|
|
||||||
### From VM image files packaged into container images
|
crun-vm can also launch VMs from [bootc bootable container images], which are
|
||||||
|
containers that package a full operating system:
|
||||||
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:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
|
||||||
--runtime crun-vm \
|
Converting quay.io/crun-vm/example-fedora-bootc:40 into a VM image...
|
||||||
-it --rm \
|
[...]
|
||||||
quay.io/containerdisks/fedora:39 \
|
Caching VM image as a containerdisk...
|
||||||
"" # unused, but must specify command because container image does not
|
[...]
|
||||||
|
Booting VM...
|
||||||
|
[...]
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use `util/package-vm-image.sh` to easily package a VM image into a
|
crun-vm generates a VM image from the bootable container and then boots it. The
|
||||||
container image, and `util/extract-vm-image.sh` to extract a VM image contained
|
generated VM image is packaged as a containerdisk and cached in the host's
|
||||||
in a container image.
|
container storage, so that subsequent runs will boot faster:
|
||||||
|
|
||||||
Note that flag `--persistent` has no effect when running VMs from container
|
```console
|
||||||
images.
|
$ 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
|
### cloud-init
|
||||||
|
|
||||||
In the examples above, you were able to boot the VM but not to log in. To fix
|
You can provide a full [cloud-init] NoCloud configuration to a VM by passing in
|
||||||
this and do other first-boot customization, you can provide a [cloud-init]
|
the crun-vm specific option [`--cloud-init`] *after* the image specification:
|
||||||
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`.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ ls examples/cloud-init/config/
|
$ ls my-cloud-init-config/
|
||||||
meta-data user-data vendor-data
|
meta-data user-data
|
||||||
|
|
||||||
$ podman run \
|
$ cat my-cloud-init-config/meta-data # empty
|
||||||
--runtime crun-vm \
|
|
||||||
-it --rm \
|
|
||||||
quay.io/containerdisks/fedora:39 \
|
|
||||||
--cloud-init examples/cloud-init/config
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to log in with the default `fedora` username and password
|
$ cat my-cloud-init-config/user-data
|
||||||
`pass`.
|
#cloud-config
|
||||||
|
write_files:
|
||||||
|
- path: $home/file
|
||||||
|
content: |
|
||||||
|
hello
|
||||||
|
|
||||||
Alternatively, you can set the default user's password with the `--password`
|
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 \
|
||||||
option:
|
--cloud-init $PWD/my-cloud-init-config/ # path must be absolute
|
||||||
|
|
||||||
```console
|
|
||||||
$ podman run \
|
|
||||||
--runtime crun-vm \
|
|
||||||
-it --rm \
|
|
||||||
quay.io/containerdisks/fedora:39 \
|
|
||||||
--password pass
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ignition
|
### Ignition
|
||||||
|
|
||||||
Similarly, you can provide an [Ignition] configuration to the VM by passing in
|
You can also provide an [Ignition] configuration to a VM using the crun-vm
|
||||||
the `--ignition` option:
|
specific [`--ignition`] option:
|
||||||
|
|
||||||
> For this command to work with Docker, you must provide an absolute path to
|
|
||||||
> `--ignition`.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ cat my-ignition-config.ign
|
||||||
--runtime crun-vm \
|
{
|
||||||
-it --rm \
|
"ignition": {
|
||||||
quay.io/crun-vm/example-fedora-coreos:39 \
|
"version": "3.0.0"
|
||||||
--ignition examples/ignition/config.ign
|
},
|
||||||
|
"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
|
## Interacting with VMs
|
||||||
`pass`.
|
|
||||||
|
|
||||||
Note that the `--password` option requires cloud-init support and doesn't work
|
### Exec'ing into VMs
|
||||||
if the VM uses Ignition.
|
|
||||||
|
|
||||||
## SSH'ing into the VM
|
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:
|
||||||
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.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm --detach quay.io/containerdisks/fedora:40
|
||||||
--runtime crun-vm \
|
|
||||||
--detach --rm \
|
|
||||||
quay.io/containerdisks/fedora:39 \
|
|
||||||
""
|
|
||||||
8068a2c180e0f4bf494f5e0baa37d9f13a9810f76b361c0771b73666e47ec383
|
8068a2c180e0f4bf494f5e0baa37d9f13a9810f76b361c0771b73666e47ec383
|
||||||
|
|
||||||
$ podman exec --latest fedora whoami
|
$ podman exec --latest whoami
|
||||||
fedora
|
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 ~]$
|
[fedora@8068a2c180e0 ~]$
|
||||||
```
|
```
|
||||||
|
|
||||||
With cloud-init, the default user can vary between VM images. With Ignition,
|
When a VM supports cloud-init, `authorized_keys` is automatically set up to
|
||||||
`core` is considered to be the default user. In both cases, if the SSH server
|
allow SSH access by podman-exec for users `root` and the default user as set in
|
||||||
allows password authentication, you should also be able to log in as any other
|
the image's cloud-init configuration. With Ignition, this is set up for users
|
||||||
user.
|
`root` and `core`.
|
||||||
|
|
||||||
The `fedora` argument to podman-exec above, which would typically correspond to
|
### Port forwarding
|
||||||
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.
|
|
||||||
|
|
||||||
If you actually just want to exec into the container in which the VM is running
|
You can use podman-run's standard `-p`/`--publish` option to enable TCP and/or
|
||||||
(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
|
|
||||||
UDP port forwarding:
|
UDP port forwarding:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
|
||||||
--runtime crun-vm \
|
|
||||||
--detach --rm \
|
|
||||||
-p 8000:80 \
|
|
||||||
quay.io/crun-vm/example-http-server:latest \
|
|
||||||
""
|
|
||||||
36c8705482589cfc4336a03d3802e7699f5fb228123d18e693488ac7b80116d1
|
36c8705482589cfc4336a03d3802e7699f5fb228123d18e693488ac7b80116d1
|
||||||
|
|
||||||
$ curl localhost:8000
|
$ 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
|
### Directories
|
||||||
|
|
||||||
Bind mounting directories into the VM is supported:
|
It is also possible to bind mount directories into a VM:
|
||||||
|
|
||||||
> [!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.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm -it \
|
||||||
--runtime crun-vm \
|
|
||||||
-it --rm \
|
|
||||||
-v ./util:/home/fedora/util:z \
|
-v ./util:/home/fedora/util:z \
|
||||||
quay.io/containerdisks/fedora:39 \
|
quay.io/containerdisks/fedora:40
|
||||||
--password pass
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If the VM supports cloud-init or Ignition, the volume will automatically be
|
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
|
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:
|
according to the order the `-v`/`--volume` or `--mount` flags where given in:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ mount -t virtiofs virtiofs-<index> /home/fedora/util
|
$ 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
|
### Block devices
|
||||||
|
|
||||||
If cloud-init or Ignition are supported by the VM, it is possible to pass block
|
If cloud-init or Ignition are supported by a VM, it is possible to pass block
|
||||||
devices through to it at a specific path using podman-run's `--device` flag
|
devices through to it and make them appear at a specific path using podman-run's
|
||||||
(this example assumes `/dev/ram0` to exist and to be accessible by the current
|
`--device` flag. For instance, assuming `/dev/ram0` exists on the host and is
|
||||||
user):
|
accessible by the current user:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm -it \
|
||||||
--runtime crun-vm \
|
|
||||||
-it --rm \
|
|
||||||
--device /dev/ram0:/home/fedora/my-disk \
|
--device /dev/ram0:/home/fedora/my-disk \
|
||||||
quay.io/containerdisks/fedora:39 \
|
quay.io/containerdisks/fedora:40
|
||||||
--password pass
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use the more powerful `--blockdev
|
You can also use the more powerful, crun-vm specific [`--blockdev`]
|
||||||
source=<path>,target=<path>,format=<fmt>` custom option to this effect. This
|
`source=<path>,target=<path>,format=<fmt>` option to this effect. This option
|
||||||
option also allows you specify a regular file as the source, and the source may
|
also allows you to specify a regular file as the source, and the source may be
|
||||||
be in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`,
|
in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`, raw
|
||||||
raw format is assumed):
|
format is assumed):
|
||||||
|
|
||||||
> For this command to work with Docker, you must provide absolute paths to
|
|
||||||
> `--blockdev`.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ podman run \
|
$ podman run --runtime crun-vm -it \
|
||||||
--runtime crun-vm \
|
quay.io/containerdisks/fedora:40 \
|
||||||
-it --rm \
|
--blockdev source=$PWD/my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2 # paths must be absolute
|
||||||
quay.io/containerdisks/fedora:39 \
|
|
||||||
--password pass \
|
|
||||||
--blockdev source=my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced options
|
[`--as`]: 5-crun-vm.1.ronn#exec-options
|
||||||
|
[`--blockdev`]: 5-crun-vm.1.ronn#createrun-options
|
||||||
### PCI device assignment
|
[`--cloud-init`]: 5-crun-vm.1.ronn#createrun-options
|
||||||
|
[`--ignition`]: 5-crun-vm.1.ronn#createrun-options
|
||||||
vfio-pci devices can be passed through to the VM by specifying the non-standard
|
[`--password`]: 5-crun-vm.1.ronn#createrun-options
|
||||||
`--vfio-pci` option with a path to the device's sysfs directory (this example
|
[bootc bootable container images]: https://containers.github.io/bootable/
|
||||||
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.
|
|
||||||
|
|
||||||
[cloud-init]: https://cloud-init.io/
|
[cloud-init]: https://cloud-init.io/
|
||||||
|
[crun-vm(1)]: 5-crun-vm.1.ronn
|
||||||
[domain XML definition]: https://libvirt.org/formatdomain.html
|
[domain XML definition]: https://libvirt.org/formatdomain.html
|
||||||
[Ignition]: https://coreos.github.io/ignition/
|
[Ignition]: https://coreos.github.io/ignition/
|
||||||
[KubeVirt `containerDisk`s]: https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk
|
[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
|
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 \
|
mkdir -p \
|
||||||
/etc/libvirt \
|
/etc/libvirt \
|
||||||
/tmp \
|
/tmp \
|
||||||
|
@ -50,6 +55,22 @@ virsh --connect "qemu+unix:///session?socket=$socket" "\$@"
|
||||||
EOF
|
EOF
|
||||||
chmod +x /crun-vm/virsh
|
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
|
# launch VM
|
||||||
|
|
||||||
function __bg_ensure_tty() {
|
function __bg_ensure_tty() {
|
||||||
|
@ -64,12 +85,9 @@ function __bg_ensure_tty() {
|
||||||
|
|
||||||
virsh=( virsh --connect "qemu+unix:///session?socket=$socket" --quiet )
|
virsh=( virsh --connect "qemu+unix:///session?socket=$socket" --quiet )
|
||||||
|
|
||||||
# If our container was stopped and is being restarted, the domain may still be
|
if [[ -z "$( "${virsh[@]}" list --all --name )" ]]; then
|
||||||
# defined from the previous run, which would cause `virsh define` below to fail,
|
"${virsh[@]}" define /crun-vm/domain.xml
|
||||||
# so we first undefine it.
|
fi
|
||||||
"${virsh[@]}" undefine domain &>/dev/null || true
|
|
||||||
|
|
||||||
"${virsh[@]}" define /crun-vm/domain.xml
|
|
||||||
|
|
||||||
# trigger graceful shutdown and wait for VM to terminate
|
# trigger graceful shutdown and wait for VM to terminate
|
||||||
function __shutdown() {
|
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 lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::commands::create::runtime_env::RuntimeEnv;
|
use crate::commands::create::engine::Engine;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Blockdev {
|
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)]
|
#[derive(clap::Parser, Debug)]
|
||||||
struct CustomOptionsRaw {
|
pub struct CustomOptions {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
blockdev: Vec<Blockdev>,
|
pub blockdev: Vec<Blockdev>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
persistent: bool,
|
pub persistent: bool,
|
||||||
|
|
||||||
#[clap(long)]
|
#[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)]
|
#[clap(long)]
|
||||||
ignition: Option<Utf8PathBuf>,
|
pub bootc_disk_size: Option<String>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
vfio_pci: Vec<Utf8PathBuf>,
|
pub cloud_init: Option<Utf8PathBuf>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
vfio_pci_mdev: Vec<Utf8PathBuf>,
|
pub ignition: Option<Utf8PathBuf>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
merge_libvirt_xml: Vec<Utf8PathBuf>,
|
pub merge_libvirt_xml: Vec<Utf8PathBuf>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[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 {
|
impl CustomOptions {
|
||||||
pub fn from_spec(spec: &oci_spec::runtime::Spec, env: RuntimeEnv) -> Result<Self> {
|
pub fn from_spec(spec: &oci_spec::runtime::Spec, engine: Engine) -> Result<Self> {
|
||||||
let args = spec
|
let mut args: Vec<&String> = spec
|
||||||
.process()
|
.process()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.args()
|
.args()
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.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
|
if let Some(&first_arg) = args.first() {
|
||||||
// container image or through --entrypoint). Must somehow find whether the first arg is the
|
let ignore = [
|
||||||
// entrypoint and ignore it in that case.
|
"no-entrypoint",
|
||||||
let mut options = CustomOptionsRaw::parse_from(
|
"/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),
|
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 {
|
fn all_are_absolute(iter: impl IntoIterator<Item = impl AsRef<Utf8Path>>) -> bool {
|
||||||
iter.into_iter().all(|p| p.as_ref().is_absolute())
|
iter.into_iter().all(|p| p.as_ref().is_absolute())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_in_container_into_path_in_host(
|
fn path_in_container_into_path_in_host(
|
||||||
spec: &oci_spec::runtime::Spec,
|
spec: &oci_spec::runtime::Spec,
|
||||||
path: impl AsRef<Utf8Path>,
|
path: &Utf8Path,
|
||||||
) -> Result<Utf8PathBuf> {
|
) -> Result<Utf8PathBuf> {
|
||||||
let mount = spec
|
let mount = spec
|
||||||
.mounts()
|
.mounts()
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|m| m.source().is_some())
|
.filter(|m| m.source().is_some())
|
||||||
.filter(|m| path.as_ref().starts_with(m.destination()))
|
.filter(|m| path.starts_with(m.destination()))
|
||||||
.last()
|
.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 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);
|
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)
|
Ok(path_in_host)
|
||||||
}
|
}
|
||||||
|
|
||||||
match env {
|
// Docker doesn't run the runtime with the same working directory as the process
|
||||||
RuntimeEnv::Docker => {
|
// that launched `docker-run`. Similarly, custom option paths in Kubernetes refer to
|
||||||
// Docker doesn't run the runtime with the same working directory as the process
|
// paths in the container/VM, and there isn't a reasonable notion of what the
|
||||||
// that launched `docker-run`, so we require custom option paths to be absolute.
|
// current directory is. We thus simply always require custom option paths to be
|
||||||
//
|
// absolute.
|
||||||
// TODO: There must be a better way...
|
ensure!(
|
||||||
ensure!(
|
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
|
||||||
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
|
&& all_are_absolute(&options.cloud_init)
|
||||||
&& all_are_absolute(&options.cloud_init)
|
&& all_are_absolute(&options.ignition)
|
||||||
&& all_are_absolute(&options.ignition)
|
&& all_are_absolute(&options.merge_libvirt_xml),
|
||||||
&& all_are_absolute(&options.vfio_pci)
|
concat!(
|
||||||
&& all_are_absolute(&options.vfio_pci_mdev)
|
"paths specified using --blockdev, --cloud-init, --ignition, or",
|
||||||
&& all_are_absolute(&options.merge_libvirt_xml),
|
" --merge-libvirt-xml must be absolute",
|
||||||
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",
|
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!(
|
if let Some(path) = &mut options.cloud_init {
|
||||||
options.vfio_pci.is_empty() && options.vfio_pci_mdev.is_empty(),
|
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||||
concat!(
|
}
|
||||||
"options --vfio-pci and --vfio-pci-mdev are not allowed when using",
|
|
||||||
" crun-vm as a Kubernetes runtime",
|
if let Some(path) = &mut options.ignition {
|
||||||
)
|
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||||
);
|
}
|
||||||
|
|
||||||
for blockdev in &mut options.blockdev {
|
for path in &mut options.merge_libvirt_xml {
|
||||||
blockdev.source = path_in_container_into_path_in_host(spec, &blockdev.source)?;
|
*path = path_in_container_into_path_in_host(spec, path)?;
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
RuntimeEnv::Other => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options.try_into()
|
Ok(options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Write};
|
use std::io::{BufReader, Write};
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ use anyhow::{ensure, Result};
|
||||||
use camino::Utf8Path;
|
use camino::Utf8Path;
|
||||||
use xml::writer::XmlEvent;
|
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::commands::create::Mounts;
|
||||||
use crate::util::{SpecExt, VmImageInfo};
|
use crate::util::{SpecExt, VmImageInfo};
|
||||||
|
|
||||||
|
@ -36,10 +37,16 @@ fn generate(
|
||||||
.perform_indent(true)
|
.perform_indent(true)
|
||||||
.create_writer(File::create(path.as_ref())?);
|
.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")?;
|
st(w, "name", &[], "domain")?;
|
||||||
|
|
||||||
se(w, "cpu", &[("mode", "host-model")])?;
|
se(w, "cpu", &[("mode", "maximum")])?;
|
||||||
|
|
||||||
let vcpus = get_vcpu_count(spec).to_string();
|
let vcpus = get_vcpu_count(spec).to_string();
|
||||||
if let Some(cpu_set) = get_cpu_set(spec) {
|
if let Some(cpu_set) = get_cpu_set(spec) {
|
||||||
st(w, "vcpu", &[("cpuset", cpu_set.as_str())], vcpus.as_str())?;
|
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();
|
let memory = get_memory_size(spec).to_string();
|
||||||
st(w, "memory", &[("unit", "b")], memory.as_str())?;
|
st(w, "memory", &[("unit", "b")], memory.as_str())?;
|
||||||
|
|
||||||
s(w, "os", &[], |w| {
|
s(w, "os", &[("firmware", "efi")], |w| {
|
||||||
st(w, "type", &[("machine", "q35")], "hvm")
|
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
|
// 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(())
|
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<'_> {
|
impl FirstBootConfig<'_> {
|
||||||
/// Returns `true` if a cloud-init config should be passed to the VM.
|
|
||||||
pub fn apply_to_cloud_init_config(
|
pub fn apply_to_cloud_init_config(
|
||||||
&self,
|
&self,
|
||||||
in_config_dir_path: Option<impl AsRef<Utf8Path>>,
|
in_config_dir_path: Option<impl AsRef<Utf8Path>>,
|
||||||
|
@ -132,11 +131,38 @@ impl FirstBootConfig<'_> {
|
||||||
|
|
||||||
ssh_authorized_keys.push(self.container_public_key.into());
|
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
|
// create block device symlinks and udev rules
|
||||||
|
|
||||||
let block_device_symlinks = self.get_block_device_symlinks();
|
let block_device_symlinks = self.get_block_device_symlinks();
|
||||||
let block_device_udev_rules = self.get_block_device_udev_rules();
|
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() {
|
if !block_device_symlinks.is_empty() || block_device_udev_rules.is_some() {
|
||||||
let runcmd = match user_data_mapping
|
let runcmd = match user_data_mapping
|
||||||
.entry("runcmd".into())
|
.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
|
// generate iso
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -263,15 +273,17 @@ impl FirstBootConfig<'_> {
|
||||||
_ => bail!("invalid config file"),
|
_ => bail!("invalid config file"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let users_contains_core = users.iter().any(|u| match u {
|
for user in ["root", "core"] {
|
||||||
serde_json::Value::Object(m) => m.get("name") == Some(&"core".into()),
|
let user_exists = users.iter().any(|u| match u {
|
||||||
_ => false,
|
serde_json::Value::Object(m) => m.get("name") == Some(&user.into()),
|
||||||
});
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
if !users_contains_core {
|
if !user_exists {
|
||||||
users.push(serde_json::json!({
|
users.push(serde_json::json!({
|
||||||
"name": "core",
|
"name": user,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for user in users {
|
for user in users {
|
||||||
|
@ -280,7 +292,9 @@ impl FirstBootConfig<'_> {
|
||||||
_ => bail!("invalid config file"),
|
_ => 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
|
let keys = match map
|
||||||
.entry("sshAuthorizedKeys")
|
.entry("sshAuthorizedKeys")
|
||||||
.or_insert_with(|| serde_json::json!([]))
|
.or_insert_with(|| serde_json::json!([]))
|
||||||
|
@ -290,8 +304,6 @@ impl FirstBootConfig<'_> {
|
||||||
};
|
};
|
||||||
|
|
||||||
keys.push(self.container_public_key.into());
|
keys.push(self.container_public_key.into());
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,115 +2,273 @@
|
||||||
|
|
||||||
mod custom_opts;
|
mod custom_opts;
|
||||||
mod domain;
|
mod domain;
|
||||||
|
mod engine;
|
||||||
mod first_boot;
|
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::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||||
use std::path::Path;
|
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 camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use nix::sys::stat::{major, makedev, minor, mknod, Mode, SFlag};
|
use nix::sys::stat::{major, makedev, minor, mknod, Mode, SFlag};
|
||||||
|
use regex::Regex;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
use crate::commands::create::custom_opts::CustomOptions;
|
use crate::commands::create::custom_opts::CustomOptions;
|
||||||
use crate::commands::create::domain::set_up_libvirt_domain_xml;
|
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::first_boot::FirstBootConfig;
|
||||||
use crate::commands::create::runtime_env::RuntimeEnv;
|
|
||||||
use crate::crun::crun_create;
|
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image,
|
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image, crun,
|
||||||
find_single_file_in_dirs, set_file_context, SpecExt, VmImageInfo,
|
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 bundle_path: &Utf8Path = args.bundle.as_path().try_into()?;
|
||||||
let config_path = bundle_path.join("config.json");
|
let config_path = bundle_path.join("config.json");
|
||||||
|
|
||||||
let mut spec = oci_spec::runtime::Spec::load(&config_path)?;
|
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 original_root_path: Utf8PathBuf = spec.root_path()?.canonicalize()?.try_into()?; // ensure absolute
|
||||||
let custom_options = CustomOptions::from_spec(&spec, runtime_env)?;
|
|
||||||
|
|
||||||
set_up_container_root(&mut spec, bundle_path, &custom_options)?;
|
let engine = Engine::detect(&args.container_id, bundle_path, &spec, &original_root_path)?;
|
||||||
let base_vm_image_info =
|
let custom_options = CustomOptions::from_spec(&spec, engine)?;
|
||||||
set_up_vm_image(&spec, bundle_path, &original_root_path, &custom_options)?;
|
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();
|
let mut mounts = Mounts::default();
|
||||||
set_up_mounts(&mut spec, &mut mounts)?;
|
set_up_mounts(&mut spec, &mut mounts)?;
|
||||||
set_up_devices(&mut spec, &mut mounts)?;
|
set_up_devices(&mut spec, &mut mounts)?;
|
||||||
set_up_blockdevs(&mut spec, &mut mounts, &custom_options)?;
|
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_security(&mut spec);
|
||||||
|
|
||||||
set_up_first_boot_config(&spec, &mounts, &custom_options)?;
|
let ssh_pub_key = set_up_ssh_key_pair(
|
||||||
set_up_libvirt_domain_xml(&spec, &base_vm_image_info, &mounts, &custom_options)?;
|
&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);
|
adjust_container_rlimits_and_resources(&mut spec);
|
||||||
|
|
||||||
spec.save(&config_path)?;
|
spec.save(&config_path)?;
|
||||||
spec.save(spec.root_path()?.join("crun-vm/config.json"))?; // to aid debugging
|
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(())
|
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(
|
fn set_up_container_root(
|
||||||
spec: &mut oci_spec::runtime::Spec,
|
spec: &mut oci_spec::runtime::Spec,
|
||||||
bundle_path: &Utf8Path,
|
priv_dir_path: &Utf8Path,
|
||||||
custom_options: &CustomOptions,
|
custom_options: &CustomOptions,
|
||||||
|
is_bootc_container: bool,
|
||||||
) -> Result<()> {
|
) -> 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
|
// create root directory
|
||||||
|
|
||||||
spec.set_root(Some(
|
spec.set_root(Some(
|
||||||
oci_spec::runtime::RootBuilder::default()
|
oci_spec::runtime::RootBuilder::default()
|
||||||
.path(bundle_path.join("crun-vm-root"))
|
.path(&new_root_path)
|
||||||
.readonly(false)
|
.readonly(false)
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
fs::create_dir_all(spec.root_path()?)?;
|
// set up container files
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "scripts/"]
|
#[folder = "embed/"]
|
||||||
struct Scripts;
|
struct Embed;
|
||||||
|
|
||||||
for path in Scripts::iter() {
|
for path in Embed::iter() {
|
||||||
let path_in_host = spec.root_path()?.join("crun-vm").join(path.as_ref());
|
let path_in_host = new_root_path.join("crun-vm").join(path.as_ref());
|
||||||
fs::create_dir_all(path_in_host.parent().unwrap())?;
|
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::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
|
// configure container entrypoint
|
||||||
|
|
||||||
let command = match custom_options.print_libvirt_xml {
|
let command = if custom_options.print_libvirt_xml {
|
||||||
true => vec!["cat", "/crun-vm/domain.xml"],
|
vec!["cat", "/crun-vm/domain.xml"]
|
||||||
false => vec!["/crun-vm/entrypoint.sh"],
|
} 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({
|
spec.set_process({
|
||||||
let mut process = spec.process().clone().unwrap();
|
let mut process = spec.process().clone().unwrap();
|
||||||
|
|
||||||
process.set_cwd(".".into());
|
process.set_cwd(".".into());
|
||||||
process.set_command_line(None);
|
process.set_command_line(None);
|
||||||
process.set_args(Some(command.into_iter().map(String::from).collect()));
|
process.set_args(Some(command.into_iter().map(String::from).collect()));
|
||||||
|
|
||||||
|
fix_selinux_label(&mut process);
|
||||||
|
|
||||||
Some(process)
|
Some(process)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -119,10 +277,24 @@ fn set_up_container_root(
|
||||||
|
|
||||||
fn set_up_vm_image(
|
fn set_up_vm_image(
|
||||||
spec: &oci_spec::runtime::Spec,
|
spec: &oci_spec::runtime::Spec,
|
||||||
bundle_path: &Utf8Path,
|
|
||||||
original_root_path: &Utf8Path,
|
original_root_path: &Utf8Path,
|
||||||
|
priv_dir_path: &Utf8Path,
|
||||||
custom_options: &CustomOptions,
|
custom_options: &CustomOptions,
|
||||||
|
is_first_create: bool,
|
||||||
|
is_bootc_container: bool,
|
||||||
) -> Result<VmImageInfo> {
|
) -> 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
|
// where inside the container to look for the VM image
|
||||||
const VM_IMAGE_SEARCH_PATHS: [&str; 2] = ["./", "disk/"];
|
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
|
// mount user-provided VM image file into container
|
||||||
|
|
||||||
let mirror_vm_image_path_in_container =
|
// Make VM image file available in a subtree that doesn't overlap our internal container root so
|
||||||
Utf8Path::new("crun-vm/image").join(vm_image_path_in_host.file_name().unwrap());
|
// overlayfs works.
|
||||||
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);
|
|
||||||
|
|
||||||
let private_dir = if custom_options.persistent {
|
let image_dir_path = priv_dir_path.join("image");
|
||||||
let vm_image_dir_path = vm_image_path_in_host.parent().unwrap();
|
fs::create_dir_all(&image_dir_path)?;
|
||||||
let vm_image_dir_name = vm_image_dir_path.file_name().unwrap();
|
|
||||||
|
|
||||||
let overlay_private_dir_name = format!(".crun-vm.{}.tmp", vm_image_dir_name);
|
if !image_dir_path.join("image").try_exists()? {
|
||||||
let overlay_private_dir_path = vm_image_dir_path
|
fs::hard_link(vm_image_path_in_host, image_dir_path.join("image"))?;
|
||||||
.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 custom_options.persistent {
|
if custom_options.persistent {
|
||||||
// We want to propagate writes but not removal, so that the user's file isn't deleted by
|
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
|
||||||
// Podman on cleanup, so we bind mount it on top of itself.
|
// 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)?;
|
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;
|
vm_image_info.path = mirror_vm_image_path_in_container;
|
||||||
|
|
||||||
|
Ok(vm_image_info)
|
||||||
} else {
|
} else {
|
||||||
// The overlayfs mount already isolates the user's original image file from writes, but to
|
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
|
||||||
// ensure that we get copy-on-write and page cache sharing even when the underlying file
|
// can always access it.
|
||||||
// system doesn't support reflinks, we create a qcow2 overlay and use that as the image.
|
|
||||||
|
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_container = Utf8PathBuf::from("crun-vm/image-overlay.qcow2");
|
||||||
let overlay_vm_image_path_in_host =
|
let overlay_vm_image_path_in_host =
|
||||||
|
@ -191,13 +366,19 @@ fn set_up_vm_image(
|
||||||
let overlay_vm_image_path_in_container =
|
let overlay_vm_image_path_in_container =
|
||||||
Utf8Path::new("/").join(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;
|
let mut base_vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
|
||||||
create_overlay_vm_image(&overlay_vm_image_path_in_host, &vm_image_info)?;
|
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)]
|
#[derive(Default)]
|
||||||
|
@ -406,7 +587,10 @@ fn set_up_blockdevs(
|
||||||
Ok(())
|
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>) {
|
fn add_bind_mount(spec: &mut oci_spec::runtime::Spec, path: impl AsRef<Path>) {
|
||||||
spec.mounts_push(
|
spec.mounts_push(
|
||||||
oci_spec::runtime::MountBuilder::default()
|
oci_spec::runtime::MountBuilder::default()
|
||||||
|
@ -457,9 +641,17 @@ fn set_up_extra_container_mounts_and_devices(spec: &mut oci_spec::runtime::Spec)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
add_bind_mount(spec, "/dev/kvm");
|
if !custom_options.emulated {
|
||||||
add_char_dev(spec, "/dev/kvm")?;
|
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")? {
|
for entry in fs::read_dir("/dev/vfio")? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
if entry.metadata()?.file_type().is_char_device() {
|
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
|
// 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
|
// 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?
|
// 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()
|
oci_spec::runtime::LinuxSyscallBuilder::default()
|
||||||
.names(["mount", "pivot_root", "umount2", "unshare"].map(String::from))
|
.names(["mount", "pivot_root", "umount2", "unshare"].map(String::from))
|
||||||
.action(oci_spec::runtime::LinuxSeccompAction::ScmpActAllow)
|
.action(oci_spec::runtime::LinuxSeccompAction::ScmpActAllow)
|
||||||
|
@ -499,12 +691,11 @@ fn set_up_first_boot_config(
|
||||||
spec: &oci_spec::runtime::Spec,
|
spec: &oci_spec::runtime::Spec,
|
||||||
mounts: &Mounts,
|
mounts: &Mounts,
|
||||||
custom_options: &CustomOptions,
|
custom_options: &CustomOptions,
|
||||||
|
container_public_key: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let container_public_key = gen_container_ssh_key_pair(spec)?;
|
|
||||||
|
|
||||||
let config = FirstBootConfig {
|
let config = FirstBootConfig {
|
||||||
hostname: spec.hostname().as_deref(),
|
hostname: spec.hostname().as_deref(),
|
||||||
container_public_key: &container_public_key,
|
container_public_key,
|
||||||
password: custom_options.password.as_deref(),
|
password: custom_options.password.as_deref(),
|
||||||
mounts,
|
mounts,
|
||||||
};
|
};
|
||||||
|
@ -528,25 +719,64 @@ fn set_up_first_boot_config(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the public key for the container.
|
/// 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() {
|
let user_ssh_dir = user_home.join(".ssh");
|
||||||
fs::create_dir_all(&ssh_path)?;
|
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")
|
let status = Command::new("ssh-keygen")
|
||||||
.arg("-q")
|
.arg("-q")
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
.arg(ssh_path.join("id_rsa"))
|
.arg(container_ssh_dir.join("id_rsa"))
|
||||||
.arg("-N")
|
.arg("-N")
|
||||||
.arg("")
|
.arg("")
|
||||||
|
.arg("-C")
|
||||||
|
.arg("")
|
||||||
.spawn()?
|
.spawn()?
|
||||||
.wait()?;
|
.wait()?;
|
||||||
|
|
||||||
ensure!(status.success(), "ssh-keygen failed");
|
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) {
|
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
|
// Forwarding all UDP and TCP traffic requires passt to open many sockets. Ensure that
|
||||||
// the container's RLIMIT_NOFILE is large enough.
|
// 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(
|
rlimits.push(
|
||||||
oci_spec::runtime::LinuxRlimitBuilder::default()
|
oci_spec::runtime::PosixRlimitBuilder::default()
|
||||||
.typ(oci_spec::runtime::LinuxRlimitType::RlimitNofile)
|
.typ(oci_spec::runtime::PosixRlimitType::RlimitNofile)
|
||||||
.hard(262144u64)
|
.hard(262144u64)
|
||||||
.soft(262144u64)
|
.soft(262144u64)
|
||||||
.build()
|
.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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
use std::{
|
use std::env;
|
||||||
fs::File,
|
use std::ffi::OsStr;
|
||||||
io::{BufReader, BufWriter},
|
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());
|
assert!(args.command.is_empty());
|
||||||
|
|
||||||
|
// load exec process config
|
||||||
|
|
||||||
let process_config_path = args.process.as_ref().expect("process config");
|
let process_config_path = args.process.as_ref().expect("process config");
|
||||||
let mut process: oci_spec::runtime::Process =
|
let mut process: oci_spec::runtime::Process =
|
||||||
serde_json::from_reader(File::open(process_config_path).map(BufReader::new)?)?;
|
serde_json::from_reader(File::open(process_config_path).map(BufReader::new)?)?;
|
||||||
|
|
||||||
let command = process.args().as_ref().expect("command specified");
|
let command = process.args().as_ref().expect("command specified");
|
||||||
|
|
||||||
let ssh_user = command
|
let new_command = build_command(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());
|
|
||||||
}
|
|
||||||
|
|
||||||
process.set_args(Some(new_command));
|
process.set_args(Some(new_command));
|
||||||
|
|
||||||
|
fix_selinux_label(&mut process);
|
||||||
|
|
||||||
|
// store modified exec process config
|
||||||
|
|
||||||
serde_json::to_writer(
|
serde_json::to_writer(
|
||||||
File::create(process_config_path).map(BufWriter::new)?,
|
File::create(process_config_path).map(BufWriter::new)?,
|
||||||
&process,
|
&process,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
crun_exec(global_args, args)?;
|
// actually exec
|
||||||
|
|
||||||
|
crun(raw_args)?;
|
||||||
|
|
||||||
Ok(())
|
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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
pub mod create;
|
pub mod create;
|
||||||
|
pub mod delete;
|
||||||
pub mod exec;
|
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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod crun;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crun::crun;
|
use util::crun;
|
||||||
|
|
||||||
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(no_binary_name = true)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
global: liboci_cli::GlobalOpts,
|
global: liboci_cli::GlobalOpts,
|
||||||
|
@ -22,7 +21,7 @@ struct Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
enum Command {
|
enum Command {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
Standard(Box<liboci_cli::StandardCmd>),
|
Standard(Box<liboci_cli::StandardCmd>),
|
||||||
|
@ -32,27 +31,43 @@ enum Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
|
pub fn main(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
|
||||||
let args = args
|
let raw_args = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| a.as_ref().to_os_string())
|
.map(|a| a.as_ref().to_os_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let parsed_args =
|
let parsed_args = Args::parse_from(&raw_args);
|
||||||
Args::parse_from(iter::once(&OsStr::new("crun-vm").to_os_string()).chain(&args));
|
|
||||||
|
|
||||||
match parsed_args.command {
|
match parsed_args.command {
|
||||||
Command::Standard(cmd) => {
|
Command::Standard(cmd) => {
|
||||||
if let liboci_cli::StandardCmd::Create(create_args) = *cmd {
|
match *cmd {
|
||||||
return commands::create::create(&parsed_args.global, &create_args);
|
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) => {
|
Command::Common(cmd) => {
|
||||||
if let liboci_cli::CommonCmd::Exec(exec_args) = *cmd {
|
match *cmd {
|
||||||
return commands::exec::exec(&parsed_args.global, &exec_args);
|
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() {
|
fn main() {
|
||||||
if let Err(e) = crun_vm::main(env::args_os().skip(1)) {
|
if let Err(e) = crun_vm::main(env::args_os().skip(1)) {
|
||||||
eprintln!("{:#}", e);
|
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);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
168
src/util.rs
168
src/util.rs
|
@ -1,32 +1,68 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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::fs::{self, OpenOptions, Permissions};
|
||||||
use std::io;
|
use std::io::{self, ErrorKind};
|
||||||
use std::os::unix::ffi::OsStrExt;
|
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::process::{Command, Stdio};
|
||||||
|
use std::str;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use nix::mount::MsFlags;
|
use nix::mount::{MntFlags, MsFlags};
|
||||||
use serde::Deserialize;
|
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<()> {
|
pub fn set_file_context(path: impl AsRef<Utf8Path>, context: &str) -> Result<()> {
|
||||||
extern "C" {
|
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 path = CString::new(path.as_ref().as_os_str().as_bytes())?;
|
||||||
let context = CString::new(context.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());
|
return Err(io::Error::last_os_error().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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<()> {
|
pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) -> Result<()> {
|
||||||
// ensure target exists
|
// ensure target exists
|
||||||
|
|
||||||
|
@ -59,52 +95,59 @@ pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) ->
|
||||||
Ok(())
|
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.
|
/// 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`.
|
/// 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
|
/// If `read_only` is false, `scratch_dir` must belong to the same file system as `from` and be a
|
||||||
/// be a separate subtree.
|
/// separate subtree.
|
||||||
///
|
///
|
||||||
/// TODO: Is this a neat relabeling trick or simply a bad hack?
|
/// TODO: Is this a neat relabeling trick or simply a bad hack?
|
||||||
pub fn bind_mount_dir_with_different_context(
|
pub fn bind_mount_dir_with_different_context(
|
||||||
from: impl AsRef<Utf8Path>,
|
from: impl AsRef<Utf8Path>,
|
||||||
to: impl AsRef<Utf8Path>,
|
to: impl AsRef<Utf8Path>,
|
||||||
|
scratch_dir: impl AsRef<Utf8Path>,
|
||||||
context: Option<&str>,
|
context: Option<&str>,
|
||||||
propagate_changes: bool,
|
read_only: bool,
|
||||||
private_dir: impl AsRef<Utf8Path>,
|
|
||||||
) -> Result<()> {
|
) -> 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())?;
|
fs::create_dir_all(to.as_ref())?;
|
||||||
|
|
||||||
fn escape_path(mount_option: &Utf8Path) -> String {
|
let mut options = if read_only {
|
||||||
mount_option
|
fs::create_dir_all(scratch_dir.as_ref())?;
|
||||||
.as_str()
|
|
||||||
.replace('\\', "\\\\")
|
|
||||||
.replace(',', "\\,")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn escape_context(mount_option: &str) -> String {
|
format!(
|
||||||
assert!(!mount_option.contains('"'));
|
"lowerdir={}:{}",
|
||||||
format!("\"{}\"", mount_option)
|
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 {
|
fs::create_dir_all(&layer_dir)?;
|
||||||
true => (layer_dir.as_path(), from.as_ref()),
|
fs::create_dir_all(&work_dir)?;
|
||||||
false => (from.as_ref(), layer_dir.as_path()),
|
|
||||||
|
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 {
|
if let Some(context) = context {
|
||||||
options = format!("{},context={}", options, escape_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
|
if !read_only {
|
||||||
// user that Podman is running under.
|
// Make any necessary manual cleanup a bit easier by ensuring the workdir is accessible to
|
||||||
fs::set_permissions(work_dir.join("work"), Permissions::from_mode(0o700))?;
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -142,7 +198,7 @@ pub trait SpecExt {
|
||||||
linux_device_cgroup: oci_spec::runtime::LinuxDeviceCgroup,
|
linux_device_cgroup: oci_spec::runtime::LinuxDeviceCgroup,
|
||||||
);
|
);
|
||||||
fn process_capabilities_insert_beip(&mut self, capability: oci_spec::runtime::Capability);
|
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 {
|
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({
|
self.set_linux({
|
||||||
let mut linux = self.linux().clone().expect("linux config");
|
let mut linux = self.linux().clone().expect("linux config");
|
||||||
linux.set_seccomp({
|
linux.set_seccomp({
|
||||||
|
@ -228,7 +287,7 @@ impl SpecExt for oci_spec::runtime::Spec {
|
||||||
if let Some(seccomp) = &mut seccomp {
|
if let Some(seccomp) = &mut seccomp {
|
||||||
seccomp.set_syscalls({
|
seccomp.set_syscalls({
|
||||||
let mut syscalls = seccomp.syscalls().clone().unwrap_or_default();
|
let mut syscalls = seccomp.syscalls().clone().unwrap_or_default();
|
||||||
syscalls.push(linux_syscall);
|
syscalls.insert(0, linux_syscall);
|
||||||
Some(syscalls)
|
Some(syscalls)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -294,7 +353,11 @@ impl VmImageInfo {
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.output()?;
|
.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)?;
|
let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?;
|
||||||
info.path = vm_image_path;
|
info.path = vm_image_path;
|
||||||
|
@ -307,7 +370,7 @@ pub fn create_overlay_vm_image(
|
||||||
overlay_vm_image_path: &Utf8Path,
|
overlay_vm_image_path: &Utf8Path,
|
||||||
base_vm_image_info: &VmImageInfo,
|
base_vm_image_info: &VmImageInfo,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let status = Command::new("qemu-img")
|
let output = Command::new("qemu-img")
|
||||||
.arg("create")
|
.arg("create")
|
||||||
.arg("-q")
|
.arg("-q")
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
|
@ -319,10 +382,23 @@ pub fn create_overlay_vm_image(
|
||||||
.arg(&base_vm_image_info.path)
|
.arg(&base_vm_image_info.path)
|
||||||
.arg(overlay_vm_image_path)
|
.arg(overlay_vm_image_path)
|
||||||
.arg(base_vm_image_info.size.to_string())
|
.arg(base_vm_image_info.size.to_string())
|
||||||
.spawn()?
|
.output()?;
|
||||||
.wait()?;
|
|
||||||
|
|
||||||
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(())
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
>&2 echo "Extracting ${candidates[0]} to ${output_vm_image_file}"
|
||||||
|
|
||||||
tar -C "${temp_dir}" -xf "${temp_dir}/root.tar" "${candidates[0]}"
|
tar -C "${temp_dir}" -xf "${temp_dir}/root.tar" "${candidates[0]}"
|
||||||
chmod +w "${temp_dir}/${candidates[0]}"
|
chmod +w "${temp_dir}/${candidates[0]}"
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
set -o errexit -o pipefail -o nounset
|
set -o errexit -o pipefail -o nounset
|
||||||
|
|
||||||
script_dir="$( dirname "$0" | xargs readlink -e )"
|
script_dir="$( dirname "$0" | xargs readlink -e )"
|
||||||
|
repo_root=$script_dir/../..
|
||||||
|
|
||||||
__minikube() {
|
__minikube() {
|
||||||
minikube -p=crun-vm-example "$@"
|
minikube -p=crun-vm-example "$@"
|
||||||
|
@ -25,7 +26,7 @@ __apt_get() {
|
||||||
|
|
||||||
# build runtime
|
# build runtime
|
||||||
|
|
||||||
cargo build
|
make -C "$repo_root"
|
||||||
|
|
||||||
# create minikube cluster
|
# create minikube cluster
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ __ssh 'tee --append /etc/crio/crio.conf <<EOF
|
||||||
runtime_path = "/usr/local/bin/crun-vm"
|
runtime_path = "/usr/local/bin/crun-vm"
|
||||||
EOF'
|
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
|
__ssh chmod +x /usr/local/bin/crun-vm
|
||||||
|
|
||||||
# reload cluster so that the new runtime is picked up
|
# reload cluster so that the new runtime is picked up
|
|
@ -12,9 +12,11 @@ fi
|
||||||
vm_image_file=$1
|
vm_image_file=$1
|
||||||
container_image_tag=$2
|
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
|
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
|
EOF
|
||||||
|
|
Loading…
Reference in New Issue