sal-virt 0.1.0

SAL Virt - Virtualization and containerization tools including Buildah, Nerdctl, and RFS
Documentation
use crate::rfs::{
    get_mount_info, list_contents, list_mounts, pack_directory, unmount, unmount_all, unpack,
    verify, MountType, RfsBuilder, StoreSpec,
};
use rhai::{Array, Engine, EvalAltResult, Map};

/// Register RFS functions with the Rhai engine
pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register mount functions
    engine.register_fn("rfs_mount", rfs_mount);
    engine.register_fn("rfs_unmount", rfs_unmount);
    engine.register_fn("rfs_list_mounts", rfs_list_mounts);
    engine.register_fn("rfs_unmount_all", rfs_unmount_all);
    engine.register_fn("rfs_get_mount_info", rfs_get_mount_info);

    // Register pack functions
    engine.register_fn("rfs_pack", rfs_pack);
    engine.register_fn("rfs_unpack", rfs_unpack);
    engine.register_fn("rfs_list_contents", rfs_list_contents);
    engine.register_fn("rfs_verify", rfs_verify);

    Ok(())
}

/// Mount a filesystem
///
/// # Arguments
///
/// * `source` - Source path or URL
/// * `target` - Target mount point
/// * `mount_type` - Mount type (e.g., "local", "ssh", "s3", "webdav")
/// * `options` - Mount options as a map
///
/// # Returns
///
/// * `Result<Map, Box<EvalAltResult>>` - Mount information or error
fn rfs_mount(
    source: &str,
    target: &str,
    mount_type: &str,
    options: Map,
) -> Result<Map, Box<EvalAltResult>> {
    // Convert mount type string to MountType enum
    let mount_type_enum = MountType::from_string(mount_type);

    // Create a builder
    let mut builder = RfsBuilder::new(source, target, mount_type_enum);

    // Add options
    for (key, value) in options.iter() {
        if let Ok(value_str) = value.clone().into_string() {
            builder = builder.with_option(key, &value_str);
        }
    }

    // Mount the filesystem
    let mount = builder.mount().map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to mount filesystem: {}", e).into(),
            rhai::Position::NONE,
        ))
    })?;

    // Convert Mount to Map
    let mut result = Map::new();
    result.insert("id".into(), mount.id.into());
    result.insert("source".into(), mount.source.into());
    result.insert("target".into(), mount.target.into());
    result.insert("fs_type".into(), mount.fs_type.into());

    let options_array: Array = mount.options.iter().map(|opt| opt.clone().into()).collect();
    result.insert("options".into(), options_array.into());

    Ok(result)
}

/// Unmount a filesystem
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Success or error
fn rfs_unmount(target: &str) -> Result<(), Box<EvalAltResult>> {
    unmount(target).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to unmount filesystem: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// List all mounted filesystems
///
/// # Returns
///
/// * `Result<Array, Box<EvalAltResult>>` - List of mounts or error
fn rfs_list_mounts() -> Result<Array, Box<EvalAltResult>> {
    let mounts = list_mounts().map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to list mounts: {}", e).into(),
            rhai::Position::NONE,
        ))
    })?;

    let mut result = Array::new();

    for mount in mounts {
        let mut mount_map = Map::new();
        mount_map.insert("id".into(), mount.id.into());
        mount_map.insert("source".into(), mount.source.into());
        mount_map.insert("target".into(), mount.target.into());
        mount_map.insert("fs_type".into(), mount.fs_type.into());

        let options_array: Array = mount.options.iter().map(|opt| opt.clone().into()).collect();
        mount_map.insert("options".into(), options_array.into());

        result.push(mount_map.into());
    }

    Ok(result)
}

/// Unmount all filesystems
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Success or error
fn rfs_unmount_all() -> Result<(), Box<EvalAltResult>> {
    unmount_all().map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to unmount all filesystems: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// Get information about a mounted filesystem
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<Map, Box<EvalAltResult>>` - Mount information or error
fn rfs_get_mount_info(target: &str) -> Result<Map, Box<EvalAltResult>> {
    let mount = get_mount_info(target).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to get mount info: {}", e).into(),
            rhai::Position::NONE,
        ))
    })?;

    let mut result = Map::new();
    result.insert("id".into(), mount.id.into());
    result.insert("source".into(), mount.source.into());
    result.insert("target".into(), mount.target.into());
    result.insert("fs_type".into(), mount.fs_type.into());

    let options_array: Array = mount.options.iter().map(|opt| opt.clone().into()).collect();
    result.insert("options".into(), options_array.into());

    Ok(result)
}

/// Pack a directory into a filesystem layer
///
/// # Arguments
///
/// * `directory` - Directory to pack
/// * `output` - Output file
/// * `store_specs` - Store specifications as a string (e.g., "file:path=/path/to/store,s3:bucket=my-bucket")
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Success or error
fn rfs_pack(directory: &str, output: &str, store_specs: &str) -> Result<(), Box<EvalAltResult>> {
    // Parse store specs
    let specs = parse_store_specs(store_specs);

    // Pack the directory
    pack_directory(directory, output, &specs).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to pack directory: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// Unpack a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
/// * `directory` - Directory to unpack to
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Success or error
fn rfs_unpack(input: &str, directory: &str) -> Result<(), Box<EvalAltResult>> {
    unpack(input, directory).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to unpack filesystem layer: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// List the contents of a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<String, Box<EvalAltResult>>` - File listing or error
fn rfs_list_contents(input: &str) -> Result<String, Box<EvalAltResult>> {
    list_contents(input).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to list contents: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// Verify a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<bool, Box<EvalAltResult>>` - Whether the layer is valid or error
fn rfs_verify(input: &str) -> Result<bool, Box<EvalAltResult>> {
    verify(input).map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Failed to verify filesystem layer: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

/// Parse store specifications from a string
///
/// # Arguments
///
/// * `specs_str` - Store specifications as a string
///
/// # Returns
///
/// * `Vec<StoreSpec>` - Store specifications
fn parse_store_specs(specs_str: &str) -> Vec<StoreSpec> {
    let mut result = Vec::new();

    // Split by comma
    for spec_str in specs_str.split(',') {
        // Skip empty specs
        if spec_str.trim().is_empty() {
            continue;
        }

        // Split by colon to get type and options
        let parts: Vec<&str> = spec_str.split(':').collect();

        if parts.is_empty() {
            continue;
        }

        // Get spec type
        let spec_type = parts[0].trim();

        // Create store spec
        let mut store_spec = StoreSpec::new(spec_type);

        // Add options if any
        if parts.len() > 1 {
            let options_str = parts[1];

            // Split options by comma
            for option in options_str.split(',') {
                // Split option by equals sign
                let option_parts: Vec<&str> = option.split('=').collect();

                if option_parts.len() == 2 {
                    store_spec =
                        store_spec.with_option(option_parts[0].trim(), option_parts[1].trim());
                }
            }
        }

        result.push(store_spec);
    }

    result
}