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

318
virt/src/rhai/buildah.rs Normal file
View File

@@ -0,0 +1,318 @@
//! Rhai wrappers for Buildah module functions
//!
//! This module provides Rhai wrappers for the functions in the Buildah module.
use crate::buildah::{BuildahError, Builder, ContentOperations, Image};
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
use sal_process::CommandResult;
use std::collections::HashMap;
/// Register Buildah module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register types
register_bah_types(engine)?;
// Register Builder constructor
engine.register_fn("bah_new", bah_new);
// Register Builder instance methods
engine.register_fn("run", builder_run);
engine.register_fn("run_with_isolation", builder_run_with_isolation);
engine.register_fn("copy", builder_copy);
engine.register_fn("add", builder_add);
engine.register_fn("commit", builder_commit);
engine.register_fn("remove", builder_remove);
engine.register_fn("reset", builder_reset);
engine.register_fn("config", builder_config);
// Register Builder instance methods for entrypoint, cmd, and content operations
engine.register_fn("set_entrypoint", builder_set_entrypoint);
engine.register_fn("set_cmd", builder_set_cmd);
engine.register_fn("write_content", builder_write_content);
engine.register_fn("read_content", builder_read_content);
// Register Builder static methods
engine.register_fn("images", builder_images);
engine.register_fn("image_remove", builder_image_remove);
engine.register_fn("image_pull", builder_image_pull);
engine.register_fn("image_push", builder_image_push);
engine.register_fn("image_tag", builder_image_tag);
engine.register_fn("build", builder_build);
engine.register_fn("read_content", builder_read_content);
Ok(())
}
/// Register Buildah module types with the Rhai engine
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register Builder type
engine.register_type_with_name::<Builder>("BuildahBuilder");
// Register getters for Builder properties
engine.register_get("container_id", get_builder_container_id);
engine.register_get("name", get_builder_name);
engine.register_get("image", get_builder_image);
engine.register_get("debug_mode", get_builder_debug);
engine.register_set("debug_mode", set_builder_debug);
// Register Image type and methods (same as before)
engine.register_type_with_name::<Image>("BuildahImage");
// Register getters for Image properties
engine.register_get("id", |img: &mut Image| img.id.clone());
engine.register_get("names", |img: &mut Image| {
let mut array = Array::new();
for name in &img.names {
array.push(Dynamic::from(name.clone()));
}
array
});
// Add a 'name' getter that returns the first name or a default
engine.register_get("name", |img: &mut Image| {
if img.names.is_empty() {
"<none>".to_string()
} else {
img.names[0].clone()
}
});
engine.register_get("size", |img: &mut Image| img.size.clone());
engine.register_get("created", |img: &mut Image| img.created.clone());
Ok(())
}
// Helper functions for error conversion
fn bah_error_to_rhai_error<T>(result: Result<T, BuildahError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Buildah error: {}", e).into(),
rhai::Position::NONE,
))
})
}
// Helper function to convert Rhai Map to Rust HashMap
fn convert_map_to_hashmap(options: Map) -> Result<HashMap<String, String>, Box<EvalAltResult>> {
let mut config_options = HashMap::<String, String>::new();
for (key, value) in options.iter() {
if let Ok(value_str) = value.clone().into_string() {
// Convert SmartString to String
config_options.insert(key.to_string(), value_str);
} else {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Option '{}' must be a string", key).into(),
rhai::Position::NONE,
)));
}
}
Ok(config_options)
}
/// Create a new Builder
pub fn bah_new(name: &str, image: &str) -> Result<Builder, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::new(name, image))
}
// Builder instance methods
pub fn builder_run(
builder: &mut Builder,
command: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.run(command))
}
pub fn builder_run_with_isolation(
builder: &mut Builder,
command: &str,
isolation: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.run_with_isolation(command, isolation))
}
pub fn builder_copy(
builder: &mut Builder,
source: &str,
dest: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.copy(source, dest))
}
pub fn builder_add(
builder: &mut Builder,
source: &str,
dest: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.add(source, dest))
}
pub fn builder_commit(
builder: &mut Builder,
image_name: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.commit(image_name))
}
pub fn builder_remove(builder: &mut Builder) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.remove())
}
pub fn builder_config(
builder: &mut Builder,
options: Map,
) -> Result<CommandResult, Box<EvalAltResult>> {
// Convert Rhai Map to Rust HashMap
let config_options = convert_map_to_hashmap(options)?;
bah_error_to_rhai_error(builder.config(config_options))
}
/// Set the entrypoint for the container
pub fn builder_set_entrypoint(
builder: &mut Builder,
entrypoint: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.set_entrypoint(entrypoint))
}
/// Set the default command for the container
pub fn builder_set_cmd(
builder: &mut Builder,
cmd: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.set_cmd(cmd))
}
/// Write content to a file in the container
pub fn builder_write_content(
builder: &mut Builder,
content: &str,
dest_path: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
if let Some(container_id) = builder.container_id() {
bah_error_to_rhai_error(ContentOperations::write_content(
container_id,
content,
dest_path,
))
} else {
Err(Box::new(EvalAltResult::ErrorRuntime(
"No container ID available".into(),
rhai::Position::NONE,
)))
}
}
/// Read content from a file in the container
pub fn builder_read_content(
builder: &mut Builder,
source_path: &str,
) -> Result<String, Box<EvalAltResult>> {
if let Some(container_id) = builder.container_id() {
bah_error_to_rhai_error(ContentOperations::read_content(container_id, source_path))
} else {
Err(Box::new(EvalAltResult::ErrorRuntime(
"No container ID available".into(),
rhai::Position::NONE,
)))
}
}
// Builder static methods
pub fn builder_images(_builder: &mut Builder) -> Result<Array, Box<EvalAltResult>> {
let images = bah_error_to_rhai_error(Builder::images())?;
// Convert Vec<Image> to Rhai Array
let mut array = Array::new();
for image in images {
array.push(Dynamic::from(image));
}
Ok(array)
}
pub fn builder_image_remove(
_builder: &mut Builder,
image: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_remove(image))
}
pub fn builder_image_pull(
_builder: &mut Builder,
image: &str,
tls_verify: bool,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_pull(image, tls_verify))
}
pub fn builder_image_push(
_builder: &mut Builder,
image: &str,
destination: &str,
tls_verify: bool,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify))
}
pub fn builder_image_tag(
_builder: &mut Builder,
image: &str,
new_name: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_tag(image, new_name))
}
// Getter functions for Builder properties
pub fn get_builder_container_id(builder: &mut Builder) -> String {
match builder.container_id() {
Some(id) => id.clone(),
None => "".to_string(),
}
}
pub fn get_builder_name(builder: &mut Builder) -> String {
builder.name().to_string()
}
pub fn get_builder_image(builder: &mut Builder) -> String {
builder.image().to_string()
}
/// Get the debug flag from a Builder
pub fn get_builder_debug(builder: &mut Builder) -> bool {
builder.debug()
}
/// Set the debug flag on a Builder
pub fn set_builder_debug(builder: &mut Builder, debug: bool) {
builder.set_debug(debug);
}
// Reset function for Builder
pub fn builder_reset(builder: &mut Builder) -> Result<(), Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.reset())
}
// Build function for Builder
pub fn builder_build(
_builder: &mut Builder,
tag: &str,
context_dir: &str,
file: &str,
isolation: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::build(
Some(tag),
context_dir,
file,
Some(isolation),
))
}

580
virt/src/rhai/nerdctl.rs Normal file
View File

@@ -0,0 +1,580 @@
//! Rhai wrappers for Nerdctl module functions
//!
//! This module provides Rhai wrappers for the functions in the Nerdctl module.
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
use crate::nerdctl::{self, NerdctlError, Image, Container};
use sal_process::CommandResult;
// Helper functions for error conversion with improved context
fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
// Create a more detailed error message based on the error type
let error_message = match &e {
NerdctlError::CommandExecutionFailed(io_err) => {
format!("Failed to execute nerdctl command: {}. This may indicate nerdctl is not installed or not in PATH.", io_err)
},
NerdctlError::CommandFailed(msg) => {
format!("Nerdctl command failed: {}. Check container status and logs for more details.", msg)
},
NerdctlError::JsonParseError(msg) => {
format!("Failed to parse nerdctl JSON output: {}. This may indicate an incompatible nerdctl version.", msg)
},
NerdctlError::ConversionError(msg) => {
format!("Data conversion error: {}. This may indicate unexpected output format from nerdctl.", msg)
},
NerdctlError::Other(msg) => {
format!("Nerdctl error: {}. This is an unexpected error.", msg)
},
};
Box::new(EvalAltResult::ErrorRuntime(
error_message.into(),
rhai::Position::NONE
))
})
}
//
// Container Builder Pattern Implementation
//
/// Create a new Container
pub fn container_new(name: &str) -> Result<Container, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(Container::new(name))
}
/// Create a Container from an image
pub fn container_from_image(name: &str, image: &str) -> Result<Container, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(Container::from_image(name, image))
}
/// Reset the container configuration to defaults while keeping the name and image
pub fn container_reset(container: Container) -> Container {
container.reset()
}
/// Add a port mapping to a Container
pub fn container_with_port(container: Container, port: &str) -> Container {
container.with_port(port)
}
/// Add a volume mount to a Container
pub fn container_with_volume(container: Container, volume: &str) -> Container {
container.with_volume(volume)
}
/// Add an environment variable to a Container
pub fn container_with_env(container: Container, key: &str, value: &str) -> Container {
container.with_env(key, value)
}
/// Set the network for a Container
pub fn container_with_network(container: Container, network: &str) -> Container {
container.with_network(network)
}
/// Add a network alias to a Container
pub fn container_with_network_alias(container: Container, alias: &str) -> Container {
container.with_network_alias(alias)
}
/// Set CPU limit for a Container
pub fn container_with_cpu_limit(container: Container, cpus: &str) -> Container {
container.with_cpu_limit(cpus)
}
/// Set memory limit for a Container
pub fn container_with_memory_limit(container: Container, memory: &str) -> Container {
container.with_memory_limit(memory)
}
/// Set restart policy for a Container
pub fn container_with_restart_policy(container: Container, policy: &str) -> Container {
container.with_restart_policy(policy)
}
/// Set health check for a Container
pub fn container_with_health_check(container: Container, cmd: &str) -> Container {
container.with_health_check(cmd)
}
/// Add multiple port mappings to a Container
pub fn container_with_ports(mut container: Container, ports: Array) -> Container {
for port in ports.iter() {
if port.is_string() {
let port_str = port.clone().cast::<String>();
container = container.with_port(&port_str);
}
}
container
}
/// Add multiple volume mounts to a Container
pub fn container_with_volumes(mut container: Container, volumes: Array) -> Container {
for volume in volumes.iter() {
if volume.is_string() {
let volume_str = volume.clone().cast::<String>();
container = container.with_volume(&volume_str);
}
}
container
}
/// Add multiple environment variables to a Container
pub fn container_with_envs(mut container: Container, env_map: Map) -> Container {
for (key, value) in env_map.iter() {
if value.is_string() {
let value_str = value.clone().cast::<String>();
container = container.with_env(&key, &value_str);
}
}
container
}
/// Add multiple network aliases to a Container
pub fn container_with_network_aliases(mut container: Container, aliases: Array) -> Container {
for alias in aliases.iter() {
if alias.is_string() {
let alias_str = alias.clone().cast::<String>();
container = container.with_network_alias(&alias_str);
}
}
container
}
/// Set memory swap limit for a Container
pub fn container_with_memory_swap_limit(container: Container, memory_swap: &str) -> Container {
container.with_memory_swap_limit(memory_swap)
}
/// Set CPU shares for a Container
pub fn container_with_cpu_shares(container: Container, shares: &str) -> Container {
container.with_cpu_shares(shares)
}
/// Set health check with options for a Container
pub fn container_with_health_check_options(
container: Container,
cmd: &str,
interval: Option<&str>,
timeout: Option<&str>,
retries: Option<i64>,
start_period: Option<&str>
) -> Container {
// Convert i64 to u32 for retries
let retries_u32 = retries.map(|r| r as u32);
container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period)
}
/// Set snapshotter for a Container
pub fn container_with_snapshotter(container: Container, snapshotter: &str) -> Container {
container.with_snapshotter(snapshotter)
}
/// Set detach mode for a Container
pub fn container_with_detach(container: Container, detach: bool) -> Container {
container.with_detach(detach)
}
/// Build and run the Container
///
/// This function builds and runs the container using the configured options.
/// It provides detailed error information if the build fails.
pub fn container_build(container: Container) -> Result<Container, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let image = container.image.clone().unwrap_or_else(|| "none".to_string());
let ports = container.ports.clone();
let volumes = container.volumes.clone();
let env_vars = container.env_vars.clone();
// Try to build the container
let build_result = container.build();
// Handle the result with improved error context
match build_result {
Ok(built_container) => {
// Container built successfully
Ok(built_container)
},
Err(err) => {
// Add more context to the error
let enhanced_error = match err {
NerdctlError::CommandFailed(msg) => {
// Provide more detailed error information
let mut enhanced_msg = format!("Failed to build container '{}' from image '{}': {}",
container_name, image, msg);
// Add information about configured options that might be relevant
if !ports.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports));
}
if !volumes.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes));
}
if !env_vars.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured environment variables: {:?}", env_vars));
}
// Add suggestions for common issues
if msg.contains("not found") || msg.contains("no such image") {
enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull().");
} else if msg.contains("port is already allocated") {
enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port.");
} else if msg.contains("permission denied") {
enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes.");
}
NerdctlError::CommandFailed(enhanced_msg)
},
_ => err
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// Start the Container and verify it's running
///
/// This function starts the container and verifies that it's actually running.
/// It returns detailed error information if the container fails to start or
/// if it starts but stops immediately.
pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
// Try to start the container
let start_result = container.start();
// Handle the result with improved error context
match start_result {
Ok(result) => {
// Container started successfully
Ok(result)
},
Err(err) => {
// Add more context to the error
let enhanced_error = match err {
NerdctlError::CommandFailed(msg) => {
// Check if this is a "container already running" error, which is not really an error
if msg.contains("already running") {
return Ok(CommandResult {
stdout: format!("Container {} is already running", container_name),
stderr: "".to_string(),
success: true,
code: 0,
});
}
// Try to get more information about why the container might have failed to start
let mut enhanced_msg = format!("Failed to start container '{}' (ID: {}): {}",
container_name, container_id, msg);
// Try to check if the image exists
if let Some(image) = &container.image {
enhanced_msg.push_str(&format!("\nContainer was using image: {}", image));
}
NerdctlError::CommandFailed(enhanced_msg)
},
_ => err
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// Stop the Container
pub fn container_stop(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.stop())
}
/// Remove the Container
pub fn container_remove(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.remove())
}
/// Execute a command in the Container
pub fn container_exec(container: &mut Container, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.exec(command))
}
/// Get container logs
pub fn container_logs(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
// Use the nerdctl::logs function
let logs_result = nerdctl::logs(&container_id);
match logs_result {
Ok(result) => {
Ok(result)
},
Err(err) => {
// Add more context to the error
let enhanced_error = NerdctlError::CommandFailed(
format!("Failed to get logs for container '{}' (ID: {}): {}",
container_name, container_id, err)
);
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// Copy files between the Container and local filesystem
pub fn container_copy(container: &mut Container, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.copy(source, dest))
}
/// Create a new Map with default run options
pub fn new_run_options() -> Map {
let mut map = Map::new();
map.insert("name".into(), Dynamic::UNIT);
map.insert("detach".into(), Dynamic::from(true));
map.insert("ports".into(), Dynamic::from(Array::new()));
map.insert("snapshotter".into(), Dynamic::from("native"));
map
}
//
// Container Function Wrappers
//
/// Wrapper for nerdctl::run
///
/// Run a container from an image.
pub fn nerdctl_run(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::run(image, None, true, None, None))
}
/// Run a container with a name
pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, None, None))
}
/// Run a container with a port mapping
pub fn nerdctl_run_with_port(image: &str, name: &str, port: &str) -> Result<CommandResult, Box<EvalAltResult>> {
let ports = vec![port];
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, Some(&ports), None))
}
/// Wrapper for nerdctl::exec
///
/// Execute a command in a container.
pub fn nerdctl_exec(container: &str, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::exec(container, command))
}
/// Wrapper for nerdctl::copy
///
/// Copy files between container and local filesystem.
pub fn nerdctl_copy(source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::copy(source, dest))
}
/// Wrapper for nerdctl::stop
///
/// Stop a container.
pub fn nerdctl_stop(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::stop(container))
}
/// Wrapper for nerdctl::remove
///
/// Remove a container.
pub fn nerdctl_remove(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::remove(container))
}
/// Wrapper for nerdctl::list
///
/// List containers.
pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::list(all))
}
/// Wrapper for nerdctl::logs
///
/// Get container logs.
pub fn nerdctl_logs(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::logs(container))
}
//
// Image Function Wrappers
//
/// Wrapper for nerdctl::images
///
/// List images in local storage.
pub fn nerdctl_images() -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::images())
}
/// Wrapper for nerdctl::image_remove
///
/// Remove one or more images.
pub fn nerdctl_image_remove(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_remove(image))
}
/// Wrapper for nerdctl::image_push
///
/// Push an image to a registry.
pub fn nerdctl_image_push(image: &str, destination: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination))
}
/// Wrapper for nerdctl::image_tag
///
/// Add an additional name to a local image.
pub fn nerdctl_image_tag(image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_tag(image, new_name))
}
/// Wrapper for nerdctl::image_pull
///
/// Pull an image from a registry.
pub fn nerdctl_image_pull(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_pull(image))
}
/// Wrapper for nerdctl::image_commit
///
/// Commit a container to an image.
pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_commit(container, image_name))
}
/// Wrapper for nerdctl::image_build
///
/// Build an image using a Dockerfile.
pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path))
}
/// Register Nerdctl module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register types
register_nerdctl_types(engine)?;
// Register Container constructor
engine.register_fn("nerdctl_container_new", container_new);
engine.register_fn("nerdctl_container_from_image", container_from_image);
// Register Container instance methods
engine.register_fn("reset", container_reset);
engine.register_fn("with_port", container_with_port);
engine.register_fn("with_volume", container_with_volume);
engine.register_fn("with_env", container_with_env);
engine.register_fn("with_network", container_with_network);
engine.register_fn("with_network_alias", container_with_network_alias);
engine.register_fn("with_cpu_limit", container_with_cpu_limit);
engine.register_fn("with_memory_limit", container_with_memory_limit);
engine.register_fn("with_restart_policy", container_with_restart_policy);
engine.register_fn("with_health_check", container_with_health_check);
engine.register_fn("with_ports", container_with_ports);
engine.register_fn("with_volumes", container_with_volumes);
engine.register_fn("with_envs", container_with_envs);
engine.register_fn("with_network_aliases", container_with_network_aliases);
engine.register_fn("with_memory_swap_limit", container_with_memory_swap_limit);
engine.register_fn("with_cpu_shares", container_with_cpu_shares);
engine.register_fn("with_health_check_options", container_with_health_check_options);
engine.register_fn("with_snapshotter", container_with_snapshotter);
engine.register_fn("with_detach", container_with_detach);
engine.register_fn("build", container_build);
engine.register_fn("start", container_start);
engine.register_fn("stop", container_stop);
engine.register_fn("remove", container_remove);
engine.register_fn("exec", container_exec);
engine.register_fn("logs", container_logs);
engine.register_fn("copy", container_copy);
// Register legacy container functions (for backward compatibility)
engine.register_fn("nerdctl_run", nerdctl_run);
engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name);
engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port);
engine.register_fn("new_run_options", new_run_options);
engine.register_fn("nerdctl_exec", nerdctl_exec);
engine.register_fn("nerdctl_copy", nerdctl_copy);
engine.register_fn("nerdctl_stop", nerdctl_stop);
engine.register_fn("nerdctl_remove", nerdctl_remove);
engine.register_fn("nerdctl_list", nerdctl_list);
engine.register_fn("nerdctl_logs", nerdctl_logs);
// Register image functions
engine.register_fn("nerdctl_images", nerdctl_images);
engine.register_fn("nerdctl_image_remove", nerdctl_image_remove);
engine.register_fn("nerdctl_image_push", nerdctl_image_push);
engine.register_fn("nerdctl_image_tag", nerdctl_image_tag);
engine.register_fn("nerdctl_image_pull", nerdctl_image_pull);
engine.register_fn("nerdctl_image_commit", nerdctl_image_commit);
engine.register_fn("nerdctl_image_build", nerdctl_image_build);
Ok(())
}
/// Register Nerdctl module types with the Rhai engine
fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register Container type
engine.register_type_with_name::<Container>("NerdctlContainer");
// Register getters for Container properties
engine.register_get("name", |container: &mut Container| container.name.clone());
engine.register_get("container_id", |container: &mut Container| {
match &container.container_id {
Some(id) => id.clone(),
None => "".to_string(),
}
});
engine.register_get("image", |container: &mut Container| {
match &container.image {
Some(img) => img.clone(),
None => "".to_string(),
}
});
engine.register_get("ports", |container: &mut Container| {
let mut array = Array::new();
for port in &container.ports {
array.push(Dynamic::from(port.clone()));
}
array
});
engine.register_get("volumes", |container: &mut Container| {
let mut array = Array::new();
for volume in &container.volumes {
array.push(Dynamic::from(volume.clone()));
}
array
});
engine.register_get("detach", |container: &mut Container| container.detach);
// Register Image type and methods
engine.register_type_with_name::<Image>("NerdctlImage");
// Register getters for Image properties
engine.register_get("id", |img: &mut Image| img.id.clone());
engine.register_get("repository", |img: &mut Image| img.repository.clone());
engine.register_get("tag", |img: &mut Image| img.tag.clone());
engine.register_get("size", |img: &mut Image| img.size.clone());
engine.register_get("created", |img: &mut Image| img.created.clone());
Ok(())
}

300
virt/src/rhai/rfs.rs Normal file
View File

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