Compare commits

...

235 Commits
0.1.3 ... main

Author SHA1 Message Date
renovate[bot] b213606120 fix(deps): update rust crate xml-rs to 0.8.27
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 04:43:30 +01:00
renovate[bot] c1744babf0 fix(deps): update rust crate clap to 4.5.40
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 20:57:36 +01:00
renovate[bot] a8851850a4 fix(deps): update rust crate camino to 1.1.10
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 00:11:28 +01:00
renovate[bot] 3272b14a92 fix(deps): update rust crate clap to 4.5.39
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-01 08:08:51 +01:00
renovate[bot] cb9bef3e72 fix(deps): update rust crate num_cpus to 1.17.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-31 15:45:49 +01:00
renovate[bot] 0bc67e36ef fix(deps): update rust crate rust-embed to 8.7.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 22:18:54 +01:00
renovate[bot] edbb3fdcd2 fix(deps): update rust crate rust-embed to 8.7.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 21:21:01 +01:00
renovate[bot] 46a74315e0 fix(deps): update rust crate nix to 0.30.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-04 06:21:40 +01:00
renovate[bot] 06f6eb1ce4 fix(deps): update rust crate oci-spec to 0.8.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 07:26:52 +01:00
renovate[bot] f0abbc9b86 fix(deps): update rust crate clap to 4.5.37
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 07:24:49 +01:00
renovate[bot] ff78b7d276 fix(deps): update rust crate nix to 0.30.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 07:24:30 +01:00
renovate[bot] 6b95c01981 fix(deps): update rust crate oci-spec to 0.8.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 07:24:20 +01:00
renovate[bot] 9c65909097 fix(deps): update rust crate anyhow to 1.0.98
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 06:26:59 +01:00
renovate[bot] f32844c664 fix(deps): update rust crate rust-embed to 8.7.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 08:00:48 +01:00
renovate[bot] 595d94962c fix(deps): update rust crate xml-rs to 0.8.26
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 01:46:31 +01:00
renovate[bot] e069699438 fix(deps): update rust crate clap to 4.5.34
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 18:06:46 -03:00
renovate[bot] 4340739be3 fix(deps): update rust crate serde to 1.0.219
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 18:06:35 -03:00
renovate[bot] d0c87b3032 fix(deps): update rust crate liboci-cli to 0.5.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 18:59:20 -03:00
renovate[bot] dafe1d1198 fix(deps): update rust crate liboci-cli to 0.5.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 07:49:11 -03:00
renovate[bot] a2cf546d2b fix(deps): update rust crate anyhow to 1.0.97
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 00:56:31 -03:00
renovate[bot] e7503b63ac fix(deps): update rust crate serde_json to 1.0.140
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 00:55:26 -03:00
renovate[bot] 148ea2b622 fix(deps): update rust crate serde to 1.0.218
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-01 05:42:46 +00:00
renovate[bot] 422e370fd0 fix(deps): update rust crate clap to 4.5.31
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-01 05:34:56 +00:00
renovate[bot] 6351459ad0 fix(deps): update rust crate rust-embed to 8.6.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 23:21:52 +00:00
renovate[bot] 4b450db228 fix(deps): update rust crate anyhow to 1.0.96
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 12:25:03 +00:00
renovate[bot] a802098a94 fix(deps): update rust crate serde_json to 1.0.139
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 12:24:56 +00:00
renovate[bot] ef0a2b11db fix(deps): update rust crate serde_json to 1.0.138
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-01 06:19:45 +00:00
renovate[bot] 7b96f6e81b fix(deps): update rust crate clap to 4.5.27
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-01 06:19:26 +00:00
renovate[bot] 055b7fe620 fix(deps): update rust crate serde_json to 1.0.137
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 03:38:55 +00:00
renovate[bot] 842fb71e2f fix(deps): update rust crate serde_json to 1.0.136
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 14:02:31 +00:00
renovate[bot] 414f199dfa fix(deps): update rust crate liboci-cli to 0.5.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 14:50:03 +00:00
renovate[bot] 350e2ba6d8 fix(deps): update rust crate xml-rs to 0.8.25
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 18:32:07 +00:00
renovate[bot] 1db728146f fix(deps): update rust crate serde_json to 1.0.135
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 15:53:07 +00:00
renovate[bot] 14e9d73e85 fix(deps): update rust crate liboci-cli to 0.5.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 15:29:43 +00:00
renovate[bot] 2a332101d1 fix(deps): update rust crate serde to 1.0.217
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-01 09:30:14 +00:00
renovate[bot] ce5e21cf3a fix(deps): update rust crate clap to 4.5.23
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-01 09:29:37 +00:00
renovate[bot] 685995d83b fix(deps): update rust crate anyhow to 1.0.95
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-24 07:23:42 +00:00
renovate[bot] 9c2bbec37d fix(deps): update rust crate serde_json to 1.0.134
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-22 02:44:49 +00:00
renovate[bot] d5d738df41 fix(deps): update rust crate xml-rs to 0.8.24
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 14:02:20 -08:00
renovate[bot] faf99bbaf7 fix(deps): update rust crate anyhow to 1.0.94
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 12:25:22 -08:00
renovate[bot] 9c6598f096 fix(deps): update rust crate serde to 1.0.215
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-01 19:43:20 +00:00
renovate[bot] 5942f27d50 fix(deps): update rust crate clap to 4.5.21
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-01 19:43:00 +00:00
renovate[bot] fa835148f6 fix(deps): update rust crate serde_json to 1.0.133
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-16 21:59:27 -08:00
renovate[bot] add90de0b8 fix(deps): update rust crate oci-spec to 0.7.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 19:49:19 +00:00
renovate[bot] 15d04a6592 fix(deps): update rust crate xml-rs to 0.8.23
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 14:32:05 +00:00
renovate[bot] d2c8a66e09 fix(deps): update rust crate anyhow to 1.0.93
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 14:07:28 +00:00
renovate[bot] cee7a045fd fix(deps): update rust crate clap to 4.5.20
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 17:54:18 +00:00
renovate[bot] 85a8e735ba fix(deps): update rust crate anyhow to 1.0.92
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 17:54:09 +00:00
renovate[bot] fe799ab7ac fix(deps): update rust crate serde to 1.0.214
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 17:54:00 +00:00
renovate[bot] 4d4b49dbb3 fix(deps): update rust crate anyhow to 1.0.91
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 23:02:31 +01:00
renovate[bot] c2125d6b21 fix(deps): update rust crate serde_json to 1.0.132
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 05:46:36 +01:00
renovate[bot] e49a9a8560 fix(deps): update rust crate anyhow to 1.0.90
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 04:55:20 +01:00
renovate[bot] a11ed870ba fix(deps): update rust crate serde_json to 1.0.129
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 04:55:08 +01:00
Alberto Faria ed3060da6c tests: Fix home path for Fedora bootc test image
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 18:07:45 +01:00
Alberto Faria 3139d4727b exec: Drop unnecessary borrow
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 15:21:55 +01:00
Alberto Faria 3f91463462 docs: Fix some links
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 15:16:23 +01:00
Alberto Faria f28f927db9 exec: Ignore initial `--` command argument
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.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 15:00:43 +01:00
Alberto Faria 7aa4b1728c Don't add selinux=0 karg to disk images generated from bootc containers
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 14:37:34 +01:00
Alberto Faria 9e862dda38 Ensure krun container has KVM device
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-10-04 07:47:34 +01:00
renovate[bot] 55c1fa867e fix(deps): update rust crate serde to 1.0.210
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-01 11:15:56 +01:00
renovate[bot] c41930a493 fix(deps): update rust crate clap to 4.5.18
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-01 09:39:53 +01:00
renovate[bot] 417950e371 fix(deps): update rust crate serde_json to 1.0.128
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-17 15:29:21 +01:00
renovate[bot] a66506e8d4 fix(deps): update rust crate camino to 1.1.9
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-16 20:18:39 +01:00
renovate[bot] 0fd79ecacc fix(deps): update rust crate anyhow to 1.0.89
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-15 13:43:09 +01:00
renovate[bot] 4a5897a1c5 fix(deps): update rust crate oci-spec to 0.7.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-12 08:26:18 -03:00
renovate[bot] e7572785dc fix(deps): update rust crate anyhow to 1.0.88
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 16:08:20 -03:00
renovate[bot] 75cab98728 fix(deps): update rust crate xml-rs to 0.8.22
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 14:07:24 -03:00
renovate[bot] e1521a2e1f fix(deps): update rust crate anyhow to 1.0.87
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 14:07:12 -03:00
renovate[bot] e6e43cec4c fix(deps): update rust crate liboci-cli to 0.4.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 06:51:07 -03:00
renovate[bot] 9476038bc2 fix(deps): update rust crate serde to 1.0.209
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 15:48:28 -03:00
renovate[bot] 305631aa65 fix(deps): update rust crate clap to 4.5.16
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 15:48:21 -03:00
renovate[bot] 6bd181883d fix(deps): update rust crate liboci-cli to 0.4.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 20:08:18 +01:00
renovate[bot] 93f71ad3b3 fix(deps): update rust crate camino to 1.1.8
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 15:12:54 +01:00
Alberto Faria 138b168e33 docs: Fix QEMU emulator dependency names
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-08-11 18:41:54 +01:00
renovate[bot] 59113d11cb fix(deps): update rust crate xml-rs to 0.8.21
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 14:57:47 +01:00
Alberto Faria 8b061ce3a5 Release 0.3.0
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-08-03 02:08:54 +01:00
renovate[bot] 6c31744b47 fix(deps): update rust crate regex to 1.10.6
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 01:54:49 +01:00
renovate[bot] 2f79d6b6d1 fix(deps): update rust crate clap to 4.5.13
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 01:54:36 +01:00
Alberto Faria 259dad6404 create: Drop unnecessary dereferences to fix clippy warning
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-08-03 01:29:45 +01:00
renovate[bot] 6117d18b00 fix(deps): update rust crate serde to 1.0.204
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 01:15:18 +01:00
renovate[bot] a057d6960c fix(deps): update rust crate serde_json to 1.0.121
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 01:14:46 +01:00
Alberto Faria a8655b5c95 Revert to minidom 0.15 and MSRV 1.74
minidom 0.16 seems to be broken in several ways.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-24 12:15:07 +01:00
Alberto Faria c88ee40852 Increase MSRV to 1.76
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-24 12:01:38 +01:00
renovate[bot] 1885a53749 fix(deps): update rust crate minidom to 0.16.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 05:57:22 +01:00
renovate[bot] fab504a344 fix(deps): update rust crate oci-spec to 0.6.8
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 10:02:40 +01:00
Alberto Faria fc5d916371 docs: List skopeo as a runtime dependency
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-18 19:26:02 +01:00
Alberto Faria 3d65c4d471 Use make in scripts instead of cargo-build
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-18 19:25:01 +01:00
Alberto Faria d9b31eeffe
Merge pull request #60 from containers/docs
Revamp docs
2024-07-17 23:12:14 +01:00
Alberto Faria ab63bf10ec List Podman before Docker everywhere for consistency
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-17 23:11:26 +01:00
Alberto Faria 58da5ed7f2 Revamp docs
Add a quick start to the README, reorganize and make the main
documentation less reference-like, and add a man page.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-17 23:11:26 +01:00
Alberto Faria 84e98863ef
Merge pull request #54 from containers/bootc
Add support for running bootc bootable containers
2024-07-17 23:10:35 +01:00
Alberto Faria 7b026358b9 tests/env.sh: Expose TEST_ID variable to tests
Its value is a random identifier unique to the current run of the
current test.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:27 +01:00
Alberto Faria 132cc4c52a Add --bootc-disk-size option
It allows settings the disk size of the VM image that is generated from
a bootc container image.

Also improve the default disk size by basing it on the container image
size.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:21 +01:00
Alberto Faria c0888c6ffb Extend bootc container support to Docker
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:20 +01:00
Alberto Faria de388af2a4 Improve engine detection logic
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:12 +01:00
Alberto Faria ad26d7fe95 Cache VM images generated from bootc container images
Store them as untagged containerdisks in the user's container storage.
They appear in the output of `podman images`, making users aware of
their existence and size on disk, and cleanup commands like `podman
image prune` remove them.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:12 +01:00
Alberto Faria a9118ee63c Add support for running bootc bootable containers
We attempt to detect if a container image is bootable. We can't easily
retrieve the image's labels, so we check if /usr/lib/bootc/install
exists and is a directory. If so, it is a bootable container. If it is a
bootable container but we're not running under Podman, we fail with an
error.

Once our container's entrypoint starts running, a background process on
the host (outside the container) queries Podman for the image's name and
ID, which the OCI runtime does not get but bootc-install needs. It then
saves the container image as an OCI archive.

It then runs the original container to generate the VM image. We do this
using krun [1] so that elevated privileges aren't necessary. Our
entrypoint blocks until this is done, and all subsequent logic remains
the same.

We could potentially avoid the OCI archive creation step by mounting the
host's container storage into the container running under krun. This
isn't trivial to achieve due to SELinux label and context mismatches
between the host and the krun environment, so we leave this optimization
for a future date.

Closes #26.

[1] https://github.com/containers/crun/blob/main/krun.1.md

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-11 15:31:11 +01:00
Alberto Faria 19cb2349a6 Tolerate images with entrypoint /sbin/init and similar
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-10 20:55:42 +01:00
renovate[bot] b40201ba72 fix(deps): update rust crate rust-embed to 8.5.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 09:55:19 +01:00
renovate[bot] db18060978 fix(deps): update rust crate oci-spec to 0.6.7
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-04 14:19:13 +01:00
Alberto Faria f09ac901ff Fix build after non-backward compatible oci-spec update
The update from oci-spec 0.6.5 to 0.6.6 turned out to not be backward
compatible.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-07-02 13:27:36 +01:00
renovate[bot] 646be3e47b fix(deps): update rust crate oci-spec to 0.6.6
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 12:55:50 +01:00
renovate[bot] dd263a2bc4 fix(deps): update rust crate serde_json to 1.0.120
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-01 20:10:38 +01:00
Alberto Faria fe90b56767
Merge pull request #96 from containers/renovate/clap-4.x
fix(deps): update rust crate clap to 4.5.8
2024-07-01 06:37:12 +01:00
renovate[bot] aa31281168
fix(deps): update rust crate clap to 4.5.8
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-01 01:15:48 +00:00
Alberto Faria adb9f38b43
Merge pull request #89 from containers/renovate/serde_json-1.x
fix(deps): update rust crate serde_json to 1.0.119
2024-06-30 22:08:04 +01:00
renovate[bot] 75ed7d972c
fix(deps): update rust crate serde_json to 1.0.119
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-30 20:08:36 +00:00
Alberto Faria 93ee592c46
Merge pull request #88 from containers/renovate/serde_json-1.x
fix(deps): update rust crate serde_json to 1.0.118
2024-06-25 18:16:57 +01:00
renovate[bot] 2a7585eb2d
fix(deps): update rust crate serde_json to 1.0.118
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 05:18:53 +00:00
Alberto Faria 326e575f56
Merge pull request #87 from containers/renovate/lazy_static-1.x
fix(deps): update rust crate lazy_static to 1.5.0
2024-06-22 13:15:26 +01:00
renovate[bot] b3eb424766
fix(deps): update rust crate lazy_static to 1.5.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 02:39:20 +00:00
Alberto Faria 32d4db4bd7
Merge pull request #85 from containers/renovate/softprops-action-gh-release-2.x
[skip-ci] Update softprops/action-gh-release action to v2
2024-06-18 22:18:08 +01:00
Alberto Faria efa0d3629b
Merge pull request #76 from containers/renovate/oci-spec-0.x
fix(deps): update rust crate oci-spec to 0.6.5
2024-06-18 22:17:52 +01:00
Alberto Faria 3fb7b4f49d
Merge pull request #84 from containers/renovate/rust-embed-8.x
fix(deps): update rust crate rust-embed to 8.4.0
2024-06-18 22:17:02 +01:00
Alberto Faria eabb1a6590
Merge pull request #83 from containers/renovate/nix-0.x
fix(deps): update rust crate nix to 0.29.0
2024-06-18 22:16:52 +01:00
Alberto Faria c4ea481006
Merge pull request #82 from containers/renovate/xml-rs-0.x
fix(deps): update rust crate xml-rs to 0.8.20
2024-06-18 22:16:40 +01:00
Alberto Faria c1cd5b29e7
Merge pull request #81 from containers/renovate/urlencoding-2.x
fix(deps): update rust crate urlencoding to 2.1.3
2024-06-18 22:16:36 +01:00
Alberto Faria e65f60e62b
Merge pull request #80 from containers/renovate/serde_yaml-0.x
fix(deps): update rust crate serde_yaml to 0.9.34
2024-06-18 22:16:31 +01:00
Alberto Faria 999eda2c92
Merge pull request #79 from containers/renovate/serde_json-1.x
fix(deps): update rust crate serde_json to 1.0.117
2024-06-18 22:16:25 +01:00
Alberto Faria 2ed49a082b
Merge pull request #78 from containers/renovate/serde-monorepo
fix(deps): update rust crate serde to 1.0.203
2024-06-18 22:16:19 +01:00
Alberto Faria 2adee838cc
Merge pull request #77 from containers/renovate/regex-1.x
fix(deps): update rust crate regex to 1.10.5
2024-06-18 22:16:09 +01:00
Alberto Faria 9e6e91238f
Merge pull request #75 from containers/renovate/num_cpus-1.x
fix(deps): update rust crate num_cpus to 1.16.0
2024-06-18 22:15:55 +01:00
Alberto Faria b2d1e65960
Merge pull request #74 from containers/renovate/liboci-cli-0.x
fix(deps): update rust crate liboci-cli to 0.3.3
2024-06-18 22:15:50 +01:00
Alberto Faria 4bf73da5cb
Merge pull request #73 from containers/renovate/lazy_static-1.x
fix(deps): update rust crate lazy_static to 1.4.0
2024-06-18 22:15:39 +01:00
Alberto Faria 2bfbc8319e
Merge pull request #72 from containers/renovate/home-0.x
fix(deps): update rust crate home to 0.5.9
2024-06-18 22:14:53 +01:00
Alberto Faria 58557aa6e9
Merge pull request #71 from containers/renovate/clap-4.x
fix(deps): update rust crate clap to 4.5.7
2024-06-18 22:14:38 +01:00
Alberto Faria 454703095c
Merge pull request #70 from containers/renovate/camino-1.x
fix(deps): update rust crate camino to 1.1.7
2024-06-18 22:14:06 +01:00
renovate[bot] 829e121a58
[skip-ci] Update softprops/action-gh-release action to v2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:39 +00:00
renovate[bot] 91f5609c19
fix(deps): update rust crate rust-embed to 8.4.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:34 +00:00
renovate[bot] 2a78e5f0fe
fix(deps): update rust crate nix to 0.29.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:27 +00:00
renovate[bot] a1b65cea0d
fix(deps): update rust crate xml-rs to 0.8.20
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:22 +00:00
renovate[bot] 650e8fd78f
fix(deps): update rust crate urlencoding to 2.1.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:15 +00:00
renovate[bot] 569fb5d468
fix(deps): update rust crate serde_yaml to 0.9.34
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:09 +00:00
renovate[bot] f9e8caa299
fix(deps): update rust crate serde_json to 1.0.117
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:07:03 +00:00
renovate[bot] 6ea51361d7
fix(deps): update rust crate serde to 1.0.203
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:56 +00:00
renovate[bot] 44a8d0eae2
fix(deps): update rust crate regex to 1.10.5
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:50 +00:00
renovate[bot] e2d53986b7
fix(deps): update rust crate oci-spec to 0.6.5
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:43 +00:00
renovate[bot] 183c3d2682
fix(deps): update rust crate num_cpus to 1.16.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:36 +00:00
renovate[bot] d6834a960b
fix(deps): update rust crate liboci-cli to 0.3.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:29 +00:00
renovate[bot] cd82042b27
fix(deps): update rust crate lazy_static to 1.4.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:24 +00:00
renovate[bot] f977d7ac6f
fix(deps): update rust crate home to 0.5.9
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:18 +00:00
renovate[bot] f2b85be13a
fix(deps): update rust crate clap to 4.5.7
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:10 +00:00
renovate[bot] 52eab23681
fix(deps): update rust crate camino to 1.1.7
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:06:01 +00:00
Alberto Faria c8366a28e7
Merge pull request #68 from containers/renovate/anyhow-1.x
fix(deps): update rust crate anyhow to 1.0.86
2024-06-18 22:02:54 +01:00
renovate[bot] 8f788cbe1c
fix(deps): update rust crate anyhow to 1.0.86
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 21:00:08 +00:00
Alberto Faria 8aed4b02cc
Merge pull request #64 from p5/enable-renovate
chore: add minimal Renovate configuration
2024-06-18 21:59:08 +01:00
Robert Sturla 54d921467d
Add minimal Renovate configuration, inheriting from the org's standardised config
Signed-off-by: Robert Sturla <robertsturla@outlook.com>

Rename renovate.json to renovate.json5 and add comments

Signed-off-by: Robert Sturla <robertsturla@outlook.com>
2024-06-10 16:05:52 +01:00
Alberto Faria 29246880af create: Always set CPU mode to "maximum"
It's equivalent to host-passthrough when using KVM.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-05-08 21:19:34 +01:00
Alberto Faria a0c4f425c1 exec: Tolerate "pseudo-terminal will not be allocated" errors
Errors like the following sometimes occur when ssh'ing during boot:

    "Pseudo-terminal will not be allocated because stdin is not a terminal"

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-05-08 21:15:39 +01:00
Alberto Faria fa28a88d23 Disable COPR builds for now
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-05-08 21:15:35 +01:00
Alberto Faria b4d342a4f5 Release 0.2.0
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 16:57:51 +01:00
Alberto Faria 4854b26886 Reinstate testing farm tests
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 16:56:11 +01:00
Alberto Faria 6bdeaa4cf1 docs: Update example.gif to use a Fedora 40 containerdisk
Also drop the -it and --rm flags and the now-unnecessary "" argument.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 16:51:22 +01:00
Alberto Faria 57021b72fd tests/t/random-ssh-key-pair.sh: Also test exec after restart
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 12:36:56 +01:00
Alberto Faria 55756dfa7f tests/t/publish.sh: Retry curl a few times
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 12:36:24 +01:00
Alberto Faria 167a5c955d exec: Tolerate "system is booting up" errors
When attempting first ssh, tolerate errors like the following:

    "System is booting up. Unprivileged users are not permitted to log
    in yet. Please come back later. For technical details, see
    pam_nologin(8)."

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 04:26:10 +01:00
Alberto Faria 0afc66b56d create: Remove lock files left around when container is killed
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 04:26:10 +01:00
Alberto Faria 53ef61e843 create: Avoid undefining domain on restart
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 04:26:09 +01:00
Alberto Faria 9d709ae9ec tests/env.sh: Avoid printing irrelevant errors on test cleanup
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 03:25:55 +01:00
Alberto Faria 5611aa5f78 tests/env.sh: Sort tests alphabetically when 'all' are selected
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 01:44:17 +01:00
Alberto Faria a31962fabe tests/t/persistent.sh: Reduce verbosity
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 01:17:50 +01:00
Alberto Faria cb4286e770 create: Use UEFI
This is required to enable ACPI on aarch64, and probably a good idea in
general.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 01:17:50 +01:00
Alberto Faria fd6e7c644b create: Use libvirt's default machine type on non-x86[_64] arches
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 01:17:50 +01:00
Alberto Faria 01eb9bd485 create: Add an --emulated flag
Instead of falling back to system emulation when /dev/kvm is not
available, let the user decide explicitly whether they want
hardware-assisted KVM virtualization or full emulation.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 01:17:47 +01:00
Alberto Faria 530fc22d44 create: Remove /crun-vm/ssh-successful prior to starting entrypoint
Make sure a quick podman-exec after a container restart doesn't see the
file before it is deleted.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 00:46:20 +01:00
Alberto Faria e2750031d0 create: Remount user SSH key pair on container restart
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 00:46:20 +01:00
Alberto Faria c3b949f4a9 Update dependencies
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-28 00:46:20 +01:00
Alberto Faria 9fbf17928e Switch to Fedora 40 everywhere
Containerdisks now set their entrypoint to "no-entrypoint", so we can
drop the empty "" argument to podman-run and equivalents in most cases.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-25 01:10:54 +01:00
Alberto Faria 00d705ed8e tests/env.sh: Remove superfluous `shellcheck disable`
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-24 09:10:23 +01:00
Alberto Faria 253489ba30 tests/env.sh: Make build less verbose
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-24 00:04:08 +01:00
Alberto Faria 6a3437f1fe tests/env.sh: Avoid unnecessary dependency on sed
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 23:44:16 +01:00
Alberto Faria 8c4e505d94
Merge pull request #57 from containers/plans
Disable testing farm tests
2024-04-23 11:56:50 +01:00
Alberto Faria f3c37f3d68 Disable testing farm tests
Our test harness now requires nested virt, but test farm runners are
themselves VMs and do not support KVM, so the harness is effectively
unusable in testing farm right now.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:47:30 +01:00
Alberto Faria b2fd56055b Simplify Makefile
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:46:10 +01:00
Alberto Faria 977faaee0e Move lint.sh under tests/
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:46:10 +01:00
Alberto Faria 20b1326108 tests/env.sh: Fix log timestamps
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:46:09 +01:00
Alberto Faria aeb0a17328 util/extract-vm-image.sh: Clarify "Extracting ..." message
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:31:31 +01:00
Alberto Faria 7ef08f27aa exec: Disable timeout by default
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:31:31 +01:00
Alberto Faria 3a724ec6bc create: Use emulation when /dev/kvm is not available
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 11:15:13 +01:00
Alberto Faria ee504a43a2 src/main: Print backtrace on failure if requested through RUST_{LIB_}BACKTRACE
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-23 10:17:40 +01:00
Daniel J Walsh b349ec0f57
Merge pull request #55 from containers/plans
plans: Add missing dependencies
2024-04-22 20:04:22 -04:00
Alberto Faria 4c2c9673b8 plans: Add missing dependencies
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-22 23:52:00 +01:00
Alberto Faria 3d99ddce2f tests: Avoid extra dependency on bc
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-22 22:13:32 +01:00
Alberto Faria a75b65aafc CI: Validate minimal dependency versions
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-22 10:10:58 +01:00
Alberto Faria 8148bc7580 CI: Run ./lint.sh
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-22 10:06:45 +01:00
Alberto Faria ccbea36ea4 create: Add a --random-ssh-key-pair flag
This forces the generation of a new random ssh key pair for the
container and VM, never reusing the host user's key pair.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-18 00:12:56 +01:00
Alberto Faria 0b5e757037 create: Mount the user's keypair into the container instead of copying it
This should ensure we don't leave copies of the keypair around the file
system by accident.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-18 00:12:56 +01:00
Alberto Faria 3151250389 create: Fix linter error
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 23:44:44 +01:00
Alberto Faria 1197087348 tests/env.sh: Avoid race in the restart command
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 23:43:57 +01:00
Alberto Faria 4fd5f0d856 util/extract-vm-image.sh: Print message indicating path to file being extracted
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 23:43:57 +01:00
Alberto Faria 20253890fc create: Don't mention podman-run's --privileged flag in error messages
crun-vm also works with engines for which privileged containers are
enabled in other ways, for instance Kubernetes.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 22:24:52 +01:00
Alberto Faria fafc72bbdd Make --persistent incompatible with podman-run's --read-only and equivalents
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 22:22:53 +01:00
Alberto Faria c2fdb09e93
Merge pull request #52 from containers/print-config-json
create: Add a --print-config-json flag
2024-04-17 21:45:38 +01:00
Alberto Faria 9647a59164 create: Add a --print-config-json flag
Useful for debugging.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 21:19:38 +01:00
Alberto Faria 27d58182f0
Merge pull request #51 from containers/tests-faster-cleanup
tests/env.sh: Make VM cleanup faster
2024-04-17 21:19:16 +01:00
Alberto Faria 6cce28974b tests/env.sh: Make VM cleanup faster
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 21:18:51 +01:00
Alberto Faria c5af553e4e
Merge pull request #50 from containers/no-privileged
create: Forbid --privileged
2024-04-17 21:18:20 +01:00
Alberto Faria 640ddd6df9 create: Forbid --privileged
There should be no reason to use it with crun-vm and it causes problems
with additional devices being mounted into the container and crun-vm
trying to pass those through to the guest.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-17 21:16:12 +01:00
Alberto Faria 34b7b1bb83
Merge pull request #49 from containers/only-std
src/lib: Reject commands we don't understand
2024-04-16 18:08:42 +01:00
Alberto Faria bed19c087b src/lib: Reject commands we don't understand
Be conservative about what we support.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-16 18:07:25 +01:00
Alberto Faria 8b4dcf0c52
Merge pull request #48 from containers/entrypoint-fix
create: Allow empty strings anywhere in the container command
2024-04-16 17:52:35 +01:00
Alberto Faria 565ec60b4c create: Allow empty strings anywhere in the container command
Users should always be allowed to include an empty "" argument,
regardless of whether the container image sets an entrypoint.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-16 17:11:23 +01:00
Alberto Faria 1efbd7d8b0
Merge pull request #47 from containers/no-entrypoint
create: Ignore first argument if it is "no-entrypoint"
2024-04-15 23:26:10 +01:00
Alberto Faria 46628d817d util/package-vm-image.sh: Set entrypoint to "no-entrypoint"
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 23:25:28 +01:00
Alberto Faria 0ef45a885a create: Ignore first argument if it is "no-entrypoint"
This makes crun-vm compatible with containerdisks that have such an
entrypoint, which precludes the need to pass an empty "" argument to
podman-run.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 23:25:21 +01:00
Alberto Faria d1d54e61c4
Merge pull request #46 from containers/error-messages
delete: Avoid printing errors due to crun container not existing
2024-04-15 23:23:57 +01:00
Alberto Faria 0fbb8d4741 delete: Avoid printing errors due to crun container not existing
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 23:23:29 +01:00
Alberto Faria 93f277fe1d
Merge pull request #45 from containers/publish-test
tests: Add a test for --publish
2024-04-15 23:23:03 +01:00
Alberto Faria bc246172f6 tests: Add a test for --publish
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 23:21:43 +01:00
Alberto Faria 2a8c00cc70 tests: Remove LAST_RUN_ID variable
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 23:21:43 +01:00
Alberto Faria 2eb1059ff6
Merge pull request #44 from containers/better-tests
tests: Switch to a VM-based test harness
2024-04-15 14:45:34 +01:00
Alberto Faria a9d2204a54 tests: Switch to a VM-based test harness
This allows us to further control the test environment and write more
involved tests.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-15 14:40:38 +01:00
Alberto Faria 4074473885
Merge pull request #43 from containers/rootfs-fixes
--rootfs fixes
2024-04-14 21:25:49 +01:00
Alberto Faria 6952740be8 delete: Clean up crun-vm mounts
Ensure that the container engine is able to clean up the container's
root when it is subsequently removed.

The user must still manually remove the directory created under the
directory specified with --rootfs, once the container is definitively
deleted.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-14 20:21:06 +01:00
Alberto Faria f7098dd51a create: Fix --rootfs with --persistent
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-14 17:59:51 +01:00
Alberto Faria 2064469f01
Merge pull request #42 from containers/next
A series of fixes and small improvements
2024-04-13 23:10:50 +01:00
Alberto Faria ec38f3b423 util/package-vm-image.sh: Make it work with files not under the current dir
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:17 +01:00
Alberto Faria 7c32dd9bcb util/package-vm-image.sh: Build KubeVirt containerdisk-compatible images
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:17 +01:00
Alberto Faria f17c1fe88b exec: Avoid "remote host identification has changed" warning
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:17 +01:00
Alberto Faria 4115843e8f exec: Fix ssh readiness check after reboot
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:17 +01:00
Alberto Faria adb8e2c302 exec: Allow customizing timeout
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:17 +01:00
Alberto Faria fd17070d5d create: Make overlayfs/bind mounts work on all engines after a container restart
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 23:08:16 +01:00
Alberto Faria 407bd2f415 create: Use CPU mode host-passthrough
It can sometimes be more performant than host-model.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 15:59:37 +01:00
Alberto Faria bb3309afe3 create: Always require absolute paths for custom options
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-04-13 15:59:32 +01:00
Alberto Faria ce88cd119d
Merge pull request #41 from containers/as
tests/run: Fix exec invocation
2024-03-22 12:33:38 +00:00
Alberto Faria 488e53ef8a tests/run: Fix exec invocation
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-03-22 12:32:12 +00:00
Alberto Faria 4da92f3736
Merge pull request #40 from containers/fedora
docs: Add Fedora install instructions
2024-03-22 07:40:48 +00:00
Alberto Faria 99d74d5acd docs: Add Fedora install instructions
crun-vm is now packaged in Fedora 38 and above.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-03-22 07:39:21 +00:00
Alberto Faria 3b35d0610b
Merge pull request #39 from containers/drop-vfio-pci
Drop options --vfio-pci and --vfio-pci-mdev
2024-03-21 18:43:08 +00:00
Alberto Faria 4dbd7bc986 Drop options --vfio-pci and --vfio-pci-mdev
Using these options requires binding the device to the vfio-pci driver
in the host, and may require special configuration of locked memory
limits.

Without these options, the user has to customize the libvirt XML
directly to achieve PCI device passthrough, which should make it clearer
that they have to take care of all these details.

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-03-21 18:38:58 +00:00
Alberto Faria ef5a4c0057
Merge pull request #38 from containers/exec-user
src/commands/exec: Add --as option to set user to ssh as, and default to root
2024-03-21 17:18:32 +00:00
Alberto Faria 1c173e4595 src/commands/exec: Add --as option to set user to ssh as, and default to root
Closes #10

Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-03-21 17:15:35 +00:00
Alberto Faria 066501d3f8
Merge pull request #36 from containers/passphrase
Go back to using the host's SSH keypair but avoid asking for passphrase more than once
2024-03-18 17:10:57 +00:00
Alberto Faria caa191342a scripts/exec.sh: Avoid asking for passphrase more than once
Signed-off-by: Alberto Faria <afaria@redhat.com>
2024-03-18 17:07:11 +00:00
Alberto Faria 6dffba3255 Revert "src/commands/create: Never use the host's SSH key pair"
This reverts commit 1bfede6cc9.
2024-03-18 17:06:10 +00:00
66 changed files with 3339 additions and 2089 deletions

View File

@ -1,2 +0,0 @@
[profile.default]
slow-timeout = { period = "60s", terminate-after = 2 }

24
.github/renovate.json5 vendored Normal file
View File

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

44
.github/workflows/lint.yml vendored Normal file
View File

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

View File

@ -58,7 +58,7 @@ jobs:
- name: Release
if: github.event_name == 'workflow_dispatch'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
name: ${{ github.event.inputs.name }}
tag_name: ${{ github.event.inputs.name }}

3
.gitignore vendored
View File

@ -1,3 +1,2 @@
/out
/target
/bin/
/targets/

View File

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

View File

@ -1,60 +1,25 @@
---
# See the documentation for more information:
# https://packit.dev/docs/configuration/
specfile_path: rpm/crun-vm.spec
srpm_build_deps:
- cargo
- make
- openssl-devel
jobs:
- job: copr_build
trigger: pull_request
notifications:
failure_comment:
message: "Ephemeral COPR build failed. @containers/packit-build please check."
targets:
fedora-all-x86_64: {}
fedora-all-aarch64: {}
fedora-eln-x86_64:
additional_repos:
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/x86_64/"
fedora-eln-aarch64:
additional_repos:
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/aarch64/"
additional_repos:
- "copr://rhcontainerbot/podman-next"
enable_net: true
# Run on commit to main branch
- job: copr_build
trigger: commit
notifications:
failure_comment:
message: "podman-next COPR build failed. @containers/packit-build please check."
branch: main
owner: rhcontainerbot
project: podman-next
enable_net: true
# Unit tests
- job: tests
- &tests
job: tests
trigger: pull_request
skip_build: true
enable_net: true
targets:
- fedora-all-aarch64
- fedora-all-x86_64
identifier: unit_test
tmt_plan: "/plans/unit_test"
# TODO: Replace these three with fedora-all-x86_64 once Fedora 38 is gone.
- fedora-rawhide-x86_64
- fedora-40-x86_64
- fedora-39-x86_64
identifier: podman
tmt_plan: /tests/podman
# Validate test
- job: tests
trigger: pull_request
skip_build: true
targets:
# Only need to test on one target
- fedora-latest-stable-x86_64
identifier: validate_test
tmt_plan: "/plans/validate_test"
- <<: *tests
identifier: rootful-podman
tmt_plan: /tests/rootful-podman
- <<: *tests
identifier: docker
tmt_plan: /tests/docker

434
Cargo.lock generated
View File

@ -4,36 +4,36 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstyle"
version = "1.0.5"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anyhow"
version = "1.0.79"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "bitflags"
version = "2.4.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block-buffer"
@ -46,15 +46,15 @@ dependencies = [
[[package]]
name = "bytes"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "camino"
version = "1.1.6"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
[[package]]
name = "cfg-if"
@ -63,10 +63,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.18"
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
@ -74,32 +80,52 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.18"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstyle",
"clap_lex",
"strsim",
"strsim 0.11.1",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "const_format"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "cpufeatures"
@ -112,7 +138,7 @@ dependencies = [
[[package]]
name = "crun-vm"
version = "0.1.3"
version = "0.3.0"
dependencies = [
"anyhow",
"camino",
@ -129,9 +155,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"test-case",
"urlencoding",
"uuid",
"xml-rs",
]
@ -147,9 +171,9 @@ dependencies = [
[[package]]
name = "darling"
version = "0.14.4"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
dependencies = [
"darling_core",
"darling_macro",
@ -157,58 +181,58 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.14.4"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 1.0.109",
"strsim 0.10.0",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 1.0.109",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.12.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.12.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.12.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
"syn 1.0.109",
"syn",
]
[[package]]
@ -243,27 +267,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getset"
version = "0.1.2"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe"
dependencies = [
"proc-macro-error",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn",
]
[[package]]
@ -274,15 +287,15 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.4"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
[[package]]
name = "home"
@ -301,9 +314,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.2.2"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
@ -311,36 +324,36 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "liboci-cli"
version = "0.3.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fb8daab7180d25db1086b0fb73a0de935f3cb55d56d60e946ed84bec2a3ffd"
checksum = "731e7d86b6f06717b9b365895f707b229fc755e45c0122b8ac1de9c0f0cf1547"
dependencies = [
"clap",
]
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "minidom"
@ -353,20 +366,21 @@ dependencies = [
[[package]]
name = "nix"
version = "0.27.1"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
@ -374,64 +388,66 @@ dependencies = [
[[package]]
name = "oci-spec"
version = "0.6.4"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8384f8eff13954bafafba991f1910779020456f9694de25e81a13da5b7de6309"
checksum = "57e9beda9d92fac7bf4904c34c83340ef1024159faee67179a04e0277523da33"
dependencies = [
"const_format",
"derive_builder",
"getset",
"regex",
"serde",
"serde_json",
"strum",
"strum_macros",
"thiserror",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"version_check",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -441,9 +457,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
@ -452,15 +468,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rust-embed"
version = "6.8.1"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@ -469,27 +485,33 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "6.8.1"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.48",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.8.1"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "rxml"
version = "0.9.1"
@ -509,9 +531,9 @@ checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530"
[[package]]
name = "ryu"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"
@ -524,40 +546,41 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.113"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.31"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
@ -601,78 +624,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"unicode-ident",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "test-case"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-core"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "test-case-macros"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"test-case-core",
]
[[package]]
name = "thiserror"
version = "1.0.56"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn",
]
[[package]]
@ -688,10 +692,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unsafe-libyaml"
version = "0.2.10"
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "urlencoding"
@ -699,15 +709,6 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "uuid"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [
"getrandom",
]
[[package]]
name = "version_check"
version = "0.9.4"
@ -716,51 +717,23 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697"
dependencies = [
"winapi",
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -772,13 +745,14 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -787,48 +761,54 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "xml-rs"
version = "0.8.19"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7"

View File

@ -1,80 +1,71 @@
[package]
name = "crun-vm"
version = "0.1.3"
version = "0.3.0"
edition = "2021"
rust-version = "1.74"
license = "GPL-2.0-or-later"
[dependencies.anyhow]
version = "1.0.46"
version = "1.0.98"
[dependencies.camino]
version = "1.1"
version = "1.1.10"
[dependencies.clap]
version = "4.0"
version = "4.5.40"
default-features = false
features = ["cargo", "derive", "std"]
[dependencies.home]
version = "0.5.0"
version = "0.5.9"
[dependencies.lazy_static]
version = "1.1"
version = "1.5.0"
default-features = false
[dependencies.liboci-cli]
version = "0.3.0"
version = "0.5.3"
default-features = false
[dependencies.minidom]
version = "0.15.2"
[dependencies.nix]
version = "0.27.0"
version = "0.30.1"
default-features = false
features = ["fs", "mount"]
[dependencies.num_cpus]
version = "1.0"
version = "1.17.0"
default-features = false
[dependencies.oci-spec]
version = "0.6.0"
version = "0.8.1"
default-features = false
features = ["runtime"]
[dependencies.regex]
version = "1.0"
version = "1.10.6"
default-features = false
features = ["std"]
[dependencies.rust-embed]
version = "6.0"
version = "8.7.2"
default-features = false
features = ["debug-embed"]
[dependencies.serde]
version = "1.0"
version = "1.0.219"
[dependencies.serde_json]
version = "1.0"
version = "1.0.140"
[dependencies.serde_yaml]
version = "0.9.0"
version = "0.9.34"
[dependencies.urlencoding]
version = "2.0"
version = "2.1.3"
[dependencies.xml-rs]
version = "0.8.0"
version = "0.8.27"
default-features = false
[dev-dependencies.test-case]
version = "3.0"
default-features = false
[dev-dependencies.uuid]
version = "1.0"
default-features = false
features = ["std", "v4"]

View File

@ -1,80 +1,48 @@
# This Makefile is intended for developer convenience. For the most part
# all the targets here simply wrap calls to the `cargo` tool. Therefore,
# most targets must be marked 'PHONY' to prevent `make` getting in the way
#
#prog :=xnixperms
# SPDX-License-Identifier: GPL-2.0-or-later
DESTDIR ?=
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
PREFIX ?= /usr/local
CARGO ?= cargo
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
# Get crate version by parsing the line that starts with version.
CRATE_VERSION ?= $(shell grep ^version Cargo.toml | awk '{print $$3}')
GIT_TAG ?= $(shell git describe --tags)
# Set this to any non-empty string to enable unoptimized
# build w/ debugging features.
debug ?=
binpath := $(DESTDIR)/$(PREFIX)/bin/crun-vm
manpath := $(DESTDIR)/$(PREFIX)/share/man/man1/crun-vm.1.gz
# Set path to cargo executable
CARGO ?= cargo
all: out/crun-vm out/crun-vm.1.gz
# All complication artifacts, including dependencies and intermediates
# will be stored here, for all architectures. Use a non-default name
# since the (default) 'target' is used/referenced ambiguously in many
# places in the tool-chain (including 'make' itself).
CARGO_TARGET_DIR ?= targets
export CARGO_TARGET_DIR # 'cargo' is sensitive to this env. var. value.
.PHONY: out/crun-vm
out/crun-vm:
mkdir -p $(@D)
$(CARGO) build --release
cp target/release/crun-vm $@
ifdef debug
$(info debug is $(debug))
# These affect both $(CARGO_TARGET_DIR) layout and contents
# Ref: https://doc.rust-lang.org/cargo/guide/build-cache.html
release :=
profile :=debug
else
release :=--release
profile :=release
endif
.PHONY: all
all: build
bin:
mkdir -p $@
$(CARGO_TARGET_DIR):
mkdir -p $@
.PHONY: build
build: bin $(CARGO_TARGET_DIR)
$(CARGO) build $(release)
cp $(CARGO_TARGET_DIR)/$(profile)/crun-vm bin/crun-vm$(if $(debug),.debug,)
out/crun-vm.1.gz: docs/5-crun-vm.1.ronn
mkdir -p $(@D)
ronn --pipe --roff $< | gzip > $@
.PHONY: clean
clean:
rm -rf bin
if [ "$(CARGO_TARGET_DIR)" = "targets" ]; then rm -rf targets; fi
rm -fr out target
.PHONY: install
install:
install ${SELINUXOPT} -D -m0755 bin/crun-vm $(DESTDIR)/$(BINDIR)/crun-vm
install: out/crun-vm install-man
install ${SELINUXOPT} -D -m 0755 $< $(binpath)
.PHONY: install-man
install-man: out/crun-vm.1.gz
install ${SELINUXOPT} -D -m 0644 $< $(manpath)
.PHONY: uninstall
uninstall:
rm -f $(DESTDIR)/$(BINDIR)/crun-vm
rm -f $(binpath) $(manpath)
#.PHONY: unit
unit: $(CARGO_TARGET_DIR)
$(SHELL) test.sh podman
.PHONY: lint
lint:
tests/lint.sh
#.PHONY: code_coverage
code_coverage: $(CARGO_TARGET_DIR)
# Downloads tarpaulin only if same version is not present on local
$(CARGO) install cargo-tarpaulin
$(CARGO) tarpaulin -v
.PHONY: validate
validate: $(CARGO_TARGET_DIR)
$(SHELL) lint.sh
.PHONY: test
test:
tests/env.sh build
tests/env.sh start
tests/env.sh run all all

View File

@ -1,47 +1,107 @@
# The crun-vm OCI Runtime
**crun-vm** is an [OCI Runtime] that enables [Podman], [Docker], and
[Kubernetes] to run QEMU-compatible Virtual Machine (VM) images.
[Kubernetes] to run QEMU-compatible Virtual Machine (VM) images. This means you
can:
- Run **VMs** as easily as you run **containers**.
- Manage containers and VMs **together** using the **same** standard tooling.
- **No need** for in-depth knowledge on virtualization technologies like libvirt
or KubeVirt.
- Run **VMs** as easily as you run **containers**.
- Manage containers and VMs **together** using the **same** standard tooling.
---
<p align="center">
<img src="docs/example.gif" width="680" />
</p>
---
<table>
<tr>
<td width="450" valign="top">
### Quick start
Install crun-vm:
```console
$ dnf install crun-vm
```
Launch a VM from a disk image packaged in a container:
```console
$ podman run --runtime crun-vm -it \
quay.io/containerdisks/fedora:40
```
Launch a VM from a disk image under `my-image-dir/`:
```console
$ podman run --runtime crun-vm -it \
--rootfs my-image-dir/
```
Launch a VM from a [bootable container]:
```console
$ podman run --runtime crun-vm -it \
quay.io/crun-vm/example-fedora-bootc:40
```
Set the password for a VM's default user:
```console
$ podman run --runtime crun-vm -it \
quay.io/containerdisks/fedora:40 \
--password pass # for user "fedora"
```
Exec (ssh) into a VM:
```console
$ podman exec -it --latest -- --as fedora
```
<p></p>
</td>
<td valign="top">
### Major features
- Use it with **Podman**, **Docker**, or **Kubernetes**.
- Launch VMs from VM **image files** present on the host or packaged into
**container images**.
- Control VM **CPU** and **memory** allocation.
- Provide **cloud-init** and **Ignition** configurations to VMs.
- **Mount directories** into VMs.
- Pass **cloud-init** or **Ignition** configs to VMs.
- Mount **directories** into VMs.
- Pass **block devices** through to VMs.
- Expose **qcow2 files** and other **disk images** to VMs as block devices.
- Pass **vfio-pci** and **mediated vfio-pci** devices through to VMs.
- Expose additional **disk images** to VMs.
- **Forward ports** from the host to VMs.
- **`podman exec`**/**`docker exec`**/**`kubectl exec`** into VMs.
- **`podman|docker|kubectl exec`** into VMs.
---
### Documentation
1. [Installing crun-vm](docs/1-installing.md)
2. [Using crun-vm as a Podman or Docker runtime](docs/2-podman-docker.md)
3. [Using crun-vm as a Kubernetes runtime](docs/3-kubernetes.md)
2. [Running VMs with **Podman** or **Docker**](docs/2-podman-docker.md)
3. [Running VMs as **systemd** services](docs/3-systemd.md)
4. [Running VMs in **Kubernetes**](docs/4-kubernetes.md)
5. [**crun-vm(1)** man page](docs/5-crun-vm.1.ronn)
> [!TIP]
> See also how you can [combine **crun-vm** and **Podman Quadlet** to easily
> manage both containers and VMs through **systemd**](/examples/quadlet).
---
### License
This project is released under the GPL 2.0 (or later) license. See
[LICENSE](LICENSE).
<p></p>
</td>
</tr>
</table>
[bootable container]: https://containers.github.io/bootable
[Docker]: https://www.docker.com/
[Kubernetes]: https://kubernetes.io/
[Podman]: https://podman.io/
[KubeVirt]: https://kubevirt.io/
[OCI Runtime]: https://github.com/opencontainers/runtime-spec/blob/v1.1.0/spec.md
[Podman]: https://podman.io/
[systemd]: https://systemd.io/

View File

@ -1,66 +1,156 @@
# 1. Installing crun-vm
## Build and install from source (on Fedora)
There are two steps to setting up crun-vm on a system:
1. Install crun-vm's runtime dependencies:
- Installing the actual `crun-vm` binary;
- Configuring Podman, Docker, and/or Kubernetes (whichever you intend to use
crun-vm with) to recognize crun-vm as a runtime.
These steps are detailed in the sections below.
<details open>
<summary><b>Navigation</b></summary>
1. **Installing crun-vm**
- [**Installing the `crun-vm` binary**](#installing-the-crun-vm-binary)
- [On Fedora](#on-fedora)
- [From source](#from-source)
- [**Making crun-vm available as a runtime**](#making-crun-vm-available-as-a-runtime)
- [For Podman](#for-podman)
- [For Docker](#for-docker)
- [For Kubernetes](#for-kubernetes)
2. [Running VMs with **Podman** or **Docker**](2-podman-docker.md)
3. [Running VMs as **systemd** services](3-systemd.md)
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
</details>
## Installing the `crun-vm` binary
### On Fedora
```console
$ dnf install crun-vm
```
### From source
1. Install crun-vm's build dependencies:
- [cargo](https://doc.rust-lang.org/stable/cargo/getting-started/installation.html)
- [gzip](https://www.gzip.org/)
- [libselinux](https://github.com/SELinuxProject/selinux/tree/main/libselinux),
including development headers
- [ronn-ng](https://github.com/apjanke/ronn-ng)
2. Install crun-vm's runtime dependencies:
- bash
- [coreutils](https://www.gnu.org/software/coreutils/)
- [crun](https://github.com/containers/crun)
- [crun-krun](https://github.com/containers/crun/blob/main/krun.1.md)
- [genisoimage](https://github.com/Distrotech/cdrkit)
- grep
- [libselinux](https://github.com/SELinuxProject/selinux/tree/main/libselinux)
- [libvirtd](https://gitlab.com/libvirt/libvirt) or
[virtqemud](https://gitlab.com/libvirt/libvirt)
- [passt](https://passt.top/)
- [qemu-img](https://gitlab.com/qemu-project/qemu)
- qemu-system-x86_64, qemu-system-aarch64, and/or other [QEMU system
emulators](https://gitlab.com/qemu-project/qemu) for the VM architectures
you want to support
- [skopeo](https://github.com/containers/skopeo)
- ssh
- [util-linux](https://github.com/util-linux/util-linux)
- [virsh](https://gitlab.com/libvirt/libvirt)
- [virtiofsd](https://gitlab.com/virtio-fs/virtiofsd)
- [virtlogd](https://gitlab.com/libvirt/libvirt)
3. Install crun-vm's binary and man page:
```console
$ dnf install bash coreutils crun genisoimage grep libselinux-devel libvirt-client libvirt-daemon-driver-qemu libvirt-daemon-log openssh-clients qemu-img qemu-system-x86-core shadow-utils util-linux virtiofsd
$ make install
```
2. Install Rust and Cargo if you do not already have Rust tooling available:
## Making crun-vm available as a runtime
```console
$ dnf install cargo
```
### For Podman
3. Build crun-vm:
Nothing to do here, since Podman automatically recognizes crun-vm as a runtime.
Commands like `podman create` and `podman run` can be made to use the crun-vm
runtime by passing them the `--runtime crun-vm` option.
```console
$ cargo build
```
<!--
Paths search by Podman:
- `/usr/bin/crun-vm`
- `/usr/local/bin/crun-vm`
- `/usr/local/sbin/crun-vm`
- `/sbin/crun-vm`
- `/bin/crun-vm`
- `/run/current-system/sw/bin/crun-vm`
-->
4. Copy the `target/debug/crun-vm` binary to wherever you prefer:
See [2. Running VMs with **Podman** or **Docker**](2-podman-docker.md) to get
started.
```console
$ cp target/debug/crun-vm /usr/local/bin/
```
### For Docker
5. If you're using Podman:
1. Merge the following configuration into `/etc/docker/daemon.json` (creating
that directory and file if necessary):
- Merge the following configuration into
`/etc/containers/containers.conf`:
> For rootless Podman, you can instead use
> `${XDG_CONFIG_PATH}/containers/containers.conf`, where
> `$XDG_CONFIG_PATH` defaults to `${HOME}/.config`.
```toml
[engine.runtimes]
crun-vm = ["/usr/local/bin/crun-vm"]
```
If you're using Docker:
- Merge the following configuration into `/etc/docker/daemon.json`:
```json
{
"runtimes": {
"crun-vm": {
"path": "/usr/local/bin/crun-vm"
}
}
```json
{
"runtimes": {
"crun-vm": {
"path": "/usr/bin/crun-vm"
}
```
}
}
```
- Reload the `docker` service for the new configuration to take effect:
2. Reload the `docker` service for the new configuration to take effect:
```console
$ service docker reload
```
```console
$ service docker reload
```
With Podman, it is possible to use crun-vm without installing it, *i.e.*,
performing only steps 13 above. In this case, instead of setting the runtime
with `--runtime crun-vm`, specify an absolute path to the runtime binary:
`--runtime "$PWD"/target/debug/crun-vm`.
Commands like `docker create` and `docker run` can then be made to use the
crun-vm runtime by passing them the `--runtime crun-vm` option.
See [2. Using crun-vm and **Podman** or **Docker** to run a
VM](2-podman-docker.md) to get started.
### For Kubernetes
For crun-vm to be usable as a runtime in a Kubernetes cluster, the latter must
be use the [CRI-O] runtime. See the Kubernetes docs on [runtimes] for more
information.
1. Install crun-vm on all cluster nodes where pods may be scheduled, using any
of the methods [described above](#installing-the-crun-vm-binary).
2. Append the following to `/etc/crio/crio.conf`:
```toml
[crio.runtime.runtimes.crun-vm]
runtime_path = "/usr/bin/crun-vm"
```
3. Create a `RuntimeClass` object in the cluster that references crun-vm:
```yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: crun-vm # a name of your choice
handler: crun-vm
```
Pods can then be configured to use this `RuntimeClass` by specifying its name
under `Pod.spec.runtimeClassName`.
See [4. Running VMs in **Kubernetes**](4-kubernetes.md) to get started.
[runtimes]: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
[CRI-O]: https://cri-o.io/

View File

@ -1,39 +1,66 @@
# 2. Using crun-vm as a Podman or Docker runtime
# 2. Running VMs with **Podman** or **Docker**
Here we overview some of the major features provided by crun-vm. The commands
below use `podman`, but unless otherwise stated you can simply replace it with
`docker`.
Here, we outline how to use crun-vm to run VMs using the Podman or Docker
container engines. See [crun-vm(1)] for a full reference and additional details
on crun-vm specific options.
We use `podman` in the example commands below. Unless otherwise noted, the same
commands also with `docker`.
<details open>
<summary><b>Navigation</b></summary>
1. [Installing crun-vm](1-installing.md)
2. **Running VMs with **Podman** or **Docker****
- [**Booting VMs**](#booting-vms)
- [From containerdisks](#from-containerdisks)
- [From VM image files](#from-vm-image-files)
- [From bootable containers](#from-bootable-containers)
- [**Configuring VMs on first boot**](#configuring-vms-on-first-boot)
- [Default user password](#default-user-password)
- [cloud-init](#cloud-init)
- [Ignition](#ignition)
- [**Interacting with VMs**](#interacting-with-vms)
- [Exec'ing into VMs](#execing-into-vms)
- [Port forwarding](#port-forwarding)
- [**Sharing resources with VMs**](#sharing-resources-with-vms)
- [Files](#files)
- [Directories](#directories)
- [Block devices](#block-devices)
3. [Running VMs as **systemd** services](3-systemd.md)
4. [Running VMs in **Kubernetes**](4-kubernetes.md)
5. [**crun-vm(1)** man page](5-crun-vm.1.ronn)
</details>
## Booting VMs
### From regular VM image files
### From containerdisks
First, obtain a QEMU-compatible VM image and place it in a directory by itself:
A "containerdisk" is a container image that packages a sole VM image file under
`/` or `/disk/`. This is how you would boot a VM from the
quay.io/containerdisks/fedora:40 containerdisk using crun-vm:
```console
$ mkdir my-vm-image
$ curl -LO --output-dir my-vm-image https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
[...]
```
Then run:
> Several regularly-updated containerdisks may be found at
> https://quay.io/organization/containerdisks. You can also easily build your
> own:
>
> ```dockerfile
> FROM scratch
> COPY my-vm-image.qcow2 /
> ENTRYPOINT ["no-entrypoint"]
> ```
> This example does not work with Docker, as docker-run does not support the
> `--rootfs` flag; see the next section for a Docker-compatible way of running
> VM images.
```console
$ podman run \
--runtime crun-vm \
-it --rm \
--rootfs my-vm-image \
"" # unused, but must specify command
```
The VM console should take over your terminal. At this point, the
qcow2 image does not have any ssh keys, root password, or alternative
users installed, so although you can interact with the VM's login
screen, you will be unable to access a command prompt until more
options are used in later sections. To abort the VM, press `ctrl-]`.
The VM console should take over your terminal. This VM image has no users that
you may log in as using a password, so although you can interact with the VM's
login screen, you will be unable to access a command prompt for now. To abort
the VM, press `ctrl-]`.
You can also detach from the VM without terminating it by pressing `ctrl-p,
ctrl-q`. Afterwards, reattach by running:
@ -50,162 +77,174 @@ podman-run, in which case you won't be able to interact with the VM but can
still observe its console. Note that pressing `ctrl-]` will have no effect, but
you can always use the following command to terminate the VM:
> For this command to work with Docker, you must replace the `--latest` flag
> with the container's name or ID.
```container
$ podman stop --latest
```
Changes made by the VM to its image are by default not persisted in the original
image file. This can be changed by passing in the non-standard option
`--persistent` *after* the `--rootfs` option:
### From VM image files
> This feature is only supported with Podman.
It is also possible to boot VMs directly from disk image files by using Podman's
`--rootfs` option to point at a directory containing a sole image file. For
instance, these commands download and boot a Fedora 40 VM:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
--rootfs my-vm-image \
--persistent
$ mkdir my-vm-image/ && curl -LO --output-dir my-vm-image/ https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic.x86_64-40-1.14.qcow2
$ podman run --runtime crun-vm -it --rootfs my-vm-image/
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
[...]
```
> [!WARNING]
>
> When using `--persistent`, make sure that the image file is never
> simultaneously used by another process or VM, otherwise **data corruption may
> occur**.
### From bootable containers
### From VM image files packaged into container images
crun-vm also works with container images that contain a VM image file with
any name under `/` or under `/disk/`. No other files may exist in those
directories. Containers built for use as [KubeVirt `containerDisk`s] follow this
convention, so you can use those here:
crun-vm can also launch VMs from [bootc bootable container images], which are
containers that package a full operating system:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
"" # unused, but must specify command because container image does not
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
Converting quay.io/crun-vm/example-fedora-bootc:40 into a VM image...
[...]
Caching VM image as a containerdisk...
[...]
Booting VM...
[...]
```
You can also use `util/package-vm-image.sh` to easily package a VM image into a
container image, and `util/extract-vm-image.sh` to extract a VM image contained
in a container image.
crun-vm generates a VM image from the bootable container and then boots it. The
generated VM image is packaged as a containerdisk and cached in the host's
container storage, so that subsequent runs will boot faster:
Note that flag `--persistent` has no effect when running VMs from container
images.
```console
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-bootc:40
Retrieving cached VM image...
[...]
Booting VM...
[...]
```
## First-boot customization
## Configuring VMs on first boot
### Default user password
In the examples above, you were able to boot a VM but not to log in. An easy way
to fix this when a VM has [cloud-init] installed is to use the [`--password`]
option, which sets the password for the VM's "default" user (as determined in
the image's cloud-init configuration). For quay.io/containerdisks/fedora:40,
that is the `fedora` user:
```console
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 --password pass
Booting `Fedora Linux (6.8.5-301.fc40.x86_64) 40 (Cloud Edition)'
[...]
3b0232a04046 login: fedora
Password: pass
[fedora@3b0232a04046 ~]$
```
Like all crun-vm specific options, [`--password`] must be passed in *after* the
image specification.
### cloud-init
In the examples above, you were able to boot the VM but not to log in. To fix
this and do other first-boot customization, you can provide a [cloud-init]
NoCloud configuration to the VM by passing in the non-standard option
`--cloud-init` *after* the image specification:
> For this command to work with Docker, you must provide an absolute path to
> `--cloud-init`.
You can provide a full [cloud-init] NoCloud configuration to a VM by passing in
the crun-vm specific option [`--cloud-init`] *after* the image specification:
```console
$ ls examples/cloud-init/config/
meta-data user-data vendor-data
$ ls my-cloud-init-config/
meta-data user-data
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
--cloud-init examples/cloud-init/config
```
$ cat my-cloud-init-config/meta-data # empty
You should now be able to log in with the default `fedora` username and password
`pass`.
$ cat my-cloud-init-config/user-data
#cloud-config
write_files:
- path: $home/file
content: |
hello
Alternatively, you can set the default user's password with the `--password`
option:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
--password pass
$ podman run --runtime crun-vm -it quay.io/containerdisks/fedora:40 \
--cloud-init $PWD/my-cloud-init-config/ # path must be absolute
```
### Ignition
Similarly, you can provide an [Ignition] configuration to the VM by passing in
the `--ignition` option:
> For this command to work with Docker, you must provide an absolute path to
> `--ignition`.
You can also provide an [Ignition] configuration to a VM using the crun-vm
specific [`--ignition`] option:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/crun-vm/example-fedora-coreos:39 \
--ignition examples/ignition/config.ign
$ cat my-ignition-config.ign
{
"ignition": {
"version": "3.0.0"
},
"passwd": {
"users": [
{
"name": "core",
"passwordHash": "$y$j9T$USdd8CBvFNVU1xKwQUnsU/$aE.3arHcRxD0ZT3vkvsSpEsteUj6vC4ZdRHY8eOj1f4"
}
]
}
}
$ podman run --runtime crun-vm -it quay.io/crun-vm/example-fedora-coreos:40 \
--ignition $PWD/my-ignition-config.ign # path must be absolute
```
You should now be able to log in with the default `core` username and password
`pass`.
## Interacting with VMs
Note that the `--password` option requires cloud-init support and doesn't work
if the VM uses Ignition.
### Exec'ing into VMs
## SSH'ing into the VM
Assuming the VM supports cloud-init or Ignition and exposes an SSH server on
port 22, you can `ssh` into it using podman-exec as the VMs default user:
> For this command to work with Docker, you must replace the `--latest` flag
> with the container's name or ID.
Assuming a VM supports cloud-init or Ignition and exposes an SSH server on port
22, you can `ssh` into it as root using podman-exec:
```console
$ podman run \
--runtime crun-vm \
--detach --rm \
quay.io/containerdisks/fedora:39 \
""
$ podman run --runtime crun-vm --detach quay.io/containerdisks/fedora:40
8068a2c180e0f4bf494f5e0baa37d9f13a9810f76b361c0771b73666e47ec383
$ podman exec --latest fedora whoami
fedora
$ podman exec --latest whoami
Please login as the user "fedora" rather than the user "root".
```
$ podman exec -it --latest fedora
This particular VM image does not allow logging in as root. To `ssh` into the VM
as a different user, specify its username using the [`--as`] option immediately
before the command (if any). You may need to pass in `--` before this option to
prevent podman-exec from trying to interpret it:
```console
$ podman exec --latest -- --as fedora whoami
fedora
```
If you just want a login shell, pass in an empty string as the command. The
following would be the output if this VM image allowed logging in as root:
```console
$ podman exec -it --latest ""
[root@8068a2c180e0 ~]$
```
You may also log in as a specific user:
```console
$ podman exec -it --latest -- --as fedora
[fedora@8068a2c180e0 ~]$
```
With cloud-init, the default user can vary between VM images. With Ignition,
`core` is considered to be the default user. In both cases, if the SSH server
allows password authentication, you should also be able to log in as any other
user.
When a VM supports cloud-init, `authorized_keys` is automatically set up to
allow SSH access by podman-exec for users `root` and the default user as set in
the image's cloud-init configuration. With Ignition, this is set up for users
`root` and `core`.
The `fedora` argument to podman-exec above, which would typically correspond to
the command to be executed, determines instead the name of the user to `ssh`
into the VM as. A command can optionally be specified with further arguments. If
no command is specified, a login shell is initiated. In this case, you probably
also want to pass flags `-it` to podman-exec.
### Port forwarding
If you actually just want to exec into the container in which the VM is running
(probably to debug some problem with crun-vm itself), pass in `-` as the
username.
## Port forwarding
You can use podman-run's standard `-p`/`--publish` option to set up TCP and/or
You can use podman-run's standard `-p`/`--publish` option to enable TCP and/or
UDP port forwarding:
```console
$ podman run \
--runtime crun-vm \
--detach --rm \
-p 8000:80 \
quay.io/crun-vm/example-http-server:latest \
""
$ podman run --runtime crun-vm --detach -p 8000:80 quay.io/crun-vm/example-http-server:latest
36c8705482589cfc4336a03d3802e7699f5fb228123d18e693488ac7b80116d1
$ curl localhost:8000
@ -219,145 +258,73 @@ $ curl localhost:8000
[...]
```
## Passing things through to the VM
## Sharing resources with VMs
### Files
You can bind mount regular files into a VM:
```console
$ podman run --runtime crun-vm -it \
-v ./README.md:/home/fedora/README.md:z \
quay.io/containerdisks/fedora:40
```
Regular files currently appear as block devices in the VM, but this is subject
to change.
### Directories
Bind mounting directories into the VM is supported:
> [!WARNING]
>
> This example recursively modifies the SELinux context of all files under the
> path being mounted, in this case `./util`, which in the worst case **may cause
> you to lose access to your files**. This is due to the `:z` volume mount
> modifier, which instructs Podman to relabel the volume so that the VM can
> access it.
>
> Alternatively, you may remove this modifier from the command below and add
> `--security-opt label=disable` instead to disable SELinux enforcement.
It is also possible to bind mount directories into a VM:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
$ podman run --runtime crun-vm -it \
-v ./util:/home/fedora/util:z \
quay.io/containerdisks/fedora:39 \
--password pass
quay.io/containerdisks/fedora:40
```
If the VM supports cloud-init or Ignition, the volume will automatically be
mounted at the given destination path. Otherwise, you can mount it manually with
the following command, where `<index>` must be the 0-based index of the volume
the following command, where `<index>` is the 0-based index of the volume
according to the order the `-v`/`--volume` or `--mount` flags where given in:
```console
$ mount -t virtiofs virtiofs-<index> /home/fedora/util
```
### Regular files
Similarly to directories, you can bind mount regular files into the VM:
> [!WARNING]
>
> The warning about SELinux relabeling on the command above also applies here.
```console
$ podman run \
--runtime crun-vm \
-it --rm \
-v ./README.md:/home/fedora/README.md:z \
quay.io/containerdisks/fedora:39 \
--password pass
```
Regular files currently appear as block devices in the VM, but this is subject
to change.
### Block devices
If cloud-init or Ignition are supported by the VM, it is possible to pass block
devices through to it at a specific path using podman-run's `--device` flag
(this example assumes `/dev/ram0` to exist and to be accessible by the current
user):
If cloud-init or Ignition are supported by a VM, it is possible to pass block
devices through to it and make them appear at a specific path using podman-run's
`--device` flag. For instance, assuming `/dev/ram0` exists on the host and is
accessible by the current user:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
$ podman run --runtime crun-vm -it \
--device /dev/ram0:/home/fedora/my-disk \
quay.io/containerdisks/fedora:39 \
--password pass
quay.io/containerdisks/fedora:40
```
You can also use the more powerful `--blockdev
source=<path>,target=<path>,format=<fmt>` custom option to this effect. This
option also allows you specify a regular file as the source, and the source may
be in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`,
raw format is assumed):
> For this command to work with Docker, you must provide absolute paths to
> `--blockdev`.
You can also use the more powerful, crun-vm specific [`--blockdev`]
`source=<path>,target=<path>,format=<fmt>` option to this effect. This option
also allows you to specify a regular file as the source, and the source may be
in any disk format known to QEMU (*e.g.*, raw, qcow2; when using `--device`, raw
format is assumed):
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
--password pass \
--blockdev source=my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2
$ podman run --runtime crun-vm -it \
quay.io/containerdisks/fedora:40 \
--blockdev source=$PWD/my-disk.qcow2,target=/home/fedora/my-disk,format=qcow2 # paths must be absolute
```
## Advanced options
### PCI device assignment
vfio-pci devices can be passed through to the VM by specifying the non-standard
`--vfio-pci` option with a path to the device's sysfs directory (this example
assumes that the corresponding VFIO device under `/dev/vfio/` is accessible to
the current user):
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
--vfio-pci /sys/bus/pci/devices/0000:00:01.0
```
In turn, mediated (mdev) vfio-pci devices (such as vGPUs) can be passed through
with the `--vfio-pci-mdev` option, specifying a path to the mdev's sysfs
directory:
```console
$ podman run \
--runtime crun-vm \
-it --rm \
quay.io/containerdisks/fedora:39 \
--vfio-pci-mdev /sys/bus/pci/devices/0000:00:02.0/5fa530b9-9fdf-4cde-8eb7-af73fcdeeaae
```
### Inspecting and customizing the libvirt domain XML
crun-vm internally uses [libvirt] to launch a VM, generating a [domain XML
definition] from the options provided to podman-run. This XML definition can be
printed by adding the non-standard `--print-libvirt-xml` flag to your podman-run
invocation.
The generated XML definition can also be customized by specifying an XML file to
be merged with it using the non-standard option `--merge-libvirt-xml <file>`.
> [!NOTE]
>
> While `--merge-libvirt-xml` gives you maximum flexibility, it thwarts
> crun-vm's premise of isolating the user from such details as libvirt domain
> definitions, and you have instead to take care that your XML is valid *and*
> that the customized definition is compatible with what crun-vm expects.
>
> Before using this flag, consider if you would be better served using libvirt
> directly to manage your VM.
[`--as`]: 5-crun-vm.1.ronn#exec-options
[`--blockdev`]: 5-crun-vm.1.ronn#createrun-options
[`--cloud-init`]: 5-crun-vm.1.ronn#createrun-options
[`--ignition`]: 5-crun-vm.1.ronn#createrun-options
[`--password`]: 5-crun-vm.1.ronn#createrun-options
[bootc bootable container images]: https://containers.github.io/bootable/
[cloud-init]: https://cloud-init.io/
[crun-vm(1)]: 5-crun-vm.1.ronn
[domain XML definition]: https://libvirt.org/formatdomain.html
[Ignition]: https://coreos.github.io/ignition/
[KubeVirt `containerDisk`s]: https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk

View File

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

130
docs/3-systemd.md Normal file
View File

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

182
docs/4-kubernetes.md Normal file
View File

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

186
docs/5-crun-vm.1.ronn Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 365 KiB

99
embed/bootc/config.json Normal file
View File

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

58
embed/bootc/entrypoint.sh Normal file
View File

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

139
embed/bootc/prepare.sh Normal file
View File

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

View File

@ -5,6 +5,11 @@ trap 'exit 143' SIGTERM
set -o errexit -o pipefail -o nounset
is_bootc_container=$1
# clean up locks that may have been left around from the container being killed
rm -fr /var/lock
mkdir -p \
/etc/libvirt \
/tmp \
@ -50,6 +55,22 @@ virsh --connect "qemu+unix:///session?socket=$socket" "\$@"
EOF
chmod +x /crun-vm/virsh
# wait until VM image is generated from bootable container (if applicable)
if (( is_bootc_container == 1 )) && [[ ! -e /crun-vm/image/image ]]; then
fifo=/crun-vm/bootc/progress
while [[ ! -e "$fifo" ]]; do sleep 0.2; done
cat "$fifo"
rm "$fifo"
[[ -e /crun-vm/bootc/success ]]
mkdir -p /crun-vm/image
mv /crun-vm/bootc/image.qcow2 /crun-vm/image/image
fi
# launch VM
function __bg_ensure_tty() {
@ -64,12 +85,9 @@ function __bg_ensure_tty() {
virsh=( virsh --connect "qemu+unix:///session?socket=$socket" --quiet )
# If our container was stopped and is being restarted, the domain may still be
# defined from the previous run, which would cause `virsh define` below to fail,
# so we first undefine it.
"${virsh[@]}" undefine domain &>/dev/null || true
"${virsh[@]}" define /crun-vm/domain.xml
if [[ -z "$( "${virsh[@]}" list --all --name )" ]]; then
"${virsh[@]}" define /crun-vm/domain.xml
fi
# trigger graceful shutdown and wait for VM to terminate
function __shutdown() {

67
embed/exec.sh Normal file
View File

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

View File

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

View File

@ -1 +0,0 @@
local-hostname: my-vm

View File

@ -1,4 +0,0 @@
#cloud-config
password: pass
chpasswd:
expire: False

View File

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

View File

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

View File

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

View File

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

View File

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

61
plans/tests.fmf Normal file
View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ use clap::Parser;
use lazy_static::lazy_static;
use regex::Regex;
use crate::commands::create::runtime_env::RuntimeEnv;
use crate::commands::create::engine::Engine;
#[derive(Clone, Debug)]
pub struct Blockdev {
@ -41,252 +41,145 @@ impl FromStr for Blockdev {
}
}
#[derive(Clone, Debug)]
pub struct VfioPciAddress {
pub domain: u16,
pub bus: u8,
pub slot: u8,
pub function: u8,
}
impl VfioPciAddress {
fn from_path(path: impl AsRef<Utf8Path>) -> Result<Self> {
lazy_static! {
static ref PATTERN: Regex = {
let h = r"[0-9a-fA-F]".to_string();
let db = format!(r"{h}{{4}}:{h}{{2}}");
let dbsf = format!(r"{h}{{4}}:{h}{{2}}:{h}{{2}}.{h}{{1}}");
let pattern = format!(
r"^/sys/devices/pci{db}(/{dbsf})*/({h}{{4}}):({h}{{2}}):({h}{{2}}).({h}{{1}})$"
);
Regex::new(&pattern).unwrap()
};
}
let path = path.as_ref().canonicalize_utf8()?;
let capture = PATTERN
.captures(path.as_str())
.ok_or_else(|| anyhow!("not a valid vfio-pci device sysfs path"))?;
let address = VfioPciAddress {
domain: u16::from_str_radix(&capture[2], 16).unwrap(),
bus: u8::from_str_radix(&capture[3], 16).unwrap(),
slot: u8::from_str_radix(&capture[4], 16).unwrap(),
function: u8::from_str_radix(&capture[5], 16).unwrap(),
};
Ok(address)
}
}
#[derive(Clone, Debug)]
pub struct VfioPciMdevUuid(pub String);
impl VfioPciMdevUuid {
fn from_path(path: impl AsRef<Utf8Path>) -> Result<Self> {
lazy_static! {
static ref PATTERN: Regex = {
let h = r"[0-9a-zA-Z]".to_string();
let db = format!(r"{h}{{4}}:{h}{{2}}");
let dbsf = format!(r"{h}{{4}}:{h}{{2}}:{h}{{2}}.{h}{{1}}");
let uuid = format!(r"{h}{{8}}-{h}{{4}}-{h}{{4}}-{h}{{4}}-{h}{{12}}");
let pattern = format!(r"^/sys/devices/pci{db}(/{dbsf})+/({uuid})$");
Regex::new(&pattern).unwrap()
};
}
let path = path.as_ref().canonicalize_utf8()?;
let capture = PATTERN
.captures(path.as_str())
.ok_or_else(|| anyhow!("not a valid vfio-pci mediated device sysfs path"))?;
Ok(VfioPciMdevUuid(capture[2].to_string()))
}
}
#[derive(Debug)]
pub struct CustomOptions {
pub blockdev: Vec<Blockdev>,
pub persistent: bool,
pub cloud_init: Option<Utf8PathBuf>,
pub ignition: Option<Utf8PathBuf>,
pub vfio_pci: Vec<VfioPciAddress>,
pub vfio_pci_mdev: Vec<VfioPciMdevUuid>,
pub password: Option<String>,
pub merge_libvirt_xml: Vec<Utf8PathBuf>,
pub print_libvirt_xml: bool,
}
impl TryFrom<CustomOptionsRaw> for CustomOptions {
type Error = anyhow::Error;
fn try_from(opts: CustomOptionsRaw) -> Result<Self> {
Ok(Self {
blockdev: opts.blockdev,
persistent: opts.persistent,
cloud_init: opts.cloud_init,
ignition: opts.ignition,
vfio_pci: opts
.vfio_pci
.iter()
.map(VfioPciAddress::from_path)
.collect::<Result<_>>()?,
vfio_pci_mdev: opts
.vfio_pci_mdev
.iter()
.map(VfioPciMdevUuid::from_path)
.collect::<Result<_>>()?,
password: opts.password,
merge_libvirt_xml: opts.merge_libvirt_xml,
print_libvirt_xml: opts.print_libvirt_xml,
})
}
}
#[derive(clap::Parser, Debug)]
struct CustomOptionsRaw {
pub struct CustomOptions {
#[clap(long)]
blockdev: Vec<Blockdev>,
pub blockdev: Vec<Blockdev>,
#[clap(long)]
persistent: bool,
pub persistent: bool,
#[clap(long)]
cloud_init: Option<Utf8PathBuf>,
pub random_ssh_key_pair: bool,
#[clap(long, help = "Use system emulation rather than KVM")]
pub emulated: bool,
#[clap(long)]
ignition: Option<Utf8PathBuf>,
pub bootc_disk_size: Option<String>,
#[clap(long)]
vfio_pci: Vec<Utf8PathBuf>,
pub cloud_init: Option<Utf8PathBuf>,
#[clap(long)]
vfio_pci_mdev: Vec<Utf8PathBuf>,
pub ignition: Option<Utf8PathBuf>,
#[clap(long)]
password: Option<String>,
pub password: Option<String>,
#[clap(long)]
merge_libvirt_xml: Vec<Utf8PathBuf>,
pub merge_libvirt_xml: Vec<Utf8PathBuf>,
#[clap(long)]
print_libvirt_xml: bool,
pub print_libvirt_xml: bool,
#[clap(long, conflicts_with = "print_libvirt_xml")]
pub print_config_json: bool,
}
impl CustomOptions {
pub fn from_spec(spec: &oci_spec::runtime::Spec, env: RuntimeEnv) -> Result<Self> {
let args = spec
pub fn from_spec(spec: &oci_spec::runtime::Spec, engine: Engine) -> Result<Self> {
let mut args: Vec<&String> = spec
.process()
.as_ref()
.unwrap()
.args()
.iter()
.flatten()
.filter(|arg| !arg.trim().is_empty());
.filter(|a| !a.trim().is_empty())
.collect();
// TODO: We currently assume that no entrypoint is given (either by being set by in the
// container image or through --entrypoint). Must somehow find whether the first arg is the
// entrypoint and ignore it in that case.
let mut options = CustomOptionsRaw::parse_from(
if let Some(&first_arg) = args.first() {
let ignore = [
"no-entrypoint",
"/sbin/init",
"/usr/sbin/init",
"/usr/local/sbin/init",
];
if ignore.contains(&first_arg.as_str()) {
args.remove(0);
}
}
if let Some(&first_arg) = args.first() {
ensure!(
first_arg.starts_with('-'),
"unexpected entrypoint '{first_arg}' found; use an image without an entrypoint or with entrypoint \"no-entrypoint\", and/or pass in an empty \"\" entrypoint on the command line"
);
}
let mut options = CustomOptions::parse_from(
iter::once(&"podman run [<podman-opts>] <image>".to_string()).chain(args),
);
ensure!(
!spec.root().as_ref().unwrap().readonly().unwrap_or(false) || !options.persistent,
"--persistent was set but the container's root file system was mounted as read-only"
);
fn all_are_absolute(iter: impl IntoIterator<Item = impl AsRef<Utf8Path>>) -> bool {
iter.into_iter().all(|p| p.as_ref().is_absolute())
}
fn path_in_container_into_path_in_host(
spec: &oci_spec::runtime::Spec,
path: impl AsRef<Utf8Path>,
path: &Utf8Path,
) -> Result<Utf8PathBuf> {
let mount = spec
.mounts()
.iter()
.flatten()
.filter(|m| m.source().is_some())
.filter(|m| path.as_ref().starts_with(m.destination()))
.filter(|m| path.starts_with(m.destination()))
.last()
.ok_or_else(|| anyhow!("can't find {}", path.as_ref()))?;
.ok_or_else(|| anyhow!("can't find {}", path))?;
let mount_source: &Utf8Path = mount.source().as_deref().unwrap().try_into()?;
let relative_path = path.as_ref().strip_prefix(mount.destination()).unwrap();
let relative_path = path.strip_prefix(mount.destination()).unwrap();
let path_in_host = mount_source.join(relative_path);
ensure!(path_in_host.try_exists()?, "can't find {}", path.as_ref());
ensure!(path_in_host.try_exists()?, "can't find {}", path);
Ok(path_in_host)
}
match env {
RuntimeEnv::Docker => {
// Docker doesn't run the runtime with the same working directory as the process
// that launched `docker-run`, so we require custom option paths to be absolute.
//
// TODO: There must be a better way...
ensure!(
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
&& all_are_absolute(&options.cloud_init)
&& all_are_absolute(&options.ignition)
&& all_are_absolute(&options.vfio_pci)
&& all_are_absolute(&options.vfio_pci_mdev)
&& all_are_absolute(&options.merge_libvirt_xml),
concat!(
"paths specified using --blockdev, --cloud-init, --ignition, --vfio-pci,",
" --vfio-pci-mdev, or --merge-libvirt-xml must be absolute when using",
" crun-vm as a Docker runtime",
),
);
// Docker doesn't run the runtime with the same working directory as the process
// that launched `docker-run`. Similarly, custom option paths in Kubernetes refer to
// paths in the container/VM, and there isn't a reasonable notion of what the
// current directory is. We thus simply always require custom option paths to be
// absolute.
ensure!(
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
&& all_are_absolute(&options.cloud_init)
&& all_are_absolute(&options.ignition)
&& all_are_absolute(&options.merge_libvirt_xml),
concat!(
"paths specified using --blockdev, --cloud-init, --ignition, or",
" --merge-libvirt-xml must be absolute",
),
);
if engine == Engine::Kubernetes {
for blockdev in &mut options.blockdev {
blockdev.source = path_in_container_into_path_in_host(spec, &blockdev.source)?;
blockdev.target = path_in_container_into_path_in_host(spec, &blockdev.target)?;
}
RuntimeEnv::Kubernetes => {
// Custom option paths in Kubernetes refer to paths in the container/VM, and there
// isn't a reasonable notion of what the current directory is.
ensure!(
all_are_absolute(options.blockdev.iter().flat_map(|b| [&b.source, &b.target]))
&& all_are_absolute(&options.cloud_init)
&& all_are_absolute(&options.ignition)
&& all_are_absolute(&options.merge_libvirt_xml),
concat!(
"paths specified using --blockdev, --cloud-init, --ignition, or",
" --merge-libvirt-xml must be absolute when using crun-vm as a",
" Kubernetes runtime",
),
);
ensure!(
options.vfio_pci.is_empty() && options.vfio_pci_mdev.is_empty(),
concat!(
"options --vfio-pci and --vfio-pci-mdev are not allowed when using",
" crun-vm as a Kubernetes runtime",
)
);
for blockdev in &mut options.blockdev {
blockdev.source = path_in_container_into_path_in_host(spec, &blockdev.source)?;
blockdev.target = path_in_container_into_path_in_host(spec, &blockdev.target)?;
}
if let Some(path) = &mut options.cloud_init {
*path = path_in_container_into_path_in_host(spec, &path)?;
}
if let Some(path) = &mut options.ignition {
*path = path_in_container_into_path_in_host(spec, &path)?;
}
for path in &mut options.merge_libvirt_xml {
*path = path_in_container_into_path_in_host(spec, &path)?;
}
if let Some(path) = &mut options.cloud_init {
*path = path_in_container_into_path_in_host(spec, path)?;
}
if let Some(path) = &mut options.ignition {
*path = path_in_container_into_path_in_host(spec, path)?;
}
for path in &mut options.merge_libvirt_xml {
*path = path_in_container_into_path_in_host(spec, path)?;
}
RuntimeEnv::Other => {}
}
options.try_into()
Ok(options)
}
}

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::env;
use std::fs::File;
use std::io::{BufReader, Write};
@ -7,7 +8,7 @@ use anyhow::{ensure, Result};
use camino::Utf8Path;
use xml::writer::XmlEvent;
use crate::commands::create::custom_opts::{CustomOptions, VfioPciMdevUuid};
use crate::commands::create::custom_opts::CustomOptions;
use crate::commands::create::Mounts;
use crate::util::{SpecExt, VmImageInfo};
@ -36,10 +37,16 @@ fn generate(
.perform_indent(true)
.create_writer(File::create(path.as_ref())?);
s(&mut w, "domain", &[("type", "kvm")], |w| {
let domain_type = match custom_options.emulated {
true => "qemu",
false => "kvm",
};
s(&mut w, "domain", &[("type", domain_type)], |w| {
st(w, "name", &[], "domain")?;
se(w, "cpu", &[("mode", "host-model")])?;
se(w, "cpu", &[("mode", "maximum")])?;
let vcpus = get_vcpu_count(spec).to_string();
if let Some(cpu_set) = get_cpu_set(spec) {
st(w, "vcpu", &[("cpuset", cpu_set.as_str())], vcpus.as_str())?;
@ -50,8 +57,18 @@ fn generate(
let memory = get_memory_size(spec).to_string();
st(w, "memory", &[("unit", "b")], memory.as_str())?;
s(w, "os", &[], |w| {
st(w, "type", &[("machine", "q35")], "hvm")
s(w, "os", &[("firmware", "efi")], |w| {
let attrs = match ["x86", "x86_64"].contains(&env::consts::ARCH) {
true => [("machine", "q35")].as_slice(),
false => [].as_slice(), // use libvirt's default
};
st(w, "type", attrs, "hvm")?;
s(w, "firmware", &[], |w| {
se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")])
})?;
Ok(())
})?;
// fw_cfg requires ACPI
@ -158,41 +175,6 @@ fn generate(
})?;
}
for address in &custom_options.vfio_pci {
s(
w,
"hostdev",
&[("mode", "subsystem"), ("type", "pci")],
|w| {
s(w, "source", &[], |w| {
se(
w,
"address",
&[
("domain", &format!("0x{:04x}", address.domain)),
("bus", &format!("0x{:02x}", address.bus)),
("slot", &format!("0x{:02x}", address.slot)),
("function", &format!("0x{:01x}", address.function)),
],
)
})
},
)?;
}
for VfioPciMdevUuid(uuid) in &custom_options.vfio_pci_mdev {
s(
w,
"hostdev",
&[
("mode", "subsystem"),
("type", "mdev"),
("model", "vfio-pci"),
],
|w| s(w, "source", &[], |w| se(w, "address", &[("uuid", uuid)])),
)?;
}
Ok(())
})?;

View File

@ -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");
}
}

View File

@ -17,7 +17,6 @@ pub struct FirstBootConfig<'a> {
}
impl FirstBootConfig<'_> {
/// Returns `true` if a cloud-init config should be passed to the VM.
pub fn apply_to_cloud_init_config(
&self,
in_config_dir_path: Option<impl AsRef<Utf8Path>>,
@ -132,11 +131,38 @@ impl FirstBootConfig<'_> {
ssh_authorized_keys.push(self.container_public_key.into());
let write_files = match user_data_mapping
.entry("write_files".into())
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
{
serde_yaml::Value::Sequence(v) => v,
_ => bail!("invalid user-data file"),
};
write_files.push({
let mut m = serde_yaml::Mapping::new();
m.insert("path".into(), "/root/.ssh/authorized_keys".into());
m.insert(
"content".into(),
("\n".to_string() + self.container_public_key).into(),
);
m.insert("append".into(), true.into());
m.into()
});
// create block device symlinks and udev rules
let block_device_symlinks = self.get_block_device_symlinks();
let block_device_udev_rules = self.get_block_device_udev_rules();
if let Some(rules) = &block_device_udev_rules {
let mut mapping = serde_yaml::Mapping::new();
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
mapping.insert("content".into(), rules.to_string().into());
write_files.push(mapping.into());
}
if !block_device_symlinks.is_empty() || block_device_udev_rules.is_some() {
let runcmd = match user_data_mapping
.entry("runcmd".into())
@ -166,22 +192,6 @@ impl FirstBootConfig<'_> {
}
}
if let Some(rules) = block_device_udev_rules {
let write_files = match user_data_mapping
.entry("write_files".into())
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
{
serde_yaml::Value::Sequence(v) => v,
_ => bail!("invalid user-data file"),
};
let mut mapping = serde_yaml::Mapping::new();
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
mapping.insert("content".into(), rules.into());
write_files.push(mapping.into());
}
// generate iso
{
@ -263,15 +273,17 @@ impl FirstBootConfig<'_> {
_ => bail!("invalid config file"),
};
let users_contains_core = users.iter().any(|u| match u {
serde_json::Value::Object(m) => m.get("name") == Some(&"core".into()),
_ => false,
});
for user in ["root", "core"] {
let user_exists = users.iter().any(|u| match u {
serde_json::Value::Object(m) => m.get("name") == Some(&user.into()),
_ => false,
});
if !users_contains_core {
users.push(serde_json::json!({
"name": "core",
}));
if !user_exists {
users.push(serde_json::json!({
"name": user,
}));
}
}
for user in users {
@ -280,7 +292,9 @@ impl FirstBootConfig<'_> {
_ => bail!("invalid config file"),
};
if map.get("name") == Some(&"core".into()) {
let name = map.get("name");
if name == Some(&"root".into()) || name == Some(&"core".into()) {
let keys = match map
.entry("sshAuthorizedKeys")
.or_insert_with(|| serde_json::json!([]))
@ -290,8 +304,6 @@ impl FirstBootConfig<'_> {
};
keys.push(self.container_public_key.into());
break;
}
}

View File

@ -2,115 +2,273 @@
mod custom_opts;
mod domain;
mod engine;
mod first_boot;
mod runtime_env;
use std::fs::{self, Permissions};
use std::ffi::OsStr;
use std::fs::{self, File, Permissions};
use std::io::ErrorKind;
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
use std::path::Path;
use std::process::Command;
use std::process::{Command, Stdio};
use anyhow::{bail, ensure, Context, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use lazy_static::lazy_static;
use nix::sys::stat::{major, makedev, minor, mknod, Mode, SFlag};
use regex::Regex;
use rust_embed::RustEmbed;
use crate::commands::create::custom_opts::CustomOptions;
use crate::commands::create::domain::set_up_libvirt_domain_xml;
use crate::commands::create::engine::Engine;
use crate::commands::create::first_boot::FirstBootConfig;
use crate::commands::create::runtime_env::RuntimeEnv;
use crate::crun::crun_create;
use crate::util::{
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image,
find_single_file_in_dirs, set_file_context, SpecExt, VmImageInfo,
bind_mount_dir_with_different_context, bind_mount_file, create_overlay_vm_image, crun,
find_single_file_in_dirs, fix_selinux_label, is_mountpoint, set_file_context, SpecExt,
VmImageInfo,
};
pub fn create(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Create) -> Result<()> {
pub fn create(args: &liboci_cli::Create, raw_args: &[impl AsRef<OsStr>]) -> Result<()> {
let bundle_path: &Utf8Path = args.bundle.as_path().try_into()?;
let config_path = bundle_path.join("config.json");
let mut spec = oci_spec::runtime::Spec::load(&config_path)?;
let original_root_path = spec.root_path()?.to_path_buf();
ensure_unprivileged(&spec)?;
let runtime_env = RuntimeEnv::current(&spec, &original_root_path)?;
let custom_options = CustomOptions::from_spec(&spec, runtime_env)?;
let original_root_path: Utf8PathBuf = spec.root_path()?.canonicalize()?.try_into()?; // ensure absolute
set_up_container_root(&mut spec, bundle_path, &custom_options)?;
let base_vm_image_info =
set_up_vm_image(&spec, bundle_path, &original_root_path, &custom_options)?;
let engine = Engine::detect(&args.container_id, bundle_path, &spec, &original_root_path)?;
let custom_options = CustomOptions::from_spec(&spec, engine)?;
let is_bootc_container = is_bootc_container(&original_root_path, &custom_options, engine)?;
// We include container_id in our paths to ensure no overlap with the user container's contents.
let priv_dir_path = original_root_path.join(format!("crun-vm-{}", args.container_id));
fs::create_dir_all(&priv_dir_path)?;
if let Some(context) = spec.mount_label() {
// the directory we're using as the root for the container is not the one that podman
// prepared for us, so we need to set its context ourselves to prevent SELinux from getting
// angry at us
set_file_context(&priv_dir_path, context)?;
}
set_up_container_root(
&mut spec,
&priv_dir_path,
&custom_options,
is_bootc_container,
)?;
let is_first_create = is_first_create(&spec)?;
let base_vm_image_info = set_up_vm_image(
&spec,
&original_root_path,
&priv_dir_path,
&custom_options,
is_first_create,
is_bootc_container,
)?;
let mut mounts = Mounts::default();
set_up_mounts(&mut spec, &mut mounts)?;
set_up_devices(&mut spec, &mut mounts)?;
set_up_blockdevs(&mut spec, &mut mounts, &custom_options)?;
set_up_extra_container_mounts_and_devices(&mut spec)?;
set_up_extra_container_mounts_and_devices(&mut spec, &custom_options)?;
set_up_security(&mut spec);
set_up_first_boot_config(&spec, &mounts, &custom_options)?;
set_up_libvirt_domain_xml(&spec, &base_vm_image_info, &mounts, &custom_options)?;
let ssh_pub_key = set_up_ssh_key_pair(
&mut spec,
&custom_options,
engine,
&priv_dir_path,
is_first_create,
)?;
if is_first_create {
set_up_first_boot_config(&spec, &mounts, &custom_options, &ssh_pub_key)?;
set_up_libvirt_domain_xml(&spec, &base_vm_image_info, &mounts, &custom_options)?;
}
adjust_container_rlimits_and_resources(&mut spec);
spec.save(&config_path)?;
spec.save(spec.root_path()?.join("crun-vm/config.json"))?; // to aid debugging
crun_create(global_args, args)?; // actually create container
crun(raw_args)?; // actually create container
if is_first_create && is_bootc_container {
// We want to ask podman what our image name is, so we can give it to bootc-install, but we
// can't wait synchronously for a response since podman hangs until this create command
// completes. We then want to run bootc-install under krun, which already isolates the
// workload and so can be run outside of our container. We thus launch a process that
// asynchronously performs these steps, and share its progress and output with out
// container's entrypoint through a named pipe.
//
// Note that this process blocks until our container's entrypoint actually starts running,
// thus after the "start" OCI runtime command is called.
let bootc_dir = priv_dir_path.join("root/crun-vm/bootc");
fs::create_dir_all(&bootc_dir)?;
std::process::Command::new(bootc_dir.join("prepare.sh"))
.arg(engine.command().unwrap())
.arg(&args.container_id)
.arg(&original_root_path)
.arg(&priv_dir_path)
.arg(custom_options.bootc_disk_size.unwrap_or_default())
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
}
Ok(())
}
fn ensure_unprivileged(spec: &oci_spec::runtime::Spec) -> Result<()> {
if let Some(process) = spec.process().as_ref() {
if let Some(capabilities) = process.capabilities().as_ref() {
fn any_is_cap_sys_admin(caps: &Option<oci_spec::runtime::Capabilities>) -> bool {
caps.as_ref()
.is_some_and(|set| set.contains(&oci_spec::runtime::Capability::SysAdmin))
}
ensure!(
!any_is_cap_sys_admin(capabilities.bounding())
&& !any_is_cap_sys_admin(capabilities.effective())
&& !any_is_cap_sys_admin(capabilities.inheritable())
&& !any_is_cap_sys_admin(capabilities.permitted())
&& !any_is_cap_sys_admin(capabilities.ambient()),
"crun-vm is incompatible with privileged containers"
);
}
}
Ok(())
}
fn is_bootc_container(
original_root_path: &Utf8Path,
custom_options: &CustomOptions,
engine: Engine,
) -> Result<bool> {
let is_bootc_container = original_root_path.join("usr/lib/bootc/install").is_dir();
ensure!(
!is_bootc_container || engine == Engine::Podman || engine == Engine::Docker,
"bootc containers are only supported with Podman and Docker"
);
ensure!(
!is_bootc_container || !custom_options.emulated,
"--emulated is incompatible with bootable containers"
);
if let Some(size) = &custom_options.bootc_disk_size {
lazy_static! {
static ref SIZE_PATTERN: Regex = Regex::new(r"^[0-9]+[KMGT]?$").unwrap();
}
ensure!(
SIZE_PATTERN.is_match(size),
concat!(
"--bootc-disk-size value must be a number followed by an optional suffix K",
" (kilobyte, 1024), M (megabyte, 1024k), G (gigabyte, 1024M), or T (terabyte,",
" 1024G)",
)
);
ensure!(
is_bootc_container,
"--bootc-disk-size only applies to bootable containers"
);
}
Ok(is_bootc_container)
}
fn is_first_create(spec: &oci_spec::runtime::Spec) -> Result<bool> {
let path = spec.root_path()?.join("crun-vm/create-ran");
let error = File::options()
.read(true)
.write(true)
.create_new(true)
.open(path)
.err();
match error {
Some(e) if e.kind() == ErrorKind::AlreadyExists => Ok(false),
Some(e) => Err(e.into()),
None => Ok(true),
}
}
fn set_up_container_root(
spec: &mut oci_spec::runtime::Spec,
bundle_path: &Utf8Path,
priv_dir_path: &Utf8Path,
custom_options: &CustomOptions,
is_bootc_container: bool,
) -> Result<()> {
let new_root_path = priv_dir_path.join("root");
fs::create_dir_all(&new_root_path)?;
match fs::remove_file(new_root_path.join("crun-vm/ssh-successful")) {
Err(e) if e.kind() == ErrorKind::NotFound => {}
r => r?,
};
// create root directory
spec.set_root(Some(
oci_spec::runtime::RootBuilder::default()
.path(bundle_path.join("crun-vm-root"))
.path(&new_root_path)
.readonly(false)
.build()
.unwrap(),
));
fs::create_dir_all(spec.root_path()?)?;
if let Some(context) = spec.mount_label() {
// the directory we're using as the root for the container is not the one that podman
// prepared for us, so we need to set its context ourselves to prevent SELinux from getting
// angry at us
set_file_context(spec.root_path()?, context)?;
}
// set up container scripts
// set up container files
#[derive(RustEmbed)]
#[folder = "scripts/"]
struct Scripts;
#[folder = "embed/"]
struct Embed;
for path in Scripts::iter() {
let path_in_host = spec.root_path()?.join("crun-vm").join(path.as_ref());
for path in Embed::iter() {
let path_in_host = new_root_path.join("crun-vm").join(path.as_ref());
fs::create_dir_all(path_in_host.parent().unwrap())?;
let file = Scripts::get(&path).unwrap();
let file = Embed::get(&path).unwrap();
fs::write(&path_in_host, file.data)?;
fs::set_permissions(&path_in_host, Permissions::from_mode(0o555))?;
let is_script = path.as_ref().ends_with(".sh");
let mode = if is_script { 0o755 } else { 0o644 };
fs::set_permissions(&path_in_host, Permissions::from_mode(mode))?;
}
// configure container entrypoint
let command = match custom_options.print_libvirt_xml {
true => vec!["cat", "/crun-vm/domain.xml"],
false => vec!["/crun-vm/entrypoint.sh"],
let command = if custom_options.print_libvirt_xml {
vec!["cat", "/crun-vm/domain.xml"]
} else if custom_options.print_config_json {
vec!["cat", "/crun-vm/config.json"]
} else {
let arg = if is_bootc_container { "1" } else { "0" };
vec!["/crun-vm/entrypoint.sh", arg]
};
spec.set_process({
let mut process = spec.process().clone().unwrap();
process.set_cwd(".".into());
process.set_command_line(None);
process.set_args(Some(command.into_iter().map(String::from).collect()));
fix_selinux_label(&mut process);
Some(process)
});
@ -119,10 +277,24 @@ fn set_up_container_root(
fn set_up_vm_image(
spec: &oci_spec::runtime::Spec,
bundle_path: &Utf8Path,
original_root_path: &Utf8Path,
priv_dir_path: &Utf8Path,
custom_options: &CustomOptions,
is_first_create: bool,
is_bootc_container: bool,
) -> Result<VmImageInfo> {
let mirror_vm_image_path_in_container = Utf8PathBuf::from("/crun-vm/image/image");
let mirror_vm_image_path_in_host = spec.root_path()?.join("crun-vm/image/image");
if is_bootc_container {
// the image will be generated later
return Ok(VmImageInfo {
path: mirror_vm_image_path_in_container,
size: 0,
format: "qcow2".to_string(),
});
}
// where inside the container to look for the VM image
const VM_IMAGE_SEARCH_PATHS: [&str; 2] = ["./", "disk/"];
@ -136,54 +308,57 @@ fn set_up_vm_image(
// mount user-provided VM image file into container
let mirror_vm_image_path_in_container =
Utf8Path::new("crun-vm/image").join(vm_image_path_in_host.file_name().unwrap());
let mirror_vm_image_path_in_host = spec.root_path()?.join(&mirror_vm_image_path_in_container);
let mirror_vm_image_path_in_container =
Utf8Path::new("/").join(mirror_vm_image_path_in_container);
// Make VM image file available in a subtree that doesn't overlap our internal container root so
// overlayfs works.
let private_dir = if custom_options.persistent {
let vm_image_dir_path = vm_image_path_in_host.parent().unwrap();
let vm_image_dir_name = vm_image_dir_path.file_name().unwrap();
let image_dir_path = priv_dir_path.join("image");
fs::create_dir_all(&image_dir_path)?;
let overlay_private_dir_name = format!(".crun-vm.{}.tmp", vm_image_dir_name);
let overlay_private_dir_path = vm_image_dir_path
.parent()
.unwrap()
.join(overlay_private_dir_name);
overlay_private_dir_path
} else {
bundle_path.join("crun-vm-vm-image-overlayfs")
};
// We may need to change the VM image context to actually be able to access it, but we don't
// want to change the user's original image file and also don't want to do a full data copy, so
// we use an overlayfs mount, which allows us to expose the same file with a different context.
//
// TODO: Clean up `private_dir` when VM is terminated (would be best-effort, but better than
// nothing).
bind_mount_dir_with_different_context(
vm_image_path_in_host.parent().unwrap(),
mirror_vm_image_path_in_host.parent().unwrap(),
spec.mount_label(),
custom_options.persistent,
private_dir,
)?;
let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
if !image_dir_path.join("image").try_exists()? {
fs::hard_link(vm_image_path_in_host, image_dir_path.join("image"))?;
}
if custom_options.persistent {
// We want to propagate writes but not removal, so that the user's file isn't deleted by
// Podman on cleanup, so we bind mount it on top of itself.
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
// can always access it, using the file's parent as the upperdir so that writes still
// propagate to it.
if !is_mountpoint(mirror_vm_image_path_in_host.parent().unwrap())? {
bind_mount_dir_with_different_context(
image_dir_path,
mirror_vm_image_path_in_host.parent().unwrap(),
priv_dir_path.join("scratch-image"),
spec.mount_label(),
false,
)?;
}
// Prevent the container engine from deleting the user's actual VM image file by mounting it
// on top of itself under our overlayfs mount.
bind_mount_file(&mirror_vm_image_path_in_host, &mirror_vm_image_path_in_host)?;
let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
vm_image_info.path = mirror_vm_image_path_in_container;
Ok(vm_image_info)
} else {
// The overlayfs mount already isolates the user's original image file from writes, but to
// ensure that we get copy-on-write and page cache sharing even when the underlying file
// system doesn't support reflinks, we create a qcow2 overlay and use that as the image.
// Mount overlayfs to expose the user's VM image file with a different SELinux context so we
// can always access it.
if !is_mountpoint(mirror_vm_image_path_in_host.parent().unwrap())? {
bind_mount_dir_with_different_context(
image_dir_path,
mirror_vm_image_path_in_host.parent().unwrap(),
priv_dir_path.join("scratch-image"),
spec.mount_label(),
true,
)?;
}
// The overlayfs mount forbids writes to the VM image file, and also we want to get
// copy-on-write and page cache sharing even when the underlying file system doesn't support
// reflinks, so we create a qcow2 overlay image.
let overlay_vm_image_path_in_container = Utf8PathBuf::from("crun-vm/image-overlay.qcow2");
let overlay_vm_image_path_in_host =
@ -191,13 +366,19 @@ fn set_up_vm_image(
let overlay_vm_image_path_in_container =
Utf8Path::new("/").join(overlay_vm_image_path_in_container);
vm_image_info.path = mirror_vm_image_path_in_container;
create_overlay_vm_image(&overlay_vm_image_path_in_host, &vm_image_info)?;
let mut base_vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
base_vm_image_info.path = mirror_vm_image_path_in_container;
vm_image_info.path = overlay_vm_image_path_in_container;
if is_first_create {
create_overlay_vm_image(&overlay_vm_image_path_in_host, &base_vm_image_info)?;
}
Ok(VmImageInfo {
path: Utf8Path::new("/").join(overlay_vm_image_path_in_container),
size: base_vm_image_info.size,
format: "qcow2".to_string(),
})
}
Ok(vm_image_info)
}
#[derive(Default)]
@ -406,7 +587,10 @@ fn set_up_blockdevs(
Ok(())
}
fn set_up_extra_container_mounts_and_devices(spec: &mut oci_spec::runtime::Spec) -> Result<()> {
fn set_up_extra_container_mounts_and_devices(
spec: &mut oci_spec::runtime::Spec,
custom_options: &CustomOptions,
) -> Result<()> {
fn add_bind_mount(spec: &mut oci_spec::runtime::Spec, path: impl AsRef<Path>) {
spec.mounts_push(
oci_spec::runtime::MountBuilder::default()
@ -457,9 +641,17 @@ fn set_up_extra_container_mounts_and_devices(spec: &mut oci_spec::runtime::Spec)
.unwrap(),
);
add_bind_mount(spec, "/dev/kvm");
add_char_dev(spec, "/dev/kvm")?;
if !custom_options.emulated {
ensure!(
Path::new("/dev/kvm").try_exists()?,
"/dev/kvm not found; is host KVM support enabled?"
);
add_bind_mount(spec, "/dev/kvm");
add_char_dev(spec, "/dev/kvm")?;
}
// in case user sets up VFIO passthrough by overriding the libvirt XML
for entry in fs::read_dir("/dev/vfio")? {
let entry = entry?;
if entry.metadata()?.file_type().is_char_device() {
@ -485,7 +677,7 @@ fn set_up_security(spec: &mut oci_spec::runtime::Spec) {
// TODO: This doesn't seem reasonable at all. Should we just force users to use a different
// seccomp profile? Should passt provide the option to bypass a lot of the isolation that it
// does, given we're already in a container *and* under a seccomp profile?
spec.linux_seccomp_syscalls_push(
spec.linux_seccomp_syscalls_push_front(
oci_spec::runtime::LinuxSyscallBuilder::default()
.names(["mount", "pivot_root", "umount2", "unshare"].map(String::from))
.action(oci_spec::runtime::LinuxSeccompAction::ScmpActAllow)
@ -499,12 +691,11 @@ fn set_up_first_boot_config(
spec: &oci_spec::runtime::Spec,
mounts: &Mounts,
custom_options: &CustomOptions,
container_public_key: &str,
) -> Result<()> {
let container_public_key = gen_container_ssh_key_pair(spec)?;
let config = FirstBootConfig {
hostname: spec.hostname().as_deref(),
container_public_key: &container_public_key,
container_public_key,
password: custom_options.password.as_deref(),
mounts,
};
@ -528,25 +719,64 @@ fn set_up_first_boot_config(
}
/// Returns the public key for the container.
fn gen_container_ssh_key_pair(spec: &oci_spec::runtime::Spec) -> Result<String> {
let ssh_path = spec.root_path()?.join("root/.ssh");
///
/// This first attempts to use the current user's key pair, just in case the VM does not support
/// cloud-init but the user injected their public key into it themselves.
fn set_up_ssh_key_pair(
spec: &mut oci_spec::runtime::Spec,
custom_options: &CustomOptions,
engine: Engine,
priv_dir_path: &Utf8Path,
is_first_create: bool,
) -> Result<String> {
let user_home: Utf8PathBuf = home::home_dir()
.ok_or_else(|| anyhow!("could not determine user home"))?
.try_into()?;
if !ssh_path.join("id_rsa.pub").exists() {
fs::create_dir_all(&ssh_path)?;
let user_ssh_dir = user_home.join(".ssh");
let container_ssh_dir = spec.root_path()?.join("root/.ssh");
// Use the host user's key pair if:
// - The user didn't set the --random-ssh-key-pair flag; and
// - We're not running under Docker (otherwise we'd probably not be running as the user that
// invoked the engine); and
// - We're not running under Kubernetes (where there isn't a "host user"); and
// - They have a key pair.
let use_user_key_pair = !custom_options.random_ssh_key_pair
&& engine == Engine::Podman
&& user_ssh_dir.join("id_rsa.pub").is_file()
&& user_ssh_dir.join("id_rsa").is_file();
if use_user_key_pair {
// use host user's key pair
bind_mount_dir_with_different_context(
&user_ssh_dir,
&container_ssh_dir,
priv_dir_path.join("scratch-ssh"),
spec.mount_label(),
true,
)?;
} else if is_first_create {
// use new key pair
fs::create_dir_all(&container_ssh_dir)?;
let status = Command::new("ssh-keygen")
.arg("-q")
.arg("-f")
.arg(ssh_path.join("id_rsa"))
.arg(container_ssh_dir.join("id_rsa"))
.arg("-N")
.arg("")
.arg("-C")
.arg("")
.spawn()?
.wait()?;
ensure!(status.success(), "ssh-keygen failed");
}
Ok(fs::read_to_string(ssh_path.join("id_rsa.pub"))?)
Ok(fs::read_to_string(container_ssh_dir.join("id_rsa.pub"))?)
}
fn adjust_container_rlimits_and_resources(spec: &mut oci_spec::runtime::Spec) {
@ -557,10 +787,10 @@ fn adjust_container_rlimits_and_resources(spec: &mut oci_spec::runtime::Spec) {
// Forwarding all UDP and TCP traffic requires passt to open many sockets. Ensure that
// the container's RLIMIT_NOFILE is large enough.
rlimits.retain(|rl| rl.typ() != oci_spec::runtime::LinuxRlimitType::RlimitNofile);
rlimits.retain(|rl| rl.typ() != oci_spec::runtime::PosixRlimitType::RlimitNofile);
rlimits.push(
oci_spec::runtime::LinuxRlimitBuilder::default()
.typ(oci_spec::runtime::LinuxRlimitType::RlimitNofile)
oci_spec::runtime::PosixRlimitBuilder::default()
.typ(oci_spec::runtime::PosixRlimitType::RlimitNofile)
.hard(262144u64)
.soft(262144u64)
.build()

View File

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

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

@ -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()?)
}

View File

@ -1,47 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{
fs::File,
io::{BufReader, BufWriter},
};
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use anyhow::Result;
use anyhow::{bail, Result};
use clap::Parser;
use crate::crun::crun_exec;
use crate::util::{crun, fix_selinux_label};
pub fn exec(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Exec) -> Result<()> {
pub fn exec(args: &liboci_cli::Exec, raw_args: &[impl AsRef<OsStr>]) -> Result<()> {
assert!(args.command.is_empty());
// load exec process config
let process_config_path = args.process.as_ref().expect("process config");
let mut process: oci_spec::runtime::Process =
serde_json::from_reader(File::open(process_config_path).map(BufReader::new)?)?;
let command = process.args().as_ref().expect("command specified");
let ssh_user = command
.first()
.expect("first command argument is user to ssh as into the vm");
let mut new_command = vec![];
if ssh_user != "-" {
new_command.extend(["/crun-vm/exec.sh".to_string(), ssh_user.clone()]);
}
new_command.extend(command.iter().skip(1).cloned());
if ssh_user == "-" && new_command.is_empty() {
new_command.push("/bin/bash".to_string());
}
let new_command = build_command(command)?;
process.set_args(Some(new_command));
fix_selinux_label(&mut process);
// store modified exec process config
serde_json::to_writer(
File::create(process_config_path).map(BufWriter::new)?,
&process,
)?;
crun_exec(global_args, args)?;
// actually exec
crun(raw_args)?;
Ok(())
}
#[derive(Parser, Debug)]
#[clap(no_binary_name = true, disable_help_flag = true)]
struct ExecArgs {
#[clap(long = "as", default_value = "root")]
user: String,
#[clap(long, conflicts_with = "user")]
container: bool,
#[clap(long = "timeout")]
timeout: Option<u32>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
command: Vec<String>,
}
fn build_command(original_command: &Vec<String>) -> Result<Vec<String>> {
let cmd = if original_command.first() == Some(&"--".to_string()) {
// Podman gobbles up a -- before the command in some cases, but not in others. Gobble it up
// ourselves to avoid user confusion; see https://github.com/containers/crun-vm/issues/117.
&original_command[1..]
} else {
original_command
};
let mut args: ExecArgs = ExecArgs::parse_from(cmd);
let timeout = if let Some(t) = args.timeout {
t
} else if let Some(t) = env::var_os("CRUN_VM_EXEC_TIMEOUT") {
match t.to_str().and_then(|t| t.parse().ok()) {
Some(t) => t,
None => bail!("env var CRUN_VM_EXEC_TIMEOUT has invalid value"),
}
} else {
0
};
if args.command.starts_with(&["".to_string()]) {
args.command.remove(0);
}
let command = if args.container {
if args.command.is_empty() {
vec!["/bin/bash".to_string()]
} else {
args.command
}
} else {
[
"/crun-vm/exec.sh".to_string(),
timeout.to_string(),
args.user,
]
.into_iter()
.chain(args.command)
.collect()
};
Ok(command)
}

View File

@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pub mod create;
pub mod delete;
pub mod exec;

View File

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

View File

@ -1,18 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
mod commands;
mod crun;
mod util;
use std::ffi::OsStr;
use std::iter;
use anyhow::Result;
use anyhow::{bail, Result};
use clap::Parser;
use crun::crun;
use util::crun;
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
#[derive(Parser, Debug)]
#[clap(no_binary_name = true)]
struct Args {
#[clap(flatten)]
global: liboci_cli::GlobalOpts,
@ -22,7 +21,7 @@ struct Args {
}
// Adapted from https://github.com/containers/youki/blob/main/crates/youki/src/main.rs
#[derive(clap::Parser, Debug)]
#[derive(Parser, Debug)]
enum Command {
#[clap(flatten)]
Standard(Box<liboci_cli::StandardCmd>),
@ -32,27 +31,43 @@ enum Command {
}
pub fn main(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
let args = args
let raw_args = args
.into_iter()
.map(|a| a.as_ref().to_os_string())
.collect::<Vec<_>>();
let parsed_args =
Args::parse_from(iter::once(&OsStr::new("crun-vm").to_os_string()).chain(&args));
let parsed_args = Args::parse_from(&raw_args);
match parsed_args.command {
Command::Standard(cmd) => {
if let liboci_cli::StandardCmd::Create(create_args) = *cmd {
return commands::create::create(&parsed_args.global, &create_args);
match *cmd {
liboci_cli::StandardCmd::Create(args) => commands::create::create(&args, &raw_args),
liboci_cli::StandardCmd::Delete(args) => commands::delete::delete(&args, &raw_args),
liboci_cli::StandardCmd::Start(_)
| liboci_cli::StandardCmd::State(_)
| liboci_cli::StandardCmd::Kill(_) => {
// not a command we implement ourselves, pass it on to crun
crun(&raw_args)
}
}
}
Command::Common(cmd) => {
if let liboci_cli::CommonCmd::Exec(exec_args) = *cmd {
return commands::exec::exec(&parsed_args.global, &exec_args);
match *cmd {
liboci_cli::CommonCmd::Exec(args) => commands::exec::exec(&args, &raw_args),
liboci_cli::CommonCmd::Checkpointt(_)
| liboci_cli::CommonCmd::Events(_)
| liboci_cli::CommonCmd::Features(_)
| liboci_cli::CommonCmd::List(_)
| liboci_cli::CommonCmd::Pause(_)
| liboci_cli::CommonCmd::Ps(_)
| liboci_cli::CommonCmd::Resume(_)
| liboci_cli::CommonCmd::Run(_)
| liboci_cli::CommonCmd::Update(_)
| liboci_cli::CommonCmd::Spec(_) => {
// not a command we support
bail!("Unknown command")
}
}
}
}
// not a command we implement ourselves, just pass it on to crun
crun(&args)
}

View File

@ -6,6 +6,14 @@ use std::process;
fn main() {
if let Err(e) = crun_vm::main(env::args_os().skip(1)) {
eprintln!("{:#}", e);
let rust_backtrace = env::var_os("RUST_BACKTRACE").unwrap_or_default();
let rust_lib_backtrace = env::var_os("RUST_LIB_BACKTRACE").unwrap_or_default();
if (rust_backtrace == "1" || rust_lib_backtrace == "1") && rust_lib_backtrace != "0" {
eprintln!("\n{}\n", e.backtrace());
}
process::exit(1);
}
}

View File

@ -1,32 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::ffi::{c_char, CString};
use std::ffi::{c_char, CString, OsStr};
use std::fs::{self, OpenOptions, Permissions};
use std::io;
use std::io::{self, ErrorKind};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::process::{Command, Stdio};
use std::str;
use anyhow::{anyhow, bail, ensure, Result};
use camino::{Utf8Path, Utf8PathBuf};
use nix::mount::MsFlags;
use nix::mount::{MntFlags, MsFlags};
use serde::Deserialize;
// When the container image's entrypoint is /sbin/init or similar, Podman gives the entrypoint (and
// exec entrypoint) process an SELinux label of, for instance:
//
// system_u:system_r:container_init_t:s0:c276,c638
//
// However, we are going to change our entrypoint to something else, so we need to use the
// "standard" label that Podman otherwise gives, which in this case would be:
//
// system_u:system_r:container_t:s0:c276,c638
//
// This function performs that mapping.
pub fn fix_selinux_label(process: &mut oci_spec::runtime::Process) {
if let Some(label) = process.selinux_label() {
let new_label = label.replace("container_init_t", "container_t");
process.set_selinux_label(Some(new_label));
}
}
pub fn set_file_context(path: impl AsRef<Utf8Path>, context: &str) -> Result<()> {
extern "C" {
fn setfilecon(path: *const c_char, con: *const c_char) -> i32;
fn lsetfilecon(path: *const c_char, con: *const c_char) -> i32;
}
let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
let context = CString::new(context.as_bytes())?;
if unsafe { setfilecon(path.as_ptr(), context.as_ptr()) } != 0 {
if unsafe { lsetfilecon(path.as_ptr(), context.as_ptr()) } != 0 {
return Err(io::Error::last_os_error().into());
}
Ok(())
}
pub fn is_mountpoint(path: impl AsRef<Utf8Path>) -> Result<bool> {
let parent = path
.as_ref()
.parent()
.ok_or_else(|| anyhow!("path does not have a parent"))?;
let path_dev = match fs::symlink_metadata(path.as_ref()) {
Ok(meta) => meta.dev(),
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(false),
Err(e) => return Err(e.into()),
};
let parent_dev = fs::symlink_metadata(parent)?.dev();
Ok(path_dev != parent_dev)
}
pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) -> Result<()> {
// ensure target exists
@ -59,52 +95,59 @@ pub fn bind_mount_file(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) ->
Ok(())
}
fn escape_path(mount_option: impl AsRef<Utf8Path>) -> String {
mount_option
.as_ref()
.as_str()
.replace('\\', "\\\\")
.replace(',', "\\,")
}
fn escape_context(mount_option: &str) -> String {
assert!(!mount_option.contains('"'));
format!("\"{}\"", mount_option)
}
/// Expose directory `from` at `to` with the given SELinux `context`, if any, recursively applied.
///
/// This does *not* modify the SELinux context of `from` nor of files under `from`.
///
/// If `propagate_changes` is true, `private_dir` must belong to the same file system as `from` and
/// be a separate subtree.
/// If `read_only` is false, `scratch_dir` must belong to the same file system as `from` and be a
/// separate subtree.
///
/// TODO: Is this a neat relabeling trick or simply a bad hack?
pub fn bind_mount_dir_with_different_context(
from: impl AsRef<Utf8Path>,
to: impl AsRef<Utf8Path>,
scratch_dir: impl AsRef<Utf8Path>,
context: Option<&str>,
propagate_changes: bool,
private_dir: impl AsRef<Utf8Path>,
read_only: bool,
) -> Result<()> {
let layer_dir = private_dir.as_ref().join("layer");
let work_dir = private_dir.as_ref().join("work");
fs::create_dir_all(&layer_dir)?;
fs::create_dir_all(&work_dir)?;
fs::create_dir_all(to.as_ref())?;
fn escape_path(mount_option: &Utf8Path) -> String {
mount_option
.as_str()
.replace('\\', "\\\\")
.replace(',', "\\,")
}
let mut options = if read_only {
fs::create_dir_all(scratch_dir.as_ref())?;
fn escape_context(mount_option: &str) -> String {
assert!(!mount_option.contains('"'));
format!("\"{}\"", mount_option)
}
format!(
"lowerdir={}:{}",
escape_path(scratch_dir.as_ref()),
escape_path(from)
)
} else {
let layer_dir = scratch_dir.as_ref().join("layer");
let work_dir = scratch_dir.as_ref().join("work");
let (lower_dir, upper_dir) = match propagate_changes {
true => (layer_dir.as_path(), from.as_ref()),
false => (from.as_ref(), layer_dir.as_path()),
fs::create_dir_all(&layer_dir)?;
fs::create_dir_all(&work_dir)?;
format!(
"lowerdir={},upperdir={},workdir={}",
escape_path(layer_dir),
escape_path(from),
escape_path(&work_dir),
)
};
let mut options = format!(
"lowerdir={},upperdir={},workdir={}",
escape_path(lower_dir),
escape_path(upper_dir),
escape_path(&work_dir),
);
if let Some(context) = context {
options = format!("{},context={}", options, escape_context(context));
}
@ -124,9 +167,22 @@ pub fn bind_mount_dir_with_different_context(
);
}
// Make any necessary manual cleanup a bit easier by ensuring the workdir is accessible to the
// user that Podman is running under.
fs::set_permissions(work_dir.join("work"), Permissions::from_mode(0o700))?;
if !read_only {
// Make any necessary manual cleanup a bit easier by ensuring the workdir is accessible to
// the user that Podman is running under.
fs::set_permissions(
scratch_dir.as_ref().join("work/work"),
Permissions::from_mode(0o700),
)?;
}
Ok(())
}
pub fn ensure_unmounted(path: impl AsRef<Utf8Path>) -> Result<()> {
while is_mountpoint(&path)? {
nix::mount::umount2(path.as_ref().as_std_path(), MntFlags::MNT_DETACH)?;
}
Ok(())
}
@ -142,7 +198,7 @@ pub trait SpecExt {
linux_device_cgroup: oci_spec::runtime::LinuxDeviceCgroup,
);
fn process_capabilities_insert_beip(&mut self, capability: oci_spec::runtime::Capability);
fn linux_seccomp_syscalls_push(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall);
fn linux_seccomp_syscalls_push_front(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall);
}
impl SpecExt for oci_spec::runtime::Spec {
@ -220,7 +276,10 @@ impl SpecExt for oci_spec::runtime::Spec {
});
}
fn linux_seccomp_syscalls_push(&mut self, linux_syscall: oci_spec::runtime::LinuxSyscall) {
fn linux_seccomp_syscalls_push_front(
&mut self,
linux_syscall: oci_spec::runtime::LinuxSyscall,
) {
self.set_linux({
let mut linux = self.linux().clone().expect("linux config");
linux.set_seccomp({
@ -228,7 +287,7 @@ impl SpecExt for oci_spec::runtime::Spec {
if let Some(seccomp) = &mut seccomp {
seccomp.set_syscalls({
let mut syscalls = seccomp.syscalls().clone().unwrap_or_default();
syscalls.push(linux_syscall);
syscalls.insert(0, linux_syscall);
Some(syscalls)
});
}
@ -294,7 +353,11 @@ impl VmImageInfo {
.stdout(Stdio::piped())
.output()?;
ensure!(output.status.success(), "`qemu-img info` failed");
ensure!(
output.status.success(),
"`qemu-img info` failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?;
info.path = vm_image_path;
@ -307,7 +370,7 @@ pub fn create_overlay_vm_image(
overlay_vm_image_path: &Utf8Path,
base_vm_image_info: &VmImageInfo,
) -> Result<()> {
let status = Command::new("qemu-img")
let output = Command::new("qemu-img")
.arg("create")
.arg("-q")
.arg("-f")
@ -319,10 +382,23 @@ pub fn create_overlay_vm_image(
.arg(&base_vm_image_info.path)
.arg(overlay_vm_image_path)
.arg(base_vm_image_info.size.to_string())
.spawn()?
.wait()?;
.output()?;
ensure!(status.success(), "`qemu-img create` failed");
ensure!(
output.status.success(),
"`qemu-img create` failed: {}",
String::from_utf8_lossy(&output.stderr)
);
Ok(())
}
/// Run `crun`.
///
/// `crun` will inherit this process' standard streams.
pub fn crun(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<()> {
let status = Command::new("crun").args(args).spawn()?.wait()?;
ensure!(status.success(), "crun failed");
Ok(())
}

53
test.sh
View File

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

447
tests/env.sh Executable file
View File

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

View File

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

View File

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

36
tests/t/cloud-init.sh Normal file
View File

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

4
tests/t/emulated.sh Normal file
View File

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

42
tests/t/hostname.sh Normal file
View File

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

39
tests/t/ignition.sh Normal file
View File

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

46
tests/t/mount.sh Normal file
View File

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

37
tests/t/persistent.sh Normal file
View File

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

33
tests/t/publish.sh Normal file
View File

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

View File

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

14
tests/t/stop-start.sh Normal file
View File

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

View File

@ -29,6 +29,8 @@ elif (( ${#candidates[@]} > 1 )); then
exit 1
fi
>&2 echo "Extracting ${candidates[0]} to ${output_vm_image_file}"
tar -C "${temp_dir}" -xf "${temp_dir}/root.tar" "${candidates[0]}"
chmod +w "${temp_dir}/${candidates[0]}"

View File

@ -4,6 +4,7 @@
set -o errexit -o pipefail -o nounset
script_dir="$( dirname "$0" | xargs readlink -e )"
repo_root=$script_dir/../..
__minikube() {
minikube -p=crun-vm-example "$@"
@ -25,7 +26,7 @@ __apt_get() {
# build runtime
cargo build
make -C "$repo_root"
# create minikube cluster
@ -57,7 +58,7 @@ __ssh 'tee --append /etc/crio/crio.conf <<EOF
runtime_path = "/usr/local/bin/crun-vm"
EOF'
__cp "${script_dir}/../../target/debug/crun-vm" /usr/local/bin/crun-vm
__cp "$repo_root/out/crun-vm" /usr/local/bin/crun-vm
__ssh chmod +x /usr/local/bin/crun-vm
# reload cluster so that the new runtime is picked up

View File

@ -12,9 +12,11 @@ fi
vm_image_file=$1
container_image_tag=$2
image_path_in_container=/$( basename "${vm_image_file}" )
abs_vm_image_file=$( readlink -e "${vm_image_file}" )
image_path_in_container=/disk/$( basename "${vm_image_file}" )
podman image build --file=- --tag="${container_image_tag}" . <<EOF
podman image build --file=- --tag="${container_image_tag}" / <<EOF
FROM scratch
COPY ${vm_image_file@Q} ${image_path_in_container@Q}
COPY ${abs_vm_image_file@Q} ${image_path_in_container@Q}
ENTRYPOINT ["no-entrypoint"]
EOF