herolib-virt 0.3.13

Virtualization and container management for herolib (buildah, nerdctl, kubernetes)
Documentation
use serde::{Deserialize, Serialize};
#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "linux")]
use std::path::Path;
use std::process::Command;

#[cfg(feature = "rhai")]
pub mod rhai;

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct VirtInfo {
    pub is_virtualized: bool,
    pub virtualization_type: Option<String>,
    pub hypervisor: Option<String>,
    pub container: Option<String>,
    pub platform: String, // e.g., "apple-silicon", "intel-mac", "linux-x86_64"
}

/// Detect virtualization environment
pub fn get_virt_what() -> VirtInfo {
    let mut info = VirtInfo::default();

    #[cfg(target_os = "linux")]
    detect_linux(&mut info);

    #[cfg(target_os = "macos")]
    detect_macos(&mut info);

    info.is_virtualized = info.virtualization_type.is_some() || info.container.is_some();
    info
}

#[cfg(target_os = "linux")]
fn detect_linux(info: &mut VirtInfo) {
    info.platform = "linux".to_string();
    #[cfg(target_arch = "x86_64")]
    {
        info.platform = "linux-x86_64".to_string();
    }
    #[cfg(target_arch = "aarch64")]
    {
        info.platform = "linux-aarch64".to_string();
    }

    // Check for Docker
    if Path::new("/.dockerenv").exists() {
        info.container = Some("docker".to_string());
    } else if let Ok(cgroup) = fs::read_to_string("/proc/self/cgroup") {
        if cgroup.contains("docker") {
            info.container = Some("docker".to_string());
        }
    }

    // Check for Podman
    if Path::new("/run/.containerenv").exists() {
        info.container = Some("podman".to_string());
    }

    // Check DMI info
    if let Ok(product_name) = fs::read_to_string("/sys/class/dmi/id/product_name") {
        let product_name = product_name.trim().to_lowercase();
        if product_name.contains("kvm") {
            info.virtualization_type = Some("kvm".to_string());
        } else if product_name.contains("qemu") {
            info.virtualization_type = Some("qemu".to_string());
        } else if product_name.contains("vmware") {
            info.virtualization_type = Some("vmware".to_string());
        } else if product_name.contains("virtualbox") {
            info.virtualization_type = Some("virtualbox".to_string());
        } else if product_name.contains("xen") {
            info.virtualization_type = Some("xen".to_string());
        } else if product_name.contains("hyper-v") {
            info.virtualization_type = Some("hyper-v".to_string());
        }
    }

    // Check for WSL
    if let Ok(version) = fs::read_to_string("/proc/version") {
        if version.to_lowercase().contains("microsoft") || version.to_lowercase().contains("wsl") {
            info.virtualization_type = Some("wsl".to_string());
            info.hypervisor = Some("hyper-v".to_string());
        }
    }

    // Check /proc/cpuinfo for hypervisor flag
    if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
        if cpuinfo.contains("hypervisor") {
            if info.virtualization_type.is_none() {
                info.virtualization_type = Some("generic-vm".to_string());
            }
        }
    }
}

#[cfg(target_os = "macos")]
fn detect_macos(info: &mut VirtInfo) {
    info.platform = "macos".to_string();
    #[cfg(target_arch = "aarch64")]
    {
        info.platform = "apple-silicon".to_string();
    }
    #[cfg(target_arch = "x86_64")]
    {
        info.platform = "intel-mac".to_string();
    }

    // Check for Apple Virtualization Framework
    let output = Command::new("sysctl")
        .args(["-n", "machdep.cpu.features"])
        .output();

    if let Ok(output) = output {
        let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
        if stdout.contains("vmm") {
            info.virtualization_type = Some("generic-vm".to_string());
        }
    }

    // Check ioreg for specific platforms
    let ioreg = Command::new("ioreg")
        .args(["-l"])
        .output();

    if let Ok(ioreg) = ioreg {
        let stdout = String::from_utf8_lossy(&ioreg.stdout).to_lowercase();
        if stdout.contains("apple-virt-io") || stdout.contains("applevirtualplatform") {
            info.virtualization_type = Some("apple-virtualization".to_string());
        } else if stdout.contains("vmware") {
            info.virtualization_type = Some("vmware".to_string());
        } else if stdout.contains("parallels") {
            info.virtualization_type = Some("parallels".to_string());
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_virt_what() {
        let info = get_virt_what();
        println!("Detected info: {:?}", info);
        // At least the platform should be set
        assert!(!info.platform.is_empty());

        #[cfg(target_os = "macos")]
        {
            assert!(info.platform.contains("mac") || info.platform.contains("apple"));
        }

        #[cfg(target_os = "linux")]
        {
            assert!(info.platform.contains("linux"));
        }
    }

    #[cfg(feature = "rhai")]
    #[test]
    fn test_virtwhat_rhai() {
        use ::rhai::Engine;
        let mut engine = Engine::new();
        crate::virt_what::rhai::register_virtwhat_module(&mut engine).unwrap();

        let result = engine.eval::<::rhai::Map>("virt_what()").unwrap();
        assert!(result.contains_key("platform"));
        assert!(result.contains_key("is_virtualized"));

        let platform = result
            .get("platform")
            .unwrap()
            .clone()
            .into_string()
            .unwrap();
        assert!(!platform.is_empty());
    }

    #[test]
    #[cfg(target_os = "macos")]
    fn test_detect_macos() {
        let mut info = VirtInfo::default();
        detect_macos(&mut info);
        assert!(info.platform.contains("mac") || info.platform.contains("apple"));
    }
}