diff --git a/crates/kit/src/images.rs b/crates/kit/src/images.rs index 569d9379b..9f8fe9cd8 100644 --- a/crates/kit/src/images.rs +++ b/crates/kit/src/images.rs @@ -208,6 +208,40 @@ pub fn inspect(name: &str) -> Result { r.pop().ok_or_else(|| eyre!("No such image")) } +pub fn get_image_digest(name: &str) -> Result { + // Use podman to inspect if the image to be installed is from containers-storage + if !img_has_transport(name) || img_from_containers_storage(name) { + let name = name.strip_prefix("containers-storage:").unwrap_or(name); + let i = inspect(name)?; + return Ok(i.digest.to_string()); + } + + let r = Command::new("skopeo") + .args(["inspect", "--format", "{{.Digest}}", name]) + .run_get_string() + .map_err(|e| eyre!("{e}"))?; + + Ok(r.trim().into()) +} + +pub fn img_has_transport(name: &str) -> bool { + ["docker://", "containers-storage:", "oci:", "dir:"] + .iter() + .any(|t| name.starts_with(t)) +} + +pub fn img_from_containers_storage(name: &str) -> bool { + name.starts_with("containers-storage:") +} + +pub fn prepend_transport_to_img(name: &str) -> String { + if img_has_transport(name) { + return name.into(); + } + + return format!("containers-storage:{name}"); +} + /// Get container image size in bytes for disk space planning. pub fn get_image_size(name: &str) -> Result { tracing::debug!("Getting size for image: {}", name); diff --git a/crates/kit/src/libvirt/base_disks.rs b/crates/kit/src/libvirt/base_disks.rs index 8b5d50f5b..a5b6f67be 100644 --- a/crates/kit/src/libvirt/base_disks.rs +++ b/crates/kit/src/libvirt/base_disks.rs @@ -15,12 +15,13 @@ use tracing::{debug, info}; /// Find or create a base disk for the given parameters pub fn find_or_create_base_disk( source_image: &str, + image_to_install: &str, image_digest: &str, install_options: &InstallOptions, connect_uri: Option<&str>, virtiofsd_binary: Option<&str>, ) -> Result { - let metadata = DiskImageMetadata::from(install_options, image_digest, source_image); + let metadata = DiskImageMetadata::from(install_options, image_digest, image_to_install); let cache_hash = metadata.compute_cache_hash(); // Extract short hash for filename (first 16 chars after "sha256:") @@ -44,7 +45,7 @@ pub fn find_or_create_base_disk( if crate::cache_metadata::check_cached_disk( base_disk_path.as_std_path(), image_digest, - source_image, + image_to_install, install_options, )? .is_ok() @@ -65,6 +66,7 @@ pub fn find_or_create_base_disk( create_base_disk( &base_disk_path, source_image, + image_to_install, image_digest, install_options, connect_uri, @@ -78,6 +80,7 @@ pub fn find_or_create_base_disk( fn create_base_disk( base_disk_path: &Utf8Path, source_image: &str, + image_to_install: &str, image_digest: &str, install_options: &InstallOptions, connect_uri: Option<&str>, @@ -107,6 +110,7 @@ fn create_base_disk( // Create the disk using to_disk at temporary location let to_disk_opts = ToDiskOpts { source_image: source_image.to_string(), + image_to_install: Some(image_to_install.to_string()), target_disk: temp_disk_path.clone(), install: install_options.clone(), additional: ToDiskAdditionalOpts { @@ -135,7 +139,7 @@ fn create_base_disk( let metadata_valid = crate::cache_metadata::check_cached_disk( temp_disk_path.as_std_path(), image_digest, - source_image, + image_to_install, install_options, ) .context("Querying cached disk")?; diff --git a/crates/kit/src/libvirt/base_disks_cli.rs b/crates/kit/src/libvirt/base_disks_cli.rs index d9301c1da..996f570ab 100644 --- a/crates/kit/src/libvirt/base_disks_cli.rs +++ b/crates/kit/src/libvirt/base_disks_cli.rs @@ -10,7 +10,7 @@ use serde_json; use super::base_disks::{find_or_create_base_disk, list_base_disks, prune_base_disks}; use super::OutputFormat; -use crate::images; +use crate::images::{get_image_digest, prepend_transport_to_img}; use crate::install_options::InstallOptions; /// Options for base-disks command @@ -24,6 +24,12 @@ pub struct LibvirtBaseDisksOpts { #[derive(Debug, Parser)] pub struct CreateBaseDiskOpts { pub source_image: String, + /// The image to use for creating the base disk + /// If None, the `source_image` cli option is used for installation + /// + /// Ex. docker://quay.io/fedora/fedora-bootc:44 + #[clap(long)] + pub image_to_install: Option, #[clap(flatten)] pub install_options: InstallOptions, } @@ -69,11 +75,13 @@ pub fn run_create( opts: CreateBaseDiskOpts, ) -> Result<()> { let connect_uri = global_opts.connect.as_deref(); - let inspect = images::inspect(&opts.source_image)?; - let image_digest = inspect.digest.to_string(); + let image_to_install = opts.image_to_install.unwrap_or(opts.source_image.clone()); + let image_to_install = prepend_transport_to_img(&image_to_install); + let image_digest = get_image_digest(&image_to_install)?; let path = find_or_create_base_disk( &opts.source_image, + &image_to_install, &image_digest, &opts.install_options, connect_uri, diff --git a/crates/kit/src/libvirt/run.rs b/crates/kit/src/libvirt/run.rs index 67eaa645e..843b457ed 100644 --- a/crates/kit/src/libvirt/run.rs +++ b/crates/kit/src/libvirt/run.rs @@ -15,6 +15,9 @@ use tracing::{debug, info}; use crate::common_opts::MemoryOpts; use crate::domain_list::DomainLister; +use crate::images::{ + get_image_digest, img_from_containers_storage, img_has_transport, prepend_transport_to_img, +}; use crate::install_options::InstallOptions; use crate::libvirt::domain::VirtiofsFilesystem; use crate::utils::parse_memory_to_mb; @@ -340,6 +343,13 @@ pub struct LibvirtRunOpts { /// `--log-dir=journal=/tmp/logs/` #[clap(long, value_name = "STREAMS=DIR")] pub log_dir: Option, + + /// The image to use for creating the base disk + /// If None, the `image` cli option is used for installation + /// + /// Ex. docker://quay.io/fedora/fedora-bootc:44 + #[clap(long)] + pub image_to_install: Option, } impl LibvirtRunOpts { @@ -429,8 +439,6 @@ fn wait_for_ssh_ready( /// Execute the libvirt run command pub fn run(global_opts: &crate::libvirt::LibvirtOptions, mut opts: LibvirtRunOpts) -> Result<()> { - use crate::images; - // Validate labels don't contain commas opts.validate_labels()?; @@ -478,25 +486,27 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, mut opts: LibvirtRunOpt None => generate_unique_vm_name(&opts.image, &existing_domains), }; + let image_to_install = opts.image_to_install.as_ref().unwrap_or(&opts.image); + let image_to_install = prepend_transport_to_img(&image_to_install); + println!( "Creating libvirt domain '{}' (install source container image: {})", - vm_name, opts.image + vm_name, image_to_install, ); // Get the image digest for caching - let inspect = images::inspect(&opts.image)?; - let image_digest = inspect.digest.to_string(); + let image_digest = get_image_digest(&image_to_install)?; debug!("Image digest: {}", image_digest); // Check Ignition support and validate config file path early if let Some(ref ignition_path) = opts.ignition_config { - let has_ignition = check_ignition_support(&opts.image)?; + let has_ignition = check_ignition_support(&image_to_install)?; if !has_ignition { return Err(eyre!( "Image does not support Ignition. See man bcvk-libvirt-run for details." )); } - debug!("Image {} supports Ignition", opts.image); + debug!("Image {} supports Ignition", image_to_install); // Validate that the Ignition config file exists before proceeding if !ignition_path.try_exists()? { @@ -521,6 +531,7 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, mut opts: LibvirtRunOpt // Phase 1: Find or create a base disk image let base_disk_path = crate::libvirt::base_disks::find_or_create_base_disk( &opts.image, + &image_to_install, &image_digest, &opts.install, connect_uri, @@ -1107,9 +1118,20 @@ fn check_ignition_support(image: &str) -> Result { use std::collections::HashMap; use std::process::Stdio; + let mut args = vec!["inspect", "--format", "{{json .Labels}}"]; + + let cmd = if img_from_containers_storage(image) || !img_has_transport(image) { + args.insert(0, "image"); + args.push(image.strip_prefix("containers-storage:").unwrap_or(image)); + "podman" + } else { + args.push(image); + "skopeo" + }; + // Fetch all labels with a single podman inspect call - let output = std::process::Command::new("podman") - .args(["image", "inspect", "--format", "{{json .Labels}}", image]) + let output = std::process::Command::new(cmd) + .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() diff --git a/crates/kit/src/libvirt/upload.rs b/crates/kit/src/libvirt/upload.rs index e2960c688..2446923a6 100644 --- a/crates/kit/src/libvirt/upload.rs +++ b/crates/kit/src/libvirt/upload.rs @@ -4,6 +4,7 @@ //! to libvirt storage pools, maintaining container image metadata as libvirt annotations. use crate::common_opts::MemoryOpts; +use crate::images::get_image_digest; use crate::install_options::InstallOptions; use crate::to_disk::{run as to_disk, ToDiskAdditionalOpts, ToDiskOpts}; use crate::{images, utils}; @@ -20,6 +21,13 @@ pub struct LibvirtUploadOpts { /// Container image to install and upload pub source_image: String, + /// The image to use for creating the base disk + /// If None, the `image` cli option is used for installation + /// + /// Ex. docker://quay.io/fedora/fedora-bootc:44 + #[clap(long)] + pub image_to_install: Option, + /// Name for the libvirt volume (defaults to sanitized image name) #[clap(long)] pub volume_name: Option, @@ -183,9 +191,10 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtUploadOpts opts.source_image ); + let image_to_install = opts.image_to_install.as_ref().unwrap_or(&opts.source_image); + // Phase 1: Extract image digest for caching - let inspect = images::inspect(&opts.source_image)?; - let image_digest = &inspect.digest.to_string(); + let image_digest = get_image_digest(image_to_install)?; debug!("Container image digest: {}", image_digest); // Phase 2: Calculate disk size to use @@ -194,6 +203,9 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtUploadOpts utils::parse_size(size_str)? } else { // Use same logic as to_disk: 2x source image size with 4GB minimum + // NOTE: Using opts.source_image to estimate the required size here + // as getting the size from registry images might not be always accurate + // as we will get the compressed size and not the final on-disk size let image_size = images::get_image_size(&opts.source_image)?; std::cmp::max(image_size * 2, 4u64 * 1024 * 1024 * 1024) @@ -208,6 +220,7 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtUploadOpts let install_opts = ToDiskOpts { source_image: opts.source_image.clone(), + image_to_install: opts.image_to_install.clone(), target_disk: temp_disk_path.clone(), install: opts.install.clone(), additional: ToDiskAdditionalOpts { diff --git a/crates/kit/src/libvirt_upload_disk.rs b/crates/kit/src/libvirt_upload_disk.rs index b2fda0017..6c530835a 100644 --- a/crates/kit/src/libvirt_upload_disk.rs +++ b/crates/kit/src/libvirt_upload_disk.rs @@ -279,6 +279,7 @@ pub fn run(opts: LibvirtUploadDiskOpts) -> Result<()> { let install_opts = ToDiskOpts { source_image: opts.source_image.clone(), + image_to_install: None, target_disk: temp_disk.clone(), install: opts.install.clone(), additional: ToDiskAdditionalOpts { diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index aa7ad7657..96d9bc4c4 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -113,6 +113,7 @@ pub fn default_vcpus() -> u32 { .unwrap_or(2) } +use crate::images::{img_from_containers_storage, img_has_transport}; use crate::qemu::{self, QemuConfigExt}; use crate::{ boot_progress, @@ -195,7 +196,7 @@ impl std::str::FromStr for LogDir { other => { return Err(color_eyre::eyre::eyre!( "--log-dir unknown stream name {other:?}; expected `journal` or `console`" - )) + )); } } } @@ -1239,9 +1240,20 @@ fn check_ignition_support(image: &str) -> Result { use std::collections::HashMap; use std::process::Stdio; + let mut args = vec!["inspect", "--format", "{{json .Labels}}"]; + + let cmd = if img_from_containers_storage(image) || !img_has_transport(image) { + args.insert(0, "image"); + args.push(image.strip_prefix("containers-storage:").unwrap_or(image)); + "podman" + } else { + args.push(image); + "skopeo" + }; + // Fetch all labels with a single podman inspect call - let output = Command::new("podman") - .args(["image", "inspect", "--format", "{{json .Labels}}", image]) + let output = Command::new(cmd) + .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() @@ -1994,7 +2006,9 @@ Options= if let Some(ref dns_servers) = opts.host_dns_servers { debug!("DNS servers configured for QEMU slirp: {:?}", dns_servers); } else { - warn!("No host DNS servers available, QEMU slirp will use container's resolv.conf which may not work"); + warn!( + "No host DNS servers available, QEMU slirp will use container's resolv.conf which may not work" + ); } if opts.common.ssh_keygen { diff --git a/crates/kit/src/to_disk.rs b/crates/kit/src/to_disk.rs index 7f47a5950..663f825f3 100644 --- a/crates/kit/src/to_disk.rs +++ b/crates/kit/src/to_disk.rs @@ -76,6 +76,7 @@ use std::io::IsTerminal; use crate::cache_metadata::DiskImageMetadata; +use crate::images::get_image_digest; use crate::install_options::InstallOptions; use crate::run_ephemeral::{run_detached, CommonVmOpts, RunEphemeralOpts}; use crate::run_ephemeral_ssh::wait_for_ssh_ready; @@ -161,6 +162,13 @@ pub struct ToDiskOpts { /// Container image to install pub source_image: String, + /// The image to use for creating the base disk + /// If None, the `image` cli option is used for installation + /// + /// Ex. docker://quay.io/fedora/fedora-bootc:44 + #[clap(long)] + pub image_to_install: Option, + /// Target disk/device path pub target_disk: Utf8PathBuf, @@ -194,12 +202,13 @@ impl ToDiskOpts { } /// Generate the complete bootc installation command arguments for SSH execution - fn generate_bootc_install_command( - &self, - disk_size: u64, - use_oci_layout: bool, - ) -> Result> { - let source_imgref = format!("containers-storage:{}", self.source_image); + fn generate_bootc_install_command(&self, disk_size: u64) -> Result> { + let source_imgref = match &self.image_to_install { + Some(img_to_install) => img_to_install, + None => &format!("containers-storage:{}", self.source_image), + }; + + let allow_network = source_imgref.starts_with("docker://"); // Quote each bootc argument individually to prevent shell injection let mut quoted_bootc_args = Vec::new(); @@ -216,7 +225,7 @@ impl ToDiskOpts { .to_string(); // Quote the source image name for local storage operations - let quoted_source_image = shlex::try_quote(&self.source_image) + let quoted_src_img_wo_transport_prefix = shlex::try_quote(&self.source_image) .map_err(|e| { eyre!( "Failed to quote source image '{}': {}", @@ -255,27 +264,6 @@ impl ToDiskOpts { .map_err(|e| eyre!("Failed to quote tmpfs size: {}", e))? .to_string(); - // For composefs-backend installs, mount the OCI layout in the VM - // and pass --source-imgref so bootc reads from it directly, - // preserving the correct manifest with compressed layer digests (#307). - let (oci_setup, oci_volume, oci_source_arg) = if use_oci_layout { - ( - indoc! {r#" - # Ensure OCI layout virtiofs mount is available (#307) - OCI=/run/virtiofs-mnt-ociimage - if ! mountpoint -q ${OCI} &>/dev/null; then - mkdir -p ${OCI} - mount -t virtiofs mount_ociimage ${OCI} -o ro - fi - "#} - .to_string(), - "-v /run/virtiofs-mnt-ociimage:/run/virtiofs-mnt-ociimage:ro".to_string(), - "--source-imgref oci:/run/virtiofs-mnt-ociimage:latest".to_string(), - ) - } else { - (String::new(), String::new(), String::new()) - }; - let script = indoc! {r#" set -euo pipefail @@ -295,8 +283,6 @@ impl ToDiskOpts { mount -t virtiofs mount_hoststorage ${AIS} -o ro fi - {OCI_SETUP} - echo "Starting bootc installation..." echo "Source image: {SOURCE_IMGREF}" echo "Additional args: {BOOTC_ARGS}" @@ -314,18 +300,17 @@ impl ToDiskOpts { # Mount /var/tmp into inner container to avoid cross-device link errors (issue #125) set +e # Don't exit on error, we'll check for signature error and retry ERROR_LOG=$(mktemp) - podman run --rm -i ${tty} --privileged --pid=host --net=none -v /sys:/sys:ro \ + podman run --rm -i ${tty} --privileged --pid=host {NETWORK} -v /sys:/sys:ro \ -v /var/lib/containers:/var/lib/containers -v /var/tmp:/var/tmp -v /dev:/dev -v "${AIS}:${AIS}" \ - {OCI_VOLUME} \ --security-opt label=type:unconfined_t \ --env=STORAGE_OPTS \ {INSTALL_LOG} \ {EXTRA_PODMAN_ARGS} \ - {SOURCE_IMGREF} \ + {CONTAINER_IMAGE} \ bootc install to-disk \ --generic-image \ --skip-fetch-check \ - {OCI_SOURCE_ARG} \ + --source-imgref {SOURCE_IMGREF} \ {BOOTC_ARGS} \ /dev/disk/by-id/virtio-output 2> "$ERROR_LOG" BOOTC_EXIT=$? @@ -352,12 +337,11 @@ impl ToDiskOpts { EOF # Copy image without signatures - skopeo copy --remove-signatures {SOURCE_IMGREF} containers-storage:{SOURCE_IMAGE} + skopeo copy --remove-signatures {CONTAINER_IMAGE} containers-storage:{SOURCE_IMAGE} # Retry bootc install with the unsigned local copy - podman run --rm -i ${tty} --privileged --pid=host --net=none -v /sys:/sys:ro \ + podman run --rm -i ${tty} --privileged --pid=host {NETWORK} -v /sys:/sys:ro \ -v /var/lib/containers:/var/lib/containers -v /var/tmp:/var/tmp -v /dev:/dev -v "${AIS}:${AIS}" \ - {OCI_VOLUME} \ --security-opt label=type:unconfined_t \ --env=STORAGE_OPTS \ {INSTALL_LOG} \ @@ -366,7 +350,7 @@ EOF bootc install to-disk \ --generic-image \ --skip-fetch-check \ - {OCI_SOURCE_ARG} \ + --source-imgref {SOURCE_IMGREF} \ {BOOTC_ARGS} \ /dev/disk/by-id/virtio-output elif [ $BOOTC_EXIT -ne 0 ]; then @@ -381,14 +365,13 @@ EOF echo "Installation completed successfully!" "#} .replace("{TMPFS_SIZE}", &tmpfs_size_quoted) - .replace("{OCI_SETUP}", &oci_setup) - .replace("{OCI_VOLUME}", &oci_volume) - .replace("{OCI_SOURCE_ARG}", &oci_source_arg) .replace("{SOURCE_IMGREF}", "ed_source_imgref) - .replace("{SOURCE_IMAGE}", "ed_source_image) + .replace("{SOURCE_IMAGE}", "ed_src_img_wo_transport_prefix) .replace("{INSTALL_LOG}", &install_log) .replace("{EXTRA_PODMAN_ARGS}", &extra_podman_args) - .replace("{BOOTC_ARGS}", &bootc_args); + .replace("{BOOTC_ARGS}", &bootc_args) + .replace("{CONTAINER_IMAGE}", "ed_src_img_wo_transport_prefix) + .replace("{NETWORK}", if allow_network { "--net=host" } else { "--net=none" }); Ok(vec!["/bin/bash".to_string(), "-c".to_string(), script]) } @@ -436,64 +419,16 @@ pub enum RunOutcome { DryRunWouldRegenerate, } -/// Export a container image to a temporary OCI layout directory. -/// -/// This preserves the original manifest with correct compressed layer digests. -/// The containers-storage `additionalimagestore` reconstructs manifests with -/// uncompressed layer digests when layers are accessed via virtiofs, producing -/// incorrect manifest digests. -/// -/// Note: `podman push` to the `oci:` transport will convert Docker v2s2 -/// manifests to OCI format, changing the digest. Bootc images use OCI -/// manifests natively, so this is not an issue in practice. -/// -/// See -fn export_to_oci_layout(source_image: &str) -> Result { - // Use /var/tmp rather than /tmp because /tmp is often tmpfs (RAM-backed) - // on Fedora/RHEL, and OCI layouts for bootc images can be several GB. - let tmpdir = tempfile::Builder::new() - .prefix("bcvk-oci-") - .tempdir_in("/var/tmp") - .context("Failed to create temp directory in /var/tmp for OCI layout")?; - - let dst = format!("oci:{}:latest", tmpdir.path().display()); - - debug!("Exporting image to OCI layout: {} -> {}", source_image, dst); - - let output = std::process::Command::new("podman") - .args(["push", source_image, &dst]) - .output() - .context("Failed to run 'podman push'")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(eyre!("Failed to export image to OCI layout: {}", stderr)); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - if !stdout.is_empty() { - debug!("podman push stdout: {}", stdout); - } - let stderr = String::from_utf8_lossy(&output.stderr); - if !stderr.is_empty() { - debug!("podman push stderr: {}", stderr); - } - - Ok(tmpdir) -} - /// Execute a bootc installation using an ephemeral VM with SSH /// /// Main entry point for the bootc installation process. See module-level documentation /// for details on the installation workflow and architecture. -pub fn run(mut opts: ToDiskOpts) -> Result { - // Normalize the source image name by stripping containers-storage: prefix if present. - // The containers-storage: prefix is a transport specifier used by some tools like skopeo, - // but podman commands (image inspect, run --mount=type=image) expect just the image name. - // We'll add it back where needed (e.g., in bootc install commands). - if let Some(stripped) = opts.source_image.strip_prefix("containers-storage:") { - opts.source_image = stripped.to_string(); - } +pub fn run(opts: ToDiskOpts) -> Result { + let image_to_install = opts + .image_to_install + .as_ref() + .unwrap_or(&opts.source_image) + .as_str(); // Phase 0: Check for existing cached disk image let would_reuse = if opts.target_disk.exists() { @@ -503,14 +438,13 @@ pub fn run(mut opts: ToDiskOpts) -> Result { ); // Get the image digest for comparison - let inspect = images::inspect(&opts.source_image)?; - let image_digest = inspect.digest.to_string(); + let image_digest = get_image_digest(image_to_install)?; // Check if cached disk matches our requirements match crate::cache_metadata::check_cached_disk( opts.target_disk.as_std_path(), &image_digest, - &opts.source_image, + &image_to_install, &opts.install, )? { Ok(()) => { @@ -596,27 +530,9 @@ pub fn run(mut opts: ToDiskOpts) -> Result { } } - // For composefs-backend installs, export the image to an OCI layout - // on the host. This preserves the correct manifest with compressed - // layer digests, working around containers-storage additionalimagestore - // reconstructing manifests with uncompressed digests via virtiofs (#307). - let use_oci_layout = opts.install.composefs_backend; - let oci_tmpdir = if use_oci_layout { - tracing::info!("Exporting image to OCI layout..."); - match export_to_oci_layout(&opts.source_image) { - Ok(tmpdir) => Some(tmpdir), - Err(e) => { - let _ = std::fs::remove_file(&opts.target_disk); - return Err(e); - } - } - } else { - None - }; - // Phase 3: Installation command generation // Generate complete script including storage setup and bootc install - let bootc_install_command = opts.generate_bootc_install_command(disk_size, use_oci_layout)?; + let bootc_install_command = opts.generate_bootc_install_command(disk_size)?; // Phase 4: Ephemeral VM configuration let mut common_opts = opts.additional.common.clone(); @@ -630,10 +546,6 @@ pub fn run(mut opts: ToDiskOpts) -> Result { // - Mount host storage read-only for image access // - Attach target disk via virtio-blk // - Disable networking (using local storage only) - let mut ro_bind_mounts = Vec::new(); - if let Some(ref tmpdir) = oci_tmpdir { - ro_bind_mounts.push(format!("{}:ociimage", tmpdir.path().display())); - } let ephemeral_opts = RunEphemeralOpts { host_dns_servers: None, @@ -651,7 +563,7 @@ pub fn run(mut opts: ToDiskOpts) -> Result { // when fetching, so we need enough memory to do so. add_swap: Some(format!("{disk_size}")), bind_mounts: Vec::new(), // No additional bind mounts needed - ro_bind_mounts, + ro_bind_mounts: Vec::new(), systemd_units_dir: None, // No custom systemd units bind_storage_ro: true, // Mount host container storage read-only mount_disk_files: vec![format!( @@ -713,7 +625,7 @@ pub fn run(mut opts: ToDiskOpts) -> Result { // Write metadata to the disk image for caching // Extract values before they're potentially moved let write_result = write_disk_metadata( - &opts.source_image, + &image_to_install, &opts.target_disk, &opts.install, &opts.additional.format, @@ -742,8 +654,7 @@ fn write_disk_metadata( // as they're stored in the filesystem metadata, not inside the disk image // Get the image digest - let inspect = images::inspect(source_image)?; - let digest = inspect.digest.to_string(); + let digest = get_image_digest(source_image)?; // Prepare metadata using the new helper method let metadata = DiskImageMetadata::from(install_options, &digest, source_image); @@ -777,6 +688,7 @@ mod tests { // Test with explicit disk size let opts = ToDiskOpts { source_image: "test:latest".to_string(), + image_to_install: Some("test:latest".to_string()), target_disk: "/tmp/test.img".into(), install: InstallOptions { filesystem: Some("ext4".to_string()), @@ -795,6 +707,7 @@ mod tests { // Test with another size format let opts2 = ToDiskOpts { source_image: "test:latest".to_string(), + image_to_install: Some("test:latest".into()), target_disk: "/tmp/test.img".into(), install: InstallOptions { filesystem: Some("ext4".to_string()), diff --git a/crates/kit/src/varlink_ipc.rs b/crates/kit/src/varlink_ipc.rs index c1194fd4b..ef35e8d59 100644 --- a/crates/kit/src/varlink_ipc.rs +++ b/crates/kit/src/varlink_ipc.rs @@ -362,6 +362,7 @@ impl BcvkService { let opts = crate::to_disk::ToDiskOpts { source_image, + image_to_install: None, target_disk: Utf8PathBuf::from(&target_disk), install: crate::install_options::InstallOptions { filesystem, diff --git a/docs/src/man/bcvk-libvirt-run.md b/docs/src/man/bcvk-libvirt-run.md index b9cbbe847..c1fce2635 100644 --- a/docs/src/man/bcvk-libvirt-run.md +++ b/docs/src/man/bcvk-libvirt-run.md @@ -162,6 +162,10 @@ Run a bootable container as a persistent VM Path to Ignition config file (JSON format) for first-boot provisioning +**--virtiofsd**=*VIRTIOFSD_BINARY* + + Path to virtiofsd binary (overrides auto-detection for disk creation) + **--console-log**=*CONSOLE_LOG* Log virtio console (OS/journald on hvc0) to this file (created if absent) @@ -174,6 +178,10 @@ Run a bootable container as a persistent VM Write VM log streams to files in DIR +**--image-to-install**=*IMAGE_TO_INSTALL* + + The image to use for creating the base disk If None, the `image` cli option is used for installation + # EXAMPLES diff --git a/docs/src/man/bcvk-libvirt-to-base-disk.md b/docs/src/man/bcvk-libvirt-to-base-disk.md index 2384e2cc6..343725081 100644 --- a/docs/src/man/bcvk-libvirt-to-base-disk.md +++ b/docs/src/man/bcvk-libvirt-to-base-disk.md @@ -17,6 +17,10 @@ Create a base disk image for libvirt VMs This argument is required. +**--image-to-install**=*IMAGE_TO_INSTALL* + + The image to use for creating the base disk If None, the `source_image` cli option is used for installation + **--filesystem**=*FILESYSTEM* Root filesystem type (e.g. ext4, xfs, btrfs) diff --git a/docs/src/man/bcvk-libvirt-upload.md b/docs/src/man/bcvk-libvirt-upload.md index 07743c6bc..75c33ad9b 100644 --- a/docs/src/man/bcvk-libvirt-upload.md +++ b/docs/src/man/bcvk-libvirt-upload.md @@ -19,6 +19,10 @@ Upload bootc disk images to libvirt with metadata annotations This argument is required. +**--image-to-install**=*IMAGE_TO_INSTALL* + + The image to use for creating the base disk If None, the `image` cli option is used for installation + **--volume-name**=*VOLUME_NAME* Name for the libvirt volume (defaults to sanitized image name) diff --git a/docs/src/man/bcvk-to-disk.md b/docs/src/man/bcvk-to-disk.md index f552c5440..e89466399 100644 --- a/docs/src/man/bcvk-to-disk.md +++ b/docs/src/man/bcvk-to-disk.md @@ -35,6 +35,10 @@ The installation process: This argument is required. +**--image-to-install**=*IMAGE_TO_INSTALL* + + The image to use for creating the base disk If None, the `image` cli option is used for installation + **--filesystem**=*FILESYSTEM* Root filesystem type (e.g. ext4, xfs, btrfs)