@@ -120,6 +120,15 @@ use crate::{
120120 systemd, utils, CONTAINER_STATEDIR ,
121121} ;
122122
123+ /// fw_cfg name for Ignition configuration (per FCOS documentation)
124+ const IGNITION_FW_CFG_NAME : & str = "opt/com.coreos/config" ;
125+
126+ /// virtio-blk serial name for Ignition configuration (per FCOS documentation)
127+ const IGNITION_SERIAL_NAME : & str = "ignition" ;
128+
129+ /// Mount path for Ignition config inside the container
130+ const IGNITION_CONFIG_MOUNT_PATH : & str = "/run/ignition-config.json" ;
131+
123132/// Common container lifecycle options for podman commands.
124133#[ derive( Parser , Debug , Clone , Default , Serialize , Deserialize ) ]
125134pub struct CommonPodmanOptions {
@@ -287,6 +296,12 @@ pub struct RunEphemeralOpts {
287296 #[ clap( long = "karg" , help = "Additional kernel command line arguments" ) ]
288297 pub kernel_args : Vec < String > ,
289298
299+ #[ clap(
300+ long = "ignition" ,
301+ help = "Path to Ignition config file (JSON format) to inject via fw_cfg"
302+ ) ]
303+ pub ignition_config : Option < String > ,
304+
290305 /// Host DNS servers (read on host, configured via podman --dns flags)
291306 /// Not a CLI option - populated automatically from host's /etc/resolv.conf
292307 #[ clap( skip) ]
@@ -401,6 +416,27 @@ fn prepare_run_command_with_temp(
401416) -> Result < ( std:: process:: Command , tempfile:: TempDir ) > {
402417 debug ! ( "Running QEMU inside hybrid container for {}" , opts. image) ;
403418
419+ // Check Ignition support early (before launching container) if --ignition is specified
420+ if opts. ignition_config . is_some ( ) {
421+ let has_ignition = check_ignition_support ( & opts. image ) ?;
422+ if !has_ignition {
423+ return Err ( eyre ! (
424+ "Image does not support Ignition.\n \
425+ \n \
426+ To use Ignition with bootc images, build a custom image with Ignition included.\n \
427+ See: https://docs.fedoraproject.org/en-US/bootc/initramfs/\n \
428+ \n \
429+ Alternatively, use an image that includes Ignition by default:\n \
430+ - quay.io/fedora/fedora-coreos\n \
431+ \n \
432+ Images are detected as Ignition-capable if they have:\n \
433+ - Label 'coreos.ignition=1' (recommended), or\n \
434+ - Label 'com.coreos.osname' (CoreOS-based images)"
435+ ) ) ;
436+ }
437+ debug ! ( "Image {} supports Ignition" , opts. image) ;
438+ }
439+
404440 let script = include_str ! ( "../scripts/entrypoint.sh" ) ;
405441
406442 let td = tempfile:: tempdir ( ) ?;
@@ -581,6 +617,35 @@ fn prepare_run_command_with_temp(
581617 cmd. args ( [ "-v" , & format ! ( "{}:/run/systemd-units:ro" , units_dir) ] ) ;
582618 }
583619
620+ // Mount Ignition config file if specified
621+ if let Some ( ref ignition_path) = opts. ignition_config {
622+ // Convert to absolute path if needed
623+ let path = Utf8Path :: new ( ignition_path) ;
624+ let ignition_abs = if path. is_absolute ( ) {
625+ path. to_owned ( )
626+ } else {
627+ let current_dir = Utf8PathBuf :: try_from ( std:: env:: current_dir ( ) ?)
628+ . context ( "Current directory path is not valid UTF-8" ) ?;
629+ current_dir. join ( path)
630+ } ;
631+
632+ // Validate file exists and is readable
633+ if !ignition_abs. exists ( ) {
634+ return Err ( eyre ! ( "Ignition config file not found: {}" , ignition_abs) ) ;
635+ }
636+ if !ignition_abs. is_file ( ) {
637+ return Err ( eyre ! (
638+ "Ignition config path is not a regular file: {}" ,
639+ ignition_abs
640+ ) ) ;
641+ }
642+
643+ cmd. args ( [
644+ "-v" ,
645+ & format ! ( "{}:{}:ro" , ignition_abs, IGNITION_CONFIG_MOUNT_PATH ) ,
646+ ] ) ;
647+ }
648+
584649 // Read host DNS servers and configure them via podman --dns flags
585650 // This fixes DNS resolution issues when QEMU runs inside containers.
586651 // QEMU's slirp reads /etc/resolv.conf from the container's network namespace,
@@ -834,6 +899,64 @@ fn check_required_container_binaries() -> Result<()> {
834899 Ok ( ( ) )
835900}
836901
902+ /// Check if the container image has Ignition support
903+ ///
904+ /// Checks for labels indicating Ignition support:
905+ /// - 'coreos.ignition' (future convention, not yet widely used)
906+ /// - 'com.coreos.osname' (heuristic: CoreOS-based images likely have Ignition)
907+ ///
908+ /// Returns true if the image is likely to support Ignition.
909+ fn check_ignition_support ( image : & str ) -> Result < bool > {
910+ use std:: collections:: HashMap ;
911+ use std:: process:: Stdio ;
912+
913+ // Fetch all labels with a single podman inspect call
914+ let output = Command :: new ( "podman" )
915+ . args ( [ "image" , "inspect" , "--format" , "{{json .Labels}}" , image] )
916+ . stdout ( Stdio :: piped ( ) )
917+ . stderr ( Stdio :: piped ( ) )
918+ . output ( )
919+ . context ( "Failed to inspect image for labels" ) ?;
920+
921+ if !output. status . success ( ) {
922+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
923+ return Err ( eyre ! (
924+ "Failed to inspect image {} for labels: {}" ,
925+ image,
926+ stderr. trim( )
927+ ) ) ;
928+ }
929+
930+ // Parse the JSON output
931+ let labels: HashMap < String , String > = serde_json:: from_slice ( & output. stdout )
932+ . context ( "Failed to parse image labels as JSON" ) ?;
933+
934+ // Check for coreos.ignition label (future convention)
935+ if let Some ( ignition_value) = labels. get ( "coreos.ignition" ) {
936+ if matches ! (
937+ ignition_value. to_lowercase( ) . as_str( ) ,
938+ "1" | "true" | "yes" | "enabled"
939+ ) {
940+ debug ! ( "Image {} has coreos.ignition=1 label" , image) ;
941+ return Ok ( true ) ;
942+ }
943+ }
944+
945+ // Fallback: check for com.coreos.osname (CoreOS-based images)
946+ if let Some ( osname_value) = labels. get ( "com.coreos.osname" ) {
947+ if !osname_value. is_empty ( ) {
948+ debug ! (
949+ "Image {} has com.coreos.osname={}, assuming Ignition support" ,
950+ image, osname_value
951+ ) ;
952+ return Ok ( true ) ;
953+ }
954+ }
955+
956+ debug ! ( "Image {} does not appear to support Ignition" , image) ;
957+ Ok ( false )
958+ }
959+
837960/// VM execution inside container: extracts kernel/initramfs, starts virtiofsd processes,
838961/// generates systemd mount units, sets up command execution, launches QEMU.
839962pub ( crate ) async fn run_impl ( opts : RunEphemeralOpts ) -> Result < ( ) > {
@@ -1265,6 +1388,45 @@ StandardOutput=file:/dev/virtio-ports/executestatus
12651388 kernel_cmdline. extend ( opts. kernel_args . clone ( ) ) ;
12661389 qemu_config. set_kernel_cmdline ( kernel_cmdline) ;
12671390
1391+ // Add Ignition config if specified
1392+ // Different architectures require different methods (per FCOS docs):
1393+ // - x86_64/aarch64: fw_cfg
1394+ // - s390x/ppc64le: virtio-blk with serial "ignition"
1395+ if opts. ignition_config . is_some ( ) {
1396+ let ignition_path = Utf8Path :: new ( IGNITION_CONFIG_MOUNT_PATH ) ;
1397+ if !ignition_path. exists ( ) {
1398+ return Err ( eyre ! (
1399+ "Ignition config not found at expected location: {}\n \
1400+ This is an internal error - the config should have been mounted by podman.",
1401+ ignition_path
1402+ ) ) ;
1403+ }
1404+
1405+ let arch = std:: env:: consts:: ARCH ;
1406+ match arch {
1407+ "x86_64" | "aarch64" => {
1408+ debug ! ( "Adding Ignition config via fw_cfg: {}" , ignition_path) ;
1409+ qemu_config. add_fw_cfg ( IGNITION_FW_CFG_NAME . to_string ( ) , ignition_path. to_owned ( ) ) ;
1410+ }
1411+ "s390x" | "powerpc64" => {
1412+ debug ! ( "Adding Ignition config via virtio-blk: {}" , ignition_path) ;
1413+ qemu_config. add_virtio_blk_device_with_format_ro (
1414+ ignition_path. to_string ( ) ,
1415+ IGNITION_SERIAL_NAME . to_string ( ) ,
1416+ crate :: to_disk:: Format :: Raw ,
1417+ true , // readonly as required by FCOS
1418+ ) ;
1419+ }
1420+ _ => {
1421+ return Err ( eyre ! (
1422+ "Ignition config injection not supported on architecture: {}\n \
1423+ Supported architectures: x86_64, aarch64, s390x, powerpc64",
1424+ arch
1425+ ) ) ;
1426+ }
1427+ }
1428+ }
1429+
12681430 // TODO allocate unlinked unnamed file and pass via fd
12691431 let mut tmp_swapfile = None ;
12701432 if let Some ( size) = opts. add_swap {
@@ -1366,6 +1528,7 @@ Options=
13661528 disk_file,
13671529 serial,
13681530 format : format. into ( ) ,
1531+ readonly : false ,
13691532 } ) ;
13701533 }
13711534 }
0 commit comments