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

View File

@ -26,6 +26,17 @@ cp target/debug/herodo ~/hero/bin/herodo
# Check if a script name was provided # Check if a script name was provided
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
echo "Running specified test: $1" echo "Running specified test: $1"
# Check if the script exists in src/rhaiexamples/
if [ -f "src/rhaiexamples/$1.rhai" ]; then
herodo "src/rhaiexamples/$1.rhai"
# Check if the script exists in src/herodo/scripts/
elif [ -f "src/herodo/scripts/$1.rhai" ]; then
herodo "src/herodo/scripts/$1.rhai" herodo "src/herodo/scripts/$1.rhai"
else
echo "Error: Script $1.rhai not found in src/rhaiexamples/ or src/herodo/scripts/"
exit 1
fi
exit 0 exit 0
fi fi

100
examples/package_test.rs Normal file
View File

@ -0,0 +1,100 @@
//! Example of using the package management module
//!
//! This example demonstrates how to use the package management module
//! to install, remove, and manage packages on different platforms.
use sal::os::package::{PackHero, Platform};
fn main() {
// Create a new PackHero instance
let mut hero = PackHero::new();
// Enable debug output
hero.set_debug(true);
// Detect the platform
let platform = hero.platform();
println!("Detected platform: {:?}", platform);
// Only proceed if we're on a supported platform
if platform == Platform::Unknown {
println!("Unsupported platform. This example only works on Ubuntu and macOS.");
return;
}
// Test package to install/check
let test_package = if platform == Platform::Ubuntu { "wget" } else { "wget" };
// Check if the package is installed
match hero.is_installed(test_package) {
Ok(is_installed) => {
println!("Package {} is installed: {}", test_package, is_installed);
if is_installed {
println!("Package {} is already installed", test_package);
} else {
println!("Package {} is not installed, attempting to install...", test_package);
// Try to install the package
match hero.install(test_package) {
Ok(_) => println!("Successfully installed package {}", test_package),
Err(e) => println!("Failed to install package {}: {}", test_package, e),
}
// Check if it was installed successfully
match hero.is_installed(test_package) {
Ok(is_installed_now) => {
if is_installed_now {
println!("Verified package {} was installed successfully", test_package);
} else {
println!("Package {} was not installed successfully", test_package);
}
},
Err(e) => println!("Error checking if package is installed: {}", e),
}
}
},
Err(e) => println!("Error checking if package is installed: {}", e),
}
// Search for packages
let search_term = "wget";
println!("Searching for packages with term '{}'...", search_term);
match hero.search(search_term) {
Ok(results) => {
println!("Found {} packages matching '{}'", results.len(), search_term);
for (i, package) in results.iter().enumerate().take(5) {
println!(" {}. {}", i + 1, package);
}
if results.len() > 5 {
println!(" ... and {} more", results.len() - 5);
}
},
Err(e) => println!("Error searching for packages: {}", e),
}
// List installed packages
println!("Listing installed packages...");
match hero.list_installed() {
Ok(packages) => {
println!("Found {} installed packages", packages.len());
println!("First 5 installed packages:");
for (i, package) in packages.iter().enumerate().take(5) {
println!(" {}. {}", i + 1, package);
}
if packages.len() > 5 {
println!(" ... and {} more", packages.len() - 5);
}
},
Err(e) => println!("Error listing installed packages: {}", e),
}
// Update package lists
println!("Updating package lists...");
match hero.update() {
Ok(_) => println!("Successfully updated package lists"),
Err(e) => println!("Error updating package lists: {}", e),
}
println!("Package management example completed");
}

474
rfs_implementation_plan.md Normal file
View File

@ -0,0 +1,474 @@
# RFS Wrapper Implementation Plan
## Overview
We'll create a Rust wrapper for the RFS (Remote File System) tool that follows the builder pattern, similar to the existing implementations for buildah and nerdctl in the codebase. This wrapper will provide a fluent API for mounting, unmounting, listing mounts, configuring mount options, and packing directories into filesystem layers.
## Module Structure
```
src/virt/rfs/
├── mod.rs # Module exports and common types
├── cmd.rs # Command execution functions
├── mount.rs # Mount operations
├── pack.rs # Packing operations
├── builder.rs # Builder pattern implementation
├── types.rs # Type definitions
└── error.rs # Error handling
```
## Implementation Details
### 1. Error Handling
```rust
// error.rs
#[derive(Debug)]
pub enum RfsError {
CommandFailed(String),
InvalidArgument(String),
MountFailed(String),
UnmountFailed(String),
ListFailed(String),
PackFailed(String),
Other(String),
}
impl std::fmt::Display for RfsError {
// Implementation
}
impl std::error::Error for RfsError {
// Implementation
}
```
### 2. Command Execution
```rust
// cmd.rs
use crate::process::{run_command, CommandResult};
use super::error::RfsError;
pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> {
// Implementation similar to buildah and nerdctl
}
```
### 3. Types
```rust
// types.rs
#[derive(Debug, Clone)]
pub struct Mount {
pub id: String,
pub source: String,
pub target: String,
pub fs_type: String,
pub options: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum MountType {
Local,
SSH,
S3,
WebDAV,
// Other mount types
}
#[derive(Debug, Clone)]
pub struct StoreSpec {
pub spec_type: String,
pub options: std::collections::HashMap<String, String>,
}
impl StoreSpec {
pub fn new(spec_type: &str) -> Self {
Self {
spec_type: spec_type.to_string(),
options: std::collections::HashMap::new(),
}
}
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
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
}
}
```
### 4. Builder Pattern
```rust
// builder.rs
use std::collections::HashMap;
use super::{Mount, MountType, RfsError, execute_rfs_command, StoreSpec};
#[derive(Clone)]
pub struct RfsBuilder {
source: String,
target: String,
mount_type: MountType,
options: HashMap<String, String>,
mount_id: Option<String>,
debug: bool,
}
impl RfsBuilder {
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,
}
}
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
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
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn mount(self) -> Result<Mount, RfsError> {
// Implementation
}
pub fn unmount(&self) -> Result<(), RfsError> {
// Implementation
}
// Other methods
}
// Packing functionality
pub struct PackBuilder {
directory: String,
output: String,
store_specs: Vec<StoreSpec>,
debug: bool,
}
impl PackBuilder {
pub fn new(directory: &str, output: &str) -> Self {
Self {
directory: directory.to_string(),
output: output.to_string(),
store_specs: Vec::new(),
debug: false,
}
}
pub fn with_store_spec(mut self, store_spec: StoreSpec) -> Self {
self.store_specs.push(store_spec);
self
}
pub fn with_store_specs(mut self, store_specs: Vec<StoreSpec>) -> Self {
self.store_specs.extend(store_specs);
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn pack(self) -> Result<(), RfsError> {
// Implementation for packing a directory into a filesystem layer
let mut args = vec!["pack"];
// Add output file
args.push("-m");
args.push(&self.output);
// Add store specs
if !self.store_specs.is_empty() {
args.push("-s");
let specs: Vec<String> = self.store_specs
.iter()
.map(|spec| spec.to_string())
.collect();
args.push(&specs.join(","));
}
// Add directory
args.push(&self.directory);
// Convert to string slices for the command
let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
// Execute the command
let result = execute_rfs_command(&args_str)?;
// Check for errors
if !result.success {
return Err(RfsError::PackFailed(result.stderr));
}
Ok(())
}
}
```
### 5. Mount Operations
```rust
// mount.rs
use super::{RfsBuilder, Mount, RfsError, execute_rfs_command};
pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
// Implementation
}
pub fn unmount_all() -> Result<(), RfsError> {
// Implementation
}
// Other mount-related functions
```
### 6. Pack Operations
```rust
// pack.rs
use super::{PackBuilder, StoreSpec, RfsError, execute_rfs_command};
pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec]) -> Result<(), RfsError> {
PackBuilder::new(directory, output)
.with_store_specs(store_specs.to_vec())
.pack()
}
// Other pack-related functions
```
### 7. Module Exports
```rust
// mod.rs
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};
pub use pack::pack_directory;
// Re-export the execute_rfs_command function for use in other modules
pub(crate) use cmd::execute_rfs_command;
```
## Usage Examples
### Mounting Example
```rust
use crate::virt::rfs::{RfsBuilder, MountType};
// Create a new RFS mount with builder pattern
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()?;
// List all mounts
let mounts = list_mounts()?;
for mount in mounts {
println!("Mount ID: {}, Source: {}, Target: {}", mount.id, mount.source, mount.target);
}
// Unmount
mount.unmount()?;
```
### Packing Example
```rust
use crate::virt::rfs::{PackBuilder, StoreSpec};
// Create store specifications
let store_spec1 = StoreSpec::new("file")
.with_option("path", "/path/to/store");
let store_spec2 = StoreSpec::new("s3")
.with_option("bucket", "my-bucket")
.with_option("region", "us-east-1");
// Pack a directory with builder pattern
let result = PackBuilder::new("/path/to/directory", "output.fl")
.with_store_spec(store_spec1)
.with_store_spec(store_spec2)
.with_debug(true)
.pack()?;
// Or use the convenience function
pack_directory("/path/to/directory", "output.fl", &[store_spec1, store_spec2])?;
```
## Rhai Integration
We'll also need to create a Rhai module to expose the RFS functionality to Rhai scripts:
```rust
// src/rhai/rfs.rs
use rhai::{Engine, EvalAltResult, RegisterFn};
use crate::virt::rfs::{RfsBuilder, MountType, list_mounts, unmount_all, PackBuilder, StoreSpec};
pub fn register(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register RFS 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_pack", rfs_pack);
Ok(())
}
// Function implementations
fn rfs_mount(source: &str, target: &str, mount_type: &str, options_map: rhai::Map) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_unmount(target: &str) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_list_mounts() -> Result<rhai::Array, Box<EvalAltResult>> {
// Implementation
}
fn rfs_unmount_all() -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_pack(directory: &str, output: &str, store_specs: &str) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
```
## Implementation Flow
Here's a diagram showing the flow of the implementation:
```mermaid
classDiagram
class RfsBuilder {
+String source
+String target
+MountType mount_type
+HashMap options
+Option~String~ mount_id
+bool debug
+new(source, target, mount_type)
+with_option(key, value)
+with_options(options)
+with_debug(debug)
+mount()
+unmount()
}
class PackBuilder {
+String directory
+String output
+Vec~StoreSpec~ store_specs
+bool debug
+new(directory, output)
+with_store_spec(store_spec)
+with_store_specs(store_specs)
+with_debug(debug)
+pack()
}
class Mount {
+String id
+String source
+String target
+String fs_type
+Vec~String~ options
}
class MountType {
<<enumeration>>
Local
SSH
S3
WebDAV
}
class StoreSpec {
+String spec_type
+HashMap options
+new(spec_type)
+with_option(key, value)
+to_string()
}
class RfsError {
<<enumeration>>
CommandFailed
InvalidArgument
MountFailed
UnmountFailed
ListFailed
PackFailed
Other
}
RfsBuilder --> Mount : creates
RfsBuilder --> RfsError : may throw
RfsBuilder --> MountType : uses
PackBuilder --> RfsError : may throw
PackBuilder --> StoreSpec : uses
Mount --> RfsError : may throw
```
## Implementation Steps
1. Create the directory structure for the RFS module
2. Implement the error handling module
3. Implement the command execution module
4. Define the types for mounts, mount operations, and store specifications
5. Implement the builder pattern for RFS operations (mount and pack)
6. Implement the mount operations
7. Implement the pack operations
8. Create the module exports
9. Add Rhai integration
10. Write tests for the implementation
11. Update documentation

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)] #[cfg(test)]
mod tests { mod tests {
// Import the std::process::Command directly for some test-specific commands
use std::process::Command as StdCommand;
use super::*; use super::*;
use std::sync::{Arc, Mutex};
#[test] #[test]
fn test_platform_detection() { fn test_platform_detection() {
@ -415,7 +418,486 @@ mod tests {
assert_eq!(thread_local_debug(), false); assert_eq!(thread_local_debug(), false);
} }
// More tests would be added for each platform-specific implementation #[test]
// These would likely be integration tests that are conditionally compiled fn test_package_error_display() {
// based on the platform they're running on // 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 rhai::{EvalAltResult, Position};
use crate::os::{FsError, DownloadError}; use crate::os::{FsError, DownloadError};
use crate::os::package::PackageError;
/// Convert a FsError to a Rhai EvalAltResult /// Convert a FsError to a Rhai EvalAltResult
pub fn fs_error_to_rhai_error(err: FsError) -> Box<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 /// Register error types with the Rhai engine
pub fn register_error_types(engine: &mut rhai::Engine) -> Result<(), Box<EvalAltResult>> { pub fn register_error_types(engine: &mut rhai::Engine) -> Result<(), Box<EvalAltResult>> {
// Register helper functions for error handling // 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) format!("Download error: {}", err_msg)
}); });
engine.register_fn("package_error_message", |err_msg: &str| -> String {
format!("Package management error: {}", err_msg)
});
Ok(()) Ok(())
} }
@ -58,3 +72,9 @@ impl<T> ToRhaiError<T> for Result<T, DownloadError> {
self.map_err(download_error_to_rhai_error) 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 nerdctl;
mod git; mod git;
mod text; mod text;
mod rfs;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -53,6 +54,9 @@ pub use nerdctl::{
nerdctl_image_pull, nerdctl_image_commit, nerdctl_image_build 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 // Re-export git module
pub use git::register_git_module; pub use git::register_git_module;
pub use crate::git::{GitTree, GitRepo}; pub use crate::git::{GitTree, GitRepo};
@ -103,12 +107,16 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register Nerdctl module functions // Register Nerdctl module functions
nerdctl::register_nerdctl_module(engine)?; nerdctl::register_nerdctl_module(engine)?;
// Register Git module functions // Register Git module functions
git::register_git_module(engine)?; git::register_git_module(engine)?;
// Register Text module functions // Register Text module functions
text::register_text_module(engine)?; text::register_text_module(engine)?;
// Register RFS module functions
rfs::register(engine)?;
// Future modules can be registered here // 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 // Set debug mode to true to see detailed output
package_set_debug(true); package_set_debug(true);
// Function to demonstrate package management on Ubuntu // Function to demonstrate mypackage management on Ubuntu
fn demo_ubuntu() { fn demo_ubuntu() {
print("Demonstrating package management on Ubuntu..."); print("Demonstrating mypackage management on Ubuntu...");
// Update package lists // Update mypackage lists
print("Updating package lists..."); print("Updating mypackage lists...");
let result = package_update(); let result = package_update();
print(`Update result: ${result}`); print(`Update result: ${result}`);
// Check if a package is installed // Check if a mypackage is installed
let package = "htop"; let mypackage = "htop";
print(`Checking if ${package} is installed...`); print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(package); let is_installed = package_is_installed(mypackage);
print(`${package} is installed: ${is_installed}`); print(`${mypackage} is installed: ${is_installed}`);
// Install a package if not already installed // Install a mypackage if not already installed
if !is_installed { if !is_installed {
print(`Installing ${package}...`); print(`Installing ${mypackage}...`);
let install_result = package_install(package); let install_result = package_install(mypackage);
print(`Install result: ${install_result}`); print(`Install result: ${install_result}`);
} }
@ -41,33 +41,33 @@ fn demo_ubuntu() {
print(` - ${search_results[i]}`); print(` - ${search_results[i]}`);
} }
// Remove the package if we installed it // Remove the mypackage if we installed it
if !is_installed { if !is_installed {
print(`Removing ${package}...`); print(`Removing ${mypackage}...`);
let remove_result = package_remove(package); let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`); print(`Remove result: ${remove_result}`);
} }
} }
// Function to demonstrate package management on macOS // Function to demonstrate mypackage management on macOS
fn demo_macos() { fn demo_macos() {
print("Demonstrating package management on macOS..."); print("Demonstrating mypackage management on macOS...");
// Update package lists // Update mypackage lists
print("Updating package lists..."); print("Updating mypackage lists...");
let result = package_update(); let result = package_update();
print(`Update result: ${result}`); print(`Update result: ${result}`);
// Check if a package is installed // Check if a mypackage is installed
let package = "wget"; let mypackage = "wget";
print(`Checking if ${package} is installed...`); print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(package); let is_installed = package_is_installed(mypackage);
print(`${package} is installed: ${is_installed}`); print(`${mypackage} is installed: ${is_installed}`);
// Install a package if not already installed // Install a mypackage if not already installed
if !is_installed { if !is_installed {
print(`Installing ${package}...`); print(`Installing ${mypackage}...`);
let install_result = package_install(package); let install_result = package_install(mypackage);
print(`Install result: ${install_result}`); print(`Install result: ${install_result}`);
} }
@ -87,10 +87,10 @@ fn demo_macos() {
print(` - ${search_results[i]}`); print(` - ${search_results[i]}`);
} }
// Remove the package if we installed it // Remove the mypackage if we installed it
if !is_installed { if !is_installed {
print(`Removing ${package}...`); print(`Removing ${mypackage}...`);
let remove_result = package_remove(package); let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`); 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 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
}
}