diff --git a/container_builder_implementation_plan.md b/container_builder_implementation_plan.md new file mode 100644 index 0000000..06b33d1 --- /dev/null +++ b/container_builder_implementation_plan.md @@ -0,0 +1,238 @@ +# Container Builder Implementation Plan + +## Overview + +This document outlines the plan for redesigning the nerdctl interface in the `src/virt/nerdctl` directory to use an object-oriented approach with a Container struct that supports method chaining for the builder pattern. This will replace the existing function-based approach while maintaining all current functionality. + +## Architecture + +```mermaid +classDiagram + class Container { + -String name + -String container_id + -String? image + -HashMap~String, String~ config + -Vec~String~ ports + -Vec~String~ volumes + -HashMap~String, String~ env_vars + -Option~String~ network + -Vec~String~ network_aliases + -Option~String~ cpu_limit + -Option~String~ memory_limit + -Option~String~ memory_swap_limit + -Option~String~ cpu_shares + -Option~String~ restart_policy + -Option~HealthCheck~ health_check + +new(name: &str) -> Result~Container, NerdctlError~ + +from_image(name: &str, image: &str) -> Result~Container, NerdctlError~ + +with_port(port: &str) -> Container + +with_ports(ports: &[&str]) -> Container + +with_volume(volume: &str) -> Container + +with_volumes(volumes: &[&str]) -> Container + +with_env(key: &str, value: &str) -> Container + +with_envs(env_map: &HashMap<&str, &str>) -> Container + +with_network(network: &str) -> Container + +with_network_alias(alias: &str) -> Container + +with_network_aliases(aliases: &[&str]) -> Container + +with_cpu_limit(cpus: &str) -> Container + +with_memory_limit(memory: &str) -> Container + +with_memory_swap_limit(memory_swap: &str) -> Container + +with_cpu_shares(shares: &str) -> Container + +with_restart_policy(policy: &str) -> Container + +with_health_check(cmd: &str) -> Container + +with_health_check_options(cmd: &str, interval: Option<&str>, timeout: Option<&str>, retries: Option, start_period: Option<&str>) -> Container + +with_snapshotter(snapshotter: &str) -> Container + +with_detach(detach: bool) -> Container + +build() -> Result~Container, NerdctlError~ + +start() -> Result~CommandResult, NerdctlError~ + +stop() -> Result~CommandResult, NerdctlError~ + +remove() -> Result~CommandResult, NerdctlError~ + +exec(command: &str) -> Result~CommandResult, NerdctlError~ + +copy(source: &str, dest: &str) -> Result~CommandResult, NerdctlError~ + +export(path: &str) -> Result~CommandResult, NerdctlError~ + +commit(image_name: &str) -> Result~CommandResult, NerdctlError~ + +status() -> Result~ContainerStatus, NerdctlError~ + +health_status() -> Result~String, NerdctlError~ + +resources() -> Result~ResourceUsage, NerdctlError~ + } + + class HealthCheck { + +String cmd + +Option~String~ interval + +Option~String~ timeout + +Option~u32~ retries + +Option~String~ start_period + } + + class ContainerStatus { + +String state + +String status + +String created + +String started + +Option~String~ health_status + +Option~String~ health_output + } + + class ResourceUsage { + +String cpu_usage + +String memory_usage + +String memory_limit + +String memory_percentage + +String network_input + +String network_output + +String block_input + +String block_output + +String pids + } + + class NerdctlError { + +CommandExecutionFailed(io::Error) + +CommandFailed(String) + +JsonParseError(String) + +ConversionError(String) + +Other(String) + } + + Container --> ContainerStatus : returns + Container --> ResourceUsage : returns + Container --> HealthCheck : contains + Container --> NerdctlError : may throw +``` + +## Implementation Steps + +### 1. Create Container Struct and Basic Methods + +Create a new file `src/virt/nerdctl/container.rs` with the Container struct and basic methods. + +### 2. Implement Builder Pattern Methods + +Add builder pattern methods to the Container struct for configuration. + +### 3. Implement Container Operations + +Add methods for container operations like start, stop, exec, etc. + +### 4. Implement Status and Resource Usage Methods + +Add methods for getting container status and resource usage information. + +### 5. Update mod.rs to Export the New Container Struct + +Update `src/virt/nerdctl/mod.rs` to include the new container module. + +### 6. Create Example Usage + +Create an example file to demonstrate the new Container API. + +## Key Features + +### Container Creation and Configuration + +- Method chaining for the builder pattern +- Support for multiple ports and volumes +- Environment variable configuration +- Network configuration and aliases +- Resource limits (CPU, memory) +- Restart policies +- Health checks + +### Container Operations + +- Start, stop, and remove containers +- Execute commands in containers +- Copy files between container and host +- Export containers to tarballs +- Commit containers to images + +### Container Monitoring + +- Get container status information +- Get container health status +- Get resource usage information + +## Example Usage + +```rust +// Create a container with various configuration options +let container = Container::from_image("my-web-app", "nginx:latest")? + .with_ports(&["8080:80", "8443:443"]) + .with_volumes(&[ + "./html:/usr/share/nginx/html", + "./config/nginx.conf:/etc/nginx/nginx.conf" + ]) + .with_env("NGINX_HOST", "example.com") + .with_env("NGINX_PORT", "80") + .with_network("app-network") + .with_network_alias("web") + .with_cpu_limit("0.5") + .with_memory_limit("512m") + .with_restart_policy("always") + .with_health_check_options( + "curl -f http://localhost/ || exit 1", + Some("10s"), + Some("5s"), + Some(3), + Some("30s") + ) + .with_detach(true) + .build()?; + +// Start the container +container.start()?; + +// Execute a command in the container +let result = container.exec("echo 'Hello from container'")?; +println!("Command output: {}", result.stdout); + +// Get container status +let status = container.status()?; +println!("Container state: {}", status.state); +println!("Container status: {}", status.status); + +// Get resource usage +let resources = container.resources()?; +println!("CPU usage: {}", resources.cpu_usage); +println!("Memory usage: {}", resources.memory_usage); + +// Stop and remove the container +container.stop()?; +container.remove()?; +``` + +## Network Management + +```rust +// Create a network +Container::create_network("app-network", Some("bridge"))?; + +// Create containers in the network +let db = Container::from_image("db", "postgres:latest")? + .with_network("app-network") + .with_network_alias("database") + .with_env("POSTGRES_PASSWORD", "example") + .build()?; + +let app = Container::from_image("app", "my-app:latest")? + .with_network("app-network") + .with_env("DATABASE_URL", "postgres://postgres:example@database:5432/postgres") + .build()?; + +// Remove the network when done +Container::remove_network("app-network")?; +``` + +## Migration Strategy + +1. Create the new Container struct and its methods +2. Update the mod.rs file to export the new Container struct +3. Create example usage to demonstrate the new API +4. Deprecate the old function-based API (but keep it for backward compatibility) +5. Update documentation to reflect the new API + +## Testing Strategy + +1. Unit tests for the Container struct and its methods +2. Integration tests for the Container API +3. Manual testing with real containers diff --git a/examples/buildah.rs b/examples/buildah.rs deleted file mode 100644 index 2648f6b..0000000 --- a/examples/buildah.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Example usage of the buildah module -//! -//! This file demonstrates how to use the buildah module to perform -//! common container operations like creating containers, running commands, -//! and managing images. - -use sal::virt::buildah::{BuildahError, Builder}; -use std::collections::HashMap; - -/// Run a complete buildah workflow example -pub fn run_buildah_example() -> Result<(), BuildahError> { - println!("Starting buildah example workflow..."); - - // Step 1: Create a container from an image using the Builder - println!("\n=== Creating container from fedora:latest ==="); - let mut builder = Builder::new("my-fedora-container", "fedora:latest")?; - - // Reset the builder to remove any existing container - println!("\n=== Resetting the builder to start fresh ==="); - builder.reset()?; - - // Create a new container (or continue with existing one) - println!("\n=== Creating container from fedora:latest ==="); - builder = Builder::new("my-fedora-container", "fedora:latest")?; - println!("Created container: {}", builder.container_id().unwrap_or(&"unknown".to_string())); - - // Step 2: Run a command in the container - println!("\n=== Installing nginx in container ==="); - // Use chroot isolation to avoid BPF issues - let install_result = builder.run_with_isolation("dnf install -y nginx", "chroot")?; - println!("{:#?}", install_result); - println!("Installation output: {}", install_result.stdout); - - // Step 3: Copy a file into the container - println!("\n=== Copying configuration file to container ==="); - builder.copy("./example.conf", "/etc/example.conf")?; - - // Step 4: Configure container metadata - println!("\n=== Configuring container metadata ==="); - let mut config_options = HashMap::new(); - config_options.insert("port".to_string(), "80".to_string()); - config_options.insert("label".to_string(), "maintainer=example@example.com".to_string()); - config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string()); - builder.config(config_options)?; - println!("Container configured"); - - // Step 5: Commit the container to create a new image - println!("\n=== Committing container to create image ==="); - let image_name = "my-nginx:latest"; - builder.commit(image_name)?; - println!("Created image: {}", image_name); - - // Step 6: List images to verify our new image exists - println!("\n=== Listing images ==="); - let images = Builder::images()?; - println!("Found {} images:", images.len()); - for image in images { - println!(" ID: {}", image.id); - println!(" Names: {}", image.names.join(", ")); - println!(" Size: {}", image.size); - println!(" Created: {}", image.created); - println!(); - } - - // Step 7: Clean up (optional in a real workflow) - println!("\n=== Cleaning up ==="); - Builder::image_remove(image_name)?; - builder.remove()?; - - println!("\nBuildah example workflow completed successfully!"); - Ok(()) -} - -/// Demonstrate how to build an image from a Containerfile/Dockerfile -pub fn build_image_example() -> Result<(), BuildahError> { - println!("Building an image from a Containerfile..."); - - // Use the build function with tag, context directory, and isolation to avoid BPF issues - let result = Builder::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?; - - println!("Build output: {}", result.stdout); - println!("Image built successfully!"); - - Ok(()) -} - -/// Example of pulling and pushing images -pub fn registry_operations_example() -> Result<(), BuildahError> { - println!("Demonstrating registry operations..."); - - // Pull an image - println!("\n=== Pulling an image ==="); - Builder::image_pull("docker.io/library/alpine:latest", true)?; - println!("Image pulled successfully"); - - // Tag the image - println!("\n=== Tagging the image ==="); - Builder::image_tag("alpine:latest", "my-alpine:v1.0")?; - println!("Image tagged successfully"); - - // Push an image (this would typically go to a real registry) - // println!("\n=== Pushing an image (example only) ==="); - // println!("In a real scenario, you would push to a registry with:"); - // println!("Builder::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)"); - - Ok(()) -} - -/// Main function to run all examples -pub fn run_all_examples() -> Result<(), BuildahError> { - println!("=== BUILDAH MODULE EXAMPLES ===\n"); - - run_buildah_example()?; - build_image_example()?; - registry_operations_example()?; - - Ok(()) -} - -fn main() { - let _ = run_all_examples().unwrap(); -} \ No newline at end of file diff --git a/examples/container_example.rs b/examples/container_example.rs new file mode 100644 index 0000000..d572b3d --- /dev/null +++ b/examples/container_example.rs @@ -0,0 +1,62 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/examples/container_example.rs + +use std::error::Error; +use sal::virt::nerdctl::Container; + +fn main() -> Result<(), Box> { + // Create a container from an image + println!("Creating container from image..."); + let container = Container::from_image("my-nginx", "nginx:latest")? + .with_port("8080:80") + .with_env("NGINX_HOST", "example.com") + .with_volume("/tmp/nginx:/usr/share/nginx/html") + .with_health_check("curl -f http://localhost/ || exit 1") + .with_detach(true) + .build()?; + + println!("Container created successfully"); + + // Execute a command in the container + println!("Executing command in container..."); + let result = container.exec("echo 'Hello from container'")?; + println!("Command output: {}", result.stdout); + + // Get container status + println!("Getting container status..."); + let status = container.status()?; + println!("Container status: {}", status.status); + + // Get resource usage + println!("Getting resource usage..."); + let resources = container.resources()?; + println!("CPU usage: {}", resources.cpu_usage); + println!("Memory usage: {}", resources.memory_usage); + + // Stop and remove the container + println!("Stopping and removing container..."); + container.stop()?; + container.remove()?; + + println!("Container stopped and removed"); + + // Get a container by name (if it exists) + println!("\nGetting a container by name..."); + match Container::new("existing-container") { + Ok(container) => { + if container.container_id.is_some() { + println!("Found container with ID: {}", container.container_id.unwrap()); + + // Perform operations on the existing container + let status = container.status()?; + println!("Container status: {}", status.status); + } else { + println!("Container exists but has no ID"); + } + }, + Err(e) => { + println!("Error getting container: {}", e); + } + } + + Ok(()) +} \ No newline at end of file diff --git a/examples/nerdctl.rs b/examples/nerdctl.rs deleted file mode 100644 index b9757df..0000000 --- a/examples/nerdctl.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Example usage of the nerdctl module -//! -//! This file demonstrates how to use the nerdctl module to perform -//! common container operations like creating containers, running commands, -//! and managing images. - -use sal::virt::nerdctl::{self, NerdctlError}; - -/// Run a complete nerdctl workflow example -pub fn run_nerdctl_example() -> Result<(), NerdctlError> { - println!("Starting nerdctl example workflow..."); - - // Step 1: Pull an image - println!("\n=== Pulling nginx:latest image ==="); - let pull_result = nerdctl::image_pull("nginx:latest")?; - println!("Pull output: {}", pull_result.stdout); - - // Step 2: Create a container from the image - println!("\n=== Creating container from nginx:latest ==="); - // Use "native" snapshotter to avoid overlay mount issues - let run_result = nerdctl::run("nginx:latest", Some("my-nginx"), true, Some(&["8080:80"]), Some("native"))?; - println!("Container created: {}", run_result.stdout.trim()); - let container_id = "my-nginx"; // Using the name we specified - - // Step 3: Execute a command in the container - println!("\n=== Installing curl in container ==="); - let update_result = nerdctl::exec(container_id, "apt-get update")?; - println!("Update output: {}", update_result.stdout); - - let install_result = nerdctl::exec(container_id, "apt-get install -y curl")?; - println!("Installation output: {}", install_result.stdout); - - // Step 4: Copy a file into the container (assuming nginx.conf exists) - println!("\n=== Copying configuration file to container ==="); - nerdctl::copy("./nginx.conf", format!("{}:/etc/nginx/nginx.conf", container_id).as_str())?; - - // Step 5: Commit the container to create a new image - println!("\n=== Committing container to create image ==="); - let image_name = "my-custom-nginx:latest"; - nerdctl::image_commit(container_id, image_name)?; - println!("Created image: {}", image_name); - - // Step 6: Stop and remove the container - println!("\n=== Stopping and removing container ==="); - nerdctl::stop(container_id)?; - nerdctl::remove(container_id)?; - println!("Container stopped and removed"); - - // Step 7: Create a new container from our custom image - println!("\n=== Creating container from custom image ==="); - // Use "native" snapshotter to avoid overlay mount issues - nerdctl::run(image_name, Some("nginx-custom"), true, Some(&["8081:80"]), Some("native"))?; - println!("Custom container created"); - - // Step 8: List images - println!("\n=== Listing images ==="); - let images_result = nerdctl::images()?; - println!("Images: \n{}", images_result.stdout); - - // Step 9: Clean up (optional in a real workflow) - println!("\n=== Cleaning up ==="); - nerdctl::stop("nginx-custom")?; - nerdctl::remove("nginx-custom")?; - nerdctl::image_remove(image_name)?; - - println!("\nNerdctl example workflow completed successfully!"); - Ok(()) -} - -/// Main function to run all examples -pub fn run_all_examples() -> Result<(), NerdctlError> { - println!("=== NERDCTL MODULE EXAMPLES ===\n"); - - run_nerdctl_example()?; - - println!("\nNote that these examples require nerdctl to be installed on your system"); - println!("and may require root/sudo privileges depending on your setup."); - - Ok(()) -} - -fn main() { - let _ = run_all_examples().unwrap(); -} \ No newline at end of file diff --git a/examples/rhai_buildah_example.rs b/examples/rhai_buildah_example.rs deleted file mode 100644 index dfafeb3..0000000 --- a/examples/rhai_buildah_example.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Example of using the Rhai integration with SAL Buildah module -//! -//! This example demonstrates how to use the Rhai scripting language -//! with the System Abstraction Layer (SAL) Buildah module. - -use rhai::Engine; -use std::error::Error; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register println function - engine.register_fn("println", |s: &str| println!("{}", s)); - - // Register SAL functions with the engine - sal::rhai::register(&mut engine)?; - - // Run a Rhai script that uses SAL Buildah functions - let script = r#" - // List available images - println("Listing available images:"); - let images = buildah_images(); - println("Found " + images.len() + " images"); - - // Create a container from an image (uncomment if you have a valid image) - // let container = buildah_from("alpine:latest"); - // println("Created container: " + container.stdout.trim()); - - // Build an image using options - let build_options = buildah_new_build_options(); - build_options.tag = "example-image:latest"; - build_options.context_dir = "."; - build_options.file = "example_Dockerfile"; - - println("Building image with options:"); - println(" Tag: " + build_options.tag); - println(" Context: " + build_options.context_dir); - println(" Dockerfile: " + build_options.file); - - // Uncomment to actually build the image - // let build_result = buildah_build(build_options); - // println("Build result: " + build_result.success); - - // Create a container configuration - let config_options = buildah_new_config_options(); - config_options.author = "Rhai Example"; - config_options.cmd = "/bin/sh -c 'echo Hello from Buildah'"; - - println("Container config options:"); - println(" Author: " + config_options.author); - println(" Command: " + config_options.cmd); - - // Commit options - let commit_options = buildah_new_commit_options(); - commit_options.format = "docker"; - commit_options.squash = true; - commit_options.rm = true; - - println("Commit options:"); - println(" Format: " + commit_options.format); - println(" Squash: " + commit_options.squash); - println(" Remove container: " + commit_options.rm); - - // Return success - true - "#; - - // Evaluate the script - let result = engine.eval::(script)?; - println!("Script execution successful: {}", result); - - Ok(()) -} \ No newline at end of file diff --git a/examples/rhai_git_example.rs b/examples/rhai_git_example.rs deleted file mode 100644 index d3c666c..0000000 --- a/examples/rhai_git_example.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Example of using the Git module with Rhai -//! -//! This example demonstrates how to use the Git module functions -//! through the Rhai scripting language. - -use sal::rhai::{self, Engine}; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - rhai::register(&mut engine)?; - - // Run a Rhai script that uses Git functions - let script = r#" - // Print a header - print("=== Testing Git Module Functions ===\n"); - - // Test git_list function - print("Listing git repositories..."); - let repos = git_list(); - print(`Found ${repos.len()} repositories`); - - // Print the first few repositories - if repos.len() > 0 { - print("First few repositories:"); - let count = if repos.len() > 3 { 3 } else { repos.len() }; - for i in range(0, count) { - print(` - ${repos[i]}`); - } - } - - // Test find_matching_repos function - if repos.len() > 0 { - print("\nTesting repository search..."); - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - - print(`Searching for repositories containing "${repo_name}"`); - let matching = find_matching_repos(repo_name); - - print(`Found ${matching.len()} matching repositories`); - for repo in matching { - print(` - ${repo}`); - } - - // Check if a repository has changes - print("\nChecking for changes in repository..."); - let has_changes = git_has_changes(repo_path); - print(`Repository ${repo_path} has changes: ${has_changes}`); - } - - print("\n=== Git Module Test Complete ==="); - "#; - - // Evaluate the script - match engine.eval::<()>(script) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Script execution error: {}", e), - } - - Ok(()) -} \ No newline at end of file diff --git a/examples/rhai_process_example.rs b/examples/rhai_process_example.rs deleted file mode 100644 index 8adbd81..0000000 --- a/examples/rhai_process_example.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Example of using the Rhai integration with SAL Process module -//! -//! This example demonstrates how to use the Rhai scripting language -//! with the System Abstraction Layer (SAL) Process module. - -use rhai::Engine; -use std::error::Error; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - sal::rhai::register(&mut engine)?; - - // Run a Rhai script that uses SAL Process functions - let script = r#" - // Check if a command exists - let ls_exists = which("ls"); - println("ls command exists: " + ls_exists); - - // Run a simple command - let echo_result = run_command("echo 'Hello from Rhai!'"); - println("Echo command output: " + echo_result.stdout); - println("Echo command success: " + echo_result.success); - - // Run a command silently - let silent_result = run_silent("ls -la"); - println("Silent command success: " + silent_result.success); - - // Run a command with custom options using a Map - let options = new_run_options(); - options["die"] = false; // Don't return error if command fails - options["silent"] = true; // Suppress output to stdout/stderr - options["async_exec"] = false; // Run synchronously - options["log"] = true; // Log command execution - - let custom_result = run("echo 'Custom options'", options); - println("Custom command success: " + custom_result.success); - - // List processes - let processes = process_list(""); - println("Number of processes: " + processes.len()); - - // Return success - true - "#; - - // Evaluate the script - let result = engine.eval::(script)?; - println!("Script execution successful: {}", result); - - Ok(()) -} \ No newline at end of file diff --git a/rhaiexamples/git_match_test.rhai b/rhaiexamples/git_match_test.rhai deleted file mode 100644 index 8436dd7..0000000 --- a/rhaiexamples/git_match_test.rhai +++ /dev/null @@ -1,135 +0,0 @@ -// Test script for Git partial path matching functionality - -print("===== Git Path Matching Test ====="); - -// Test git availability -print("\n=== Checking Git Availability ==="); -let git_cmd = which("git"); -if git_cmd != false { - print(`Git is available at: ${git_cmd}`); -} else { - print("WARNING: Git is not installed. Tests will be skipped."); - return "Git not available - test skipped"; -} - -// Helper function for test assertions -fn assert(condition, message) { - if (condition == false) { - print(`FAILED: ${message}`); - throw `Assertion failed: ${message}`; - } else { - print(`PASSED: ${message}`); - } -} - -print("\n=== Setting up test repositories ==="); -// Get current repos from git_list -let repos = git_list(); -print(`Found ${repos.len()} local git repositories for testing`); - -if repos.len() == 0 { - print("No repositories found for testing. Creating a test repo..."); - // Create a test repo in a temporary directory - let test_dir = "~/tmp_test_repo"; - print(`Creating test directory: ${test_dir}`); - - // Initialize a git repo with run commands - let result = run_silent(` - mkdir -p ${test_dir}/test1 - cd ${test_dir}/test1 - git init - echo "Test content" > README.md - git add README.md - git config --global user.email "test@example.com" - git config --global user.name "Test User" - git commit -m "Initial commit" - `); - - if result.success { - print("Created test repository successfully"); - // Update the repos list - repos = git_list(); - print(`Now found ${repos.len()} local git repositories`); - } else { - print("Failed to create test repository"); - return "Test setup failed - could not create test repository"; - } -} - -// Simple function to get just the repo name from the path for easier debugging -fn get_repo_name(path) { - let parts = path.split("/"); - return parts[parts.len()-1]; -} - -print("\n=== Test 1: Testing git_update with exact path ==="); -// Using the first repo in the list for testing -let first_repo = repos[0]; -print(`Using repository: ${first_repo}`); -print(`Repository name: ${get_repo_name(first_repo)}`); - -// Test with exact path -let exact_result = git_update(first_repo); -print(`Result with exact path: ${exact_result}`); - -// Check if the result was as expected -// Note: The function might find multiple repos even with exact path -// so we also check for that case -let is_success = exact_result.contains("up to date") || - exact_result.contains("updated") || - exact_result.contains("has local changes"); - -let multiple_match = exact_result.contains("Multiple repositories"); - -assert(is_success || multiple_match, - "git_update with path should succeed, indicate local changes, or report multiple matches"); - -print("\n=== Test 2: Testing git_update with partial path ==="); -// Extract part of the path (last part of the path) -let repo_name = get_repo_name(first_repo); -print(`Testing partial match with: ${repo_name}`); -let partial_result = git_update(repo_name); -print(`Result with partial path: ${partial_result}`); - -// Check if the result was as expected - similar to exact path test -let partial_success = partial_result.contains("up to date") || - partial_result.contains("updated") || - partial_result.contains("has local changes"); - -let partial_multiple = partial_result.contains("Multiple repositories"); - -assert(partial_success || partial_multiple, - "git_update with partial path should succeed, indicate local changes, or report multiple matches"); - -print("\n=== Test 3: Testing git_update with non-existent path ==="); -let fake_repo = "this_repo_does_not_exist_anywhere"; -print(`Testing with non-existent path: ${fake_repo}`); -let nonexist_result = git_update(fake_repo); -print(`Result with non-existent path: ${nonexist_result}`); - -// Check that it properly reports an error -assert(nonexist_result.contains("No repositories found"), - "git_update with non-existent path should indicate no matching repos"); - -print("\n=== Test 4: Testing wildcard matching ==="); -// Try to find a common substring in multiple repos -// For this test, we'll use a known path pattern likely to match multiple repos -let common_part = "code"; -print(`Testing wildcard match with: ${common_part}*`); - -let wildcard_result = git_update(common_part + "*"); -print(`Result with wildcard: ${wildcard_result}`); - -// For wildcard, we expect at least one match but not an error -// Implementation might return the first match or a success message -let wildcard_success = wildcard_result.contains("up to date") || - wildcard_result.contains("updated") || - wildcard_result.contains("has local changes"); - -// Just check that it didn't report no matches found -assert(!wildcard_result.contains("No repositories found"), - "Wildcard matching should find at least one repository"); - -print("\n===== All Git path matching tests completed! ====="); - -"Git path matching functionality works correctly" diff --git a/rhaiexamples/git_test.rhai b/rhaiexamples/git_test.rhai deleted file mode 100644 index 8cd8c6e..0000000 --- a/rhaiexamples/git_test.rhai +++ /dev/null @@ -1,124 +0,0 @@ -// Git operations test script (primarily focused on validation) -// Note: Many git operations are destructive or require network access, -// so this test primarily validates availability and URL handling. - -print("===== Git Operations Test ====="); - -// Test git availability -print("\n=== Test Git Availability ==="); -let git_cmd = which("git"); -if git_cmd { - print(`Git is available at: ${git_cmd}`); -} else { - print("WARNING: Git is not installed. Some tests will be skipped."); -} - -// Test git URL parsing (testing internal implementation through git operations) -print("\n=== Test Git URL Parsing ==="); -// HTTPS URLs -let https_urls = [ - "https://github.com/user/repo.git", - "https://github.com/user/repo", - "https://example.com/user/repo.git" -]; - -print("Testing HTTPS GitHub URLs:"); -for url in https_urls { - // For testing purposes, we'll use the URL rather than actually cloning - // Just check if git_clone responds with "already exists" message which indicates - // it recognized and parsed the URL correctly - let result = git_clone(url); - - // Check if the result contains a path with expected structure - let contains_path = result.contains("/code/") && - (result.contains("github.com") || result.contains("example.com")); - - print(` URL: ${url}`); - print(` Path structure valid: ${contains_path ? "yes" : "no"}`); - - // Extract the expected path components from the parsing - if contains_path { - let parts = result.split("/"); - let domain_part = ""; - let user_part = ""; - let repo_part = ""; - - let found_code = false; - for i in 0..parts.len() { - if parts[i] == "code" && (i+3) < parts.len() { - domain_part = parts[i+1]; - user_part = parts[i+2]; - repo_part = parts[i+3]; - found_code = true; - break; - } - } - - if found_code { - print(` Parsed domain: ${domain_part}`); - print(` Parsed user: ${user_part}`); - print(` Parsed repo: ${repo_part}`); - } - } -} - -// SSH URLs -let ssh_urls = [ - "git@github.com:user/repo.git", - "git@example.com:organization/repository.git" -]; - -print("\nTesting SSH Git URLs:"); -for url in ssh_urls { - // Similar approach to HTTPS testing - let result = git_clone(url); - let contains_path = result.contains("/code/"); - - print(` URL: ${url}`); - print(` Path structure valid: ${contains_path ? "yes" : "no"}`); -} - -// Test git_list -print("\n=== Test git_list() ==="); -let repos = git_list(); -print(`Found ${repos.len()} local git repositories`); -if repos.len() > 0 { - print("First 3 repositories (or fewer if less available):"); - let max = Math.min(3, repos.len()); - for i in 0..max { - print(` ${i+1}. ${repos[i]}`); - } -} - -// Test git command access through run -print("\n=== Test Git Command Access ==="); -if git_cmd { - let git_version = run("git --version"); - print(`Git version info: ${git_version.stdout}`); - - // Test git status command (this is safe and doesn't modify anything) - let git_status = run("git status"); - print("Git status command:"); - print(` Exit code: ${git_status.code}`); - if git_status.success { - print(" Git status executed successfully in current directory"); - } else { - print(" Git status failed. This might not be a git repository."); - } -} - - - -// Minimal testing of other git operations (these are mostly destructive) -print("\n=== Git Operations Availability Check ==="); -print("The following git operations are available:"); -print(" - git_clone: Available (tested above)"); -print(" - git_list: Available (tested above)"); -print(" - git_update: Available (not tested to avoid modifying repos)"); -print(" - git_update_force: Available (not tested to avoid modifying repos)"); -print(" - git_update_commit: Available (not tested to avoid modifying repos)"); -print(" - git_update_commit_push: Available (not tested to avoid modifying repos)"); - - -print("\nGit Operations Test completed!"); -"Git Test Success" diff --git a/src/docs/rhai/git.md b/src/docs/rhai/git.md index 9421334..26c38b2 100644 --- a/src/docs/rhai/git.md +++ b/src/docs/rhai/git.md @@ -177,6 +177,7 @@ print("All repositories:"); for repo_path in all_repos { print(` - ${repo_path}`); } +``` ## Error Handling diff --git a/src/examples/git_test.rs b/src/examples/git_test.rs deleted file mode 100644 index f6b7141..0000000 --- a/src/examples/git_test.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Example of using the Git module with Rhai -//! -//! This example demonstrates how to use the Git module functions -//! through the Rhai scripting language. - -use sal::rhai::{self, Engine}; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - rhai::register(&mut engine)?; - - // Run a Rhai script that uses Git functions - let script = r#" - // Print a header - print("=== Testing Git Module Functions ===\n"); - - // Create a new GitTree object - let home_dir = env("HOME"); - let git_tree = gittree_new(`${home_dir}/code`); - print(`Created GitTree with base path: ${home_dir}/code`); - - // Test list method - print("\nListing git repositories..."); - let repos = git_tree.list(); - print(`Found ${repos.len()} repositories`); - - // Print the first few repositories - if repos.len() > 0 { - print("First few repositories:"); - let count = if repos.len() > 3 { 3 } else { repos.len() }; - for i in range(0, count) { - print(` - ${repos[i]}`); - } - } - - // Test find method - if repos.len() > 0 { - print("\nTesting repository search..."); - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - - print(`Searching for repositories containing "${repo_name}"`); - let matching = git_tree.find(repo_name + "*"); - - print(`Found ${matching.len()} matching repositories`); - for repo in matching { - print(` - ${repo}`); - } - - // Test get method - print("\nTesting get method..."); - let git_repos = git_tree.get(repo_name); - print(`Found ${git_repos.len()} GitRepo objects`); - - // Test GitRepo methods - if git_repos.len() > 0 { - let git_repo = git_repos[0]; - print(`\nTesting GitRepo methods on: ${git_repo.path()}`); - - // Check if a repository has changes - print("Checking for changes in repository..."); - let has_changes = git_repo.has_changes(); - print(`Repository has changes: ${has_changes}`); - - // Test method chaining (only if there are no changes to avoid errors) - if !has_changes { - print("\nTesting method chaining (pull)..."); - let result = git_repo.pull(); - print("Pull operation completed successfully"); - } - } - } - - print("\n=== Git Module Test Complete ==="); - "#; - - // Evaluate the script - match engine.eval::<()>(script) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Script execution error: {}", e), - } - - Ok(()) -} \ No newline at end of file diff --git a/examples/rhai_example.rs b/src/examples/rhai_example.rs similarity index 100% rename from examples/rhai_example.rs rename to src/examples/rhai_example.rs diff --git a/src/examples/rhai_git_example.rs b/src/examples/rhai_git_example.rs deleted file mode 100644 index f6b7141..0000000 --- a/src/examples/rhai_git_example.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Example of using the Git module with Rhai -//! -//! This example demonstrates how to use the Git module functions -//! through the Rhai scripting language. - -use sal::rhai::{self, Engine}; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - rhai::register(&mut engine)?; - - // Run a Rhai script that uses Git functions - let script = r#" - // Print a header - print("=== Testing Git Module Functions ===\n"); - - // Create a new GitTree object - let home_dir = env("HOME"); - let git_tree = gittree_new(`${home_dir}/code`); - print(`Created GitTree with base path: ${home_dir}/code`); - - // Test list method - print("\nListing git repositories..."); - let repos = git_tree.list(); - print(`Found ${repos.len()} repositories`); - - // Print the first few repositories - if repos.len() > 0 { - print("First few repositories:"); - let count = if repos.len() > 3 { 3 } else { repos.len() }; - for i in range(0, count) { - print(` - ${repos[i]}`); - } - } - - // Test find method - if repos.len() > 0 { - print("\nTesting repository search..."); - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - - print(`Searching for repositories containing "${repo_name}"`); - let matching = git_tree.find(repo_name + "*"); - - print(`Found ${matching.len()} matching repositories`); - for repo in matching { - print(` - ${repo}`); - } - - // Test get method - print("\nTesting get method..."); - let git_repos = git_tree.get(repo_name); - print(`Found ${git_repos.len()} GitRepo objects`); - - // Test GitRepo methods - if git_repos.len() > 0 { - let git_repo = git_repos[0]; - print(`\nTesting GitRepo methods on: ${git_repo.path()}`); - - // Check if a repository has changes - print("Checking for changes in repository..."); - let has_changes = git_repo.has_changes(); - print(`Repository has changes: ${has_changes}`); - - // Test method chaining (only if there are no changes to avoid errors) - if !has_changes { - print("\nTesting method chaining (pull)..."); - let result = git_repo.pull(); - print("Pull operation completed successfully"); - } - } - } - - print("\n=== Git Module Test Complete ==="); - "#; - - // Evaluate the script - match engine.eval::<()>(script) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Script execution error: {}", e), - } - - Ok(()) -} \ No newline at end of file diff --git a/src/examples/run_test_git.rs b/src/examples/run_test_git.rs deleted file mode 100644 index e410804..0000000 --- a/src/examples/run_test_git.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Example of running the test_git.rhai script -//! -//! This example demonstrates how to run the test_git.rhai script -//! through the Rhai scripting engine. - -use sal::rhai::{self, Engine}; -use std::fs; -use std::error::Error; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - rhai::register(&mut engine)?; - - // Read the test script - let script = fs::read_to_string("src/test_git.rhai")?; - - // Evaluate the script - match engine.eval::<()>(&script) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Script execution error: {}", e), - } - - Ok(()) -} \ No newline at end of file diff --git a/src/examples/simple_git_test.rs b/src/examples/simple_git_test.rs deleted file mode 100644 index a174c01..0000000 --- a/src/examples/simple_git_test.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Simple example of using the Git module with Rhai -//! -//! This example demonstrates how to use the Git module functions -//! through the Rhai scripting language. - -use sal::rhai::{self, Engine}; -use std::fs; -use std::error::Error; - -fn main() -> Result<(), Box> { - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register SAL functions with the engine - rhai::register(&mut engine)?; - - // Read the test script - let script = fs::read_to_string("simple_git_test.rhai")?; - - // Evaluate the script - match engine.eval::<()>(&script) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Script execution error: {}", e), - } - - Ok(()) -} \ No newline at end of file diff --git a/src/process/run.rs b/src/process/run.rs index 59e376d..6c447d5 100644 --- a/src/process/run.rs +++ b/src/process/run.rs @@ -98,16 +98,35 @@ fn prepare_script_file(script_content: &str) -> Result<(PathBuf, String, tempfil let (ext, interpreter) = (".bat", "cmd.exe".to_string()); #[cfg(any(target_os = "macos", target_os = "linux"))] - let (ext, interpreter) = (".sh", "/bin/sh".to_string()); + let (ext, interpreter) = (".sh", "/bin/bash".to_string()); // Create the script file let script_path = temp_dir.path().join(format!("script{}", ext)); let mut file = File::create(&script_path) .map_err(RunError::FileCreationFailed)?; - // Write the script content - file.write_all(dedented.as_bytes()) - .map_err(RunError::FileWriteFailed)?; + // For Unix systems, ensure the script has a shebang line with -e flag + #[cfg(any(target_os = "macos", target_os = "linux"))] + { + let script_with_shebang = if dedented.trim_start().starts_with("#!") { + // Script already has a shebang, use it as is + dedented + } else { + // Add shebang with -e flag to ensure script fails on errors + format!("#!/bin/bash -e\n{}", dedented) + }; + + // Write the script content with shebang + file.write_all(script_with_shebang.as_bytes()) + .map_err(RunError::FileWriteFailed)?; + } + + // For Windows, just write the script as is + #[cfg(target_os = "windows")] + { + file.write_all(dedented.as_bytes()) + .map_err(RunError::FileWriteFailed)?; + } // Make the script executable (Unix only) #[cfg(any(target_os = "macos", target_os = "linux"))] @@ -166,7 +185,8 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result Result) -> Result Result { + // Print the script being executed if not silent + if !silent { + println!("\x1b[36mExecuting script:\x1b[0m"); + for (i, line) in script.lines().enumerate() { + println!("\x1b[36m{:3}: {}\x1b[0m", i + 1, line); + } + println!("\x1b[36m---\x1b[0m"); + } + let (script_path, interpreter, _temp_dir) = prepare_script_file(script)?; // _temp_dir is kept in scope until the end of this function to ensure // it's not dropped prematurely, which would clean up the directory - execute_script_internal(&interpreter, &script_path, silent) + + // 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 let Err(ref e) = result { + eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e); + } + + result } /// A builder for configuring and executing commands or scripts @@ -338,21 +420,41 @@ impl<'a> RunBuilder<'a> { // Log command execution if enabled if self.log { - println!("[LOG] Executing command: {}", trimmed); + println!("\x1b[36m[LOG] Executing command: {}\x1b[0m", trimmed); } // Handle async execution if self.async_exec { let cmd_copy = trimmed.to_string(); let silent = self.silent; + let log = self.log; // Spawn a thread to run the command asynchronously thread::spawn(move || { - let _ = if cmd_copy.contains('\n') { + if log { + println!("\x1b[36m[ASYNC] Starting execution\x1b[0m"); + } + + let result = if cmd_copy.contains('\n') { run_script_internal(&cmd_copy, silent) } else { run_command_internal(&cmd_copy, silent) }; + + if log { + match &result { + Ok(res) => { + if res.success { + println!("\x1b[32m[ASYNC] Command completed successfully\x1b[0m"); + } else { + eprintln!("\x1b[31m[ASYNC] Command failed with exit code: {}\x1b[0m", res.code); + } + }, + Err(e) => { + eprintln!("\x1b[31m[ASYNC] Command failed with error: {}\x1b[0m", e); + } + } + } }); // Return a placeholder result for async execution @@ -375,8 +477,17 @@ impl<'a> RunBuilder<'a> { // Handle die=false: convert errors to CommandResult with success=false match result { - Ok(res) => Ok(res), + Ok(res) => { + // If the command failed but die is false, print a warning + if !res.success && !self.die && !self.silent { + eprintln!("\x1b[33mWarning: Command failed with exit code {} but 'die' is false\x1b[0m", res.code); + } + Ok(res) + }, Err(e) => { + // Always print the error, even if die is false + eprintln!("\x1b[31mCommand error: {}\x1b[0m", e); + if self.die { Err(e) } else { diff --git a/src/rhai/tests/git_test.rhai b/src/rhai/tests/git_test.rhai deleted file mode 100644 index 12a5f42..0000000 --- a/src/rhai/tests/git_test.rhai +++ /dev/null @@ -1,170 +0,0 @@ -// Test script for Git module functions - -// Import required modules -import "os" as os; -import "process" as process; - -// Test GitTree creation -fn test_git_tree_creation() { - // Get home directory - let home_dir = env("HOME"); - let code_dir = `${home_dir}/code`; - - print("Testing GitTree creation..."); - let git_tree = gittree_new(code_dir); - - print(`Created GitTree with base path: ${code_dir}`); - return true; -} - -// Test GitTree list method -fn test_git_tree_list() { - // Get home directory - let home_dir = env("HOME"); - let code_dir = `${home_dir}/code`; - - print("Testing GitTree list method..."); - let git_tree = gittree_new(code_dir); - let repos = git_tree.list(); - - print(`Found ${repos.len()} repositories`); - - // Print the first few repositories - let count = if repos.len() > 3 { 3 } else { repos.len() }; - for i in range(0, count) { - print(` - ${repos[i]}`); - } - - return repos.len() >= 0; // Success even if no repos found -} - -// Test GitTree find method -fn test_git_tree_find() { - // Get home directory - let home_dir = env("HOME"); - let code_dir = `${home_dir}/code`; - - print("Testing GitTree find method..."); - let git_tree = gittree_new(code_dir); - let repos = git_tree.list(); - - if repos.len() == 0 { - print("No repositories found, skipping test"); - return true; - } - - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - let search_pattern = repo_name + "*"; - - print(`Searching for repositories matching pattern: ${search_pattern}`); - let matching = git_tree.find(search_pattern); - - print(`Found ${matching.len()} matching repositories`); - for repo in matching { - print(` - ${repo}`); - } - - return matching.len() > 0; -} - -// Test GitTree get method -fn test_git_tree_get() { - // Get home directory - let home_dir = env("HOME"); - let code_dir = `${home_dir}/code`; - - print("Testing GitTree get method..."); - let git_tree = gittree_new(code_dir); - let repos = git_tree.list(); - - if repos.len() == 0 { - print("No repositories found, skipping test"); - return true; - } - - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - - print(`Getting GitRepo objects for: ${repo_name}`); - let git_repos = git_tree.get(repo_name); - - print(`Found ${git_repos.len()} GitRepo objects`); - for repo in git_repos { - print(` - ${repo.path()}`); - } - - return git_repos.len() > 0; -} - -// Test GitRepo has_changes method -fn test_git_repo_has_changes() { - // Get home directory - let home_dir = env("HOME"); - let code_dir = `${home_dir}/code`; - - print("Testing GitRepo has_changes method..."); - let git_tree = gittree_new(code_dir); - let repos = git_tree.list(); - - if repos.len() == 0 { - print("No repositories found, skipping test"); - return true; - } - - // Get the first repo - let git_repos = git_tree.get(repos[0]); - if git_repos.len() == 0 { - print("Failed to get GitRepo object, skipping test"); - return true; - } - - let git_repo = git_repos[0]; - let has_changes = git_repo.has_changes(); - - print(`Repository ${git_repo.path()} has changes: ${has_changes}`); - return true; -} - -// Run the tests -fn run_tests() { - let tests = [ - #{ name: "git_tree_creation", fn: test_git_tree_creation }, - #{ name: "git_tree_list", fn: test_git_tree_list }, - #{ name: "git_tree_find", fn: test_git_tree_find }, - #{ name: "git_tree_get", fn: test_git_tree_get }, - #{ name: "git_repo_has_changes", fn: test_git_repo_has_changes } - ]; - - let passed = 0; - let failed = 0; - - for test in tests { - print(`\nRunning test: ${test.name}`); - - let result = false; - try { - result = test.fn(); - } catch(err) { - print(`Test ${test.name} threw an error: ${err}`); - result = false; - } - - if result { - print(`Test ${test.name} PASSED`); - passed += 1; - } else { - print(`Test ${test.name} FAILED`); - failed += 1; - } - } - - print(`\nTest summary: ${passed} passed, ${failed} failed`); -} - -// Run all tests -run_tests(); \ No newline at end of file diff --git a/rhaiexamples/01_hello_world.rhai b/src/rhaiexamples/01_hello_world.rhai similarity index 100% rename from rhaiexamples/01_hello_world.rhai rename to src/rhaiexamples/01_hello_world.rhai diff --git a/rhaiexamples/02_file_operations.rhai b/src/rhaiexamples/02_file_operations.rhai similarity index 100% rename from rhaiexamples/02_file_operations.rhai rename to src/rhaiexamples/02_file_operations.rhai diff --git a/rhaiexamples/03_process_management.rhai b/src/rhaiexamples/03_process_management.rhai similarity index 100% rename from rhaiexamples/03_process_management.rhai rename to src/rhaiexamples/03_process_management.rhai diff --git a/rhaiexamples/04_buildah_operations.rhai b/src/rhaiexamples/04_buildah_operations.rhai similarity index 100% rename from rhaiexamples/04_buildah_operations.rhai rename to src/rhaiexamples/04_buildah_operations.rhai diff --git a/rhaiexamples/05_directory_operations.rhai b/src/rhaiexamples/05_directory_operations.rhai similarity index 100% rename from rhaiexamples/05_directory_operations.rhai rename to src/rhaiexamples/05_directory_operations.rhai diff --git a/rhaiexamples/06_file_read_write.rhai b/src/rhaiexamples/06_file_read_write.rhai similarity index 100% rename from rhaiexamples/06_file_read_write.rhai rename to src/rhaiexamples/06_file_read_write.rhai diff --git a/rhaiexamples/07_nerdctl_operations.rhai b/src/rhaiexamples/07_nerdctl_operations.rhai similarity index 100% rename from rhaiexamples/07_nerdctl_operations.rhai rename to src/rhaiexamples/07_nerdctl_operations.rhai diff --git a/rhaiexamples/08_nerdctl_web_server.rhai b/src/rhaiexamples/08_nerdctl_web_server.rhai similarity index 100% rename from rhaiexamples/08_nerdctl_web_server.rhai rename to src/rhaiexamples/08_nerdctl_web_server.rhai diff --git a/rhaiexamples/download_test.rhai b/src/rhaiexamples/download_test.rhai similarity index 100% rename from rhaiexamples/download_test.rhai rename to src/rhaiexamples/download_test.rhai diff --git a/rhaiexamples/fs_test.rhai b/src/rhaiexamples/fs_test.rhai similarity index 100% rename from rhaiexamples/fs_test.rhai rename to src/rhaiexamples/fs_test.rhai diff --git a/src/rhaiexamples/git_test.rhai b/src/rhaiexamples/git_test.rhai new file mode 100644 index 0000000..6413aeb --- /dev/null +++ b/src/rhaiexamples/git_test.rhai @@ -0,0 +1,164 @@ +// Simplified test script for Git module functions + +// Ensure test directory exists using a bash script +fn ensure_test_dir() { + print("Ensuring test directory exists at /tmp/code"); + + // Create a bash script to set up the test environment + let setup_script = `#!/bin/bash -ex + rm -rf /tmp/code + mkdir -p /tmp/code + cd /tmp/code + + mkdir -p myserver.com/myaccount/repogreen + mkdir -p myserver.com/myaccount/repored + + cd myserver.com/myaccount/repogreen + git init + echo 'Initial test file' > test.txt + git add test.txt + git config --local user.email 'test@example.com' + git config --local user.name 'Test User' + git commit -m 'Initial commit' + + cd myserver.com/myaccount/repored + git init + echo 'Initial test file' > test2.txt + git add test2.txt + git config --local user.email 'test@example.com' + git config --local user.name 'Test User' + git commit -m 'Initial commit' + + //now we have 2 repos + + `; + + // Run the setup script + let result = run(setup_script); + if !result.success { + print("Failed to set up test directory"); + print(`Error: ${result.stderr}`); + throw "Test setup failed"; + } +} + +// Test GitTree creation +fn test_git_tree_creation() { + print("\n=== Testing GitTree creation ==="); + let git_tree = gittree_new("/tmp/code"); + print(`Created GitTree with base path: /tmp/code`); +} + +// Test GitTree list method +fn test_git_tree_list() { + print("\n=== Testing GitTree list method ==="); + let git_tree = gittree_new("/tmp/code"); + let repos = git_tree.list(); + + print(`Found ${repos.len()} repositories`); + + // Print repositories + for repo in repos { + print(` - ${repo}`); + } + + if repos.len() == 0 { + print("No repositories found, which is unexpected"); + throw "No repositories found"; + } + + if repos.len() != 2 { + print("No enough repositories found, needs to be 2"); + throw "No enough repositories found"; + } +} + +// Test GitTree find method +fn test_git_tree_find() { + print("\n=== Testing GitTree find method ==="); + let git_tree = gittree_new("/tmp/code"); + + // Search for repositories with "code" in the name + let search_pattern = "myaccount/repo"; //we need to check if we need *, would be better not + print(`Searching for repositories matching pattern: ${search_pattern}`); + let matching = git_tree.find(search_pattern); + + print(`Found ${matching.len()} matching repositories`); + for repo in matching { + print(` - ${repo}`); + } + + if matching.len() == 0 { + print("No matching repositories found, which is unexpected"); + throw "No matching repositories found"; + } + if repos.len() != 2 { + print("No enough repositories found, needs to be 2"); + throw "No enough repositories found"; + } +} + +// Test GitRepo operations +fn test_git_repo_operations() { + print("\n=== Testing GitRepo operations ==="); + let git_tree = gittree_new("/tmp/code"); + let repos = git_tree.list(); + + if repos.len() == 0 { + print("No repositories found, which is unexpected"); + throw "No repositories found"; + } + + // Get the first repo + let repo_path = repos[0]; + print(`Testing operations on repository: ${repo_path}`); + + // Get GitRepo object + let git_repos = git_tree.get(repo_path); + if git_repos.len() == 0 { + print("Failed to get GitRepo object"); + throw "Failed to get GitRepo object"; + } + + let git_repo = git_repos[0]; + + // Test has_changes method + print("Testing has_changes method"); + let has_changes = git_repo.has_changes(); + print(`Repository has changes: ${has_changes}`); + + // Create a change to test + print("Creating a change to test"); + file_write("/tmp/code/test2.txt", "Another test file"); + + // Check if changes are detected + let has_changes_after = git_repo.has_changes(); + print(`Repository has changes after modification: ${has_changes_after}`); + + if !has_changes_after { + print("Changes not detected, which is unexpected"); + throw "Changes not detected"; + } + + // Clean up the change + delete("/tmp/code/test2.txt"); +} + +// Run all tests +fn run_all_tests() { + print("Starting Git module tests..."); + + // Ensure test directory exists + ensure_test_dir(); + + // Run tests + test_git_tree_creation(); + test_git_tree_list(); + test_git_tree_find(); + test_git_repo_operations(); + + print("\nAll tests completed successfully!"); +} + +// Run all tests +run_all_tests(); \ No newline at end of file diff --git a/rhaiexamples/install_deb.rhai b/src/rhaiexamples/install_deb.rhai similarity index 100% rename from rhaiexamples/install_deb.rhai rename to src/rhaiexamples/install_deb.rhai diff --git a/rhaiexamples/install_nerdctl.rhai b/src/rhaiexamples/install_nerdctl.rhai similarity index 100% rename from rhaiexamples/install_nerdctl.rhai rename to src/rhaiexamples/install_nerdctl.rhai diff --git a/rhaiexamples/nerdctl_test.rhai b/src/rhaiexamples/nerdctl_test.rhai similarity index 100% rename from rhaiexamples/nerdctl_test.rhai rename to src/rhaiexamples/nerdctl_test.rhai diff --git a/rhaiexamples/process_long.rhai b/src/rhaiexamples/process_long.rhai similarity index 100% rename from rhaiexamples/process_long.rhai rename to src/rhaiexamples/process_long.rhai diff --git a/rhaiexamples/process_silent_test.rhai b/src/rhaiexamples/process_silent_test.rhai similarity index 100% rename from rhaiexamples/process_silent_test.rhai rename to src/rhaiexamples/process_silent_test.rhai diff --git a/rhaiexamples/process_test.rhai b/src/rhaiexamples/process_test.rhai similarity index 100% rename from rhaiexamples/process_test.rhai rename to src/rhaiexamples/process_test.rhai diff --git a/rhaiexamples/rhai_file_test_dir/append_file.txt b/src/rhaiexamples/rhai_file_test_dir/append_file.txt similarity index 100% rename from rhaiexamples/rhai_file_test_dir/append_file.txt rename to src/rhaiexamples/rhai_file_test_dir/append_file.txt diff --git a/rhaiexamples/rhai_file_test_dir/test_file.txt b/src/rhaiexamples/rhai_file_test_dir/test_file.txt similarity index 100% rename from rhaiexamples/rhai_file_test_dir/test_file.txt rename to src/rhaiexamples/rhai_file_test_dir/test_file.txt diff --git a/rhaiexamples/run_all_tests.rhai b/src/rhaiexamples/run_all_tests.rhai similarity index 100% rename from rhaiexamples/run_all_tests.rhai rename to src/rhaiexamples/run_all_tests.rhai diff --git a/rhaiexamples/run_test.rhai b/src/rhaiexamples/run_test.rhai similarity index 100% rename from rhaiexamples/run_test.rhai rename to src/rhaiexamples/run_test.rhai diff --git a/rhaiexamples/sample.rhai b/src/rhaiexamples/sample.rhai similarity index 100% rename from rhaiexamples/sample.rhai rename to src/rhaiexamples/sample.rhai diff --git a/src/rhaiexamples/stdout_test.rhai b/src/rhaiexamples/stdout_test.rhai new file mode 100644 index 0000000..0954f96 --- /dev/null +++ b/src/rhaiexamples/stdout_test.rhai @@ -0,0 +1,33 @@ + + +// Create a bash script to set up the test environment +let setup_script = ` +rm -rf /tmp/code +mkdir -p /tmp/code +cd /tmp/code + +mkdir -p myserver.com/myaccount/repogreen +mkdir -p myserver.com/myaccount/repored + +cd myserver.com/myaccount/repogreen +git init +echo 'Initial test file' > test.txt +git add test.txt +git config --local user.email 'test@example.com' +git config --local user.name 'Test User' +git commit -m 'Initial commit' + +cd myserver.com/myaccount/repored +git init +echo 'Initial test file' > test2.txt +git add test2.txt +git config --local user.email 'test@example.com' +git config --local user.name 'Test User' +git commit -m 'Initial commit' + +//now we have 2 repos + +`; + +// Run the setup script +let result = run(setup_script); diff --git a/src/simple_git_test.rhai b/src/simple_git_test.rhai deleted file mode 100644 index c1fe1f8..0000000 --- a/src/simple_git_test.rhai +++ /dev/null @@ -1,11 +0,0 @@ -// Simple test script for Git module functions - -// Print a header -print("=== Testing Git Module Functions ===\n"); - -// Create a new GitTree object -let home_dir = env("HOME"); -let git_tree = gittree_new(`${home_dir}/code`); -print(`Created GitTree with base path: ${home_dir}/code`); - -print("\n=== Git Module Test Complete ==="); \ No newline at end of file diff --git a/src/test_git.rhai b/src/test_git.rhai deleted file mode 100644 index 727a4df..0000000 --- a/src/test_git.rhai +++ /dev/null @@ -1,65 +0,0 @@ -// Simple test script for Git module functions - -// Print a header -print("=== Testing Git Module Functions ===\n"); - -// Create a new GitTree object -let home_dir = env("HOME"); -let git_tree = gittree_new(`${home_dir}/code`); -print(`Created GitTree with base path: ${home_dir}/code`); - -// Test list method -print("\nListing git repositories..."); -let repos = git_tree.list(); -print(`Found ${repos.len()} repositories`); - -// Print the first few repositories -if repos.len() > 0 { - print("First few repositories:"); - let count = if repos.len() > 3 { 3 } else { repos.len() }; - for i in range(0, count) { - print(` - ${repos[i]}`); - } -} - -// Test find method -if repos.len() > 0 { - print("\nTesting repository search..."); - // Extract a part of the first repo name to search for - let repo_path = repos[0]; - let parts = repo_path.split("/"); - let repo_name = parts[parts.len() - 1]; - - print(`Searching for repositories containing "${repo_name}"`); - let matching = git_tree.find(repo_name); - - print(`Found ${matching.len()} matching repositories`); - for repo in matching { - print(` - ${repo}`); - } - - // Test get method - print("\nTesting get method..."); - let git_repos = git_tree.get(repo_name); - print(`Found ${git_repos.len()} GitRepo objects`); - - // Test GitRepo methods - if git_repos.len() > 0 { - let git_repo = git_repos[0]; - print(`\nTesting GitRepo methods on: ${git_repo.path()}`); - - // Check if a repository has changes - print("Checking for changes in repository..."); - let has_changes = git_repo.has_changes(); - print(`Repository has changes: ${has_changes}`); - - // Test method chaining (only if there are no changes to avoid errors) - if !has_changes { - print("\nTesting method chaining (pull)..."); - let result = git_repo.pull(); - print("Pull operation completed successfully"); - } - } -} - -print("\n=== Git Module Test Complete ==="); \ No newline at end of file diff --git a/src/virt/nerdctl/README.md b/src/virt/nerdctl/README.md new file mode 100644 index 0000000..c0d6d78 --- /dev/null +++ b/src/virt/nerdctl/README.md @@ -0,0 +1,374 @@ +# Container API for nerdctl + +This module provides a Rust API for managing containers using nerdctl, a Docker-compatible CLI for containerd. + +## Overview + +The Container API is designed with a builder pattern to make it easy to create and manage containers. It provides a fluent interface for configuring container options and performing operations on containers. + +## Key Components + +- `Container`: The main struct representing a container +- `HealthCheck`: Configuration for container health checks +- `ContainerStatus`: Information about a container's status +- `ResourceUsage`: Information about a container's resource usage + +## Getting Started + +### Prerequisites + +- nerdctl must be installed on your system +- containerd must be running + +### Basic Usage + +Add the following to your `Cargo.toml`: + +```toml +[dependencies] +sal = { path = "/path/to/sal" } +``` + +Then import the Container API in your Rust code: + +```rust +use sal::virt::nerdctl::Container; +``` + +## Usage Examples + +### Getting a Container by Name + +You can get a reference to an existing container by name: + +```rust +use sal::virt::nerdctl::Container; + +// Get a container by name (if it exists) +match Container::new("existing-container") { + Ok(container) => { + if container.container_id.is_some() { + println!("Found container with ID: {}", container.container_id.unwrap()); + + // Perform operations on the existing container + let status = container.status()?; + println!("Container status: {}", status.status); + } else { + println!("Container exists but has no ID"); + } + }, + Err(e) => { + println!("Error getting container: {}", e); + } +} +``` + +### Creating a Container + +You can create a new container from an image using the builder pattern: + +```rust +use sal::virt::nerdctl::Container; + +// Create a container from an image +let container = Container::from_image("my-nginx", "nginx:latest")? + .with_port("8080:80") + .with_env("NGINX_HOST", "example.com") + .with_volume("/tmp/nginx:/usr/share/nginx/html") + .with_health_check("curl -f http://localhost/ || exit 1") + .with_detach(true) + .build()?; +``` + +### Container Operations + +Once you have a container, you can perform various operations on it: + +```rust +// Execute a command in the container +let result = container.exec("echo 'Hello from container'")?; +println!("Command output: {}", result.stdout); + +// Get container status +let status = container.status()?; +println!("Container status: {}", status.status); + +// Get resource usage +let resources = container.resources()?; +println!("CPU usage: {}", resources.cpu_usage); +println!("Memory usage: {}", resources.memory_usage); + +// Stop and remove the container +container.stop()?; +container.remove()?; +``` + +## Container Configuration Options + +The Container API supports a wide range of configuration options through its builder pattern: + +### Ports + +Map container ports to host ports: + +```rust +// Map a single port +.with_port("8080:80") + +// Map multiple ports +.with_ports(&["8080:80", "8443:443"]) +``` + +### Volumes + +Mount host directories or volumes in the container: + +```rust +// Mount a single volume +.with_volume("/host/path:/container/path") + +// Mount multiple volumes +.with_volumes(&["/host/path1:/container/path1", "/host/path2:/container/path2"]) +``` + +### Environment Variables + +Set environment variables in the container: + +```rust +// Set a single environment variable +.with_env("KEY", "value") + +// Set multiple environment variables +let mut env_map = HashMap::new(); +env_map.insert("KEY1", "value1"); +env_map.insert("KEY2", "value2"); +.with_envs(&env_map) +``` + +### Network Configuration + +Configure container networking: + +```rust +// Set the network +.with_network("bridge") + +// Add a network alias +.with_network_alias("my-container") + +// Add multiple network aliases +.with_network_aliases(&["alias1", "alias2"]) +``` + +### Resource Limits + +Set CPU and memory limits: + +```rust +// Set CPU limit (e.g., 0.5 for half a CPU, 2 for 2 CPUs) +.with_cpu_limit("0.5") + +// Set memory limit (e.g., 512m for 512MB, 1g for 1GB) +.with_memory_limit("512m") + +// Set memory swap limit +.with_memory_swap_limit("1g") + +// Set CPU shares (relative weight) +.with_cpu_shares("1024") +``` + +### Health Checks + +Configure container health checks: + +```rust +// Simple health check +.with_health_check("curl -f http://localhost/ || exit 1") + +// Health check with custom options +.with_health_check_options( + "curl -f http://localhost/ || exit 1", // Command + Some("30s"), // Interval + Some("10s"), // Timeout + Some(3), // Retries + Some("5s") // Start period +) +``` + +### Other Options + +Other container configuration options: + +```rust +// Set restart policy +.with_restart_policy("always") // Options: no, always, on-failure, unless-stopped + +// Set snapshotter +.with_snapshotter("native") // Options: native, fuse-overlayfs, etc. + +// Set detach mode +.with_detach(true) // Run in detached mode +``` + +## Container Operations + +Once a container is created, you can perform various operations on it: + +### Basic Operations + +```rust +// Start the container +container.start()?; + +// Stop the container +container.stop()?; + +// Remove the container +container.remove()?; +``` + +### Command Execution + +```rust +// Execute a command in the container +let result = container.exec("echo 'Hello from container'")?; +println!("Command output: {}", result.stdout); +``` + +### File Operations + +```rust +// Copy files between the container and host +container.copy("container_name:/path/in/container", "/path/on/host")?; +container.copy("/path/on/host", "container_name:/path/in/container")?; + +// Export the container to a tarball +container.export("/path/to/export.tar")?; +``` + +### Image Operations + +```rust +// Commit the container to an image +container.commit("my-custom-image:latest")?; +``` + +### Status and Monitoring + +```rust +// Get container status +let status = container.status()?; +println!("Container state: {}", status.state); +println!("Container status: {}", status.status); +println!("Created: {}", status.created); +println!("Started: {}", status.started); + +// Get health status +let health_status = container.health_status()?; +println!("Health status: {}", health_status); + +// Get resource usage +let resources = container.resources()?; +println!("CPU usage: {}", resources.cpu_usage); +println!("Memory usage: {}", resources.memory_usage); +println!("Memory limit: {}", resources.memory_limit); +println!("Memory percentage: {}", resources.memory_percentage); +println!("Network I/O: {} / {}", resources.network_input, resources.network_output); +println!("Block I/O: {} / {}", resources.block_input, resources.block_output); +println!("PIDs: {}", resources.pids); +``` + +## Error Handling + +The Container API uses a custom error type `NerdctlError` that can be one of the following: + +- `CommandExecutionFailed`: The nerdctl command failed to execute +- `CommandFailed`: The nerdctl command executed but returned an error +- `JsonParseError`: Failed to parse JSON output +- `ConversionError`: Failed to convert data +- `Other`: Generic error + +Example error handling: + +```rust +match Container::new("non-existent-container") { + Ok(container) => { + // Container exists + println!("Container found"); + }, + Err(e) => { + match e { + NerdctlError::CommandExecutionFailed(io_error) => { + println!("Failed to execute nerdctl command: {}", io_error); + }, + NerdctlError::CommandFailed(error_msg) => { + println!("nerdctl command failed: {}", error_msg); + }, + _ => { + println!("Other error: {}", e); + } + } + } +} +``` + +## Implementation Details + +The Container API is implemented in several modules: + +- `container_types.rs`: Contains the struct definitions +- `container.rs`: Contains the main Container implementation +- `container_builder.rs`: Contains the builder pattern methods +- `container_operations.rs`: Contains the container operations +- `health_check.rs`: Contains the HealthCheck implementation + +This modular approach makes the code more maintainable and easier to understand. + +## Complete Example + +Here's a complete example that demonstrates the Container API: + +```rust +use std::error::Error; +use sal::virt::nerdctl::Container; + +fn main() -> Result<(), Box> { + // Create a container from an image + println!("Creating container from image..."); + let container = Container::from_image("my-nginx", "nginx:latest")? + .with_port("8080:80") + .with_env("NGINX_HOST", "example.com") + .with_volume("/tmp/nginx:/usr/share/nginx/html") + .with_health_check("curl -f http://localhost/ || exit 1") + .with_detach(true) + .build()?; + + println!("Container created successfully"); + + // Execute a command in the container + println!("Executing command in container..."); + let result = container.exec("echo 'Hello from container'")?; + println!("Command output: {}", result.stdout); + + // Get container status + println!("Getting container status..."); + let status = container.status()?; + println!("Container status: {}", status.status); + + // Get resource usage + println!("Getting resource usage..."); + let resources = container.resources()?; + println!("CPU usage: {}", resources.cpu_usage); + println!("Memory usage: {}", resources.memory_usage); + + // Stop and remove the container + println!("Stopping and removing container..."); + container.stop()?; + container.remove()?; + + println!("Container stopped and removed"); + + Ok(()) +} \ No newline at end of file diff --git a/src/virt/nerdctl/container.rs b/src/virt/nerdctl/container.rs new file mode 100644 index 0000000..8291188 --- /dev/null +++ b/src/virt/nerdctl/container.rs @@ -0,0 +1,69 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container.rs + +use std::collections::HashMap; +use crate::process::CommandResult; +use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; +use super::container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; + +impl Container { + /// Create a new container reference with the given name + /// + /// # Arguments + /// + /// * `name` - Name for the container + /// + /// # Returns + /// + /// * `Result` - Container instance or error + pub fn new(name: &str) -> Result { + // Check if container exists + let result = execute_nerdctl_command(&["ps", "-a", "--format", "{{.Names}} {{.ID}}"])?; + + // Look for the container name in the output + let container_id = result.stdout.lines() + .filter_map(|line| { + if line.starts_with(&format!("{} ", name)) { + Some(line.split_whitespace().nth(1)?.to_string()) + } else { + None + } + }) + .next(); + + Ok(Self { + name: name.to_string(), + container_id, + image: None, + config: HashMap::new(), + ports: Vec::new(), + volumes: Vec::new(), + env_vars: HashMap::new(), + network: None, + network_aliases: Vec::new(), + cpu_limit: None, + memory_limit: None, + memory_swap_limit: None, + cpu_shares: None, + restart_policy: None, + health_check: None, + detach: false, + snapshotter: None, + }) + } + + /// Create a container from an image + /// + /// # Arguments + /// + /// * `name` - Name for the container + /// * `image` - Image to create the container from + /// + /// # Returns + /// + /// * `Result` - Container instance or error + pub fn from_image(name: &str, image: &str) -> Result { + let mut container = Self::new(name)?; + container.image = Some(image.to_string()); + Ok(container) + } +} diff --git a/src/virt/nerdctl/container_builder.rs b/src/virt/nerdctl/container_builder.rs new file mode 100644 index 0000000..f79ee54 --- /dev/null +++ b/src/virt/nerdctl/container_builder.rs @@ -0,0 +1,460 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_builder.rs + +use std::collections::HashMap; +use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; +use super::container_types::{Container, HealthCheck}; + +impl Container { + /// Add a port mapping + /// + /// # Arguments + /// + /// * `port` - Port mapping (e.g., "8080:80") + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_port(mut self, port: &str) -> Self { + self.ports.push(port.to_string()); + self + } + + /// Add multiple port mappings + /// + /// # Arguments + /// + /// * `ports` - Array of port mappings (e.g., ["8080:80", "8443:443"]) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_ports(mut self, ports: &[&str]) -> Self { + for port in ports { + self.ports.push(port.to_string()); + } + self + } + + /// Add a volume mount + /// + /// # Arguments + /// + /// * `volume` - Volume mount (e.g., "/host/path:/container/path") + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_volume(mut self, volume: &str) -> Self { + self.volumes.push(volume.to_string()); + self + } + + /// Add multiple volume mounts + /// + /// # Arguments + /// + /// * `volumes` - Array of volume mounts (e.g., ["/host/path1:/container/path1", "/host/path2:/container/path2"]) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_volumes(mut self, volumes: &[&str]) -> Self { + for volume in volumes { + self.volumes.push(volume.to_string()); + } + self + } + + /// Add an environment variable + /// + /// # Arguments + /// + /// * `key` - Environment variable name + /// * `value` - Environment variable value + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_env(mut self, key: &str, value: &str) -> Self { + self.env_vars.insert(key.to_string(), value.to_string()); + self + } + + /// Add multiple environment variables + /// + /// # Arguments + /// + /// * `env_map` - Map of environment variable names to values + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_envs(mut self, env_map: &HashMap<&str, &str>) -> Self { + for (key, value) in env_map { + self.env_vars.insert(key.to_string(), value.to_string()); + } + self + } + + /// Set the network for the container + /// + /// # Arguments + /// + /// * `network` - Network name + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_network(mut self, network: &str) -> Self { + self.network = Some(network.to_string()); + self + } + + /// Add a network alias for the container + /// + /// # Arguments + /// + /// * `alias` - Network alias + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_network_alias(mut self, alias: &str) -> Self { + self.network_aliases.push(alias.to_string()); + self + } + + /// Add multiple network aliases for the container + /// + /// # Arguments + /// + /// * `aliases` - Array of network aliases + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_network_aliases(mut self, aliases: &[&str]) -> Self { + for alias in aliases { + self.network_aliases.push(alias.to_string()); + } + self + } + + /// Set CPU limit for the container + /// + /// # Arguments + /// + /// * `cpus` - CPU limit (e.g., "0.5" for half a CPU, "2" for 2 CPUs) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_cpu_limit(mut self, cpus: &str) -> Self { + self.cpu_limit = Some(cpus.to_string()); + self + } + + /// Set memory limit for the container + /// + /// # Arguments + /// + /// * `memory` - Memory limit (e.g., "512m" for 512MB, "1g" for 1GB) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_memory_limit(mut self, memory: &str) -> Self { + self.memory_limit = Some(memory.to_string()); + self + } + + /// Set memory swap limit for the container + /// + /// # Arguments + /// + /// * `memory_swap` - Memory swap limit (e.g., "1g" for 1GB) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_memory_swap_limit(mut self, memory_swap: &str) -> Self { + self.memory_swap_limit = Some(memory_swap.to_string()); + self + } + + /// Set CPU shares for the container (relative weight) + /// + /// # Arguments + /// + /// * `shares` - CPU shares (e.g., "1024" for default, "512" for half) + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_cpu_shares(mut self, shares: &str) -> Self { + self.cpu_shares = Some(shares.to_string()); + self + } + + /// Set restart policy for the container + /// + /// # Arguments + /// + /// * `policy` - Restart policy (e.g., "no", "always", "on-failure", "unless-stopped") + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_restart_policy(mut self, policy: &str) -> Self { + self.restart_policy = Some(policy.to_string()); + self + } + + /// Set a simple health check for the container + /// + /// # Arguments + /// + /// * `cmd` - Command to run for health check (e.g., "curl -f http://localhost/ || exit 1") + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_health_check(mut self, cmd: &str) -> Self { + self.health_check = Some(HealthCheck { + cmd: cmd.to_string(), + interval: None, + timeout: None, + retries: None, + start_period: None, + }); + self + } + + /// Set a health check with custom options for the container + /// + /// # Arguments + /// + /// * `cmd` - Command to run for health check + /// * `interval` - Optional time between running the check (e.g., "30s", "1m") + /// * `timeout` - Optional maximum time to wait for a check to complete (e.g., "30s", "1m") + /// * `retries` - Optional number of consecutive failures needed to consider unhealthy + /// * `start_period` - Optional start period for the container to initialize before counting retries (e.g., "30s", "1m") + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_health_check_options( + mut self, + cmd: &str, + interval: Option<&str>, + timeout: Option<&str>, + retries: Option, + start_period: Option<&str>, + ) -> Self { + let mut health_check = HealthCheck { + cmd: cmd.to_string(), + interval: None, + timeout: None, + retries: None, + start_period: None, + }; + + if let Some(interval_value) = interval { + health_check.interval = Some(interval_value.to_string()); + } + + if let Some(timeout_value) = timeout { + health_check.timeout = Some(timeout_value.to_string()); + } + + if let Some(retries_value) = retries { + health_check.retries = Some(retries_value); + } + + if let Some(start_period_value) = start_period { + health_check.start_period = Some(start_period_value.to_string()); + } + + self.health_check = Some(health_check); + self + } + + /// Set the snapshotter + /// + /// # Arguments + /// + /// * `snapshotter` - Snapshotter to use + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_snapshotter(mut self, snapshotter: &str) -> Self { + self.snapshotter = Some(snapshotter.to_string()); + self + } + + /// Set whether to run in detached mode + /// + /// # Arguments + /// + /// * `detach` - Whether to run in detached mode + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_detach(mut self, detach: bool) -> Self { + self.detach = detach; + self + } + + /// Build the container + /// + /// # Returns + /// + /// * `Result` - Container instance or error + pub fn build(self) -> Result { + // If container already exists, return it + if self.container_id.is_some() { + return Ok(self); + } + + // If no image is specified, return an error + let image = match &self.image { + Some(img) => img, + None => return Err(NerdctlError::Other("No image specified for container creation".to_string())), + }; + + // Build the command arguments as strings + let mut args_strings = Vec::new(); + args_strings.push("run".to_string()); + + if self.detach { + args_strings.push("-d".to_string()); + } + + args_strings.push("--name".to_string()); + args_strings.push(self.name.clone()); + + // Add port mappings + for port in &self.ports { + args_strings.push("-p".to_string()); + args_strings.push(port.clone()); + } + + // Add volume mounts + for volume in &self.volumes { + args_strings.push("-v".to_string()); + args_strings.push(volume.clone()); + } + + // Add environment variables + for (key, value) in &self.env_vars { + args_strings.push("-e".to_string()); + args_strings.push(format!("{}={}", key, value)); + } + + // Add network configuration + if let Some(network) = &self.network { + args_strings.push("--network".to_string()); + args_strings.push(network.clone()); + } + + // Add network aliases + for alias in &self.network_aliases { + args_strings.push("--network-alias".to_string()); + args_strings.push(alias.clone()); + } + + // Add resource limits + if let Some(cpu_limit) = &self.cpu_limit { + args_strings.push("--cpus".to_string()); + args_strings.push(cpu_limit.clone()); + } + + if let Some(memory_limit) = &self.memory_limit { + args_strings.push("--memory".to_string()); + args_strings.push(memory_limit.clone()); + } + + if let Some(memory_swap_limit) = &self.memory_swap_limit { + args_strings.push("--memory-swap".to_string()); + args_strings.push(memory_swap_limit.clone()); + } + + if let Some(cpu_shares) = &self.cpu_shares { + args_strings.push("--cpu-shares".to_string()); + args_strings.push(cpu_shares.clone()); + } + + // Add restart policy + if let Some(restart_policy) = &self.restart_policy { + args_strings.push("--restart".to_string()); + args_strings.push(restart_policy.clone()); + } + + // Add health check + if let Some(health_check) = &self.health_check { + args_strings.push("--health-cmd".to_string()); + args_strings.push(health_check.cmd.clone()); + + if let Some(interval) = &health_check.interval { + args_strings.push("--health-interval".to_string()); + args_strings.push(interval.clone()); + } + + if let Some(timeout) = &health_check.timeout { + args_strings.push("--health-timeout".to_string()); + args_strings.push(timeout.clone()); + } + + if let Some(retries) = &health_check.retries { + args_strings.push("--health-retries".to_string()); + args_strings.push(retries.to_string()); + } + + if let Some(start_period) = &health_check.start_period { + args_strings.push("--health-start-period".to_string()); + args_strings.push(start_period.clone()); + } + } + + if let Some(snapshotter_value) = &self.snapshotter { + args_strings.push("--snapshotter".to_string()); + args_strings.push(snapshotter_value.clone()); + } + + // Add flags to avoid BPF issues + args_strings.push("--cgroup-manager=cgroupfs".to_string()); + + args_strings.push(image.clone()); + + // Convert to string slices for the command + let args: Vec<&str> = args_strings.iter().map(|s| s.as_str()).collect(); + + // Execute the command + let result = execute_nerdctl_command(&args)?; + + // Get the container ID from the output + let container_id = result.stdout.trim().to_string(); + + Ok(Self { + name: self.name, + container_id: Some(container_id), + image: self.image, + config: self.config, + ports: self.ports, + volumes: self.volumes, + env_vars: self.env_vars, + network: self.network, + network_aliases: self.network_aliases, + cpu_limit: self.cpu_limit, + memory_limit: self.memory_limit, + memory_swap_limit: self.memory_swap_limit, + cpu_shares: self.cpu_shares, + restart_policy: self.restart_policy, + health_check: self.health_check, + detach: self.detach, + snapshotter: self.snapshotter, + }) + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/container_functions.rs b/src/virt/nerdctl/container_functions.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/virt/nerdctl/container_operations.rs b/src/virt/nerdctl/container_operations.rs new file mode 100644 index 0000000..fab4d96 --- /dev/null +++ b/src/virt/nerdctl/container_operations.rs @@ -0,0 +1,317 @@ +// 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 + /// + /// # Returns + /// + /// * `Result` - Command result or error + pub fn start(&self) -> Result { + if let Some(container_id) = &self.container_id { + execute_nerdctl_command(&["start", container_id]) + } 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 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())) + } + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/container_test.rs b/src/virt/nerdctl/container_test.rs new file mode 100644 index 0000000..f857de2 --- /dev/null +++ b/src/virt/nerdctl/container_test.rs @@ -0,0 +1,76 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_test.rs + +#[cfg(test)] +mod tests { + use super::super::container_types::{Container, ContainerStatus, ResourceUsage}; + use super::super::NerdctlError; + use std::error::Error; + + #[test] + fn test_container_builder_pattern() { + // Create a container with builder pattern + let container = Container::new("test-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-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); + } + + #[test] + fn test_container_from_image() { + // Create a container from image + let container = Container::from_image("test-container", "alpine:latest").unwrap(); + + // Verify container properties + assert_eq!(container.name, "test-container"); + assert_eq!(container.image.as_ref().unwrap(), "alpine:latest"); + } + + #[test] + fn test_container_health_check() { + // Create a container with health check + let container = Container::new("test-container").unwrap() + .with_health_check("curl -f http://localhost/ || exit 1"); + + // Verify health check + assert!(container.health_check.is_some()); + let health_check = container.health_check.unwrap(); + assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1"); + assert!(health_check.interval.is_none()); + assert!(health_check.timeout.is_none()); + assert!(health_check.retries.is_none()); + assert!(health_check.start_period.is_none()); + } + + #[test] + fn test_container_health_check_options() { + // Create a container with health check options + let container = Container::new("test-container").unwrap() + .with_health_check_options( + "curl -f http://localhost/ || exit 1", + Some("30s"), + Some("10s"), + Some(3), + Some("5s") + ); + + // Verify health check options + assert!(container.health_check.is_some()); + let health_check = container.health_check.unwrap(); + assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1"); + assert_eq!(health_check.interval.as_ref().unwrap(), "30s"); + assert_eq!(health_check.timeout.as_ref().unwrap(), "10s"); + assert_eq!(health_check.retries.unwrap(), 3); + assert_eq!(health_check.start_period.as_ref().unwrap(), "5s"); + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/container_types.rs b/src/virt/nerdctl/container_types.rs new file mode 100644 index 0000000..0712851 --- /dev/null +++ b/src/virt/nerdctl/container_types.rs @@ -0,0 +1,97 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_types.rs + +use std::collections::HashMap; + +/// Container struct for nerdctl operations +#[derive(Clone)] +pub struct Container { + /// Name of the container + pub name: String, + /// Container ID + pub container_id: Option, + /// Base image (if created from an image) + pub image: Option, + /// Configuration options + pub config: HashMap, + /// Port mappings + pub ports: Vec, + /// Volume mounts + pub volumes: Vec, + /// Environment variables + pub env_vars: HashMap, + /// Network to connect to + pub network: Option, + /// Network aliases + pub network_aliases: Vec, + /// CPU limit + pub cpu_limit: Option, + /// Memory limit + pub memory_limit: Option, + /// Memory swap limit + pub memory_swap_limit: Option, + /// CPU shares + pub cpu_shares: Option, + /// Restart policy + pub restart_policy: Option, + /// Health check + pub health_check: Option, + /// Whether to run in detached mode + pub detach: bool, + /// Snapshotter to use + pub snapshotter: Option, +} + +/// Health check configuration for a container +#[derive(Debug, Clone)] +pub struct HealthCheck { + /// Command to run for health check + pub cmd: String, + /// Time between running the check (default: 30s) + pub interval: Option, + /// Maximum time to wait for a check to complete (default: 30s) + pub timeout: Option, + /// Number of consecutive failures needed to consider unhealthy (default: 3) + pub retries: Option, + /// Start period for the container to initialize before counting retries (default: 0s) + pub start_period: Option, +} + +/// Container status information +#[derive(Debug, Clone)] +pub struct ContainerStatus { + /// Container state (e.g., running, stopped) + pub state: String, + /// Container status + pub status: String, + /// Creation time + pub created: String, + /// Start time + pub started: String, + /// Health status (if health check is configured) + pub health_status: Option, + /// Health check output (if health check is configured) + pub health_output: Option, +} + +/// Container resource usage information +#[derive(Debug, Clone)] +pub struct ResourceUsage { + /// CPU usage percentage + pub cpu_usage: String, + /// Memory usage + pub memory_usage: String, + /// Memory limit + pub memory_limit: String, + /// Memory usage percentage + pub memory_percentage: String, + /// Network input + pub network_input: String, + /// Network output + pub network_output: String, + /// Block input + pub block_input: String, + /// Block output + pub block_output: String, + /// PIDs + pub pids: String, +} \ No newline at end of file diff --git a/src/virt/nerdctl/containers.rs b/src/virt/nerdctl/containers.rs deleted file mode 100644 index 273d0b2..0000000 --- a/src/virt/nerdctl/containers.rs +++ /dev/null @@ -1,105 +0,0 @@ -// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/containers.rs - -use crate::virt::nerdctl::execute_nerdctl_command; -use crate::process::CommandResult; -use super::NerdctlError; - -/// Run a container from an image -/// -/// # Arguments -/// -/// * `image` - The image to run -/// * `name` - Optional container name -/// * `detach` - Whether to run in detached mode -/// * `ports` - Optional port mappings (e.g., ["8080:80"]) -/// * `snapshotter` - Optional snapshotter to use (e.g., "native", "fuse-overlayfs") -pub fn run(image: &str, name: Option<&str>, detach: bool, ports: Option<&[&str]>, snapshotter: Option<&str>) -> Result { - // First, try to remove any existing container with the same name to avoid conflicts - if let Some(name_str) = name { - // Ignore errors since the container might not exist - let _ = execute_nerdctl_command(&["rm", "-f", name_str]); - } - - let mut args = vec!["run"]; - - if detach { - args.push("-d"); - } - - if let Some(name_str) = name { - args.push("--name"); - args.push(name_str); - } - - if let Some(port_mappings) = ports { - for port in port_mappings { - 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` - The container ID or name -/// * [command](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:303:0-324:1) - The command to run -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](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:55:4-64:5) - Source path (can be container:path or local path) -/// * `dest` - Destination path (can be container:path or local path) -pub fn copy(source: &str, dest: &str) -> Result { - execute_nerdctl_command(&["cp", source, dest]) -} - -/// Stop a container -/// -/// # Arguments -/// -/// * `container` - The container ID or name -pub fn stop(container: &str) -> Result { - execute_nerdctl_command(&["stop", container]) -} - -/// Remove a container -/// -/// # Arguments -/// -/// * `container` - The container ID or name -pub fn remove(container: &str) -> Result { - execute_nerdctl_command(&["rm", container]) -} - -/// List containers -/// -/// # Arguments -/// -/// * `all` - Whether to show all containers (including stopped ones) -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/health_check.rs b/src/virt/nerdctl/health_check.rs new file mode 100644 index 0000000..f2e1c7f --- /dev/null +++ b/src/virt/nerdctl/health_check.rs @@ -0,0 +1,40 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/health_check.rs + +use super::container_types::HealthCheck; + +impl HealthCheck { + /// Create a new health check with the given command + pub fn new(cmd: &str) -> Self { + Self { + cmd: cmd.to_string(), + interval: None, + timeout: None, + retries: None, + start_period: None, + } + } + + /// Set the interval between health checks + pub fn with_interval(mut self, interval: &str) -> Self { + self.interval = Some(interval.to_string()); + self + } + + /// Set the timeout for health checks + pub fn with_timeout(mut self, timeout: &str) -> Self { + self.timeout = Some(timeout.to_string()); + self + } + + /// Set the number of retries for health checks + pub fn with_retries(mut self, retries: u32) -> Self { + self.retries = Some(retries); + self + } + + /// Set the start period for health checks + pub fn with_start_period(mut self, start_period: &str) -> Self { + self.start_period = Some(start_period.to_string()); + self + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/mod.rs b/src/virt/nerdctl/mod.rs index fad385f..1ca625d 100644 --- a/src/virt/nerdctl/mod.rs +++ b/src/virt/nerdctl/mod.rs @@ -1,6 +1,12 @@ -mod containers; mod images; mod cmd; +mod container_types; +mod container; +mod container_builder; +mod health_check; +mod container_operations; +#[cfg(test)] +mod container_test; use std::fmt; use std::error::Error; @@ -42,6 +48,6 @@ impl Error for NerdctlError { } } -pub use containers::*; pub use images::*; -pub use cmd::*; \ No newline at end of file +pub use cmd::*; +pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage}; \ No newline at end of file