diff --git a/src/os/fs.rs b/src/os/fs.rs index c87541a..f9df3d9 100644 --- a/src/os/fs.rs +++ b/src/os/fs.rs @@ -14,6 +14,7 @@ pub enum FsError { CopyFailed(io::Error), DeleteFailed(io::Error), CommandFailed(String), + CommandNotFound(String), CommandExecutionError(io::Error), InvalidGlobPattern(glob::PatternError), NotADirectory(String), @@ -36,6 +37,7 @@ impl fmt::Display for FsError { FsError::CopyFailed(e) => write!(f, "Failed to copy file: {}", e), FsError::DeleteFailed(e) => write!(f, "Failed to delete: {}", e), FsError::CommandFailed(e) => write!(f, "{}", e), + FsError::CommandNotFound(e) => write!(f, "Command not found: {}", e), FsError::CommandExecutionError(e) => write!(f, "Failed to execute command: {}", e), FsError::InvalidGlobPattern(e) => write!(f, "Invalid glob pattern: {}", e), FsError::NotADirectory(path) => write!(f, "Path '{}' exists but is not a directory", path), @@ -740,3 +742,105 @@ pub fn file_write_append(path: &str, content: &str) -> Result { Ok(format!("Successfully appended to file '{}'", path)) } + +/** + * Check if a command exists in the system PATH. + * + * # Arguments + * + * * `command` - The command to check + * + * # Returns + * + * * `String` - Empty string if the command doesn't exist, path to the command if it does + * + * # Examples + * + * ``` + * let cmd_path = which("ls"); + * if cmd_path != "" { + * println!("ls is available at: {}", cmd_path); + * } + * ``` + */ +pub fn which(command: &str) -> String { + // Use the appropriate command based on the platform + #[cfg(target_os = "windows")] + let output = Command::new("where") + .arg(command) + .output(); + + #[cfg(not(target_os = "windows"))] + let output = Command::new("which") + .arg(command) + .output(); + + match output { + Ok(out) => { + if out.status.success() { + let path = String::from_utf8_lossy(&out.stdout).trim().to_string(); + path + } else { + String::new() + } + }, + Err(_) => String::new(), + } +} + +/** + * Ensure that one or more commands exist in the system PATH. + * If any command doesn't exist, an error is thrown. + * + * # Arguments + * + * * `commands` - The command(s) to check, comma-separated for multiple commands + * + * # Returns + * + * * `Ok(String)` - A success message indicating all commands exist + * * `Err(FsError)` - An error if any command doesn't exist + * + * # Examples + * + * ``` + * // Check if a single command exists + * let result = cmd_ensure_exists("nerdctl")?; + * + * // Check if multiple commands exist + * let result = cmd_ensure_exists("nerdctl,docker,containerd")?; + * ``` + */ +pub fn cmd_ensure_exists(commands: &str) -> Result { + // Split the input by commas to handle multiple commands + let command_list: Vec<&str> = commands.split(',') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect(); + + if command_list.is_empty() { + return Err(FsError::CommandFailed("No commands specified to check".to_string())); + } + + let mut missing_commands = Vec::new(); + + // Check each command + for cmd in &command_list { + let cmd_path = which(cmd); + if cmd_path.is_empty() { + missing_commands.push(cmd.to_string()); + } + } + + // If any commands are missing, return an error + if !missing_commands.is_empty() { + return Err(FsError::CommandNotFound(missing_commands.join(", "))); + } + + // All commands exist + if command_list.len() == 1 { + Ok(format!("Command '{}' exists", command_list[0])) + } else { + Ok(format!("All commands exist: {}", command_list.join(", "))) + } +} diff --git a/src/rhai/nerdctl.rs b/src/rhai/nerdctl.rs index f95ce62..10861a5 100644 --- a/src/rhai/nerdctl.rs +++ b/src/rhai/nerdctl.rs @@ -95,6 +95,79 @@ pub fn container_with_health_check(mut container: Container, cmd: &str) -> Conta container.with_health_check(cmd) } +/// Add multiple port mappings to a Container +pub fn container_with_ports(mut container: Container, ports: Array) -> Container { + for port in ports.iter() { + if port.is_string() { + let port_str = port.clone().cast::(); + container = container.with_port(&port_str); + } + } + container +} + +/// Add multiple volume mounts to a Container +pub fn container_with_volumes(mut container: Container, volumes: Array) -> Container { + for volume in volumes.iter() { + if volume.is_string() { + let volume_str = volume.clone().cast::(); + container = container.with_volume(&volume_str); + } + } + container +} + +/// Add multiple environment variables to a Container +pub fn container_with_envs(mut container: Container, env_map: Map) -> Container { + for (key, value) in env_map.iter() { + if value.is_string() { + let value_str = value.clone().cast::(); + container = container.with_env(&key, &value_str); + } + } + container +} + +/// Add multiple network aliases to a Container +pub fn container_with_network_aliases(mut container: Container, aliases: Array) -> Container { + for alias in aliases.iter() { + if alias.is_string() { + let alias_str = alias.clone().cast::(); + container = container.with_network_alias(&alias_str); + } + } + container +} + +/// Set memory swap limit for a Container +pub fn container_with_memory_swap_limit(mut container: Container, memory_swap: &str) -> Container { + container.with_memory_swap_limit(memory_swap) +} + +/// Set CPU shares for a Container +pub fn container_with_cpu_shares(mut container: Container, shares: &str) -> Container { + container.with_cpu_shares(shares) +} + +/// Set health check with options for a Container +pub fn container_with_health_check_options( + mut 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) +} + +/// Set snapshotter for a Container +pub fn container_with_snapshotter(mut container: Container, snapshotter: &str) -> Container { + container.with_snapshotter(snapshotter) +} + /// Set detach mode for a Container pub fn container_with_detach(mut container: Container, detach: bool) -> Container { container.with_detach(detach) @@ -412,6 +485,14 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box Result<(), Box> engine.register_fn("file_write", file_write); engine.register_fn("file_write_append", file_write_append); + // Register command check functions + engine.register_fn("which", which); + engine.register_fn("cmd_ensure_exists", cmd_ensure_exists); + // Register download functions engine.register_fn("download", download); engine.register_fn("download_install", download_install); @@ -176,4 +180,19 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result Result> { os::download_install(url, min_size_kb).to_rhai_error() +} + +/// Wrapper for os::which +/// +/// Check if a command exists in the system PATH. +pub fn which(command: &str) -> String { + os::which(command) +} + +/// Wrapper for os::cmd_ensure_exists +/// +/// Ensure that one or more commands exist in the system PATH. +/// If any command doesn't exist, an error is thrown. +pub fn cmd_ensure_exists(commands: &str) -> Result> { + os::cmd_ensure_exists(commands).to_rhai_error() } \ No newline at end of file diff --git a/src/rhaiexamples/07_nerdctl_operations.rhai b/src/rhaiexamples/07_nerdctl_operations.rhai deleted file mode 100644 index e3ac8cc..0000000 --- a/src/rhaiexamples/07_nerdctl_operations.rhai +++ /dev/null @@ -1,100 +0,0 @@ -// 07_nerdctl_operations.rhai -// Demonstrates container operations using SAL's nerdctl integration -// Note: This script requires nerdctl to be installed and may need root privileges - -// Check if nerdctl is installed -let nerdctl_exists = which("nerdctl"); -println(`Nerdctl exists: ${nerdctl_exists}`); - -// List available images (only if nerdctl is installed) -println("Listing available container images:"); -if nerdctl_exists == "" { - println("Nerdctl is not installed. Please install it first."); - // You can use the install_nerdctl.rhai script to install nerdctl - // EXIT -} - -// List images -let images_result = nerdctl_images(); -println(`Images result: success=${images_result.success}, code=${images_result.code}`); -println(`Images output:\n${images_result.stdout}`); - -// Pull an image if needed -println("\nPulling alpine:latest image:"); -let pull_result = nerdctl_image_pull("alpine:latest"); -println(`Pull result: success=${pull_result.success}, code=${pull_result.code}`); -println(`Pull output: ${pull_result.stdout}`); - -// Create a container using the simple run function -println("\nCreating a container from alpine image:"); -let container_name = "rhai-nerdctl-test"; -let container = nerdctl_run("alpine:latest", container_name); -println(`Container result: success=${container.success}, code=${container.code}`); -println(`Container stdout: "${container.stdout}"`); -println(`Container stderr: "${container.stderr}"`); - -// Run a command in the container -println("\nRunning a command in the container:"); -let run_result = nerdctl_exec(container_name, "echo 'Hello from container'"); -println(`Command output: ${run_result.stdout}`); - -// Create a test file and copy it to the container -println("\nAdding a file to the container:"); -let test_file = "test_file.txt"; -run(`echo "Test content" > ${test_file}`); -let copy_result = nerdctl_copy(test_file, `${container_name}:/`); -println(`Copy result: ${copy_result.success}`); - -// Commit the container to create a new image -println("\nCommitting the container to create a new image:"); -let image_name = "my-custom-alpine:latest"; -let commit_result = nerdctl_image_commit(container_name, image_name); -println(`Commit result: ${commit_result.success}`); - -// Stop and remove the container -println("\nStopping the container:"); -let stop_result = nerdctl_stop(container_name); -println(`Stop result: ${stop_result.success}`); - -println("\nRemoving the container:"); -let remove_result = nerdctl_remove(container_name); -println(`Remove result: ${remove_result.success}`); - -// Clean up the test file -delete(test_file); - -// Demonstrate run options -println("\nDemonstrating run options:"); -let run_options = nerdctl_new_run_options(); -run_options.name = "rhai-nerdctl-options-test"; -run_options.detach = true; -run_options.ports = ["8080:80"]; -run_options.snapshotter = "native"; - -println("Run options configured:"); -println(` - Name: ${run_options.name}`); -println(` - Detach: ${run_options.detach}`); -println(` - Ports: ${run_options.ports}`); -println(` - Snapshotter: ${run_options.snapshotter}`); - -// Create a container with options -println("\nCreating a container with options:"); -let container_with_options = nerdctl_run_with_options("alpine:latest", run_options); -println(`Container with options result: ${container_with_options.success}`); - -// Clean up the container with options -println("\nCleaning up the container with options:"); -nerdctl_stop("rhai-nerdctl-options-test"); -nerdctl_remove("rhai-nerdctl-options-test"); - -// List all containers (including stopped ones) -println("\nListing all containers:"); -let list_result = nerdctl_list(true); -println(`List result: ${list_result.stdout}`); - -// Remove the custom image -println("\nRemoving the custom image:"); -let image_remove_result = nerdctl_image_remove(image_name); -println(`Image remove result: ${image_remove_result.success}`); - -"Nerdctl operations script completed successfully!" \ No newline at end of file diff --git a/src/rhaiexamples/08_nerdctl_web_server.rhai b/src/rhaiexamples/08_nerdctl_web_server.rhai deleted file mode 100644 index efe1344..0000000 --- a/src/rhaiexamples/08_nerdctl_web_server.rhai +++ /dev/null @@ -1,150 +0,0 @@ -// 08_nerdctl_web_server.rhai -// Demonstrates a complete workflow to set up a web server using nerdctl -// Note: This script requires nerdctl to be installed and may need root privileges - -// Check if nerdctl is installed -let nerdctl_exists = which("nerdctl"); -if nerdctl_exists == "" { - println("Nerdctl is not installed. Please install it first."); - // You can use the install_nerdctl.rhai script to install nerdctl - // EXIT -} - -println("Starting nerdctl web server workflow..."); - -// Step 1: Pull the nginx image -println("\n=== Pulling nginx:latest image ==="); -let pull_result = nerdctl_image_pull("nginx:latest"); -if !pull_result.success { - println(`Failed to pull nginx image: ${pull_result.stderr}`); - // EXIT -} -println("Successfully pulled nginx:latest image"); - -// Step 2: Create a custom nginx configuration file -println("\n=== Creating custom nginx configuration ==="); -let config_content = ` -server { - listen 80; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html; - } -} -`; - -let config_file = "custom-nginx.conf"; -run(`echo "${config_content}" > ${config_file}`); -println("Created custom nginx configuration file"); - -// Step 3: Create a custom index.html file -println("\n=== Creating custom index.html ==="); -let html_content = ` - - - - Rhai Nerdctl Demo - - - -

Hello from Rhai Nerdctl!

-

This page is served by an Nginx container created using the Rhai nerdctl wrapper.

-

Current time: ${now()}

- - -`; - -let html_file = "index.html"; -run(`echo "${html_content}" > ${html_file}`); -println("Created custom index.html file"); - -// Step 4: Create and run the nginx container with options -println("\n=== Creating nginx container ==="); -let container_name = "rhai-nginx-demo"; - -// First, try to remove any existing container with the same name -let _ = nerdctl_remove(container_name); - -let run_options = nerdctl_new_run_options(); -run_options.name = container_name; -run_options.detach = true; -run_options.ports = ["8080:80"]; -run_options.snapshotter = "native"; - -let container = nerdctl_run_with_options("nginx:latest", run_options); -if !container.success { - println(`Failed to create container: ${container.stderr}`); - // EXIT -} -println(`Successfully created container: ${container_name}`); - -// Step 5: Copy the custom files to the container -println("\n=== Copying custom files to container ==="); -let copy_config = nerdctl_copy(config_file, `${container_name}:/etc/nginx/conf.d/default.conf`); -if !copy_config.success { - println(`Failed to copy config file: ${copy_config.stderr}`); -} - -let copy_html = nerdctl_copy(html_file, `${container_name}:/usr/share/nginx/html/index.html`); -if !copy_html.success { - println(`Failed to copy HTML file: ${copy_html.stderr}`); -} -println("Successfully copied custom files to container"); - -// Step 6: Restart the container to apply changes -println("\n=== Restarting container to apply changes ==="); -let stop_result = nerdctl_stop(container_name); -if !stop_result.success { - println(`Failed to stop container: ${stop_result.stderr}`); -} - -let start_result = nerdctl_exec(container_name, "nginx -s reload"); -if !start_result.success { - println(`Failed to reload nginx: ${start_result.stderr}`); -} -println("Successfully restarted container"); - -// Step 7: Commit the container to create a custom image -println("\n=== Committing container to create custom image ==="); -let image_name = "rhai-nginx-custom:latest"; -let commit_result = nerdctl_image_commit(container_name, image_name); -if !commit_result.success { - println(`Failed to commit container: ${commit_result.stderr}`); -} -println(`Successfully created custom image: ${image_name}`); - -// Step 8: Display information about the running container -println("\n=== Container Information ==="); -println("The nginx web server is now running."); -println("You can access it at: http://localhost:8080"); -println("Container name: " + container_name); -println("Custom image: " + image_name); - -// Step 9: Clean up (commented out for demonstration purposes) -// println("\n=== Cleaning up ==="); -// nerdctl_stop(container_name); -// nerdctl_remove(container_name); -// nerdctl_image_remove(image_name); -// delete(config_file); -// delete(html_file); - -println("\nNerdctl web server workflow completed successfully!"); -println("The web server is running at http://localhost:8080"); -println("To clean up, run the following commands:"); -println(` nerdctl stop ${container_name}`); -println(` nerdctl rm ${container_name}`); -println(` nerdctl rmi ${image_name}`); - -"Nerdctl web server script completed successfully!" \ No newline at end of file diff --git a/src/rhaiexamples/install_nerdctl.rhai b/src/rhaiexamples/nerdctl_install.rhai similarity index 97% rename from src/rhaiexamples/install_nerdctl.rhai rename to src/rhaiexamples/nerdctl_install.rhai index 7b0656c..75bc2b6 100644 --- a/src/rhaiexamples/install_nerdctl.rhai +++ b/src/rhaiexamples/nerdctl_install.rhai @@ -2,7 +2,7 @@ fn nerdctl_download(){ - let name="nerctl"; + let name="nerdctl"; let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz"; download(url,`/tmp/${name}`,20); copy(`/tmp/${name}/*`,"/root/hero/bin/"); diff --git a/src/rhaiexamples/nerdctl_test.rhai b/src/rhaiexamples/nerdctl_test.rhai deleted file mode 100644 index 4506735..0000000 --- a/src/rhaiexamples/nerdctl_test.rhai +++ /dev/null @@ -1,189 +0,0 @@ -// nerdctl_test.rhai -// Tests the nerdctl wrapper functionality without requiring a running containerd daemon - -// Check if nerdctl is installed -let nerdctl_exists = which("nerdctl"); -println(`Nerdctl exists: ${nerdctl_exists}`); - -// Test creating run options -println("\nTesting run options creation:"); -let run_options = new_run_options(); -println(`Default run options created: ${run_options}`); -println(`- name: ${run_options.name}`); -println(`- detach: ${run_options.detach}`); -println(`- ports: ${run_options.ports}`); -println(`- snapshotter: ${run_options.snapshotter}`); - -// Modify run options -println("\nModifying run options:"); -run_options.name = "test-container"; -run_options.detach = false; -run_options.ports = ["8080:80", "8443:443"]; -run_options.snapshotter = "overlayfs"; -println(`Modified run options: ${run_options}`); -println(`- name: ${run_options.name}`); -println(`- detach: ${run_options.detach}`); -println(`- ports: ${run_options.ports}`); -println(`- snapshotter: ${run_options.snapshotter}`); - -// Test function availability -println("\nTesting function availability:"); -let functions = [ - // Legacy functions - "nerdctl_run", - "nerdctl_run_with_name", - "nerdctl_run_with_port", - "nerdctl_exec", - "nerdctl_copy", - "nerdctl_stop", - "nerdctl_remove", - "nerdctl_list", - "nerdctl_images", - "nerdctl_image_remove", - "nerdctl_image_push", - "nerdctl_image_tag", - "nerdctl_image_pull", - "nerdctl_image_commit", - "nerdctl_image_build", - - // Builder pattern functions - "nerdctl_container_new", - "nerdctl_container_from_image", - "with_port", - "with_volume", - "with_env", - "with_network", - "with_network_alias", - "with_cpu_limit", - "with_memory_limit", - "with_restart_policy", - "with_health_check", - "with_detach", - "build", - "start", - "stop", - "remove", - "exec", - "copy" -]; - -// Try to access each function (this will throw an error if the function doesn't exist) -for func in functions { - let exists = is_function_registered(func); - println(`Function ${func} registered: ${exists}`); -} - -// Helper function to get current timestamp in seconds -fn timestamp() { - // Use the current time in seconds since epoch as a unique identifier - return now(); -} - -// Test the builder pattern with actual container creation and execution -println("\nTesting container builder pattern with actual container:"); -try { - // Generate a unique container name based on timestamp - let container_name = "test-alpine-container"; - - // First, try to remove any existing container with this name - println(`Cleaning up any existing container named '${container_name}'...`); - try { - // Try to stop the container first (in case it's running) - nerdctl_stop(container_name); - println("Stopped existing container"); - } catch(e) { - println("No running container to stop"); - } - - try { - // Try to remove the container - nerdctl_remove(container_name); - println("Removed existing container"); - } catch(e) { - println("No container to remove"); - } - - // Create a container with builder pattern using Alpine image with a command that keeps it running - println("\nCreating new container from Alpine image..."); - let container = nerdctl_container_from_image(container_name, "alpine:latest"); - println(`Created container from image: ${container.name} (${container.image})`); - - // Configure the container - container = container - .with_port("8080:80") - .with_volume("/tmp:/data") - .with_env("TEST_ENV", "test_value") - .with_detach(true); - - // Print container properties before building - println("Container properties before building:"); - println(`- name: ${container.name}`); - println(`- image: ${container.image}`); - println(`- ports: ${container.ports}`); - println(`- volumes: ${container.volumes}`); - println(`- detach: ${container.detach}`); - - // Build the container - println("\nBuilding the container..."); - container = container.build(); - println(`Container built successfully with ID: ${container.container_id}`); - - // Start the container - println("\nStarting the container..."); - let start_result = container.start(); - println(`Start result: ${start_result.success}`); - - // Execute a command in the running container - println("\nExecuting command in the container..."); - let exec_result = container.exec("echo 'Hello from Alpine container!'"); - println(`Command output: ${exec_result.stdout}`); - println("\nExecuting command in the container..."); - let run_cmd = container.exec("echo 'Hello from Alpine container'"); - println(`Command output: ${run_cmd.stdout}`); - - // List all containers to verify it's running - println("\nListing all containers:"); - let list_result = nerdctl_list(true); - println(`Container list: ${list_result.stdout}`); - - // Stop and remove the container - println("\nStopping and removing the container..."); - let stop_result = container.stop(); - println(`Stop result: ${stop_result.success}`); - - let remove_result = container.remove(); - println(`Remove result: ${remove_result.success}`); - println("Container stopped and removed successfully"); - - // Return success message only if everything worked - return "Container builder pattern test completed successfully!"; -} catch(e) { - // Print the error and return a failure message - println(`ERROR: ${e}`); - - // Try to clean up if possible - try { - println("Attempting to clean up after error..."); - nerdctl_stop("test-alpine-container"); - nerdctl_remove("test-alpine-container"); - println("Cleanup completed"); - } catch(cleanup_error) { - println(`Cleanup failed: ${cleanup_error}`); - } - - // Return failure message - return "Container builder pattern test FAILED!"; -} - -// Helper function to check if a function is registered -fn is_function_registered(name) { - try { - // This will throw an error if the function doesn't exist - eval(`${name}`); - return true; - } catch { - return false; - } -} - -// The final result depends on the outcome of the container test \ No newline at end of file diff --git a/src/rhaiexamples/nerdctl_webserver copy.rhai b/src/rhaiexamples/nerdctl_webserver copy.rhai new file mode 100644 index 0000000..4e20b76 --- /dev/null +++ b/src/rhaiexamples/nerdctl_webserver copy.rhai @@ -0,0 +1,130 @@ +// 08_nerdctl_web_server.rhai +// Demonstrates a complete workflow to set up a web server using nerdctl +// Note: This script requires nerdctl to be installed and may need root privileges + +// Ensure nerdctl is installed +println("Checking if nerdctl is installed..."); +// Fix the typo in nerdctl and check all required commands +let result = cmd_ensure_exists("nerdctl,runc,buildah"); +println("All required commands are installed and available."); + +println("Starting nerdctl web server workflow..."); + +// Create and use a temporary directory for all files +let work_dir = "/tmp/nerdctl"; +mkdir(work_dir); +chdir(work_dir); +println(`Working in directory: ${work_dir}`); + + +println("\n=== Creating custom nginx configuration ==="); +let config_content = ` +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + } +} +`; + +let config_file = `${work_dir}/custom-nginx.conf`; +// Use file_write instead of run command +file_write(config_file, config_content); +println(`Created custom nginx configuration file at ${config_file}`); + +// Step 3: Create a custom index.html file +println("\n=== Creating custom index.html ==="); +let html_content = ` + + + + Rhai Nerdctl Demo + + + +

Hello from Rhai Nerdctl!

+

This page is served by an Nginx container created using the Rhai nerdctl wrapper.

+

Current time: ${now()}

+ + +`; + +let html_file = `${work_dir}/index.html`; +// Use file_write instead of run command +file_write(html_file, html_content); +println(`Created custom index.html file at ${html_file}`); + +println("\n=== Creating nginx container ==="); +let container_name = "rhai-nginx-demo"; + +// First, try to remove any existing container with the same name +nerdctl_remove(container_name); + +let env_map = #{}; // Create an empty map +env_map["NGINX_HOST"] = "localhost"; +env_map["NGINX_PORT"] = "80"; +env_map["NGINX_WORKER_PROCESSES"] = "auto"; +let network_aliases = ["web-server", "nginx-demo", "http-service"]; + +// Create a container with a rich set of options using batch methods +let container = nerdctl_container_from_image(container_name, "nginx:latest") + .with_detach(true) + .with_ports(["8080:80"]) // Add multiple ports at once + .with_volumes([`${work_dir}:/usr/share/nginx/html`, "/var/log:/var/log/nginx"]) // Mount our work dir + .with_envs(env_map) // Add multiple environment variables at once + .with_network("bridge") + .with_network_aliases(network_aliases) // Add multiple network aliases at once + .with_cpu_limit("1.0") + .with_memory_limit("512m") + .with_memory_swap_limit("1g") // New method + .with_cpu_shares("1024") // New method + // .with_restart_policy("unless-stopped") + // .with_snapshotter("native") + // Add health check with a multiline script +// .with_health_check_options( +// `#!/bin/bash +// # Health check script for nginx container +// # This script will check if the nginx server is responding correctly + +// # Try to connect to the server +// response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/ || echo "failed") + +// # Check the response +// if [ "$response" = "200" ]; then +// echo "Nginx is healthy" +// exit 0 +// else +// echo "Nginx is not healthy, got response: $response" +// exit 1 +// fi`, +// "5s", // Interval +// "3s", // Timeout +// 3, // Retries +// "10s" // Start period +// ); + +// Build and start the container +println("Building and starting the container..."); +let built_container = container.build(); +let start_result = built_container.start(); +println(`Container started: ${start_result.success}`); + +println(`Successfully created and started container: ${container_name}`); + +println("\nNerdctl web server workflow completed successfully!"); +println("The web server is running at http://localhost:8080"); + +"Nerdctl web server script completed successfully!" \ No newline at end of file diff --git a/src/rhaiexamples/nerdctl_webserver.rhai b/src/rhaiexamples/nerdctl_webserver.rhai new file mode 100644 index 0000000..33697de --- /dev/null +++ b/src/rhaiexamples/nerdctl_webserver.rhai @@ -0,0 +1,85 @@ +// 08__web_server.rhai +// Demonstrates a complete workflow to set up a web server using +// Note: This script requires to be installed and may need root privileges + +println("Starting web server workflow..."); + +// Create and use a temporary directory for all files +let work_dir = "/tmp/"; +mkdir(work_dir); +chdir(work_dir); +println(`Working in directory: ${work_dir}`); + + +println("\n=== Creating custom nginx configuration ==="); +let config_content = ` +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + } +} +`; + +let config_file = `${work_dir}/custom-nginx.conf`; +// Use file_write instead of run command +file_write(config_file, config_content); +println(`Created custom nginx configuration file at ${config_file}`); + +// Step 3: Create a custom index.html file +println("\n=== Creating custom index.html ==="); +let html_content = ` + + + + Demo + + + +

Hello from HeroScript !

+

This page is served by an Nginx container.

+ + +`; + +let html_file = `${work_dir}/index.html`; +// Use file_write instead of run command +file_write(html_file, html_content); +println(`Created custom index.html file at ${html_file}`); + +println("\n=== Creating nginx container ==="); +let container_name = "nginx-demo"; + +let env_map = #{}; // Create an empty map +env_map["NGINX_HOST"] = "localhost"; +env_map["NGINX_PORT"] = "80"; +env_map["NGINX_WORKER_PROCESSES"] = "auto"; + +// Create a container with a rich set of options using batch methods +let container = _container_from_image(container_name, "nginx:latest") + .reset() + .with_detach(true) + .with_ports(["8080:80"]) // Add multiple ports at once + .with_volumes([`${work_dir}:/usr/share/nginx/html`, "/var/log:/var/log/nginx"]) // Mount our work dir + .with_envs(env_map) // Add multiple environment variables at once + .with_cpu_limit("1.0") + .with_memory_limit("512m") + + +println("\n web server workflow completed successfully!"); +println("The web server is running at http://localhost:8080"); + +"Web server script completed successfully!" \ No newline at end of file diff --git a/src/virt/nerdctl/container.rs b/src/virt/nerdctl/container.rs index 8291188..f4ce3d2 100644 --- a/src/virt/nerdctl/container.rs +++ b/src/virt/nerdctl/container.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::process::CommandResult; use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; +use crate::os; use super::container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; impl Container { @@ -16,6 +17,15 @@ impl Container { /// /// * `Result` - Container instance or error pub fn new(name: &str) -> Result { + // Check if required commands exist + match os::cmd_ensure_exists("nerdctl,runc,buildah") { + Err(e) => return Err(NerdctlError::CommandExecutionFailed( + std::io::Error::new(std::io::ErrorKind::NotFound, + format!("Required commands not found: {}", e)) + )), + _ => {} + } + // Check if container exists let result = execute_nerdctl_command(&["ps", "-a", "--format", "{{.Names}} {{.ID}}"])?; diff --git a/src/virt/nerdctl/container_builder.rs b/src/virt/nerdctl/container_builder.rs index f79ee54..029cc8b 100644 --- a/src/virt/nerdctl/container_builder.rs +++ b/src/virt/nerdctl/container_builder.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; use super::container_types::{Container, HealthCheck}; +use super::health_check_script::prepare_health_check_command; impl Container { /// Add a port mapping @@ -220,8 +221,11 @@ impl Container { /// /// * `Self` - The container instance for method chaining pub fn with_health_check(mut self, cmd: &str) -> Self { + // Use the health check script module to prepare the command + let prepared_cmd = prepare_health_check_command(cmd, &self.name); + self.health_check = Some(HealthCheck { - cmd: cmd.to_string(), + cmd: prepared_cmd, interval: None, timeout: None, retries: None, @@ -251,8 +255,11 @@ impl Container { retries: Option, start_period: Option<&str>, ) -> Self { + // Use the health check script module to prepare the command + let prepared_cmd = prepare_health_check_command(cmd, &self.name); + let mut health_check = HealthCheck { - cmd: cmd.to_string(), + cmd: prepared_cmd, interval: None, timeout: None, retries: None, diff --git a/src/virt/nerdctl/container_operations.rs b/src/virt/nerdctl/container_operations.rs index 878f3ec..8de3ccf 100644 --- a/src/virt/nerdctl/container_operations.rs +++ b/src/virt/nerdctl/container_operations.rs @@ -288,18 +288,18 @@ impl Container { } 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 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())) } } diff --git a/src/virt/nerdctl/health_check_script.rs b/src/virt/nerdctl/health_check_script.rs new file mode 100644 index 0000000..b71e9d2 --- /dev/null +++ b/src/virt/nerdctl/health_check_script.rs @@ -0,0 +1,79 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/health_check_script.rs + +use std::fs; +use std::path::Path; +use std::os::unix::fs::PermissionsExt; + +/// Handles health check scripts for containers +/// +/// This module provides functionality to create and manage health check scripts +/// for containers, allowing for more complex health checks than simple commands. + +/// Converts a health check command or script to a usable command +/// +/// If the input is a single-line command, it is returned as is. +/// If the input is a multi-line script, it is written to a file in the +/// /root/hero/var/containers directory and the path to that file is returned. +/// +/// # Arguments +/// +/// * `cmd` - The command or script to convert +/// * `container_name` - The name of the container, used to create a unique script name +/// +/// # Returns +/// +/// * `String` - The command to use for the health check +pub fn prepare_health_check_command(cmd: &str, container_name: &str) -> String { + // If the command is a multiline script, write it to a file + if cmd.contains("\n") { + // Create the directory if it doesn't exist + let dir_path = "/root/hero/var/containers"; + if let Err(_) = fs::create_dir_all(dir_path) { + // If we can't create the directory, just use the command as is + return cmd.to_string(); + } + + // Create a unique filename based on container name + let script_path = format!("{}/healthcheck_{}.sh", dir_path, container_name); + + // Write the script to the file + if let Err(_) = fs::write(&script_path, cmd) { + // If we can't write the file, just use the command as is + return cmd.to_string(); + } + + // Make the script executable + if let Ok(metadata) = fs::metadata(&script_path) { + let mut perms = metadata.permissions(); + perms.set_mode(0o755); + if let Err(_) = fs::set_permissions(&script_path, perms) { + // If we can't set permissions, just use the script path with sh + return format!("sh {}", script_path); + } + } else { + // If we can't get metadata, just use the script path with sh + return format!("sh {}", script_path); + } + + // Use the script path as the command + script_path + } else { + // If it's a single line command, use it as is + cmd.to_string() + } +} + +/// Cleans up health check scripts for a container +/// +/// # Arguments +/// +/// * `container_name` - The name of the container whose health check scripts should be cleaned up +pub fn cleanup_health_check_scripts(container_name: &str) { + let dir_path = "/root/hero/var/containers"; + let script_path = format!("{}/healthcheck_{}.sh", dir_path, container_name); + + // Try to remove the script file if it exists + if Path::new(&script_path).exists() { + let _ = fs::remove_file(script_path); + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/mod.rs b/src/virt/nerdctl/mod.rs index 7612c3c..00c2e98 100644 --- a/src/virt/nerdctl/mod.rs +++ b/src/virt/nerdctl/mod.rs @@ -4,6 +4,7 @@ mod container_types; mod container; mod container_builder; mod health_check; +mod health_check_script; mod container_operations; mod container_functions; #[cfg(test)] @@ -52,4 +53,5 @@ impl Error for NerdctlError { pub use images::*; pub use cmd::*; pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; -pub use container_functions::*; \ No newline at end of file +pub use container_functions::*; +pub use health_check_script::*; \ No newline at end of file