feat: Add support for virt package
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run

- Add sal-virt package to the workspace members
- Update MONOREPO_CONVERSION_PLAN.md to reflect the
  completion of sal-process and sal-virt packages
- Update src/lib.rs to include sal-virt
- Update src/postgresclient to use sal-virt instead of local
  virt module
- Update tests to use sal-virt
This commit is contained in:
Mahmoud-Emad
2025-06-23 02:37:14 +03:00
parent 3e3d0a1d45
commit 455f84528b
112 changed files with 2924 additions and 579 deletions

165
virt/src/rfs/README.md Normal file
View File

@@ -0,0 +1,165 @@
# SAL RFS (Remote File System) Module (`sal::virt::rfs`)
## Overview
The `sal::virt::rfs` module provides a Rust interface for interacting with an underlying `rfs` command-line tool. This tool facilitates mounting various types of remote and local filesystems and managing packed filesystem layers.
The module allows Rust applications and `herodo` Rhai scripts to:
- Mount and unmount filesystems from different sources (e.g., local paths, SSH, S3, WebDAV).
- List currently mounted filesystems and retrieve information about specific mounts.
- Pack directories into filesystem layers, potentially using specified storage backends.
- Unpack, list contents of, and verify these filesystem layers.
All operations are performed by invoking the `rfs` CLI tool and parsing its output.
## Key Design Points
- **CLI Wrapper**: This module acts as a wrapper around an external `rfs` command-line utility. The actual filesystem operations and layer management are delegated to this tool.
- **Asynchronous Operations (Implicit)**: While the Rust functions themselves might be synchronous, the underlying `execute_rfs_command` (presumably from `super::cmd`) likely handles command execution, which could be asynchronous or blocking depending on its implementation.
- **Filesystem Abstraction**: Supports mounting diverse filesystem types such as `local`, `ssh`, `s3`, and `webdav` through the `rfs` tool's capabilities.
- **Layer Management**: Provides functionalities to `pack` directories into portable layers, `unpack` them, `list_contents`, and `verify` their integrity. This is useful for creating and managing reproducible filesystem snapshots or components.
- **Store Specifications (`StoreSpec`)**: The packing functionality allows specifying `StoreSpec` types, suggesting that packed layers can be stored or referenced using different backend mechanisms (e.g., local files, S3 buckets). This enables flexible storage and retrieval of filesystem layers.
- **Builder Pattern**: Uses `RfsBuilder` for constructing mount commands with various options and `PackBuilder` for packing operations, providing a fluent interface for complex configurations.
- **Rhai Scriptability**: Most functionalities are exposed to Rhai scripts via `herodo` through the `sal::rhai::rfs` bridge, enabling automation of filesystem and layer management tasks.
- **Structured Error Handling**: Defines `RfsError` for specific error conditions encountered during `rfs` command execution or output parsing.
## Rhai Scripting with `herodo`
The `sal::virt::rfs` module is scriptable via `herodo`. The following functions are available in Rhai, prefixed with `rfs_`:
### Mount Operations
- `rfs_mount(source: String, target: String, mount_type: String, options: Map) -> Map`
- Mounts a filesystem.
- `source`: The source path or URL (e.g., `/path/to/local_dir`, `ssh://user@host:/remote/path`, `s3://bucket/key`).
- `target`: The local path where the filesystem will be mounted.
- `mount_type`: A string specifying the type of filesystem (e.g., "local", "ssh", "s3", "webdav").
- `options`: A Rhai map of additional mount options (e.g., `#{ "read_only": true, "uid": 1000 }`).
- Returns a map containing details of the mount (id, source, target, fs_type, options) on success.
- `rfs_unmount(target: String) -> ()`
- Unmounts the filesystem at the specified target path.
- `rfs_list_mounts() -> Array`
- Lists all currently mounted filesystems managed by `rfs`.
- Returns an array of maps, each representing a mount with its details.
- `rfs_unmount_all() -> ()`
- Unmounts all filesystems currently managed by `rfs`.
- `rfs_get_mount_info(target: String) -> Map`
- Retrieves information about a specific mounted filesystem.
- Returns a map with mount details if found.
### Pack/Layer Operations
- `rfs_pack(directory: String, output: String, store_specs: String) -> ()`
- Packs the contents of a `directory` into an `output` file (layer).
- `store_specs`: A comma-separated string defining storage specifications for the layer (e.g., `"file:path=/path/to/local_store,s3:bucket=my-archive,region=us-west-1"`). Each spec is `type:key=value,key2=value2`.
- `rfs_unpack(input: String, directory: String) -> ()`
- Unpacks an `input` layer file into the specified `directory`.
- `rfs_list_contents(input: String) -> String`
- Lists the contents of an `input` layer file.
- Returns a string containing the file listing (raw output from the `rfs` tool).
- `rfs_verify(input: String) -> bool`
- Verifies the integrity of an `input` layer file.
- Returns `true` if the layer is valid, `false` otherwise.
### Rhai Example
```rhai
// Example: Mounting a local directory (ensure /mnt/my_local_mount exists)
let source_dir = "/tmp/my_data_source"; // Create this directory first
let target_mount = "/mnt/my_local_mount";
// Create source_dir if it doesn't exist for the example to run
// In a real script, you might use sal::os::dir_create or ensure it exists.
// For this example, assume it's manually created or use: os_run_command(`mkdir -p ${source_dir}`);
print(`Mounting ${source_dir} to ${target_mount}...`);
let mount_result = rfs_mount(source_dir, target_mount, "local", #{});
if mount_result.is_ok() {
print(`Mount successful: ${mount_result}`);
} else {
print(`Mount failed: ${mount_result}`);
}
// List mounts
print("\nCurrent mounts:");
let mounts = rfs_list_mounts();
if mounts.is_ok() {
for m in mounts {
print(` Target: ${m.target}, Source: ${m.source}, Type: ${m.fs_type}`);
}
} else {
print(`Error listing mounts: ${mounts}`);
}
// Example: Packing a directory
let dir_to_pack = "/tmp/pack_this_dir"; // Create and populate this directory
let packed_file = "/tmp/my_layer.pack";
// os_run_command(`mkdir -p ${dir_to_pack}`);
// os_run_command(`echo 'hello' > ${dir_to_pack}/file1.txt`);
print(`\nPacking ${dir_to_pack} to ${packed_file}...`);
// Using a file-based store spec for simplicity
let pack_store_specs = "file:path=/tmp/rfs_store";
// os_run_command(`mkdir -p /tmp/rfs_store`);
let pack_result = rfs_pack(dir_to_pack, packed_file, pack_store_specs);
if pack_result.is_ok() {
print("Packing successful.");
// List contents of the packed file
print(`\nContents of ${packed_file}:`);
let contents = rfs_list_contents(packed_file);
if contents.is_ok() {
print(contents);
} else {
print(`Error listing contents: ${contents}`);
}
// Verify the packed file
print(`\nVerifying ${packed_file}...`);
let verify_result = rfs_verify(packed_file);
if verify_result.is_ok() && verify_result {
print("Verification successful: Layer is valid.");
} else {
print(`Verification failed or error: ${verify_result}`);
}
// Example: Unpacking
let unpack_dir = "/tmp/unpacked_layer_here";
// os_run_command(`mkdir -p ${unpack_dir}`);
print(`\nUnpacking ${packed_file} to ${unpack_dir}...`);
let unpack_result = rfs_unpack(packed_file, unpack_dir);
if unpack_result.is_ok() {
print("Unpacking successful.");
// You would typically check contents of unpack_dir here
// os_run_command(`ls -la ${unpack_dir}`);
} else {
print(`Error unpacking: ${unpack_result}`);
}
} else {
print(`Error packing: ${pack_result}`);
}
// Cleanup: Unmount the local mount
if mount_result.is_ok() {
print(`\nUnmounting ${target_mount}...`);
rfs_unmount(target_mount);
}
// To run this example, ensure the 'rfs' command-line tool is installed and configured,
// and that the necessary directories (/tmp/my_data_source, /mnt/my_local_mount, etc.)
// exist and have correct permissions.
// You might need to run herodo with sudo for mount/unmount operations.
print("\nRFS Rhai script finished.");
```
This module provides a flexible way to manage diverse filesystems and filesystem layers, making it a powerful tool for system automation and deployment tasks within the SAL ecosystem.

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

@@ -0,0 +1,372 @@
use super::{
cmd::execute_rfs_command,
error::RfsError,
types::{Mount, MountType, StoreSpec},
};
use std::collections::HashMap;
/// 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
#[allow(dead_code)]
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
}
/// Get the source path
///
/// # Returns
///
/// * `&str` - Source path
pub fn source(&self) -> &str {
&self.source
}
/// Get the target path
///
/// # Returns
///
/// * `&str` - Target path
pub fn target(&self) -> &str {
&self.target
}
/// Get the mount type
///
/// # Returns
///
/// * `&MountType` - Mount type
pub fn mount_type(&self) -> &MountType {
&self.mount_type
}
/// Get the options
///
/// # Returns
///
/// * `&HashMap<String, String>` - Mount options
pub fn options(&self) -> &HashMap<String, String> {
&self.options
}
/// Get debug mode
///
/// # Returns
///
/// * `bool` - Whether debug mode is enabled
pub fn debug(&self) -> bool {
self.debug
}
/// 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
}
/// Get the directory path
///
/// # Returns
///
/// * `&str` - Directory path
pub fn directory(&self) -> &str {
&self.directory
}
/// Get the output path
///
/// # Returns
///
/// * `&str` - Output path
pub fn output(&self) -> &str {
&self.output
}
/// Get the store specifications
///
/// # Returns
///
/// * `&Vec<StoreSpec>` - Store specifications
pub fn store_specs(&self) -> &Vec<StoreSpec> {
&self.store_specs
}
/// Get debug mode
///
/// # Returns
///
/// * `bool` - Whether debug mode is enabled
pub fn debug(&self) -> bool {
self.debug
}
/// 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(())
}
}

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

@@ -0,0 +1,61 @@
use super::error::RfsError;
use sal_process::{run_command, CommandResult};
use std::cell::RefCell;
use std::thread_local;
// Thread-local storage for debug flag
thread_local! {
static DEBUG: RefCell<bool> = RefCell::new(false);
}
/// Set the thread-local debug flag
#[allow(dead_code)]
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
virt/src/rfs/error.rs Normal file
View File

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

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

@@ -0,0 +1,14 @@
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

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

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

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

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

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

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