...
This commit is contained in:
		| @@ -185,7 +185,7 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result<CommandResult, | ||||
|                 if let Ok(l) = line { | ||||
|                     // Print the line if not silent and flush immediately | ||||
|                     if !silent_clone { | ||||
|                         // Always print stderr, even if silent is true, for error visibility | ||||
|                         // Print stderr with error prefix | ||||
|                         eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors | ||||
|                         std::io::stderr().flush().unwrap_or(()); | ||||
|                     } | ||||
| @@ -241,14 +241,8 @@ fn process_command_output(output: Result<Output, std::io::Error>) -> Result<Comm | ||||
|         Ok(out) => { | ||||
|             let stdout = String::from_utf8_lossy(&out.stdout).to_string(); | ||||
|             let stderr = String::from_utf8_lossy(&out.stderr).to_string(); | ||||
|              | ||||
|             // Print stderr if there's any, even for silent execution | ||||
|             if !stderr.is_empty() { | ||||
|                 eprintln!("\x1b[31mCommand stderr output:\x1b[0m"); | ||||
|                 for line in stderr.lines() { | ||||
|                     eprintln!("\x1b[31m{}\x1b[0m", line); | ||||
|                 } | ||||
|             } | ||||
|             // We'll collect stderr but not print it here | ||||
|             // It will be included in the error message if the command fails | ||||
|              | ||||
|             // If the command failed, print a clear error message | ||||
|             if !out.status.success() { | ||||
| @@ -356,9 +350,12 @@ fn run_script_internal(script: &str, silent: bool) -> Result<CommandResult, RunE | ||||
|     // Execute the script and handle the result | ||||
|     let result = execute_script_internal(&interpreter, &script_path, silent); | ||||
|      | ||||
|     // If there was an error, print a clear error message | ||||
|     // If there was an error, print a clear error message only if it's not a CommandFailed error | ||||
|     // (which would already have printed the stderr) | ||||
|     if let Err(ref e) = result { | ||||
|         eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e); | ||||
|         if !matches!(e, RunError::CommandFailed(_)) { | ||||
|             eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     result | ||||
| @@ -485,8 +482,11 @@ impl<'a> RunBuilder<'a> { | ||||
|                 Ok(res) | ||||
|             }, | ||||
|             Err(e) => { | ||||
|                 // Always print the error, even if die is false | ||||
|                 eprintln!("\x1b[31mCommand error: {}\x1b[0m", e); | ||||
|                 // Print the error only if it's not a CommandFailed error | ||||
|                 // (which would already have printed the stderr) | ||||
|                 if !matches!(e, RunError::CommandFailed(_)) { | ||||
|                     eprintln!("\x1b[31mCommand error: {}\x1b[0m", e); | ||||
|                 } | ||||
|                  | ||||
|                 if self.die { | ||||
|                     Err(e) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ fn nerdctl_download(){ | ||||
|     copy(`/tmp/${name}/bin/*`,"/root/hero/bin/"); | ||||
|     delete(`/tmp/${name}`); | ||||
|  | ||||
|     run("apt-get -y install buildah") | ||||
|     run("apt-get -y install buildah runc") | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,128 @@ | ||||
| // File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_functions.rs | ||||
|  | ||||
| use crate::process::CommandResult; | ||||
| use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; | ||||
|  | ||||
| /// Run a container from an image | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `image` - Image to run | ||||
| /// * `name` - Optional name for the container | ||||
| /// * `detach` - Whether to run in detached mode | ||||
| /// * `ports` - Optional port mappings | ||||
| /// * `snapshotter` - Optional snapshotter to use | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Result<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn run( | ||||
|     image: &str, | ||||
|     name: Option<&str>, | ||||
|     detach: bool, | ||||
|     ports: Option<&[&str]>, | ||||
|     snapshotter: Option<&str>, | ||||
| ) -> Result<CommandResult, NerdctlError> { | ||||
|     let mut args = vec!["run"]; | ||||
|      | ||||
|     if detach { | ||||
|         args.push("-d"); | ||||
|     } | ||||
|      | ||||
|     if let Some(name_value) = name { | ||||
|         args.push("--name"); | ||||
|         args.push(name_value); | ||||
|     } | ||||
|      | ||||
|     if let Some(ports_value) = ports { | ||||
|         for port in ports_value { | ||||
|             args.push("-p"); | ||||
|             args.push(port); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     if let Some(snapshotter_value) = snapshotter { | ||||
|         args.push("--snapshotter"); | ||||
|         args.push(snapshotter_value); | ||||
|     } | ||||
|      | ||||
|     // Add flags to avoid BPF issues | ||||
|     args.push("--cgroup-manager=cgroupfs"); | ||||
|      | ||||
|     args.push(image); | ||||
|      | ||||
|     execute_nerdctl_command(&args) | ||||
| } | ||||
|  | ||||
| /// Execute a command in a container | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `container` - Container name or ID | ||||
| /// * `command` - Command to execute | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Result<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn exec(container: &str, command: &str) -> Result<CommandResult, NerdctlError> { | ||||
|     execute_nerdctl_command(&["exec", container, "sh", "-c", command]) | ||||
| } | ||||
|  | ||||
| /// 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<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn copy(source: &str, dest: &str) -> Result<CommandResult, NerdctlError> { | ||||
|     execute_nerdctl_command(&["cp", source, dest]) | ||||
| } | ||||
|  | ||||
| /// Stop a container | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `container` - Container name or ID | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Result<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn stop(container: &str) -> Result<CommandResult, NerdctlError> { | ||||
|     execute_nerdctl_command(&["stop", container]) | ||||
| } | ||||
|  | ||||
| /// Remove a container | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `container` - Container name or ID | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Result<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn remove(container: &str) -> Result<CommandResult, NerdctlError> { | ||||
|     execute_nerdctl_command(&["rm", container]) | ||||
| } | ||||
|  | ||||
| /// List containers | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `all` - Whether to list all containers (including stopped ones) | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Result<CommandResult, NerdctlError>` - Command result or error | ||||
| pub fn list(all: bool) -> Result<CommandResult, NerdctlError> { | ||||
|     let mut args = vec!["ps"]; | ||||
|      | ||||
|     if all { | ||||
|         args.push("-a"); | ||||
|     } | ||||
|      | ||||
|     execute_nerdctl_command(&args) | ||||
| } | ||||
| @@ -5,6 +5,8 @@ mod tests { | ||||
|     use super::super::container_types::{Container, ContainerStatus, ResourceUsage}; | ||||
|     use super::super::NerdctlError; | ||||
|     use std::error::Error; | ||||
|     use std::thread; | ||||
|     use std::time::Duration; | ||||
|      | ||||
|     #[test] | ||||
|     fn test_container_builder_pattern() { | ||||
| @@ -73,4 +75,181 @@ mod tests { | ||||
|         assert_eq!(health_check.retries.unwrap(), 3); | ||||
|         assert_eq!(health_check.start_period.as_ref().unwrap(), "5s"); | ||||
|     } | ||||
|      | ||||
|     #[test] | ||||
|     #[ignore] // Ignore by default as it requires nerdctl to be installed and running | ||||
|     fn test_container_runtime_and_resources() { | ||||
|         // Check if nerdctl is available and properly configured | ||||
|         let nerdctl_check = super::super::execute_nerdctl_command(&["info"]); | ||||
|         if nerdctl_check.is_err() { | ||||
|             println!("Skipping test: nerdctl is not available or properly configured"); | ||||
|             println!("Error: {:?}", nerdctl_check.err()); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // Create a unique container name for this test | ||||
|         let container_name = format!("test-runtime-{}", std::time::SystemTime::now() | ||||
|             .duration_since(std::time::UNIX_EPOCH) | ||||
|             .unwrap() | ||||
|             .as_secs()); | ||||
|          | ||||
|         // Create and build a container that will use resources | ||||
|         // Use a simple container with a basic command to avoid dependency on external images | ||||
|         let container_result = Container::from_image(&container_name, "busybox:latest").unwrap() | ||||
|             .with_detach(true) | ||||
|             .build(); | ||||
|          | ||||
|         // Check if the build was successful | ||||
|         if container_result.is_err() { | ||||
|             println!("Failed to build container: {:?}", container_result.err()); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         let container = container_result.unwrap(); | ||||
|         println!("Container created successfully: {}", container_name); | ||||
|          | ||||
|         // Start the container with a simple command | ||||
|         let start_result = container.exec("sh -c 'for i in $(seq 1 10); do echo $i; sleep 1; done'"); | ||||
|         if start_result.is_err() { | ||||
|             println!("Failed to start container: {:?}", start_result.err()); | ||||
|             // Try to clean up | ||||
|             let _ = container.remove(); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         println!("Container started successfully"); | ||||
|          | ||||
|         // Wait for the container to start and consume resources | ||||
|         thread::sleep(Duration::from_secs(3)); | ||||
|          | ||||
|         // Check container status | ||||
|         let status_result = container.status(); | ||||
|         if status_result.is_err() { | ||||
|             println!("Failed to get container status: {:?}", status_result.err()); | ||||
|             // Try to clean up | ||||
|             let _ = container.stop(); | ||||
|             let _ = container.remove(); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         let status = status_result.unwrap(); | ||||
|         println!("Container status: {:?}", status); | ||||
|          | ||||
|         // Verify the container is running | ||||
|         if status.status != "running" { | ||||
|             println!("Container is not running, status: {}", status.status); | ||||
|             // Try to clean up | ||||
|             let _ = container.remove(); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // Check resource usage | ||||
|         let resources_result = container.resources(); | ||||
|         if resources_result.is_err() { | ||||
|             println!("Failed to get resource usage: {:?}", resources_result.err()); | ||||
|             // Try to clean up | ||||
|             let _ = container.stop(); | ||||
|             let _ = container.remove(); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         let resources = resources_result.unwrap(); | ||||
|         println!("Container resources: {:?}", resources); | ||||
|          | ||||
|         // Verify the container is using memory (if we can get the information) | ||||
|         if resources.memory_usage == "0B" || resources.memory_usage == "unknown" { | ||||
|             println!("Warning: Container memory usage is {}", resources.memory_usage); | ||||
|         } else { | ||||
|             println!("Container is using memory: {}", resources.memory_usage); | ||||
|         } | ||||
|          | ||||
|         // Clean up - stop and remove the container | ||||
|         println!("Stopping container..."); | ||||
|         let stop_result = container.stop(); | ||||
|         if stop_result.is_err() { | ||||
|             println!("Warning: Failed to stop container: {:?}", stop_result.err()); | ||||
|         } | ||||
|          | ||||
|         println!("Removing container..."); | ||||
|         let remove_result = container.remove(); | ||||
|         if remove_result.is_err() { | ||||
|             println!("Warning: Failed to remove container: {:?}", remove_result.err()); | ||||
|         } | ||||
|          | ||||
|         println!("Test completed successfully"); | ||||
|     } | ||||
|      | ||||
|     #[test] | ||||
|     fn test_container_with_custom_command() { | ||||
|         // Create a container with a custom command | ||||
|         let container = Container::new("test-command-container").unwrap() | ||||
|             .with_port("8080:80") | ||||
|             .with_volume("/tmp:/data") | ||||
|             .with_env("TEST_ENV", "test_value") | ||||
|             .with_detach(true); | ||||
|          | ||||
|         // Verify container properties | ||||
|         assert_eq!(container.name, "test-command-container"); | ||||
|         assert_eq!(container.ports.len(), 1); | ||||
|         assert_eq!(container.ports[0], "8080:80"); | ||||
|         assert_eq!(container.volumes.len(), 1); | ||||
|         assert_eq!(container.volumes[0], "/tmp:/data"); | ||||
|         assert_eq!(container.env_vars.len(), 1); | ||||
|         assert_eq!(container.env_vars.get("TEST_ENV").unwrap(), "test_value"); | ||||
|         assert_eq!(container.detach, true); | ||||
|          | ||||
|         // Convert the container to a command string that would be used to run it | ||||
|         let command_args = container_to_command_args(&container); | ||||
|          | ||||
|         // Verify the command arguments contain all the expected options | ||||
|         assert!(command_args.contains(&"--name".to_string())); | ||||
|         assert!(command_args.contains(&"test-command-container".to_string())); | ||||
|         assert!(command_args.contains(&"-p".to_string())); | ||||
|         assert!(command_args.contains(&"8080:80".to_string())); | ||||
|         assert!(command_args.contains(&"-v".to_string())); | ||||
|         assert!(command_args.contains(&"/tmp:/data".to_string())); | ||||
|         assert!(command_args.contains(&"-e".to_string())); | ||||
|         assert!(command_args.contains(&"TEST_ENV=test_value".to_string())); | ||||
|         assert!(command_args.contains(&"-d".to_string())); | ||||
|          | ||||
|         println!("Command args: {:?}", command_args); | ||||
|     } | ||||
|      | ||||
|     // Helper function to convert a container to command arguments | ||||
|     fn container_to_command_args(container: &Container) -> Vec<String> { | ||||
|         let mut args = Vec::new(); | ||||
|         args.push("run".to_string()); | ||||
|          | ||||
|         if container.detach { | ||||
|             args.push("-d".to_string()); | ||||
|         } | ||||
|          | ||||
|         args.push("--name".to_string()); | ||||
|         args.push(container.name.clone()); | ||||
|          | ||||
|         // Add port mappings | ||||
|         for port in &container.ports { | ||||
|             args.push("-p".to_string()); | ||||
|             args.push(port.clone()); | ||||
|         } | ||||
|          | ||||
|         // Add volume mounts | ||||
|         for volume in &container.volumes { | ||||
|             args.push("-v".to_string()); | ||||
|             args.push(volume.clone()); | ||||
|         } | ||||
|          | ||||
|         // Add environment variables | ||||
|         for (key, value) in &container.env_vars { | ||||
|             args.push("-e".to_string()); | ||||
|             args.push(format!("{}={}", key, value)); | ||||
|         } | ||||
|          | ||||
|         // Add image if available | ||||
|         if let Some(image) = &container.image { | ||||
|             args.push(image.clone()); | ||||
|         } | ||||
|          | ||||
|         args | ||||
|     } | ||||
| } | ||||
| @@ -5,6 +5,7 @@ mod container; | ||||
| mod container_builder; | ||||
| mod health_check; | ||||
| mod container_operations; | ||||
| mod container_functions; | ||||
| #[cfg(test)] | ||||
| mod container_test; | ||||
|  | ||||
| @@ -50,4 +51,5 @@ impl Error for NerdctlError { | ||||
|  | ||||
| pub use images::*; | ||||
| pub use cmd::*; | ||||
| pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; | ||||
| pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; | ||||
| pub use container_functions::*; | ||||
		Reference in New Issue
	
	Block a user