//! Rhai wrappers for Nerdctl module functions //! //! This module provides Rhai wrappers for the functions in the Nerdctl module. use crate::nerdctl::{self, Container, HealthCheck, 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( 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( error_message.into(), rhai::Position::NONE )) }) } // // Container Builder Pattern Implementation // /// Create a new Container pub fn container_new(name: &str) -> Result> { nerdctl_error_to_rhai_error(Container::new(name)) } /// Create a Container from an image pub fn container_from_image(name: &str, image: &str) -> Result> { nerdctl_error_to_rhai_error(Container::from_image(name, image)) } /// Reset the container configuration to defaults while keeping the name and image pub fn container_reset(container: Container) -> Container { container.reset() } // TODO: remove? /// Set health check with options for a Container pub fn container_with_health_check_options( container: Container, cmd: &str, interval: Option<&str>, timeout: Option<&str>, retries: Option, start_period: Option<&str>, ) -> Container { // Convert i64 to u32 for retries let retries_u32 = retries.map(|r| r as u32); container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period) } /// Build and run the Container /// /// This function builds and runs the container using the configured options. /// It provides detailed error information if the build fails. pub fn container_build(container: Container) -> Result> { // Get container details for better error reporting let container_name = container.name.clone(); let image = container .image .clone() .unwrap_or_else(|| "none".to_string()); let ports = container.ports.clone(); let volumes = container.volumes.clone(); let env_vars = container.env_vars.clone(); // Try to build the container let build_result = container.build(); // Handle the result with improved error context match build_result { Ok(built_container) => { // Container built successfully Ok(built_container) } Err(err) => { // Add more context to the error let enhanced_error = match err { NerdctlError::CommandFailed(msg) => { // Provide more detailed error information let mut enhanced_msg = format!( "Failed to build container '{}' from image '{}': {}", container_name, image, msg ); // Add information about configured options that might be relevant if !ports.is_empty() { enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports)); } if !volumes.is_empty() { enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes)); } if !env_vars.is_empty() { enhanced_msg.push_str(&format!( "\nConfigured environment variables: {:?}", env_vars )); } // Add suggestions for common issues if msg.contains("not found") || msg.contains("no such image") { enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull()."); } else if msg.contains("port is already allocated") { enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port."); } else if msg.contains("permission denied") { enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes."); } NerdctlError::CommandFailed(enhanced_msg) } _ => err, }; nerdctl_error_to_rhai_error(Err(enhanced_error)) } } } /// Start the Container and verify it's running /// /// This function starts the container and verifies that it's actually running. /// It returns detailed error information if the container fails to start or /// if it starts but stops immediately. pub fn container_start(container: &mut Container) -> Result> { // Get container details for better error reporting let container_name = container.name.clone(); let container_id = container .container_id .clone() .unwrap_or_else(|| "unknown".to_string()); // Try to start the container let start_result = container.start(); // Handle the result with improved error context match start_result { Ok(result) => { // Container started successfully Ok(result) } Err(err) => { // Add more context to the error let enhanced_error = match err { NerdctlError::CommandFailed(msg) => { // Check if this is a "container already running" error, which is not really an error if msg.contains("already running") { return Ok(CommandResult { stdout: format!("Container {} is already running", container_name), stderr: "".to_string(), success: true, code: 0, }); } // Try to get more information about why the container might have failed to start let mut enhanced_msg = format!( "Failed to start container '{}' (ID: {}): {}", container_name, container_id, msg ); // Try to check if the image exists if let Some(image) = &container.image { enhanced_msg.push_str(&format!("\nContainer was using image: {}", image)); } NerdctlError::CommandFailed(enhanced_msg) } _ => err, }; nerdctl_error_to_rhai_error(Err(enhanced_error)) } } } /// Stop the Container pub fn container_stop(container: &mut Container) -> Result> { nerdctl_error_to_rhai_error(container.stop()) } /// Remove the Container pub fn container_remove(container: &mut Container) -> Result> { nerdctl_error_to_rhai_error(container.remove()) } /// Execute a command in the Container pub fn container_exec( container: &mut Container, command: &str, ) -> Result> { nerdctl_error_to_rhai_error(container.exec(command)) } /// Get container logs pub fn container_logs(container: &mut Container) -> Result> { // Get container details for better error reporting let container_name = container.name.clone(); let container_id = container .container_id .clone() .unwrap_or_else(|| "unknown".to_string()); // Use the nerdctl::logs function let logs_result = nerdctl::logs(&container_id); match logs_result { Ok(result) => Ok(result), Err(err) => { // Add more context to the error let enhanced_error = NerdctlError::CommandFailed(format!( "Failed to get logs for container '{}' (ID: {}): {}", container_name, container_id, err )); nerdctl_error_to_rhai_error(Err(enhanced_error)) } } } /// Copy files between the Container and local filesystem pub fn container_copy( container: &mut Container, source: &str, dest: &str, ) -> Result> { nerdctl_error_to_rhai_error(container.copy(source, dest)) } /// Create a new Map with default run options pub fn new_run_options() -> Map { let mut map = Map::new(); map.insert("name".into(), Dynamic::UNIT); map.insert("detach".into(), Dynamic::from(true)); map.insert("ports".into(), Dynamic::from(Array::new())); map.insert("snapshotter".into(), Dynamic::from("native")); map } // // Container Function Wrappers // /// Wrapper for nerdctl::run /// /// Run a container from an image. pub fn nerdctl_run(image: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::run(image, None, true, None, None)) } /// Run a container with a name pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, None, None)) } /// Run a container with a port mapping pub fn nerdctl_run_with_port( image: &str, name: &str, port: &str, ) -> Result> { let ports = vec![port]; nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, Some(&ports), None)) } /// Wrapper for nerdctl::exec /// /// Execute a command in a container. pub fn nerdctl_exec(container: &str, command: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::exec(container, command)) } /// Wrapper for nerdctl::copy /// /// Copy files between container and local filesystem. pub fn nerdctl_copy(source: &str, dest: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::copy(source, dest)) } /// Wrapper for nerdctl::stop /// /// Stop a container. pub fn nerdctl_stop(container: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::stop(container)) } /// Wrapper for nerdctl::remove /// /// Remove a container. pub fn nerdctl_remove(container: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::remove(container)) } /// Wrapper for nerdctl::list /// /// List containers. pub fn nerdctl_list(all: bool) -> Result> { nerdctl_error_to_rhai_error(nerdctl::list(all)) } /// Wrapper for nerdctl::logs /// /// Get container logs. pub fn nerdctl_logs(container: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::logs(container)) } // // Image Function Wrappers // /// Wrapper for nerdctl::images /// /// List images in local storage. pub fn nerdctl_images() -> Result> { nerdctl_error_to_rhai_error(nerdctl::images()) } /// Wrapper for nerdctl::image_remove /// /// Remove one or more images. pub fn nerdctl_image_remove(image: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::image_remove(image)) } /// Wrapper for nerdctl::image_push /// /// Push an image to a registry. pub fn nerdctl_image_push( image: &str, destination: &str, ) -> Result> { nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination)) } /// Wrapper for nerdctl::image_tag /// /// Add an additional name to a local image. pub fn nerdctl_image_tag(image: &str, new_name: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::image_tag(image, new_name)) } /// Wrapper for nerdctl::image_pull /// /// Pull an image from a registry. pub fn nerdctl_image_pull(image: &str) -> Result> { nerdctl_error_to_rhai_error(nerdctl::image_pull(image)) } /// Wrapper for nerdctl::image_commit /// /// Commit a container to an image. pub fn nerdctl_image_commit( container: &str, image_name: &str, ) -> Result> { 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> { nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path)) } /// Register Nerdctl module functions with the Rhai engine /// /// # Arguments /// /// * `engine` - The Rhai engine to register the functions with /// /// # Returns /// /// * `Result<(), Box>` - Ok if registration was successful, Err otherwise pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box> { // 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); // TODO: check if this works! // Register Container instance methods engine.register_fn("reset", container_reset); // TODO: these functions should be getters and setter like the buildah example // engine.register_fn("with_image", container_with_image); // engine.register_fn("with_port", container_with_port); // engine.register_fn("with_volume", container_with_volume); // engine.register_fn("with_env", container_with_env); // engine.register_fn("with_network", container_with_network); // engine.register_fn("with_network_alias", container_with_network_alias); // engine.register_fn("with_cpu_limit", container_with_cpu_limit); // engine.register_fn("with_memory_limit", container_with_memory_limit); // engine.register_fn("with_restart_policy", container_with_restart_policy); // engine.register_fn("with_health_check", container_with_health_check); // engine.register_fn("with_ports", container_with_ports); // engine.register_fn("with_volumes", container_with_volumes); // engine.register_fn("with_envs", container_with_envs); // engine.register_fn("with_network_aliases", container_with_network_aliases); // engine.register_fn("with_memory_swap_limit", container_with_memory_swap_limit); // engine.register_fn("with_cpu_shares", container_with_cpu_shares); // engine.register_fn( // "with_health_check_options", // container_with_health_check_options, // ); // engine.register_fn("with_snapshotter", container_with_snapshotter); // engine.register_fn("with_detach", container_with_detach); engine.register_fn("build", container_build); engine.register_fn("start", container_start); engine.register_fn("stop", container_stop); engine.register_fn("remove", container_remove); engine.register_fn("exec", container_exec); engine.register_fn("logs", container_logs); engine.register_fn("copy", container_copy); // Register legacy container functions (for backward compatibility) engine.register_fn("nerdctl_run", nerdctl_run); engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name); engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port); engine.register_fn("new_run_options", new_run_options); engine.register_fn("nerdctl_exec", nerdctl_exec); engine.register_fn("nerdctl_copy", nerdctl_copy); engine.register_fn("nerdctl_stop", nerdctl_stop); engine.register_fn("nerdctl_remove", nerdctl_remove); engine.register_fn("nerdctl_list", nerdctl_list); engine.register_fn("nerdctl_logs", nerdctl_logs); // Register image functions engine.register_fn("nerdctl_images", nerdctl_images); engine.register_fn("nerdctl_image_remove", nerdctl_image_remove); engine.register_fn("nerdctl_image_push", nerdctl_image_push); engine.register_fn("nerdctl_image_tag", nerdctl_image_tag); engine.register_fn("nerdctl_image_pull", nerdctl_image_pull); engine.register_fn("nerdctl_image_commit", nerdctl_image_commit); engine.register_fn("nerdctl_image_build", nerdctl_image_build); Ok(()) } /// Register Nerdctl module types with the Rhai engine fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> { // Register Container type engine.register_type_with_name::("NerdctlContainer"); engine.register_type_with_name::("NerdctlHealthCheck"); // Register getters & setters for HealthCheck properties engine.register_get("cmd", |hc: &mut HealthCheck| hc.cmd.clone()); engine.register_set("cmd", |hc: &mut HealthCheck, cmd: &str| { hc.cmd = cmd.to_string(); }); engine.register_get("interval", |hc: &mut HealthCheck| { hc.interval.clone().unwrap_or_default() }); engine.register_set("interval", |hc: &mut HealthCheck, interval: &str| { hc.interval = Some(interval.to_string()); }); engine.register_get("timeout", |hc: &mut HealthCheck| { hc.timeout.clone().unwrap_or_default() }); engine.register_set("timeout", |hc: &mut HealthCheck, timeout: &str| { hc.timeout = Some(timeout.to_string()); }); engine.register_get("retries", |hc: &mut HealthCheck| { hc.retries.map_or(0, |r| r as i64) }); engine.register_set("retries", |hc: &mut HealthCheck, retries: i64| { hc.retries = Some(retries as u32); }); engine.register_get("start_period", |hc: &mut HealthCheck| { hc.start_period.clone().unwrap_or_default() }); engine.register_set("start_period", |hc: &mut HealthCheck, start_period: &str| { hc.start_period = Some(start_period.to_string()); }); // Register getters & setters for Container properties // -- name engine.register_get("name", |container: &mut Container| container.name.clone()); engine.register_set("image", |container: &mut Container, image: &str| { container.image = Some(image.to_string()); }); // -- container_id engine.register_get( "container_id", |container: &mut Container| match &container.container_id { Some(id) => id.clone(), None => "".to_string(), }, ); engine.register_set("container_id", |container: &mut Container, container_id: &str| { container.container_id = Some(container_id.to_string()); }); // -- image engine.register_get("image", |container: &mut Container| { match &container.image { Some(img) => img.clone(), None => "".to_string(), } }); engine.register_set("image", |container: &mut Container, image: &str| { container.image = Some(image.to_string()); }); // -- config engine.register_get("config", |container: &mut Container| { container .config .iter() .map(|(k, v)| (k.clone().into(), v.clone().into())) .collect::() }); engine.register_set("config", |container: &mut Container, config: Map| { container.config = config .into_iter() .map(|(k, v)| (k.to_string(), v.into_string().unwrap_or_default())) .collect(); }); // -- ports engine.register_get("ports", |container: &mut Container| { let mut array = Array::new(); for port in &container.ports { array.push(Dynamic::from(port.clone())); } array }); engine.register_set("ports", |container: &mut Container, ports: Array| { container.ports = ports .into_iter() .map(|v| v.into_string().unwrap_or_default()) .collect(); }); // -- volumes engine.register_get("volumes", |container: &mut Container| { let mut array = Array::new(); for volume in &container.volumes { array.push(Dynamic::from(volume.clone())); } array }); engine.register_set("volumes", |container: &mut Container, volumes: Array| { container.volumes = volumes .into_iter() .map(|v| v.into_string().unwrap_or_default()) .collect(); }); // -- env_vars engine.register_get("env_vars", |container: &mut Container| { container .env_vars .iter() .map(|(k, v)| (k.clone().into(), v.clone().into())) .collect::() }); engine.register_set("env_vars", |container: &mut Container, env_vars: Map| { container.env_vars = env_vars .into_iter() .map(|(k, v)| (k.to_string(), v.into_string().unwrap_or_default())) .collect(); }); // -- network engine.register_get("network", |container: &mut Container| { container.network.clone().unwrap_or_default() }); engine.register_set("network", |container: &mut Container, network: &str| { container.network = Some(network.to_string()); }); // -- network_aliases engine.register_get("network_aliases", |container: &mut Container| { container .network_aliases .iter() .map(|alias| Dynamic::from(alias.clone())) .collect::() }); engine.register_set( "network_aliases", |container: &mut Container, aliases: Array| { container.network_aliases = aliases .into_iter() .map(|a| a.into_string().unwrap_or_default()) .collect(); }, ); // -- cpu_limit engine.register_get("cpu_limit", |container: &mut Container| { container.cpu_limit.clone().unwrap_or_default() }); engine.register_set("cpu_limit", |container: &mut Container, limit: &str| { container.cpu_limit = Some(limit.to_string()); }); // -- memory_limit engine.register_get("memory_limit", |container: &mut Container| { container.memory_limit.clone().unwrap_or_default() }); engine.register_set("memory_limit", |container: &mut Container, limit: &str| { container.memory_limit = Some(limit.to_string()); }); // -- memory_swap_limit engine.register_get("memory_swap_limit", |container: &mut Container| { container.memory_swap_limit.clone().unwrap_or_default() }); engine.register_set( "memory_swap_limit", |container: &mut Container, limit: &str| { container.memory_swap_limit = Some(limit.to_string()); }, ); // -- cpu_shares engine.register_get("cpu_shares", |container: &mut Container| { container.cpu_shares.clone().unwrap_or_default() }); engine.register_set("cpu_shares", |container: &mut Container, shares: &str| { container.cpu_shares = Some(shares.to_string()); }); // -- restart_policy engine.register_get("restart_policy", |container: &mut Container| { container.restart_policy.clone().unwrap_or_default() }); engine.register_set( "restart_policy", |container: &mut Container, policy: &str| { container.restart_policy = Some(policy.to_string()); }, ); // TODO: setters and getters for health_check // -- health_check // engine.register_get("health_check", |container: &mut Container| { // container.health_check.clone() // }); // engine.register_set( // "health_check", // |container: &mut Container, health_check: HealthCheck| { // container.health_check = Some(health_check); // }, // ); // -- detach engine.register_get("detach", |container: &mut Container| container.detach); engine.register_set("detach", |container: &mut Container, detach: bool| { container.detach = detach; }); // -- snapshotter engine.register_get("snapshotter", |container: &mut Container| { container.snapshotter.clone().unwrap_or_default() }); engine.register_set("snapshotter", |container: &mut Container, snapshotter: &str| { container.snapshotter = Some(snapshotter.to_string()); }); // Register Image type and methods engine.register_type_with_name::("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(()) }