This commit is contained in:
2025-04-05 19:00:59 +02:00
parent 0fa9eddd1c
commit 21893ce225
18 changed files with 2458 additions and 36 deletions

View File

@@ -4,6 +4,7 @@
use rhai::{EvalAltResult, Position};
use crate::os::{FsError, DownloadError};
use crate::os::package::PackageError;
/// Convert a FsError to a Rhai EvalAltResult
pub fn fs_error_to_rhai_error(err: FsError) -> Box<EvalAltResult> {
@@ -23,6 +24,15 @@ pub fn download_error_to_rhai_error(err: DownloadError) -> Box<EvalAltResult> {
))
}
/// Convert a PackageError to a Rhai EvalAltResult
pub fn package_error_to_rhai_error(err: PackageError) -> Box<EvalAltResult> {
let err_msg = err.to_string();
Box::new(EvalAltResult::ErrorRuntime(
err_msg.into(),
Position::NONE
))
}
/// Register error types with the Rhai engine
pub fn register_error_types(engine: &mut rhai::Engine) -> Result<(), Box<EvalAltResult>> {
// Register helper functions for error handling
@@ -38,6 +48,10 @@ pub fn register_error_types(engine: &mut rhai::Engine) -> Result<(), Box<EvalAlt
format!("Download error: {}", err_msg)
});
engine.register_fn("package_error_message", |err_msg: &str| -> String {
format!("Package management error: {}", err_msg)
});
Ok(())
}
@@ -57,4 +71,10 @@ impl<T> ToRhaiError<T> for Result<T, DownloadError> {
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
self.map_err(download_error_to_rhai_error)
}
}
impl<T> ToRhaiError<T> for Result<T, PackageError> {
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
self.map_err(package_error_to_rhai_error)
}
}

View File

@@ -10,6 +10,7 @@ mod buildah;
mod nerdctl;
mod git;
mod text;
mod rfs;
#[cfg(test)]
mod tests;
@@ -53,6 +54,9 @@ pub use nerdctl::{
nerdctl_image_pull, nerdctl_image_commit, nerdctl_image_build
};
// Re-export RFS module
pub use rfs::register as register_rfs_module;
// Re-export git module
pub use git::register_git_module;
pub use crate::git::{GitTree, GitRepo};
@@ -103,12 +107,16 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register Nerdctl module functions
nerdctl::register_nerdctl_module(engine)?;
// Register Git module functions
git::register_git_module(engine)?;
// Register Text module functions
text::register_text_module(engine)?;
// Register RFS module functions
rfs::register(engine)?;
// Future modules can be registered here

292
src/rhai/rfs.rs Normal file
View File

@@ -0,0 +1,292 @@
use rhai::{Engine, EvalAltResult, Map, Array};
use crate::virt::rfs::{
RfsBuilder, MountType, StoreSpec,
list_mounts, unmount_all, unmount, get_mount_info,
pack_directory, unpack, list_contents, verify
};
/// Register RFS functions with the Rhai engine
pub fn register(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
}