diff --git a/buildah_builder_implementation_plan.md b/buildah_builder_implementation_plan.md deleted file mode 100644 index 08f6585..0000000 --- a/buildah_builder_implementation_plan.md +++ /dev/null @@ -1,752 +0,0 @@ -# Buildah Builder Implementation Plan - -## Introduction - -This document outlines the plan for changing the buildah interface in the `@src/virt/buildah` module to use a builder object pattern. The current implementation uses standalone functions, which makes the interface less clear and harder to use. The new implementation will use a builder object with methods, which will make the interface more intuitive and easier to use. - -## Current Architecture - -The current buildah implementation has: -- Standalone functions in the buildah module (from, run, images, etc.) -- Functions that operate on container IDs passed as parameters -- No state maintained between function calls -- Rhai wrappers that expose these functions to Rhai scripts - -Example of current usage: -```rust -// Create a container -let result = buildah::from("fedora:latest")?; -let container_id = result.stdout.trim(); - -// Run a command in the container -buildah::run(container_id, "dnf install -y nginx")?; - -// Copy a file into the container -buildah::bah_copy(container_id, "./example.conf", "/etc/example.conf")?; - -// Commit the container to create a new image -buildah::bah_commit(container_id, "my-nginx:latest")?; -``` - -## Proposed Architecture - -We'll change to a builder object pattern where: -- A `Builder` struct is created with a `new()` method that takes a name and image -- All operations (including those not specific to a container) are methods on the Builder -- The Builder maintains state (like container ID) between method calls -- Methods return operation results (CommandResult or other types) for error handling -- Rhai wrappers expose the Builder and its methods to Rhai scripts - -Example of proposed usage: -```rust -// Create a builder -let builder = Builder::new("my-container", "fedora:latest")?; - -// Run a command in the container -builder.run("dnf install -y nginx")?; - -// Copy a file into the container -builder.copy("./example.conf", "/etc/example.conf")?; - -// Commit the container to create a new image -builder.commit("my-nginx:latest")?; -``` - -## Class Diagram - -```mermaid -classDiagram - class Builder { - -String name - -String container_id - -String image - +new(name: String, image: String) -> Result - +run(command: String) -> Result - +run_with_isolation(command: String, isolation: String) -> Result - +add(source: String, dest: String) -> Result - +copy(source: String, dest: String) -> Result - +commit(image_name: String) -> Result - +remove() -> Result - +config(options: HashMap) -> Result - } - - class BuilderStatic { - +images() -> Result, BuildahError> - +image_remove(image: String) -> Result - +image_pull(image: String, tls_verify: bool) -> Result - +image_push(image: String, destination: String, tls_verify: bool) -> Result - +image_tag(image: String, new_name: String) -> Result - +image_commit(container: String, image_name: String, format: Option, squash: bool, rm: bool) -> Result - +build(tag: Option, context_dir: String, file: String, isolation: Option) -> Result - } - - Builder --|> BuilderStatic : Static methods -``` - -## Implementation Plan - -### Step 1: Create the Builder Struct - -1. Create a new file `src/virt/buildah/builder.rs` -2. Define the Builder struct with fields for name, container_id, and image -3. Implement the new() method that creates a container from the image and returns a Builder instance -4. Implement methods for all container operations (run, add, copy, commit, etc.) -5. Implement methods for all image operations (images, image_remove, image_pull, etc.) - -### Step 2: Update the Module Structure - -1. Update `src/virt/buildah/mod.rs` to include the new builder module -2. Re-export the Builder struct and its methods -3. Keep the existing functions for backward compatibility (marked as deprecated) - -### Step 3: Update the Rhai Wrapper - -1. Update `src/rhai/buildah.rs` to register the Builder type with the Rhai engine -2. Register all Builder methods with the Rhai engine -3. Create Rhai-friendly constructor for the Builder -4. Update the existing function wrappers to use the new Builder (or keep them for backward compatibility) - -### Step 4: Update Examples and Tests - -1. Update `examples/buildah.rs` to use the new Builder pattern -2. Update `rhaiexamples/04_buildah_operations.rhai` to use the new Builder pattern -3. Update any tests to use the new Builder pattern - -## Detailed Implementation - -### 1. Builder Struct Definition - -```rust -// src/virt/buildah/builder.rs -pub struct Builder { - name: String, - container_id: Option, - image: String, -} - -impl Builder { - pub fn new(name: &str, image: &str) -> Result { - let result = execute_buildah_command(&["from", "--name", name, image])?; - let container_id = result.stdout.trim().to_string(); - - Ok(Self { - name: name.to_string(), - container_id: Some(container_id), - image: image.to_string(), - }) - } - - // Container methods - pub fn run(&self, command: &str) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["run", container_id, "sh", "-c", command]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn run_with_isolation(&self, command: &str, isolation: &str) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["run", "--isolation", isolation, container_id, "sh", "-c", command]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn copy(&self, source: &str, dest: &str) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["copy", container_id, source, dest]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn add(&self, source: &str, dest: &str) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["add", container_id, source, dest]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn commit(&self, image_name: &str) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["commit", container_id, image_name]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn remove(&self) -> Result { - if let Some(container_id) = &self.container_id { - execute_buildah_command(&["rm", container_id]) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - pub fn config(&self, options: HashMap) -> Result { - if let Some(container_id) = &self.container_id { - let mut args_owned: Vec = Vec::new(); - args_owned.push("config".to_string()); - - // Process options map - for (key, value) in options.iter() { - let option_name = format!("--{}", key); - args_owned.push(option_name); - args_owned.push(value.clone()); - } - - args_owned.push(container_id.clone()); - - // Convert Vec to Vec<&str> for execute_buildah_command - let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); - - execute_buildah_command(&args) - } else { - Err(BuildahError::Other("No container ID available".to_string())) - } - } - - // Static methods - pub fn images() -> Result, BuildahError> { - // Implementation from current images() function - let result = execute_buildah_command(&["images", "--json"])?; - - // Try to parse the JSON output - match serde_json::from_str::(&result.stdout) { - Ok(json) => { - if let serde_json::Value::Array(images_json) = json { - let mut images = Vec::new(); - - for image_json in images_json { - // Extract image ID - let id = match image_json.get("id").and_then(|v| v.as_str()) { - Some(id) => id.to_string(), - None => return Err(BuildahError::ConversionError("Missing image ID".to_string())), - }; - - // Extract image names - let names = match image_json.get("names").and_then(|v| v.as_array()) { - Some(names_array) => { - let mut names_vec = Vec::new(); - for name_value in names_array { - if let Some(name_str) = name_value.as_str() { - names_vec.push(name_str.to_string()); - } - } - names_vec - }, - None => Vec::new(), // Empty vector if no names found - }; - - // Extract image size - let size = match image_json.get("size").and_then(|v| v.as_str()) { - Some(size) => size.to_string(), - None => "Unknown".to_string(), // Default value if size not found - }; - - // Extract creation timestamp - let created = match image_json.get("created").and_then(|v| v.as_str()) { - Some(created) => created.to_string(), - None => "Unknown".to_string(), // Default value if created not found - }; - - // Create Image struct and add to vector - images.push(Image { - id, - names, - size, - created, - }); - } - - Ok(images) - } else { - Err(BuildahError::JsonParseError("Expected JSON array".to_string())) - } - }, - Err(e) => { - Err(BuildahError::JsonParseError(format!("Failed to parse image list JSON: {}", e))) - } - } - } - - pub fn image_remove(image: &str) -> Result { - execute_buildah_command(&["rmi", image]) - } - - pub fn image_pull(image: &str, tls_verify: bool) -> Result { - let mut args = vec!["pull"]; - - if !tls_verify { - args.push("--tls-verify=false"); - } - - args.push(image); - - execute_buildah_command(&args) - } - - pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result { - let mut args = vec!["push"]; - - if !tls_verify { - args.push("--tls-verify=false"); - } - - args.push(image); - args.push(destination); - - execute_buildah_command(&args) - } - - pub fn image_tag(image: &str, new_name: &str) -> Result { - execute_buildah_command(&["tag", image, new_name]) - } - - pub fn image_commit(container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result { - let mut args = vec!["commit"]; - - if let Some(format_str) = format { - args.push("--format"); - args.push(format_str); - } - - if squash { - args.push("--squash"); - } - - if rm { - args.push("--rm"); - } - - args.push(container); - args.push(image_name); - - execute_buildah_command(&args) - } - - pub fn build(tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>) -> Result { - let mut args = Vec::new(); - args.push("build"); - - if let Some(tag_value) = tag { - args.push("-t"); - args.push(tag_value); - } - - if let Some(isolation_value) = isolation { - args.push("--isolation"); - args.push(isolation_value); - } - - args.push("-f"); - args.push(file); - - args.push(context_dir); - - execute_buildah_command(&args) - } -} -``` - -### 2. Updated Module Structure - -```rust -// src/virt/buildah/mod.rs -mod containers; -mod images; -mod cmd; -mod builder; -#[cfg(test)] -mod containers_test; - -use std::fmt; -use std::error::Error; -use std::io; - -/// Error type for buildah operations -#[derive(Debug)] -pub enum BuildahError { - /// The buildah command failed to execute - CommandExecutionFailed(io::Error), - /// The buildah command executed but returned an error - CommandFailed(String), - /// Failed to parse JSON output - JsonParseError(String), - /// Failed to convert data - ConversionError(String), - /// Generic error - Other(String), -} - -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::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), - BuildahError::Other(e) => write!(f, "{}", e), - } - } -} - -impl Error for BuildahError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - BuildahError::CommandExecutionFailed(e) => Some(e), - _ => None, - } - } -} - -// Re-export the Builder -pub use builder::Builder; - -// Re-export existing functions for backward compatibility -#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")] -pub use containers::*; -#[deprecated(since = "0.2.0", note = "Use Builder methods instead")] -pub use images::*; -pub use cmd::*; -``` - -### 3. Rhai Wrapper Changes - -```rust -// src/rhai/buildah.rs -//! Rhai wrappers for Buildah module functions -//! -//! This module provides Rhai wrappers for the functions in the Buildah module. - -use rhai::{Engine, EvalAltResult, Array, Dynamic, Map}; -use std::collections::HashMap; -use crate::virt::buildah::{self, BuildahError, Image, Builder}; -use crate::process::CommandResult; - -/// Register Buildah module functions with the Rhai engine -/// -/// # Arguments -/// -/// * `engine` - The Rhai engine to register the functions with -/// -/// # Returns -/// -/// * `Result<(), Box>` - Ok if registration was successful, Err otherwise -pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box> { - // 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: &mut Builder, command: &str| -> Result> { - bah_error_to_rhai_error(builder.run(command)) - }); - - engine.register_fn("run_with_isolation", |builder: &mut Builder, command: &str, isolation: &str| -> Result> { - bah_error_to_rhai_error(builder.run_with_isolation(command, isolation)) - }); - - engine.register_fn("copy", |builder: &mut Builder, source: &str, dest: &str| -> Result> { - bah_error_to_rhai_error(builder.copy(source, dest)) - }); - - engine.register_fn("add", |builder: &mut Builder, source: &str, dest: &str| -> Result> { - bah_error_to_rhai_error(builder.add(source, dest)) - }); - - engine.register_fn("commit", |builder: &mut Builder, image_name: &str| -> Result> { - bah_error_to_rhai_error(builder.commit(image_name)) - }); - - engine.register_fn("remove", |builder: &mut Builder| -> Result> { - bah_error_to_rhai_error(builder.remove()) - }); - - engine.register_fn("config", |builder: &mut Builder, options: Map| -> Result> { - // Convert Rhai Map to Rust HashMap - let config_options = convert_map_to_hashmap(options)?; - bah_error_to_rhai_error(builder.config(config_options)) - }); - - // Register Builder static methods - engine.register_fn("images", |_: &mut Builder| -> Result> { - let images = bah_error_to_rhai_error(Builder::images())?; - - // Convert Vec to Rhai Array - let mut array = Array::new(); - for image in images { - array.push(Dynamic::from(image)); - } - - Ok(array) - }); - - engine.register_fn("image_remove", |_: &mut Builder, image: &str| -> Result> { - bah_error_to_rhai_error(Builder::image_remove(image)) - }); - - engine.register_fn("image_pull", |_: &mut Builder, image: &str, tls_verify: bool| -> Result> { - bah_error_to_rhai_error(Builder::image_pull(image, tls_verify)) - }); - - engine.register_fn("image_push", |_: &mut Builder, image: &str, destination: &str, tls_verify: bool| -> Result> { - bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify)) - }); - - engine.register_fn("image_tag", |_: &mut Builder, image: &str, new_name: &str| -> Result> { - bah_error_to_rhai_error(Builder::image_tag(image, new_name)) - }); - - // Register legacy functions for backward compatibility - register_legacy_functions(engine)?; - - Ok(()) -} - -/// Register Buildah module types with the Rhai engine -fn register_bah_types(engine: &mut Engine) -> Result<(), Box> { - // Register Builder type - engine.register_type_with_name::("BuildahBuilder"); - - // Register getters for Builder properties - engine.register_get("container_id", |builder: &mut Builder| { - match builder.container_id() { - Some(id) => id.clone(), - None => "".to_string(), - } - }); - - engine.register_get("name", |builder: &mut Builder| builder.name().to_string()); - engine.register_get("image", |builder: &mut Builder| builder.image().to_string()); - - // Register Image type and methods (same as before) - engine.register_type_with_name::("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() { - "".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(()) -} - -/// Register legacy functions for backward compatibility -fn register_legacy_functions(engine: &mut Engine) -> Result<(), Box> { - // Register container functions - engine.register_fn("bah_from", bah_from_legacy); - engine.register_fn("bah_run", bah_run_legacy); - engine.register_fn("bah_run_with_isolation", bah_run_with_isolation_legacy); - engine.register_fn("bah_copy", bah_copy_legacy); - engine.register_fn("bah_add", bah_add_legacy); - engine.register_fn("bah_commit", bah_commit_legacy); - engine.register_fn("bah_remove", bah_remove_legacy); - engine.register_fn("bah_list", bah_list_legacy); - engine.register_fn("bah_build", bah_build_with_options_legacy); - engine.register_fn("bah_new_build_options", new_build_options); - - // Register image functions - engine.register_fn("bah_images", images_legacy); - engine.register_fn("bah_image_remove", image_remove_legacy); - engine.register_fn("bah_image_push", image_push_legacy); - engine.register_fn("bah_image_tag", image_tag_legacy); - engine.register_fn("bah_image_pull", image_pull_legacy); - engine.register_fn("bah_image_commit", image_commit_with_options_legacy); - engine.register_fn("bah_new_commit_options", new_commit_options); - engine.register_fn("bah_config", config_with_options_legacy); - engine.register_fn("bah_new_config_options", new_config_options); - - Ok(()) -} - -// Helper functions for error conversion -fn bah_error_to_rhai_error(result: Result) -> Result> { - 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, Box> { - let mut config_options = HashMap::::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> { - bah_error_to_rhai_error(Builder::new(name, image)) -} - -// Legacy function implementations (for backward compatibility) -// These would call the new Builder methods internally -// ... -``` - -### 4. Example Updates - -#### Rust Example - -```rust -// examples/buildah.rs -//! Example usage of the buildah module -//! -//! This file demonstrates how to use the buildah module to perform -//! common container operations like creating containers, running commands, -//! and managing images. - -use sal::virt::buildah::{Builder, BuildahError}; -use std::collections::HashMap; - -/// Run a complete buildah workflow example -pub fn run_buildah_example() -> Result<(), BuildahError> { - println!("Starting buildah example workflow..."); - - // Step 1: Create a container from an image using the Builder - println!("\n=== Creating container from fedora:latest ==="); - let builder = Builder::new("my-fedora-container", "fedora:latest")?; - println!("Created container: {}", builder.container_id().unwrap_or(&"unknown".to_string())); - - // Step 2: Run a command in the container - println!("\n=== Installing nginx in container ==="); - // Use chroot isolation to avoid BPF issues - let install_result = builder.run_with_isolation("dnf install -y nginx", "chroot")?; - println!("{:#?}", install_result); - println!("Installation output: {}", install_result.stdout); - - // Step 3: Copy a file into the container - println!("\n=== Copying configuration file to container ==="); - builder.copy("./example.conf", "/etc/example.conf")?; - - // Step 4: Configure container metadata - println!("\n=== Configuring container metadata ==="); - let mut config_options = HashMap::new(); - config_options.insert("port".to_string(), "80".to_string()); - config_options.insert("label".to_string(), "maintainer=example@example.com".to_string()); - config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string()); - builder.config(config_options)?; - println!("Container configured"); - - // Step 5: Commit the container to create a new image - println!("\n=== Committing container to create image ==="); - let image_name = "my-nginx:latest"; - builder.commit(image_name)?; - println!("Created image: {}", image_name); - - // Step 6: List images to verify our new image exists - println!("\n=== Listing images ==="); - let images = Builder::images()?; - println!("Found {} images:", images.len()); - for image in images { - println!(" ID: {}", image.id); - println!(" Names: {}", image.names.join(", ")); - println!(" Size: {}", image.size); - println!(" Created: {}", image.created); - println!(); - } - - // Step 7: Clean up (optional in a real workflow) - println!("\n=== Cleaning up ==="); - Builder::image_remove(image_name)?; - builder.remove()?; - - println!("\nBuildah example workflow completed successfully!"); - Ok(()) -} - -/// Demonstrate how to build an image from a Containerfile/Dockerfile -pub fn build_image_example() -> Result<(), BuildahError> { - println!("Building an image from a Containerfile..."); - - // Use the build function with tag, context directory, and isolation to avoid BPF issues - let result = Builder::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?; - - println!("Build output: {}", result.stdout); - println!("Image built successfully!"); - - Ok(()) -} - -/// Example of pulling and pushing images -pub fn registry_operations_example() -> Result<(), BuildahError> { - println!("Demonstrating registry operations..."); - - // Pull an image - println!("\n=== Pulling an image ==="); - Builder::image_pull("docker.io/library/alpine:latest", true)?; - println!("Image pulled successfully"); - - // Tag the image - println!("\n=== Tagging the image ==="); - Builder::image_tag("alpine:latest", "my-alpine:v1.0")?; - println!("Image tagged successfully"); - - // Push an image (this would typically go to a real registry) - // println!("\n=== Pushing an image (example only) ==="); - // println!("In a real scenario, you would push to a registry with:"); - // println!("Builder::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)"); - - Ok(()) -} - -/// Main function to run all examples -pub fn run_all_examples() -> Result<(), BuildahError> { - println!("=== BUILDAH MODULE EXAMPLES ===\n"); - - run_buildah_example()?; - build_image_example()?; - registry_operations_example()?; - - Ok(()) -} - -fn main() { - let _ = run_all_examples(); -} -``` - -#### Rhai Example - -```rhai -// rhaiexamples/04_buildah_operations.rhai -// Demonstrates container operations using SAL's buildah integration -// Note: This script requires buildah to be installed and may need root privileges - -// Check if buildah is installed -let buildah_exists = which("buildah"); -println(`Buildah exists: ${buildah_exists}`); - diff --git a/container_builder_implementation_plan.md b/container_builder_implementation_plan.md deleted file mode 100644 index 06b33d1..0000000 --- a/container_builder_implementation_plan.md +++ /dev/null @@ -1,238 +0,0 @@ -# Container Builder Implementation Plan - -## Overview - -This document outlines the plan for redesigning the nerdctl interface in the `src/virt/nerdctl` directory to use an object-oriented approach with a Container struct that supports method chaining for the builder pattern. This will replace the existing function-based approach while maintaining all current functionality. - -## Architecture - -```mermaid -classDiagram - class Container { - -String name - -String container_id - -String? image - -HashMap~String, String~ config - -Vec~String~ ports - -Vec~String~ volumes - -HashMap~String, String~ env_vars - -Option~String~ network - -Vec~String~ network_aliases - -Option~String~ cpu_limit - -Option~String~ memory_limit - -Option~String~ memory_swap_limit - -Option~String~ cpu_shares - -Option~String~ restart_policy - -Option~HealthCheck~ health_check - +new(name: &str) -> Result~Container, NerdctlError~ - +from_image(name: &str, image: &str) -> Result~Container, NerdctlError~ - +with_port(port: &str) -> Container - +with_ports(ports: &[&str]) -> Container - +with_volume(volume: &str) -> Container - +with_volumes(volumes: &[&str]) -> Container - +with_env(key: &str, value: &str) -> Container - +with_envs(env_map: &HashMap<&str, &str>) -> Container - +with_network(network: &str) -> Container - +with_network_alias(alias: &str) -> Container - +with_network_aliases(aliases: &[&str]) -> Container - +with_cpu_limit(cpus: &str) -> Container - +with_memory_limit(memory: &str) -> Container - +with_memory_swap_limit(memory_swap: &str) -> Container - +with_cpu_shares(shares: &str) -> Container - +with_restart_policy(policy: &str) -> Container - +with_health_check(cmd: &str) -> Container - +with_health_check_options(cmd: &str, interval: Option<&str>, timeout: Option<&str>, retries: Option, start_period: Option<&str>) -> Container - +with_snapshotter(snapshotter: &str) -> Container - +with_detach(detach: bool) -> Container - +build() -> Result~Container, NerdctlError~ - +start() -> Result~CommandResult, NerdctlError~ - +stop() -> Result~CommandResult, NerdctlError~ - +remove() -> Result~CommandResult, NerdctlError~ - +exec(command: &str) -> Result~CommandResult, NerdctlError~ - +copy(source: &str, dest: &str) -> Result~CommandResult, NerdctlError~ - +export(path: &str) -> Result~CommandResult, NerdctlError~ - +commit(image_name: &str) -> Result~CommandResult, NerdctlError~ - +status() -> Result~ContainerStatus, NerdctlError~ - +health_status() -> Result~String, NerdctlError~ - +resources() -> Result~ResourceUsage, NerdctlError~ - } - - class HealthCheck { - +String cmd - +Option~String~ interval - +Option~String~ timeout - +Option~u32~ retries - +Option~String~ start_period - } - - class ContainerStatus { - +String state - +String status - +String created - +String started - +Option~String~ health_status - +Option~String~ health_output - } - - class ResourceUsage { - +String cpu_usage - +String memory_usage - +String memory_limit - +String memory_percentage - +String network_input - +String network_output - +String block_input - +String block_output - +String pids - } - - class NerdctlError { - +CommandExecutionFailed(io::Error) - +CommandFailed(String) - +JsonParseError(String) - +ConversionError(String) - +Other(String) - } - - Container --> ContainerStatus : returns - Container --> ResourceUsage : returns - Container --> HealthCheck : contains - Container --> NerdctlError : may throw -``` - -## Implementation Steps - -### 1. Create Container Struct and Basic Methods - -Create a new file `src/virt/nerdctl/container.rs` with the Container struct and basic methods. - -### 2. Implement Builder Pattern Methods - -Add builder pattern methods to the Container struct for configuration. - -### 3. Implement Container Operations - -Add methods for container operations like start, stop, exec, etc. - -### 4. Implement Status and Resource Usage Methods - -Add methods for getting container status and resource usage information. - -### 5. Update mod.rs to Export the New Container Struct - -Update `src/virt/nerdctl/mod.rs` to include the new container module. - -### 6. Create Example Usage - -Create an example file to demonstrate the new Container API. - -## Key Features - -### Container Creation and Configuration - -- Method chaining for the builder pattern -- Support for multiple ports and volumes -- Environment variable configuration -- Network configuration and aliases -- Resource limits (CPU, memory) -- Restart policies -- Health checks - -### Container Operations - -- Start, stop, and remove containers -- Execute commands in containers -- Copy files between container and host -- Export containers to tarballs -- Commit containers to images - -### Container Monitoring - -- Get container status information -- Get container health status -- Get resource usage information - -## Example Usage - -```rust -// Create a container with various configuration options -let container = Container::from_image("my-web-app", "nginx:latest")? - .with_ports(&["8080:80", "8443:443"]) - .with_volumes(&[ - "./html:/usr/share/nginx/html", - "./config/nginx.conf:/etc/nginx/nginx.conf" - ]) - .with_env("NGINX_HOST", "example.com") - .with_env("NGINX_PORT", "80") - .with_network("app-network") - .with_network_alias("web") - .with_cpu_limit("0.5") - .with_memory_limit("512m") - .with_restart_policy("always") - .with_health_check_options( - "curl -f http://localhost/ || exit 1", - Some("10s"), - Some("5s"), - Some(3), - Some("30s") - ) - .with_detach(true) - .build()?; - -// Start the container -container.start()?; - -// Execute a command in the container -let result = container.exec("echo 'Hello from container'")?; -println!("Command output: {}", result.stdout); - -// Get container status -let status = container.status()?; -println!("Container state: {}", status.state); -println!("Container status: {}", status.status); - -// Get resource usage -let resources = container.resources()?; -println!("CPU usage: {}", resources.cpu_usage); -println!("Memory usage: {}", resources.memory_usage); - -// Stop and remove the container -container.stop()?; -container.remove()?; -``` - -## Network Management - -```rust -// Create a network -Container::create_network("app-network", Some("bridge"))?; - -// Create containers in the network -let db = Container::from_image("db", "postgres:latest")? - .with_network("app-network") - .with_network_alias("database") - .with_env("POSTGRES_PASSWORD", "example") - .build()?; - -let app = Container::from_image("app", "my-app:latest")? - .with_network("app-network") - .with_env("DATABASE_URL", "postgres://postgres:example@database:5432/postgres") - .build()?; - -// Remove the network when done -Container::remove_network("app-network")?; -``` - -## Migration Strategy - -1. Create the new Container struct and its methods -2. Update the mod.rs file to export the new Container struct -3. Create example usage to demonstrate the new API -4. Deprecate the old function-based API (but keep it for backward compatibility) -5. Update documentation to reflect the new API - -## Testing Strategy - -1. Unit tests for the Container struct and its methods -2. Integration tests for the Container API -3. Manual testing with real containers diff --git a/created b/created deleted file mode 100644 index e69de29..0000000 diff --git a/example.conf b/example.conf deleted file mode 100644 index d3cb182..0000000 --- a/example.conf +++ /dev/null @@ -1 +0,0 @@ -EXAMPLE FILE TO TEST \ No newline at end of file diff --git a/example_Dockerfile b/example_Dockerfile deleted file mode 100644 index 97fd0e7..0000000 --- a/example_Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM node:lts-alpine -WORKDIR /app -COPY . . -RUN yarn install --production -CMD ["node", "src/index.js"] -EXPOSE 3000 \ No newline at end of file diff --git a/rhai_integration_plan.md b/rhai_integration_plan.md deleted file mode 100644 index f7337d1..0000000 --- a/rhai_integration_plan.md +++ /dev/null @@ -1,121 +0,0 @@ -# Implementation Plan: Rhai Integration for OS Module Functions - -## 1. Project Structure Changes - -We'll create a new directory structure for the Rhai integration: - -``` -src/ -├── rhai/ -│ ├── mod.rs # Main module file for Rhai integration -│ ├── os.rs # OS module wrappers -│ └── error.rs # Error type conversions -``` - -## 2. Dependencies - -Add Rhai as a dependency in Cargo.toml: - -```toml -[dependencies] -# Existing dependencies... -rhai = { version = "1.12.0", features = ["sync"] } -``` - -## 3. Implementation Steps - -### 3.1. Create Basic Module Structure - -1. Create the `src/rhai/mod.rs` file with: - - Module declarations - - A modular registration system - - Public exports - -2. Create the `src/rhai/error.rs` file with: - - Conversions from our custom error types to Rhai's `EvalAltResult` - - Helper functions for error handling - -### 3.2. Implement OS Module Wrappers - -Create the `src/rhai/os.rs` file with: - -1. Import necessary modules and types -2. Create wrapper functions for each function in `src/os/fs.rs` and `src/os/download.rs` -3. Implement a registration function specific to the OS module -4. Expose error types to Rhai - -### 3.3. Update Main Library File - -Update `src/lib.rs` to expose the new Rhai module. - -## 4. Detailed Implementation - -### 4.1. Error Handling - -For each function that returns a `Result` where `E` is one of our custom error types: - -1. Create a wrapper function that converts our error type to Rhai's `EvalAltResult` -2. Register the error types with Rhai to allow for proper error handling in scripts - -### 4.2. Function Wrappers - -For each function in the OS module: - -1. Create a wrapper function with the same name -2. Handle any necessary type conversions -3. Convert error types to Rhai's error system -4. Register the function with the Rhai engine - -### 4.3. Registration System - -Create a modular registration system: - -1. Implement a general `register` function that takes a Rhai engine -2. Implement module-specific registration functions (e.g., `register_os_module`) -3. Design the system to be extensible for future modules - -## 5. Implementation Flow Diagram - -```mermaid -flowchart TD - A[Add Rhai dependency] --> B[Create directory structure] - B --> C[Implement error conversions] - C --> D[Implement OS module wrappers] - D --> E[Create registration system] - E --> F[Update lib.rs] - F --> G[Test the implementation] -``` - -## 6. Function Mapping - -Here's a mapping of the OS module functions to their Rhai wrappers: - -### File System Functions (from fs.rs) -- `copy` → Wrap with error conversion -- `exist` → Direct wrapper (returns bool) -- `find_file` → Wrap with error conversion -- `find_files` → Wrap with error conversion -- `find_dir` → Wrap with error conversion -- `find_dirs` → Wrap with error conversion -- `delete` → Wrap with error conversion -- `mkdir` → Wrap with error conversion -- `file_size` → Wrap with error conversion -- `rsync` → Wrap with error conversion - -### Download Functions (from download.rs) -- `download` → Wrap with error conversion -- `download_install` → Wrap with error conversion - -## 7. Error Type Handling - -We'll expose our custom error types to Rhai: - -1. Register `FsError` and `DownloadError` as custom types -2. Implement proper error conversion to allow for detailed error handling in Rhai scripts -3. Create helper functions to extract error information - -## 8. Testing Strategy - -1. Create unit tests for each wrapper function -2. Create integration tests with sample Rhai scripts -3. Test error handling scenarios \ No newline at end of file diff --git a/run_builder_implementation_plan.md b/run_builder_implementation_plan.md deleted file mode 100644 index 8ac24a1..0000000 --- a/run_builder_implementation_plan.md +++ /dev/null @@ -1,179 +0,0 @@ -# Run Builder Implementation Plan - -This document outlines the plan for refactoring the `run.rs` module to use the builder pattern. - -## Current Implementation Analysis - -The current implementation has several functions for running commands and scripts: -- `run_command` and `run_command_silent` for single commands -- `run_script` and `run_script_silent` for multiline scripts -- `run` and `run_silent` as convenience functions that detect whether the input is a command or script - -These functions don't support all the options we want (die, async, log), and they don't follow the builder pattern. - -## Builder Pattern Implementation Plan - -### 1. Create a `RunBuilder` struct - -```rust -pub struct RunBuilder<'a> { - cmd: &'a str, - die: bool, - silent: bool, - async_exec: bool, - log: bool, -} -``` - -### 2. Implement Default Values and Builder Methods - -```rust -impl<'a> RunBuilder<'a> { - pub fn new(cmd: &'a str) -> Self { - Self { - cmd, - die: true, // Default: true - silent: false, // Default: false - async_exec: false, // Default: false - log: false, // Default: false - } - } - - pub fn die(mut self, die: bool) -> Self { - self.die = die; - self - } - - pub fn silent(mut self, silent: bool) -> Self { - self.silent = silent; - self - } - - pub fn async_exec(mut self, async_exec: bool) -> Self { - self.async_exec = async_exec; - self - } - - pub fn log(mut self, log: bool) -> Self { - self.log = log; - self - } - - pub fn execute(self) -> Result { - // Implementation will go here - } -} -``` - -### 3. Implement the `execute` Method - -The `execute` method will: -1. Determine if the command is a script or a single command -2. Handle the `async_exec` option by spawning a process without waiting -3. Handle the `log` option by logging command execution if enabled -4. Handle the `die` option by returning a CommandResult instead of an Err when die=false -5. Use the existing internal functions for the actual execution - -### 4. Create a Public Function to Start the Builder - -```rust -pub fn run(cmd: &str) -> RunBuilder { - RunBuilder::new(cmd) -} -``` - -### 5. Update Existing Functions for Backward Compatibility - -Update the existing functions to use the new builder pattern internally for backward compatibility. - -## Structure Diagram - -```mermaid -classDiagram - class RunBuilder { - +String cmd - +bool die - +bool silent - +bool async_exec - +bool log - +new(cmd: &str) RunBuilder - +die(bool) RunBuilder - +silent(bool) RunBuilder - +async_exec(bool) RunBuilder - +log(bool) RunBuilder - +execute() Result - } - - class CommandResult { - +String stdout - +String stderr - +bool success - +int code - } - - RunBuilder ..> CommandResult : produces - - note for RunBuilder "Builder pattern implementation\nfor command execution" -``` - -## Implementation Details - -### Handling the `async_exec` Option - -When `async_exec` is true, we'll spawn the process but not wait for it to complete. We'll return a CommandResult with: -- Empty stdout and stderr -- success = true (since we don't know the outcome) -- code = 0 (since we don't know the exit code) - -### Handling the `log` Option - -When `log` is true, we'll log the command execution with a "[LOG]" prefix. For example: -``` -[LOG] Executing command: ls -la -``` - -### Handling the `die` Option - -When `die` is false and a command fails, instead of returning an Err, we'll return a CommandResult with: -- success = false -- The appropriate error message in stderr -- code = -1 or the actual exit code if available - -## Usage Examples - -After implementation, users will be able to use the builder pattern like this: - -```rust -// Simple usage with defaults -let result = run("ls -la").execute()?; - -// With options -let result = run("ls -la") - .silent(true) - .die(false) - .execute()?; - -// Async execution -run("long_running_command") - .async_exec(true) - .execute()?; - -// With logging -let result = run("important_command") - .log(true) - .execute()?; - -// Script execution -let result = run("echo 'Hello'\necho 'World'") - .silent(true) - .execute()?; -``` - -## Implementation Steps - -1. Add the `RunBuilder` struct and its methods -2. Implement the `execute` method -3. Create the public `run` function -4. Update the existing functions to use the builder pattern internally -5. Add tests for the new functionality -6. Update documentation \ No newline at end of file diff --git a/src/rhai/nerdctl.rs b/src/rhai/nerdctl.rs index fb4df45..5ef4f1d 100644 --- a/src/rhai/nerdctl.rs +++ b/src/rhai/nerdctl.rs @@ -7,11 +7,30 @@ use std::collections::HashMap; use crate::virt::nerdctl::{self, NerdctlError, Image, Container}; use crate::process::CommandResult; -// Helper functions for error conversion +// Helper functions for error conversion with improved context fn nerdctl_error_to_rhai_error(result: Result) -> Result> { 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( - format!("Nerdctl error: {}", e).into(), + error_message.into(), rhai::Position::NONE )) }) @@ -86,9 +105,56 @@ pub fn container_build(container: Container) -> Result Result> { - nerdctl_error_to_rhai_error(container.start()) + // 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 diff --git a/src/virt/nerdctl/container_operations.rs b/src/virt/nerdctl/container_operations.rs index fab4d96..1429526 100644 --- a/src/virt/nerdctl/container_operations.rs +++ b/src/virt/nerdctl/container_operations.rs @@ -6,14 +6,76 @@ use super::container_types::{Container, ContainerStatus, ResourceUsage}; use serde_json; impl Container { - /// Start the container + /// Start the container and verify it's running /// /// # Returns /// - /// * `Result` - Command result or error + /// * `Result` - Command result or error with detailed information pub fn start(&self) -> Result { if let Some(container_id) = &self.container_id { - execute_nerdctl_command(&["start", container_id]) + // First, try to start the container + let start_result = execute_nerdctl_command(&["start", container_id]); + + // If the start command failed, return the error with details + if let Err(err) = &start_result { + return Err(NerdctlError::CommandFailed( + format!("Failed to start container {}: {}", container_id, err) + )); + } + + // Verify the container is actually running + match self.verify_running() { + Ok(true) => start_result, + Ok(false) => { + // Container started but isn't running - try to get more details + if let Ok(status) = self.status() { + Err(NerdctlError::CommandFailed( + format!("Container {} started but is not running. Status: {}, State: {}, Health: {}", + container_id, + status.status, + status.state, + status.health_status.unwrap_or_else(|| "N/A".to_string()) + ) + )) + } else { + Err(NerdctlError::CommandFailed( + format!("Container {} started but is not running. Unable to get status details.", + container_id + ) + )) + } + }, + Err(err) => { + // Failed to verify if container is running + Err(NerdctlError::CommandFailed( + format!("Container {} may have started, but verification failed: {}", + container_id, err + ) + )) + } + } + } else { + Err(NerdctlError::Other("No container ID available".to_string())) + } + } + + /// Verify if the container is running + /// + /// # Returns + /// + /// * `Result` - True if running, false if not running, error if verification failed + fn verify_running(&self) -> Result { + if let Some(container_id) = &self.container_id { + // Use inspect to check if the container is running + let inspect_result = execute_nerdctl_command(&["inspect", "--format", "{{.State.Running}}", container_id]); + + match inspect_result { + Ok(result) => { + let running = result.stdout.trim().to_lowercase() == "true"; + Ok(running) + }, + Err(err) => Err(err) + } } else { Err(NerdctlError::Other("No container ID available".to_string())) }