feat: Migrate SAL to Cargo workspace
- 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:
@@ -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;
|
@@ -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};
|
||||
|
@@ -94,4 +94,4 @@ pub struct ResourceUsage {
|
||||
pub block_output: String,
|
||||
/// PIDs
|
||||
pub pids: String,
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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::*;
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(())
|
||||
}
|
||||
|
||||
|
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
@@ -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()));
|
||||
|
Reference in New Issue
Block a user