...
This commit is contained in:
parent
0fa9eddd1c
commit
21893ce225
@ -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
100
examples/package_test.rs
Normal 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
474
rfs_implementation_plan.md
Normal 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
154
src/docs/docs/sal/rfs.md
Normal 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.
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
292
src/rhai/rfs.rs
Normal 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
|
||||||
|
}
|
@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
121
src/rhaiexamples/rfs_example.rhai
Normal file
121
src/rhaiexamples/rfs_example.rhai
Normal 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");
|
@ -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
280
src/virt/rfs/builder.rs
Normal 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
62
src/virt/rfs/cmd.rs
Normal 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
43
src/virt/rfs/error.rs
Normal 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
15
src/virt/rfs/mod.rs
Normal 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
142
src/virt/rfs/mount.rs
Normal 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
100
src/virt/rfs/pack.rs
Normal 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
117
src/virt/rfs/types.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user