// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_operations.rs use crate::process::CommandResult; use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; use super::container_types::{Container, ContainerStatus, ResourceUsage}; use serde_json; impl Container { /// Start the container and verify it's running /// If the container hasn't been created yet, it will be created automatically. /// /// # Returns /// /// * `Result` - Command result or error with detailed information pub fn start(&self) -> Result { // If container_id is None, we need to create the container first let container = if self.container_id.is_none() { // Check if we have an image specified if self.image.is_none() { return Err(NerdctlError::Other("No image specified for container creation".to_string())); } // Clone self and create the container println!("Container not created yet. Creating container from image..."); // First, try to pull the image if it doesn't exist locally let image = self.image.as_ref().unwrap(); match execute_nerdctl_command(&["image", "inspect", image]) { Err(_) => { println!("Image '{}' not found locally. Pulling image...", image); if let Err(e) = execute_nerdctl_command(&["pull", image]) { return Err(NerdctlError::CommandFailed( format!("Failed to pull image '{}': {}", image, e) )); } println!("Image '{}' pulled successfully.", image); }, Ok(_) => { println!("Image '{}' found locally.", image); } } // Now create the container match self.clone().build() { Ok(built) => built, Err(e) => { return Err(NerdctlError::CommandFailed( format!("Failed to create container from image '{}': {}", image, e) )); } } } else { // Container already has an ID, use it as is self.clone() }; if let Some(container_id) = &container.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 container.verify_running() { Ok(true) => start_result, Ok(false) => { // Container started but isn't running - get detailed information let mut error_message = format!("Container {} started but is not running.", container_id); // Get container status if let Ok(status) = container.status() { error_message.push_str(&format!("\nStatus: {}, State: {}, Health: {}", status.status, status.state, status.health_status.unwrap_or_else(|| "N/A".to_string()) )); } // Get container logs if let Ok(logs) = execute_nerdctl_command(&["logs", container_id]) { if !logs.stdout.trim().is_empty() { error_message.push_str(&format!("\nContainer logs (stdout):\n{}", logs.stdout.trim())); } if !logs.stderr.trim().is_empty() { error_message.push_str(&format!("\nContainer logs (stderr):\n{}", logs.stderr.trim())); } } // Get container exit code if available if let Ok(inspect_result) = execute_nerdctl_command(&["inspect", "--format", "{{.State.ExitCode}}", container_id]) { let exit_code = inspect_result.stdout.trim(); if !exit_code.is_empty() && exit_code != "0" { error_message.push_str(&format!("\nContainer exit code: {}", exit_code)); } } Err(NerdctlError::CommandFailed(error_message)) }, 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("Failed to create container. 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())) } } /// Stop the container /// /// # Returns /// /// * `Result` - Command result or error pub fn stop(&self) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["stop", container_id]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Remove the container /// /// # Returns /// /// * `Result` - Command result or error pub fn remove(&self) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["rm", container_id]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Execute a command in the container /// /// # Arguments /// /// * `command` - The command to run /// /// # Returns /// /// * `Result` - Command result or error pub fn exec(&self, command: &str) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["exec", container_id, "sh", "-c", command]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Copy files between container and local filesystem /// /// # Arguments /// /// * `source` - Source path (can be container:path or local path) /// * `dest` - Destination path (can be container:path or local path) /// /// # Returns /// /// * `Result` - Command result or error pub fn copy(&self, source: &str, dest: &str) -> Result { if self.container_id.is_some() { execute_nerdctl_command(&["cp", source, dest]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Export the container to a tarball /// /// # Arguments /// /// * `path` - Path to save the tarball /// /// # Returns /// /// * `Result` - Command result or error pub fn export(&self, path: &str) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["export", "-o", path, container_id]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Commit the container to an image /// /// # Arguments /// /// * `image_name` - Name for the new image /// /// # Returns /// /// * `Result` - Command result or error pub fn commit(&self, image_name: &str) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["commit", container_id, image_name]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Get container status /// /// # Returns /// /// * `Result` - Container status or error pub fn status(&self) -> Result { if let Some(container_id) = &self.container_id { let result = execute_nerdctl_command(&["inspect", container_id])?; // Parse the JSON output match serde_json::from_str::(&result.stdout) { Ok(json) => { if let Some(container_json) = json.as_array().and_then(|arr| arr.first()) { let state = container_json .get("State") .and_then(|state| state.get("Status")) .and_then(|status| status.as_str()) .unwrap_or("unknown") .to_string(); let status = container_json .get("State") .and_then(|state| state.get("Running")) .and_then(|running| { if running.as_bool().unwrap_or(false) { Some("running") } else { Some("stopped") } }) .unwrap_or("unknown") .to_string(); let created = container_json .get("Created") .and_then(|created| created.as_str()) .unwrap_or("unknown") .to_string(); let started = container_json .get("State") .and_then(|state| state.get("StartedAt")) .and_then(|started| started.as_str()) .unwrap_or("unknown") .to_string(); // Get health status if available let health_status = container_json .get("State") .and_then(|state| state.get("Health")) .and_then(|health| health.get("Status")) .and_then(|status| status.as_str()) .map(|s| s.to_string()); // Get health check output if available let health_output = container_json .get("State") .and_then(|state| state.get("Health")) .and_then(|health| health.get("Log")) .and_then(|log| log.as_array()) .and_then(|log_array| log_array.last()) .and_then(|last_log| last_log.get("Output")) .and_then(|output| output.as_str()) .map(|s| s.to_string()); Ok(ContainerStatus { state, status, created, started, health_status, health_output, }) } else { Err(NerdctlError::JsonParseError("Invalid container inspect JSON".to_string())) } }, Err(e) => { Err(NerdctlError::JsonParseError(format!("Failed to parse container inspect JSON: {}", e))) } } } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Get the health status of the container /// /// # Returns /// /// * `Result` - Health status or error pub fn health_status(&self) -> Result { if let Some(container_id) = &self.container_id { let result = execute_nerdctl_command(&["inspect", "--format", "{{.State.Health.Status}}", container_id])?; Ok(result.stdout.trim().to_string()) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Get container logs /// /// # Returns /// /// * `Result` - Command result or error pub fn logs(&self) -> Result { if let Some(container_id) = &self.container_id { execute_nerdctl_command(&["logs", container_id]) } else { Err(NerdctlError::Other("No container ID available".to_string())) } } /// Get container resource usage /// /// # Returns /// /// * `Result` - Resource usage or error pub fn resources(&self) -> Result { if let Some(container_id) = &self.container_id { let result = execute_nerdctl_command(&["stats", "--no-stream", container_id])?; // Parse the output let lines: Vec<&str> = result.stdout.lines().collect(); if lines.len() >= 2 { let headers = lines[0]; let values = lines[1]; let headers_vec: Vec<&str> = headers.split_whitespace().collect(); let values_vec: Vec<&str> = values.split_whitespace().collect(); // Find indices for each metric let cpu_index = headers_vec.iter().position(|&h| h.contains("CPU")).unwrap_or(0); let mem_index = headers_vec.iter().position(|&h| h.contains("MEM")).unwrap_or(0); let mem_perc_index = headers_vec.iter().position(|&h| h.contains("MEM%")).unwrap_or(0); let net_in_index = headers_vec.iter().position(|&h| h.contains("NET")).unwrap_or(0); let net_out_index = if net_in_index > 0 { net_in_index + 1 } else { 0 }; let block_in_index = headers_vec.iter().position(|&h| h.contains("BLOCK")).unwrap_or(0); let block_out_index = if block_in_index > 0 { block_in_index + 1 } else { 0 }; let pids_index = headers_vec.iter().position(|&h| h.contains("PIDS")).unwrap_or(0); let cpu_usage = if cpu_index < values_vec.len() { values_vec[cpu_index].to_string() } else { "unknown".to_string() }; let memory_usage = if mem_index < values_vec.len() { values_vec[mem_index].to_string() } else { "unknown".to_string() }; let memory_limit = if mem_index + 1 < values_vec.len() { values_vec[mem_index + 1].to_string() } else { "unknown".to_string() }; let memory_percentage = if mem_perc_index < values_vec.len() { values_vec[mem_perc_index].to_string() } else { "unknown".to_string() }; let network_input = if net_in_index < values_vec.len() { values_vec[net_in_index].to_string() } else { "unknown".to_string() }; let network_output = if net_out_index < values_vec.len() { values_vec[net_out_index].to_string() } else { "unknown".to_string() }; let block_input = if block_in_index < values_vec.len() { values_vec[block_in_index].to_string() } else { "unknown".to_string() }; let block_output = if block_out_index < values_vec.len() { values_vec[block_out_index].to_string() } else { "unknown".to_string() }; let pids = if pids_index < values_vec.len() { values_vec[pids_index].to_string() } else { "unknown".to_string() }; Ok(ResourceUsage { cpu_usage, memory_usage, memory_limit, memory_percentage, network_input, network_output, block_input, block_output, pids, }) } else { Err(NerdctlError::ConversionError("Failed to parse stats output".to_string())) } } else { Err(NerdctlError::Other("No container ID available".to_string())) } } }