feat: Migrate SAL to Cargo workspace
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- Migrate individual modules to independent crates
- Refactor dependencies for improved modularity
- Update build system and testing infrastructure
- Update documentation to reflect new structure
This commit is contained in:
Mahmoud-Emad
2025-06-24 12:39:18 +03:00
parent 8012a66250
commit e125bb6511
54 changed files with 1196 additions and 1582 deletions

View File

@@ -1,13 +1,13 @@
mod containers;
mod images;
mod cmd;
mod builder;
mod content;
mod cmd;
mod containers;
#[cfg(test)]
mod containers_test;
mod content;
mod images;
use std::fmt;
use std::error::Error;
use std::fmt;
use std::io;
/// Error type for buildah operations
@@ -28,7 +28,9 @@ pub enum BuildahError {
impl fmt::Display for BuildahError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildahError::CommandExecutionFailed(e) => write!(f, "Failed to execute buildah command: {}", e),
BuildahError::CommandExecutionFailed(e) => {
write!(f, "Failed to execute buildah command: {}", e)
}
BuildahError::CommandFailed(e) => write!(f, "Buildah command failed: {}", e),
BuildahError::JsonParseError(e) => write!(f, "Failed to parse JSON: {}", e),
BuildahError::ConversionError(e) => write!(f, "Conversion error: {}", e),
@@ -49,9 +51,9 @@ impl Error for BuildahError {
pub use builder::Builder;
// Re-export existing functions for backward compatibility
pub use cmd::*;
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
pub use containers::*;
pub use content::ContentOperations;
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
pub use images::*;
pub use cmd::*;
pub use content::ContentOperations;

View File

@@ -1,24 +1,24 @@
//! # SAL Virt Package
//!
//!
//! The `sal-virt` package provides comprehensive virtualization and containerization tools
//! for building, managing, and deploying containers and filesystem layers.
//!
//!
//! ## Features
//!
//!
//! - **Buildah**: OCI/Docker image building with builder pattern API
//! - **Nerdctl**: Container lifecycle management with containerd
//! - **RFS**: Remote filesystem mounting and layer management
//! - **Cross-Platform**: Works across Windows, macOS, and Linux
//! - **Rhai Integration**: Full support for Rhai scripting language
//! - **Error Handling**: Comprehensive error types and handling
//!
//!
//! ## Modules
//!
//!
//! - [`buildah`]: Container image building with Buildah
//! - [`nerdctl`]: Container management with Nerdctl
//! - [`rfs`]: Remote filesystem operations
//!
//! This package depends on `sal-process` for command execution and `sal-os` for
//!
//! This package depends on `sal-process` for command execution and `sal-os` for
//! filesystem operations.
pub mod buildah;
@@ -28,6 +28,6 @@ pub mod rfs;
pub mod rhai;
// Re-export main types and functions for convenience
pub use buildah::{Builder, BuildahError, ContentOperations};
pub use nerdctl::{Container, NerdctlError, HealthCheck, ContainerStatus};
pub use rfs::{RfsBuilder, PackBuilder, RfsError, Mount, MountType, StoreSpec};
pub use buildah::{BuildahError, Builder, ContentOperations};
pub use nerdctl::{Container, ContainerStatus, HealthCheck, NerdctlError};
pub use rfs::{Mount, MountType, PackBuilder, RfsBuilder, RfsError, StoreSpec};

View File

@@ -94,4 +94,4 @@ pub struct ResourceUsage {
pub block_output: String,
/// PIDs
pub pids: String,
}
}

View File

@@ -13,28 +13,28 @@ impl HealthCheck {
start_period: None,
}
}
/// Set the interval between health checks
pub fn with_interval(mut self, interval: &str) -> Self {
self.interval = Some(interval.to_string());
self
}
/// Set the timeout for health checks
pub fn with_timeout(mut self, timeout: &str) -> Self {
self.timeout = Some(timeout.to_string());
self
}
/// Set the number of retries for health checks
pub fn with_retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
/// Set the start period for health checks
pub fn with_start_period(mut self, start_period: &str) -> Self {
self.start_period = Some(start_period.to_string());
self
}
}
}

View File

@@ -1,27 +1,27 @@
// File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/health_check_script.rs
use std::fs;
use std::path::Path;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
/// Handles health check scripts for containers
///
///
/// This module provides functionality to create and manage health check scripts
/// for containers, allowing for more complex health checks than simple commands.
/// Converts a health check command or script to a usable command
///
///
/// If the input is a single-line command, it is returned as is.
/// If the input is a multi-line script, it is written to a file in the
/// /root/hero/var/containers directory and the path to that file is returned.
///
///
/// # Arguments
///
///
/// * `cmd` - The command or script to convert
/// * `container_name` - The name of the container, used to create a unique script name
///
///
/// # Returns
///
///
/// * `String` - The command to use for the health check
pub fn prepare_health_check_command(cmd: &str, container_name: &str) -> String {
// If the command is a multiline script, write it to a file
@@ -32,16 +32,16 @@ pub fn prepare_health_check_command(cmd: &str, container_name: &str) -> String {
// If we can't create the directory, just use the command as is
return cmd.to_string();
}
// Create a unique filename based on container name
let script_path = format!("{}/healthcheck_{}.sh", dir_path, container_name);
// Write the script to the file
if let Err(_) = fs::write(&script_path, cmd) {
// If we can't write the file, just use the command as is
return cmd.to_string();
}
// Make the script executable
if let Ok(metadata) = fs::metadata(&script_path) {
let mut perms = metadata.permissions();
@@ -54,7 +54,7 @@ pub fn prepare_health_check_command(cmd: &str, container_name: &str) -> String {
// If we can't get metadata, just use the script path with sh
return format!("sh {}", script_path);
}
// Use the script path as the command
script_path
} else {
@@ -64,16 +64,16 @@ pub fn prepare_health_check_command(cmd: &str, container_name: &str) -> String {
}
/// Cleans up health check scripts for a container
///
///
/// # Arguments
///
///
/// * `container_name` - The name of the container whose health check scripts should be cleaned up
pub fn cleanup_health_check_scripts(container_name: &str) {
let dir_path = "/root/hero/var/containers";
let script_path = format!("{}/healthcheck_{}.sh", dir_path, container_name);
// Try to remove the script file if it exists
if Path::new(&script_path).exists() {
let _ = fs::remove_file(script_path);
}
}
}

View File

@@ -1,17 +1,17 @@
mod images;
mod cmd;
mod container_types;
mod container;
mod container_builder;
mod health_check;
mod health_check_script;
mod container_operations;
mod container_functions;
mod container_operations;
#[cfg(test)]
mod container_test;
mod container_types;
mod health_check;
mod health_check_script;
mod images;
use std::fmt;
use std::error::Error;
use std::fmt;
use std::io;
/// Error type for nerdctl operations
@@ -32,7 +32,9 @@ pub enum NerdctlError {
impl fmt::Display for NerdctlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NerdctlError::CommandExecutionFailed(e) => write!(f, "Failed to execute nerdctl command: {}", e),
NerdctlError::CommandExecutionFailed(e) => {
write!(f, "Failed to execute nerdctl command: {}", e)
}
NerdctlError::CommandFailed(e) => write!(f, "Nerdctl command failed: {}", e),
NerdctlError::JsonParseError(e) => write!(f, "Failed to parse JSON: {}", e),
NerdctlError::ConversionError(e) => write!(f, "Conversion error: {}", e),
@@ -50,8 +52,8 @@ impl Error for NerdctlError {
}
}
pub use images::*;
pub use cmd::*;
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
pub use container_functions::*;
pub use health_check_script::*;
pub use container_types::{Container, ContainerStatus, HealthCheck, ResourceUsage};
pub use health_check_script::*;
pub use images::*;

View File

@@ -1,5 +1,5 @@
use std::fmt;
use std::error::Error;
use std::fmt;
/// Error types for RFS operations
#[derive(Debug)]
@@ -40,4 +40,4 @@ impl From<std::io::Error> for RfsError {
fn from(error: std::io::Error) -> Self {
RfsError::Other(format!("IO error: {}", error))
}
}
}

View File

@@ -1,14 +1,14 @@
mod builder;
mod cmd;
mod error;
mod mount;
mod pack;
mod builder;
mod types;
pub use builder::{PackBuilder, RfsBuilder};
pub use error::RfsError;
pub use builder::{RfsBuilder, PackBuilder};
pub use mount::{get_mount_info, list_mounts, unmount, unmount_all};
pub use pack::{list_contents, pack_directory, unpack, verify};
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

View File

@@ -1,8 +1,4 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::Mount,
};
use super::{cmd::execute_rfs_command, error::RfsError, types::Mount};
/// List all mounted filesystems
///
@@ -12,38 +8,40 @@ use super::{
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())),
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) => {
@@ -54,10 +52,10 @@ pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
}
}
options_vec
},
}
None => Vec::new(), // Empty vector if no options found
};
// Create Mount struct and add to vector
mounts.push(Mount {
id,
@@ -67,15 +65,16 @@ pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
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)))
}
Err(e) => Err(RfsError::ListFailed(format!(
"Failed to parse mount list JSON: {}",
e
))),
}
}
@@ -91,12 +90,15 @@ pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
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)));
return Err(RfsError::UnmountFailed(format!(
"Failed to unmount {}: {}",
target, result.stderr
)));
}
Ok(())
}
@@ -108,12 +110,15 @@ pub fn unmount(target: &str) -> Result<(), RfsError> {
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)));
return Err(RfsError::UnmountFailed(format!(
"Failed to unmount all filesystems: {}",
result.stderr
)));
}
Ok(())
}
@@ -129,14 +134,14 @@ pub fn unmount_all() -> Result<(), RfsError> {
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)))
}
}

View File

@@ -1,9 +1,4 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::StoreSpec,
builder::PackBuilder,
};
use super::{builder::PackBuilder, cmd::execute_rfs_command, error::RfsError, types::StoreSpec};
/// Pack a directory into a filesystem layer
///
@@ -16,15 +11,19 @@ use super::{
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec]) -> Result<(), RfsError> {
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()
}
@@ -42,12 +41,15 @@ pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec])
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)));
return Err(RfsError::Other(format!(
"Failed to unpack {}: {}",
input, result.stderr
)));
}
Ok(())
}
@@ -63,12 +65,15 @@ pub fn unpack(input: &str, directory: &str) -> Result<(), RfsError> {
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)));
return Err(RfsError::Other(format!(
"Failed to list contents of {}: {}",
input, result.stderr
)));
}
Ok(result.stdout)
}
@@ -84,7 +89,7 @@ pub fn list_contents(input: &str) -> Result<String, RfsError> {
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,
@@ -92,9 +97,12 @@ pub fn verify(input: &str) -> Result<bool, RfsError> {
if result.stderr.contains("verification failed") {
return Ok(false);
}
return Err(RfsError::Other(format!("Failed to verify {}: {}", input, result.stderr)));
return Err(RfsError::Other(format!(
"Failed to verify {}: {}",
input, result.stderr
)));
}
Ok(true)
}
}

View File

@@ -41,7 +41,7 @@ impl MountType {
MountType::Custom(s) => s.clone(),
}
}
/// Create a MountType from a string
pub fn from_string(s: &str) -> Self {
match s.to_lowercase().as_str() {
@@ -102,16 +102,17 @@ impl StoreSpec {
/// * `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
let options: Vec<String> = self
.options
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
result.push_str(&options.join(","));
}
result
}
}
}

View File

@@ -21,13 +21,13 @@ pub mod rfs;
pub fn register_virt_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register Buildah module functions
buildah::register_bah_module(engine)?;
// Register Nerdctl module functions
nerdctl::register_nerdctl_module(engine)?;
// Register RFS module functions
rfs::register_rfs_module(engine)?;
Ok(())
}

View File

@@ -2,12 +2,14 @@
//!
//! 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 crate::nerdctl::{self, Container, Image, NerdctlError};
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
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>> {
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 {
@@ -27,7 +29,6 @@ fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T,
format!("Nerdctl error: {}. This is an unexpected error.", msg)
},
};
Box::new(EvalAltResult::ErrorRuntime(
error_message.into(),
rhai::Position::NONE
@@ -160,7 +161,7 @@ pub fn container_with_health_check_options(
interval: Option<&str>,
timeout: Option<&str>,
retries: Option<i64>,
start_period: Option<&str>
start_period: Option<&str>,
) -> Container {
// Convert i64 to u32 for retries
let retries_u32 = retries.map(|r| r as u32);
@@ -184,41 +185,49 @@ pub fn container_with_detach(container: Container, detach: bool) -> Container {
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 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);
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));
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().");
@@ -227,12 +236,12 @@ pub fn container_build(container: Container) -> Result<Container, Box<EvalAltRes
} 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
}
_ => err,
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
@@ -246,17 +255,20 @@ pub fn container_build(container: Container) -> Result<Container, Box<EvalAltRes
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());
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 {
@@ -270,21 +282,23 @@ pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<E
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);
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
}
_ => err,
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
@@ -301,7 +315,10 @@ pub fn container_remove(container: &mut Container) -> Result<CommandResult, Box<
}
/// Execute a command in the Container
pub fn container_exec(container: &mut Container, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
pub fn container_exec(
container: &mut Container,
command: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.exec(command))
}
@@ -309,29 +326,34 @@ pub fn container_exec(container: &mut Container, command: &str) -> Result<Comman
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());
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)
},
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)
);
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>> {
pub fn container_copy(
container: &mut Container,
source: &str,
dest: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.copy(source, dest))
}
@@ -362,7 +384,11 @@ pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result<CommandResult, B
}
/// Run a container with a port mapping
pub fn nerdctl_run_with_port(image: &str, name: &str, port: &str) -> Result<CommandResult, Box<EvalAltResult>> {
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))
}
@@ -430,7 +456,10 @@ pub fn nerdctl_image_remove(image: &str) -> Result<CommandResult, Box<EvalAltRes
/// Wrapper for nerdctl::image_push
///
/// Push an image to a registry.
pub fn nerdctl_image_push(image: &str, destination: &str) -> Result<CommandResult, Box<EvalAltResult>> {
pub fn nerdctl_image_push(
image: &str,
destination: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination))
}
@@ -451,14 +480,20 @@ pub fn nerdctl_image_pull(image: &str) -> Result<CommandResult, Box<EvalAltResul
/// 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>> {
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>> {
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))
}
@@ -474,11 +509,11 @@ pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResul
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);
@@ -496,7 +531,10 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
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_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);
@@ -506,7 +544,7 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
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);
@@ -518,7 +556,7 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
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);
@@ -527,7 +565,7 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
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(())
}
@@ -535,15 +573,16 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
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 {
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(),
@@ -565,16 +604,16 @@ fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>>
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(())
}
}

View File

@@ -4,7 +4,7 @@ use sal_virt::nerdctl::{Container, NerdctlError};
fn test_container_creation() {
// Test creating a new container
let result = Container::new("test-container");
match result {
Ok(container) => {
assert_eq!(container.name, "test-container");
@@ -25,7 +25,7 @@ fn test_container_creation() {
fn test_container_from_image() {
// Test creating a container from an image
let result = Container::from_image("test-container", "alpine:latest");
match result {
Ok(container) => {
assert_eq!(container.name, "test-container");
@@ -45,7 +45,7 @@ fn test_container_from_image() {
#[test]
fn test_container_builder_pattern() {
let result = Container::from_image("test-app", "nginx:alpine");
match result {
Ok(container) => {
// Test builder pattern methods
@@ -60,18 +60,27 @@ fn test_container_builder_pattern() {
.with_restart_policy("always")
.with_health_check("curl -f http://localhost/ || exit 1")
.with_detach(true);
// Verify configuration
assert_eq!(configured_container.name, "test-app");
assert_eq!(configured_container.image, Some("nginx:alpine".to_string()));
assert_eq!(configured_container.ports, vec!["8080:80"]);
assert_eq!(configured_container.volumes, vec!["/host/data:/app/data"]);
assert_eq!(configured_container.env_vars.get("ENV_VAR"), Some(&"test_value".to_string()));
assert_eq!(configured_container.network, Some("test-network".to_string()));
assert_eq!(
configured_container.env_vars.get("ENV_VAR"),
Some(&"test_value".to_string())
);
assert_eq!(
configured_container.network,
Some("test-network".to_string())
);
assert_eq!(configured_container.network_aliases, vec!["app-alias"]);
assert_eq!(configured_container.cpu_limit, Some("0.5".to_string()));
assert_eq!(configured_container.memory_limit, Some("512m".to_string()));
assert_eq!(configured_container.restart_policy, Some("always".to_string()));
assert_eq!(
configured_container.restart_policy,
Some("always".to_string())
);
assert!(configured_container.health_check.is_some());
assert!(configured_container.detach);
}
@@ -88,17 +97,15 @@ fn test_container_builder_pattern() {
#[test]
fn test_container_reset() {
let result = Container::from_image("test-container", "alpine:latest");
match result {
Ok(container) => {
// Configure the container
let configured = container
.with_port("8080:80")
.with_env("TEST", "value");
let configured = container.with_port("8080:80").with_env("TEST", "value");
// Reset should clear configuration but keep name and image
let reset_container = configured.reset();
assert_eq!(reset_container.name, "test-container");
assert_eq!(reset_container.image, Some("alpine:latest".to_string()));
assert!(reset_container.ports.is_empty());
@@ -120,7 +127,7 @@ fn test_nerdctl_error_types() {
// Test that our error types work correctly
let error = NerdctlError::CommandFailed("Test error".to_string());
assert!(matches!(error, NerdctlError::CommandFailed(_)));
let error_msg = format!("{}", error);
assert!(error_msg.contains("Test error"));
}
@@ -128,7 +135,7 @@ fn test_nerdctl_error_types() {
#[test]
fn test_container_multiple_ports_and_volumes() {
let result = Container::from_image("multi-config", "nginx:latest");
match result {
Ok(container) => {
let configured = container
@@ -138,15 +145,19 @@ fn test_container_multiple_ports_and_volumes() {
.with_volume("/data2:/app/data2")
.with_env("VAR1", "value1")
.with_env("VAR2", "value2");
assert_eq!(configured.ports.len(), 2);
assert!(configured.ports.contains(&"8080:80".to_string()));
assert!(configured.ports.contains(&"8443:443".to_string()));
assert_eq!(configured.volumes.len(), 2);
assert!(configured.volumes.contains(&"/data1:/app/data1".to_string()));
assert!(configured.volumes.contains(&"/data2:/app/data2".to_string()));
assert!(configured
.volumes
.contains(&"/data1:/app/data1".to_string()));
assert!(configured
.volumes
.contains(&"/data2:/app/data2".to_string()));
assert_eq!(configured.env_vars.len(), 2);
assert_eq!(configured.env_vars.get("VAR1"), Some(&"value1".to_string()));
assert_eq!(configured.env_vars.get("VAR2"), Some(&"value2".to_string()));