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

154
src/docs/docs/sal/rfs.md Normal file
View File

@@ -0,0 +1,154 @@
# RFS (Remote File System)
The RFS module provides a Rust wrapper for the RFS tool, which allows mounting remote filesystems locally and managing filesystem layers.
## Overview
RFS (Remote File System) is a tool that enables mounting various types of remote filesystems locally, as well as creating and managing filesystem layers. The SAL library provides a Rust wrapper for RFS with a fluent builder API, making it easy to use in your applications.
## Features
- Mount remote filesystems locally (SSH, S3, WebDAV, etc.)
- List mounted filesystems
- Unmount filesystems
- Pack directories into filesystem layers
- Unpack filesystem layers
- List contents of filesystem layers
- Verify filesystem layers
## Usage in Rust
### Mounting a Filesystem
```rust
use sal::virt::rfs::{RfsBuilder, MountType};
// Create a new RFS builder
let mount = RfsBuilder::new("user@example.com:/remote/path", "/local/mount/point", MountType::SSH)
.with_option("port", "2222")
.with_option("identity_file", "/path/to/key")
.with_debug(true)
.mount()?;
println!("Mounted filesystem with ID: {}", mount.id);
```
### Listing Mounts
```rust
use sal::virt::rfs::list_mounts;
// List all mounts
let mounts = list_mounts()?;
for mount in mounts {
println!("Mount ID: {}, Source: {}, Target: {}", mount.id, mount.source, mount.target);
}
```
### Unmounting a Filesystem
```rust
use sal::virt::rfs::unmount;
// Unmount a filesystem
unmount("/local/mount/point")?;
```
### Packing a Directory
```rust
use sal::virt::rfs::{PackBuilder, StoreSpec};
// Create store specifications
let store_spec = StoreSpec::new("file")
.with_option("path", "/path/to/store");
// Pack a directory with builder pattern
let result = PackBuilder::new("/path/to/directory", "output.fl")
.with_store_spec(store_spec)
.with_debug(true)
.pack()?;
```
### Unpacking a Filesystem Layer
```rust
use sal::virt::rfs::unpack;
// Unpack a filesystem layer
unpack("input.fl", "/path/to/unpack")?;
```
## Usage in Rhai Scripts
### Mounting a Filesystem
```rhai
// Create a map for mount options
let options = #{
"port": "22",
"identity_file": "/path/to/key",
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("user@example.com:/remote/path", "/local/mount/point", "ssh", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
```
### Listing Mounts
```rhai
// List all mounts
let mounts = rfs_list_mounts();
print(`Number of mounts: ${mounts.len()}`);
for mount in mounts {
print(`Mount ID: ${mount.id}, Source: ${mount.source}, Target: ${mount.target}`);
}
```
### Unmounting a Filesystem
```rhai
// Unmount the directory
rfs_unmount("/local/mount/point");
```
### Packing a Directory
```rhai
// Pack the directory
// Store specs format: "file:path=/path/to/store,s3:bucket=my-bucket"
rfs_pack("/path/to/directory", "output.fl", "file:path=/path/to/store");
```
### Unpacking a Filesystem Layer
```rhai
// Unpack the filesystem layer
rfs_unpack("output.fl", "/path/to/unpack");
```
## Mount Types
The RFS module supports various mount types:
- **Local**: Mount a local directory
- **SSH**: Mount a remote directory via SSH
- **S3**: Mount an S3 bucket
- **WebDAV**: Mount a WebDAV server
## Store Specifications
When packing a directory into a filesystem layer, you can specify one or more stores to use. Each store has a type and options:
- **File**: Store files on the local filesystem
- Options: `path` (path to the store)
- **S3**: Store files in an S3 bucket
- Options: `bucket` (bucket name), `region` (AWS region), `access_key`, `secret_key`
## Examples
See the [RFS example script](../../rhaiexamples/rfs_example.rhai) for more examples of how to use the RFS module in Rhai scripts.

View File

@@ -393,7 +393,10 @@ impl PackHero {
#[cfg(test)]
mod tests {
// Import the std::process::Command directly for some test-specific commands
use std::process::Command as StdCommand;
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn test_platform_detection() {
@@ -415,7 +418,486 @@ mod tests {
assert_eq!(thread_local_debug(), false);
}
// More tests would be added for each platform-specific implementation
// These would likely be integration tests that are conditionally compiled
// based on the platform they're running on
#[test]
fn test_package_error_display() {
// Test the Display implementation for PackageError
let err1 = PackageError::CommandFailed("command failed".to_string());
assert_eq!(err1.to_string(), "Command failed: command failed");
let err2 = PackageError::UnsupportedPlatform("test platform".to_string());
assert_eq!(err2.to_string(), "Unsupported platform: test platform");
let err3 = PackageError::Other("other error".to_string());
assert_eq!(err3.to_string(), "Error: other error");
// We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq
}
// Mock package manager for testing
struct MockPackageManager {
debug: bool,
install_called: Arc<Mutex<bool>>,
remove_called: Arc<Mutex<bool>>,
update_called: Arc<Mutex<bool>>,
upgrade_called: Arc<Mutex<bool>>,
list_installed_called: Arc<Mutex<bool>>,
search_called: Arc<Mutex<bool>>,
is_installed_called: Arc<Mutex<bool>>,
// Control what the mock returns
should_succeed: bool,
}
impl MockPackageManager {
fn new(debug: bool, should_succeed: bool) -> Self {
Self {
debug,
install_called: Arc::new(Mutex::new(false)),
remove_called: Arc::new(Mutex::new(false)),
update_called: Arc::new(Mutex::new(false)),
upgrade_called: Arc::new(Mutex::new(false)),
list_installed_called: Arc::new(Mutex::new(false)),
search_called: Arc::new(Mutex::new(false)),
is_installed_called: Arc::new(Mutex::new(false)),
should_succeed,
}
}
}
impl PackageManager for MockPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.install_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: format!("Installed package {}", package),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed("Mock install failed".to_string()))
}
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.remove_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: format!("Removed package {}", package),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed("Mock remove failed".to_string()))
}
}
fn update(&self) -> Result<CommandResult, PackageError> {
*self.update_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: "Updated package lists".to_string(),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed("Mock update failed".to_string()))
}
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
*self.upgrade_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: "Upgraded packages".to_string(),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed("Mock upgrade failed".to_string()))
}
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
*self.list_installed_called.lock().unwrap() = true;
if self.should_succeed {
Ok(vec!["package1".to_string(), "package2".to_string()])
} else {
Err(PackageError::CommandFailed("Mock list_installed failed".to_string()))
}
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
*self.search_called.lock().unwrap() = true;
if self.should_succeed {
Ok(vec![format!("result1-{}", query), format!("result2-{}", query)])
} else {
Err(PackageError::CommandFailed("Mock search failed".to_string()))
}
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
*self.is_installed_called.lock().unwrap() = true;
if self.should_succeed {
Ok(package == "installed-package")
} else {
Err(PackageError::CommandFailed("Mock is_installed failed".to_string()))
}
}
}
// Custom PackHero for testing with a mock package manager
struct TestPackHero {
platform: Platform,
debug: bool,
mock_manager: MockPackageManager,
}
impl TestPackHero {
fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self {
Self {
platform,
debug,
mock_manager: MockPackageManager::new(debug, should_succeed),
}
}
fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> {
match self.platform {
Platform::Ubuntu | Platform::MacOS => Ok(&self.mock_manager),
Platform::Unknown => Err(PackageError::UnsupportedPlatform("Unsupported platform".to_string())),
}
}
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.install(package)
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.remove(package)
}
fn update(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.update()
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.upgrade()
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.list_installed()
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.search(query)
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let pm = self.get_package_manager()?;
pm.is_installed(package)
}
}
#[test]
fn test_packhero_with_mock_success() {
// Test PackHero with a mock package manager that succeeds
let hero = TestPackHero::new(Platform::Ubuntu, false, true);
// Test install
let result = hero.install("test-package");
assert!(result.is_ok());
assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove
let result = hero.remove("test-package");
assert!(result.is_ok());
assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update
let result = hero.update();
assert!(result.is_ok());
assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade
let result = hero.upgrade();
assert!(result.is_ok());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed
let result = hero.list_installed();
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["package1".to_string(), "package2".to_string()]);
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search
let result = hero.search("query");
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["result1-query".to_string(), "result2-query".to_string()]);
assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed
let result = hero.is_installed("installed-package");
assert!(result.is_ok());
assert!(result.unwrap());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
let result = hero.is_installed("not-installed-package");
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_packhero_with_mock_failure() {
// Test PackHero with a mock package manager that fails
let hero = TestPackHero::new(Platform::Ubuntu, false, false);
// Test install
let result = hero.install("test-package");
assert!(result.is_err());
assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove
let result = hero.remove("test-package");
assert!(result.is_err());
assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update
let result = hero.update();
assert!(result.is_err());
assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade
let result = hero.upgrade();
assert!(result.is_err());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed
let result = hero.list_installed();
assert!(result.is_err());
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search
let result = hero.search("query");
assert!(result.is_err());
assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed
let result = hero.is_installed("installed-package");
assert!(result.is_err());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
}
#[test]
fn test_packhero_unsupported_platform() {
// Test PackHero with an unsupported platform
let hero = TestPackHero::new(Platform::Unknown, false, true);
// All operations should fail with UnsupportedPlatform error
let result = hero.install("test-package");
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
let result = hero.remove("test-package");
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
let result = hero.update();
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
}
// Real-world tests that actually install and remove packages on Ubuntu
// These tests will only run on Ubuntu and will be skipped on other platforms
#[test]
fn test_real_package_operations_on_ubuntu() {
// Check if we're on Ubuntu
let platform = Platform::detect();
if platform != Platform::Ubuntu {
println!("Skipping real package operations test on non-Ubuntu platform: {:?}", platform);
return;
}
println!("Running real package operations test on Ubuntu");
// Create a PackHero instance with debug enabled
let mut hero = PackHero::new();
hero.set_debug(true);
// Test package to install/remove
let test_package = "wget";
// First, check if the package is already installed
let is_installed_before = match hero.is_installed(test_package) {
Ok(result) => result,
Err(e) => {
println!("Error checking if package is installed: {}", e);
return;
}
};
println!("Package {} is installed before test: {}", test_package, is_installed_before);
// If the package is already installed, we'll remove it first
if is_installed_before {
println!("Removing existing package {} before test", test_package);
match hero.remove(test_package) {
Ok(_) => println!("Successfully removed package {}", test_package),
Err(e) => {
println!("Error removing package {}: {}", test_package, e);
return;
}
}
// Verify it was removed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if is_installed {
println!("Failed to remove package {}", test_package);
return;
} else {
println!("Verified package {} was removed", test_package);
}
},
Err(e) => {
println!("Error checking if package is installed after removal: {}", e);
return;
}
}
}
// Now install the package
println!("Installing package {}", test_package);
match hero.install(test_package) {
Ok(_) => println!("Successfully installed package {}", test_package),
Err(e) => {
println!("Error installing package {}: {}", test_package, e);
return;
}
}
// Verify it was installed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if !is_installed {
println!("Failed to install package {}", test_package);
return;
} else {
println!("Verified package {} was installed", test_package);
}
},
Err(e) => {
println!("Error checking if package is installed after installation: {}", e);
return;
}
}
// Test the search functionality
println!("Searching for packages with 'wget'");
match hero.search("wget") {
Ok(results) => {
println!("Search results: {:?}", results);
assert!(results.iter().any(|r| r.contains("wget")), "Search results should contain wget");
},
Err(e) => {
println!("Error searching for packages: {}", e);
return;
}
}
// Test listing installed packages
println!("Listing installed packages");
match hero.list_installed() {
Ok(packages) => {
println!("Found {} installed packages", packages.len());
// Check if our test package is in the list
assert!(packages.iter().any(|p| p == test_package),
"Installed packages list should contain {}", test_package);
},
Err(e) => {
println!("Error listing installed packages: {}", e);
return;
}
}
// Now remove the package if it wasn't installed before
if !is_installed_before {
println!("Removing package {} after test", test_package);
match hero.remove(test_package) {
Ok(_) => println!("Successfully removed package {}", test_package),
Err(e) => {
println!("Error removing package {}: {}", test_package, e);
return;
}
}
// Verify it was removed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if is_installed {
println!("Failed to remove package {}", test_package);
return;
} else {
println!("Verified package {} was removed", test_package);
}
},
Err(e) => {
println!("Error checking if package is installed after removal: {}", e);
return;
}
}
}
// Test update functionality
println!("Testing package list update");
match hero.update() {
Ok(_) => println!("Successfully updated package lists"),
Err(e) => {
println!("Error updating package lists: {}", e);
return;
}
}
println!("All real package operations tests passed on Ubuntu");
}
// Test to check if apt-get is available on the system
#[test]
fn test_apt_get_availability() {
// This test checks if apt-get is available on the system
let output = StdCommand::new("which")
.arg("apt-get")
.output()
.expect("Failed to execute which apt-get");
let success = output.status.success();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
println!("apt-get available: {}", success);
if success {
println!("apt-get path: {}", stdout.trim());
}
// On Ubuntu, this should pass
if Platform::detect() == Platform::Ubuntu {
assert!(success, "apt-get should be available on Ubuntu");
}
}
}

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
}

View File

@@ -1,27 +1,27 @@
// Example script demonstrating the package management functions
// Example script demonstrating the mypackage management functions
// Set debug mode to true to see detailed output
package_set_debug(true);
// Function to demonstrate package management on Ubuntu
// Function to demonstrate mypackage management on Ubuntu
fn demo_ubuntu() {
print("Demonstrating package management on Ubuntu...");
print("Demonstrating mypackage management on Ubuntu...");
// Update package lists
print("Updating package lists...");
// Update mypackage lists
print("Updating mypackage lists...");
let result = package_update();
print(`Update result: ${result}`);
// Check if a package is installed
let package = "htop";
print(`Checking if ${package} is installed...`);
let is_installed = package_is_installed(package);
print(`${package} is installed: ${is_installed}`);
// Check if a mypackage is installed
let mypackage = "htop";
print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(mypackage);
print(`${mypackage} is installed: ${is_installed}`);
// Install a package if not already installed
// Install a mypackage if not already installed
if !is_installed {
print(`Installing ${package}...`);
let install_result = package_install(package);
print(`Installing ${mypackage}...`);
let install_result = package_install(mypackage);
print(`Install result: ${install_result}`);
}
@@ -41,33 +41,33 @@ fn demo_ubuntu() {
print(` - ${search_results[i]}`);
}
// Remove the package if we installed it
// Remove the mypackage if we installed it
if !is_installed {
print(`Removing ${package}...`);
let remove_result = package_remove(package);
print(`Removing ${mypackage}...`);
let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`);
}
}
// Function to demonstrate package management on macOS
// Function to demonstrate mypackage management on macOS
fn demo_macos() {
print("Demonstrating package management on macOS...");
print("Demonstrating mypackage management on macOS...");
// Update package lists
print("Updating package lists...");
// Update mypackage lists
print("Updating mypackage lists...");
let result = package_update();
print(`Update result: ${result}`);
// Check if a package is installed
let package = "wget";
print(`Checking if ${package} is installed...`);
let is_installed = package_is_installed(package);
print(`${package} is installed: ${is_installed}`);
// Check if a mypackage is installed
let mypackage = "wget";
print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(mypackage);
print(`${mypackage} is installed: ${is_installed}`);
// Install a package if not already installed
// Install a mypackage if not already installed
if !is_installed {
print(`Installing ${package}...`);
let install_result = package_install(package);
print(`Installing ${mypackage}...`);
let install_result = package_install(mypackage);
print(`Install result: ${install_result}`);
}
@@ -87,10 +87,10 @@ fn demo_macos() {
print(` - ${search_results[i]}`);
}
// Remove the package if we installed it
// Remove the mypackage if we installed it
if !is_installed {
print(`Removing ${package}...`);
let remove_result = package_remove(package);
print(`Removing ${mypackage}...`);
let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`);
}
}

View File

@@ -0,0 +1,121 @@
// RFS Example Script
// This script demonstrates how to use the RFS wrapper in Rhai
// Mount a local directory
fn mount_local_example() {
print("Mounting a local directory...");
// Create a map for mount options
let options = #{
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("/source/path", "/target/path", "local", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// List all mounts
let mounts = rfs_list_mounts();
print(`Number of mounts: ${mounts.len()}`);
for mount in mounts {
print(`Mount ID: ${mount.id}, Source: ${mount.source}, Target: ${mount.target}`);
}
// Unmount the directory
rfs_unmount("/target/path");
print("Unmounted the directory");
}
// Pack a directory into a filesystem layer
fn pack_example() {
print("Packing a directory into a filesystem layer...");
// Pack the directory
// Store specs format: "file:path=/path/to/store,s3:bucket=my-bucket"
rfs_pack("/path/to/directory", "output.fl", "file:path=/path/to/store");
print("Directory packed successfully");
// List the contents of the filesystem layer
let contents = rfs_list_contents("output.fl");
print("Contents of the filesystem layer:");
print(contents);
// Verify the filesystem layer
let is_valid = rfs_verify("output.fl");
print(`Is the filesystem layer valid? ${is_valid}`);
// Unpack the filesystem layer
rfs_unpack("output.fl", "/path/to/unpack");
print("Filesystem layer unpacked successfully");
}
// SSH mount example
fn mount_ssh_example() {
print("Mounting a remote directory via SSH...");
// Create a map for mount options
let options = #{
"port": "22",
"identity_file": "/path/to/key",
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("user@example.com:/remote/path", "/local/mount/point", "ssh", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// Get mount info
let info = rfs_get_mount_info("/local/mount/point");
print(`Mount info: ${info}`);
// Unmount the directory
rfs_unmount("/local/mount/point");
print("Unmounted the directory");
}
// S3 mount example
fn mount_s3_example() {
print("Mounting an S3 bucket...");
// Create a map for mount options
let options = #{
"region": "us-east-1",
"access_key": "your-access-key",
"secret_key": "your-secret-key"
};
// Mount the S3 bucket
let mount = rfs_mount("s3://my-bucket", "/mnt/s3", "s3", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// Unmount the S3 bucket
rfs_unmount("/mnt/s3");
print("Unmounted the S3 bucket");
}
// Unmount all example
fn unmount_all_example() {
print("Unmounting all filesystems...");
// Unmount all filesystems
rfs_unmount_all();
print("All filesystems unmounted");
}
// Run the examples
// Note: These are commented out to prevent accidental execution
// Uncomment the ones you want to run
// mount_local_example();
// pack_example();
// mount_ssh_example();
// mount_s3_example();
// unmount_all_example();
print("RFS example script completed");

View File

@@ -1,2 +1,3 @@
pub mod buildah;
pub mod nerdctl;
pub mod nerdctl;
pub mod rfs;

280
src/virt/rfs/builder.rs Normal file
View File

@@ -0,0 +1,280 @@
use std::collections::HashMap;
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::{Mount, MountType, StoreSpec},
};
/// Builder for RFS mount operations
#[derive(Clone)]
pub struct RfsBuilder {
/// Source path or URL
source: String,
/// Target mount point
target: String,
/// Mount type
mount_type: MountType,
/// Mount options
options: HashMap<String, String>,
/// Mount ID
mount_id: Option<String>,
/// Debug mode
debug: bool,
}
impl RfsBuilder {
/// Create a new RFS builder
///
/// # Arguments
///
/// * `source` - Source path or URL
/// * `target` - Target mount point
/// * `mount_type` - Mount type
///
/// # Returns
///
/// * `Self` - New RFS builder
pub fn new(source: &str, target: &str, mount_type: MountType) -> Self {
Self {
source: source.to_string(),
target: target.to_string(),
mount_type,
options: HashMap::new(),
mount_id: None,
debug: false,
}
}
/// Add a mount option
///
/// # Arguments
///
/// * `key` - Option key
/// * `value` - Option value
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
/// Add multiple mount options
///
/// # Arguments
///
/// * `options` - Map of option keys to values
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_options(mut self, options: HashMap<&str, &str>) -> Self {
for (key, value) in options {
self.options.insert(key.to_string(), value.to_string());
}
self
}
/// Set debug mode
///
/// # Arguments
///
/// * `debug` - Whether to enable debug output
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
/// Mount the filesystem
///
/// # Returns
///
/// * `Result<Mount, RfsError>` - Mount information or error
pub fn mount(self) -> Result<Mount, RfsError> {
// Build the command string
let mut cmd = String::from("mount -t ");
cmd.push_str(&self.mount_type.to_string());
// Add options if any
if !self.options.is_empty() {
cmd.push_str(" -o ");
let mut first = true;
for (key, value) in &self.options {
if !first {
cmd.push_str(",");
}
cmd.push_str(key);
cmd.push_str("=");
cmd.push_str(value);
first = false;
}
}
// Add source and target
cmd.push_str(" ");
cmd.push_str(&self.source);
cmd.push_str(" ");
cmd.push_str(&self.target);
// Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command
let result = execute_rfs_command(&args)?;
// Parse the output to get the mount ID
let mount_id = result.stdout.trim().to_string();
if mount_id.is_empty() {
return Err(RfsError::MountFailed("Failed to get mount ID".to_string()));
}
// Create and return the Mount struct
Ok(Mount {
id: mount_id,
source: self.source,
target: self.target,
fs_type: self.mount_type.to_string(),
options: self.options.iter().map(|(k, v)| format!("{}={}", k, v)).collect(),
})
}
/// Unmount the filesystem
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount(&self) -> Result<(), RfsError> {
// Execute the unmount command
let result = execute_rfs_command(&["unmount", &self.target])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount {}: {}", self.target, result.stderr)));
}
Ok(())
}
}
/// Builder for RFS pack operations
#[derive(Clone)]
pub struct PackBuilder {
/// Directory to pack
directory: String,
/// Output file
output: String,
/// Store specifications
store_specs: Vec<StoreSpec>,
/// Debug mode
debug: bool,
}
impl PackBuilder {
/// Create a new pack builder
///
/// # Arguments
///
/// * `directory` - Directory to pack
/// * `output` - Output file
///
/// # Returns
///
/// * `Self` - New pack builder
pub fn new(directory: &str, output: &str) -> Self {
Self {
directory: directory.to_string(),
output: output.to_string(),
store_specs: Vec::new(),
debug: false,
}
}
/// Add a store specification
///
/// # Arguments
///
/// * `store_spec` - Store specification
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_store_spec(mut self, store_spec: StoreSpec) -> Self {
self.store_specs.push(store_spec);
self
}
/// Add multiple store specifications
///
/// # Arguments
///
/// * `store_specs` - Store specifications
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_store_specs(mut self, store_specs: Vec<StoreSpec>) -> Self {
self.store_specs.extend(store_specs);
self
}
/// Set debug mode
///
/// # Arguments
///
/// * `debug` - Whether to enable debug output
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
/// Pack the directory
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn pack(self) -> Result<(), RfsError> {
// Build the command string
let mut cmd = String::from("pack -m ");
cmd.push_str(&self.output);
// Add store specs if any
if !self.store_specs.is_empty() {
cmd.push_str(" -s ");
let mut first = true;
for spec in &self.store_specs {
if !first {
cmd.push_str(",");
}
let spec_str = spec.to_string();
cmd.push_str(&spec_str);
first = false;
}
}
// Add directory
cmd.push_str(" ");
cmd.push_str(&self.directory);
// Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command
let result = execute_rfs_command(&args)?;
// Check for errors
if !result.success {
return Err(RfsError::PackFailed(format!("Failed to pack {}: {}", self.directory, result.stderr)));
}
Ok(())
}
}

62
src/virt/rfs/cmd.rs Normal file
View File

@@ -0,0 +1,62 @@
use crate::process::{run_command, CommandResult};
use super::error::RfsError;
use std::thread_local;
use std::cell::RefCell;
// Thread-local storage for debug flag
thread_local! {
static DEBUG: RefCell<bool> = RefCell::new(false);
}
/// Set the thread-local debug flag
pub fn set_thread_local_debug(debug: bool) {
DEBUG.with(|d| {
*d.borrow_mut() = debug;
});
}
/// Get the current thread-local debug flag
pub fn thread_local_debug() -> bool {
DEBUG.with(|d| {
*d.borrow()
})
}
/// Execute an RFS command with the given arguments
///
/// # Arguments
///
/// * `args` - Command arguments
///
/// # Returns
///
/// * `Result<CommandResult, RfsError>` - Command result or error
pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> {
let debug = thread_local_debug();
// Construct the command string
let mut cmd = String::from("rfs");
for arg in args {
cmd.push(' ');
cmd.push_str(arg);
}
if debug {
println!("Executing RFS command: {}", cmd);
}
// Execute the command
let result = run_command(&cmd)
.map_err(|e| RfsError::CommandFailed(format!("Failed to execute RFS command: {}", e)))?;
if debug {
println!("RFS command result: {:?}", result);
}
// Check if the command was successful
if !result.success && !result.stderr.is_empty() {
return Err(RfsError::CommandFailed(result.stderr));
}
Ok(result)
}

43
src/virt/rfs/error.rs Normal file
View File

@@ -0,0 +1,43 @@
use std::fmt;
use std::error::Error;
/// Error types for RFS operations
#[derive(Debug)]
pub enum RfsError {
/// Command execution failed
CommandFailed(String),
/// Invalid argument provided
InvalidArgument(String),
/// Mount operation failed
MountFailed(String),
/// Unmount operation failed
UnmountFailed(String),
/// List operation failed
ListFailed(String),
/// Pack operation failed
PackFailed(String),
/// Other error
Other(String),
}
impl fmt::Display for RfsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RfsError::CommandFailed(msg) => write!(f, "RFS command failed: {}", msg),
RfsError::InvalidArgument(msg) => write!(f, "Invalid argument: {}", msg),
RfsError::MountFailed(msg) => write!(f, "Mount failed: {}", msg),
RfsError::UnmountFailed(msg) => write!(f, "Unmount failed: {}", msg),
RfsError::ListFailed(msg) => write!(f, "List failed: {}", msg),
RfsError::PackFailed(msg) => write!(f, "Pack failed: {}", msg),
RfsError::Other(msg) => write!(f, "Other error: {}", msg),
}
}
}
impl Error for RfsError {}
impl From<std::io::Error> for RfsError {
fn from(error: std::io::Error) -> Self {
RfsError::Other(format!("IO error: {}", error))
}
}

15
src/virt/rfs/mod.rs Normal file
View File

@@ -0,0 +1,15 @@
mod cmd;
mod error;
mod mount;
mod pack;
mod builder;
mod types;
pub use error::RfsError;
pub use builder::{RfsBuilder, PackBuilder};
pub use types::{Mount, MountType, StoreSpec};
pub use mount::{list_mounts, unmount_all, unmount, get_mount_info};
pub use pack::{pack_directory, unpack, list_contents, verify};
// Re-export the execute_rfs_command function for use in other modules
pub(crate) use cmd::execute_rfs_command;

142
src/virt/rfs/mount.rs Normal file
View File

@@ -0,0 +1,142 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::Mount,
};
/// List all mounted filesystems
///
/// # Returns
///
/// * `Result<Vec<Mount>, RfsError>` - List of mounts or error
pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
// Execute the list command
let result = execute_rfs_command(&["list", "--json"])?;
// Parse the JSON output
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
Ok(json) => {
if let serde_json::Value::Array(mounts_json) = json {
let mut mounts = Vec::new();
for mount_json in mounts_json {
// Extract mount ID
let id = match mount_json.get("id").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return Err(RfsError::ListFailed("Missing mount ID".to_string())),
};
// Extract source
let source = match mount_json.get("source").and_then(|v| v.as_str()) {
Some(source) => source.to_string(),
None => return Err(RfsError::ListFailed("Missing source".to_string())),
};
// Extract target
let target = match mount_json.get("target").and_then(|v| v.as_str()) {
Some(target) => target.to_string(),
None => return Err(RfsError::ListFailed("Missing target".to_string())),
};
// Extract filesystem type
let fs_type = match mount_json.get("type").and_then(|v| v.as_str()) {
Some(fs_type) => fs_type.to_string(),
None => return Err(RfsError::ListFailed("Missing filesystem type".to_string())),
};
// Extract options
let options = match mount_json.get("options").and_then(|v| v.as_array()) {
Some(options_array) => {
let mut options_vec = Vec::new();
for option_value in options_array {
if let Some(option_str) = option_value.as_str() {
options_vec.push(option_str.to_string());
}
}
options_vec
},
None => Vec::new(), // Empty vector if no options found
};
// Create Mount struct and add to vector
mounts.push(Mount {
id,
source,
target,
fs_type,
options,
});
}
Ok(mounts)
} else {
Err(RfsError::ListFailed("Expected JSON array".to_string()))
}
},
Err(e) => {
Err(RfsError::ListFailed(format!("Failed to parse mount list JSON: {}", e)))
}
}
}
/// Unmount a filesystem by target path
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount(target: &str) -> Result<(), RfsError> {
// Execute the unmount command
let result = execute_rfs_command(&["unmount", target])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount {}: {}", target, result.stderr)));
}
Ok(())
}
/// Unmount all filesystems
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount_all() -> Result<(), RfsError> {
// Execute the unmount all command
let result = execute_rfs_command(&["unmount", "--all"])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount all filesystems: {}", result.stderr)));
}
Ok(())
}
/// Get information about a mounted filesystem
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<Mount, RfsError>` - Mount information or error
pub fn get_mount_info(target: &str) -> Result<Mount, RfsError> {
// Get all mounts
let mounts = list_mounts()?;
// Find the mount with the specified target
for mount in mounts {
if mount.target == target {
return Ok(mount);
}
}
// Mount not found
Err(RfsError::Other(format!("No mount found at {}", target)))
}

100
src/virt/rfs/pack.rs Normal file
View File

@@ -0,0 +1,100 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::StoreSpec,
builder::PackBuilder,
};
/// Pack a directory into a filesystem layer
///
/// # Arguments
///
/// * `directory` - Directory to pack
/// * `output` - Output file
/// * `store_specs` - Store specifications
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec]) -> Result<(), RfsError> {
// Create a new pack builder
let mut builder = PackBuilder::new(directory, output);
// Add store specs
for spec in store_specs {
builder = builder.with_store_spec(spec.clone());
}
// Pack the directory
builder.pack()
}
/// Unpack a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
/// * `directory` - Directory to unpack to
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unpack(input: &str, directory: &str) -> Result<(), RfsError> {
// Execute the unpack command
let result = execute_rfs_command(&["unpack", "-m", input, directory])?;
// Check for errors
if !result.success {
return Err(RfsError::Other(format!("Failed to unpack {}: {}", input, result.stderr)));
}
Ok(())
}
/// List the contents of a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<String, RfsError>` - File listing or error
pub fn list_contents(input: &str) -> Result<String, RfsError> {
// Execute the list command
let result = execute_rfs_command(&["list", "-m", input])?;
// Check for errors
if !result.success {
return Err(RfsError::Other(format!("Failed to list contents of {}: {}", input, result.stderr)));
}
Ok(result.stdout)
}
/// Verify a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<bool, RfsError>` - Whether the layer is valid or error
pub fn verify(input: &str) -> Result<bool, RfsError> {
// Execute the verify command
let result = execute_rfs_command(&["verify", "-m", input])?;
// Check for errors
if !result.success {
// If the command failed but returned a specific error about verification,
// return false instead of an error
if result.stderr.contains("verification failed") {
return Ok(false);
}
return Err(RfsError::Other(format!("Failed to verify {}: {}", input, result.stderr)));
}
Ok(true)
}

117
src/virt/rfs/types.rs Normal file
View File

@@ -0,0 +1,117 @@
use std::collections::HashMap;
/// Represents a mounted filesystem
#[derive(Debug, Clone)]
pub struct Mount {
/// Mount ID
pub id: String,
/// Source path or URL
pub source: String,
/// Target mount point
pub target: String,
/// Filesystem type
pub fs_type: String,
/// Mount options
pub options: Vec<String>,
}
/// Types of mounts supported by RFS
#[derive(Debug, Clone)]
pub enum MountType {
/// Local filesystem
Local,
/// SSH remote filesystem
SSH,
/// S3 object storage
S3,
/// WebDAV remote filesystem
WebDAV,
/// Custom mount type
Custom(String),
}
impl MountType {
/// Convert mount type to string representation
pub fn to_string(&self) -> String {
match self {
MountType::Local => "local".to_string(),
MountType::SSH => "ssh".to_string(),
MountType::S3 => "s3".to_string(),
MountType::WebDAV => "webdav".to_string(),
MountType::Custom(s) => s.clone(),
}
}
/// Create a MountType from a string
pub fn from_string(s: &str) -> Self {
match s.to_lowercase().as_str() {
"local" => MountType::Local,
"ssh" => MountType::SSH,
"s3" => MountType::S3,
"webdav" => MountType::WebDAV,
_ => MountType::Custom(s.to_string()),
}
}
}
/// Store specification for packing operations
#[derive(Debug, Clone)]
pub struct StoreSpec {
/// Store type (e.g., "file", "s3")
pub spec_type: String,
/// Store options
pub options: HashMap<String, String>,
}
impl StoreSpec {
/// Create a new store specification
///
/// # Arguments
///
/// * `spec_type` - Store type (e.g., "file", "s3")
///
/// # Returns
///
/// * `Self` - New store specification
pub fn new(spec_type: &str) -> Self {
Self {
spec_type: spec_type.to_string(),
options: HashMap::new(),
}
}
/// Add an option to the store specification
///
/// # Arguments
///
/// * `key` - Option key
/// * `value` - Option value
///
/// # Returns
///
/// * `Self` - Updated store specification for method chaining
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
/// Convert the store specification to a string
///
/// # Returns
///
/// * `String` - String representation of the store specification
pub fn to_string(&self) -> String {
let mut result = self.spec_type.clone();
if !self.options.is_empty() {
result.push_str(":");
let options: Vec<String> = self.options
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
result.push_str(&options.join(","));
}
result
}
}