From 3803a545297f94aa8ed7b7a3be3c40b08b15b843 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 5 Apr 2025 05:59:51 +0200 Subject: [PATCH] ... --- src/process/run.rs | 26 ++-- src/rhaiexamples/install_nerdctl.rhai | 2 +- src/virt/nerdctl/container_functions.rs | 128 +++++++++++++++++ src/virt/nerdctl/container_test.rs | 179 ++++++++++++++++++++++++ src/virt/nerdctl/mod.rs | 4 +- 5 files changed, 324 insertions(+), 15 deletions(-) diff --git a/src/process/run.rs b/src/process/run.rs index 6c447d5..f0953fb 100644 --- a/src/process/run.rs +++ b/src/process/run.rs @@ -185,7 +185,7 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result) -> Result { 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 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) diff --git a/src/rhaiexamples/install_nerdctl.rhai b/src/rhaiexamples/install_nerdctl.rhai index cc8be8b..7b0656c 100644 --- a/src/rhaiexamples/install_nerdctl.rhai +++ b/src/rhaiexamples/install_nerdctl.rhai @@ -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") } diff --git a/src/virt/nerdctl/container_functions.rs b/src/virt/nerdctl/container_functions.rs index e69de29..9f736ed 100644 --- a/src/virt/nerdctl/container_functions.rs +++ b/src/virt/nerdctl/container_functions.rs @@ -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` - Command result or error +pub fn run( + image: &str, + name: Option<&str>, + detach: bool, + ports: Option<&[&str]>, + snapshotter: Option<&str>, +) -> Result { + 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` - Command result or error +pub fn exec(container: &str, command: &str) -> Result { + 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` - Command result or error +pub fn copy(source: &str, dest: &str) -> Result { + execute_nerdctl_command(&["cp", source, dest]) +} + +/// Stop a container +/// +/// # Arguments +/// +/// * `container` - Container name or ID +/// +/// # Returns +/// +/// * `Result` - Command result or error +pub fn stop(container: &str) -> Result { + execute_nerdctl_command(&["stop", container]) +} + +/// Remove a container +/// +/// # Arguments +/// +/// * `container` - Container name or ID +/// +/// # Returns +/// +/// * `Result` - Command result or error +pub fn remove(container: &str) -> Result { + execute_nerdctl_command(&["rm", container]) +} + +/// List containers +/// +/// # Arguments +/// +/// * `all` - Whether to list all containers (including stopped ones) +/// +/// # Returns +/// +/// * `Result` - Command result or error +pub fn list(all: bool) -> Result { + let mut args = vec!["ps"]; + + if all { + args.push("-a"); + } + + execute_nerdctl_command(&args) +} \ No newline at end of file diff --git a/src/virt/nerdctl/container_test.rs b/src/virt/nerdctl/container_test.rs index f857de2..a109e9f 100644 --- a/src/virt/nerdctl/container_test.rs +++ b/src/virt/nerdctl/container_test.rs @@ -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 { + 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 + } } \ No newline at end of file diff --git a/src/virt/nerdctl/mod.rs b/src/virt/nerdctl/mod.rs index 1ca625d..7612c3c 100644 --- a/src/virt/nerdctl/mod.rs +++ b/src/virt/nerdctl/mod.rs @@ -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}; \ No newline at end of file +pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; +pub use container_functions::*; \ No newline at end of file