diff --git a/rhai_tests/buildah/01_builder_pattern.rhai b/rhai_tests/buildah/01_builder_pattern.rhai index d631690..4c4d67a 100644 --- a/rhai_tests/buildah/01_builder_pattern.rhai +++ b/rhai_tests/buildah/01_builder_pattern.rhai @@ -21,13 +21,8 @@ fn assert_eq(actual, expected, message) { // Helper function to check if buildah is available fn is_buildah_available() { - try { - let command = run("which buildah"); - let result = command.execute(); - return result.success; - } catch(err) { - return false; - } + let command = run("which buildah"); + return command.silent().execute().success; } print("=== Testing Buildah Builder Pattern ==="); @@ -36,8 +31,7 @@ print("=== Testing Buildah Builder Pattern ==="); let buildah_available = is_buildah_available(); if !buildah_available { print("Buildah is not available. Skipping Buildah tests."); - // Exit gracefully without error - return; + throw err; } print("✓ Buildah is available"); diff --git a/rhai_tests/buildah/02_image_operations.rhai b/rhai_tests/buildah/02_image_operations.rhai index 8753cb5..4585cd9 100644 --- a/rhai_tests/buildah/02_image_operations.rhai +++ b/rhai_tests/buildah/02_image_operations.rhai @@ -21,13 +21,8 @@ fn assert_eq(actual, expected, message) { // Helper function to check if buildah is available fn is_buildah_available() { - try { - let command = run("which buildah"); - let result = command.execute(); - return result.success; - } catch(err) { - return false; - } + let command = run("which buildah"); + return command.silent().execute().success; } // Helper function to check if an image exists @@ -56,8 +51,7 @@ print("=== Testing Buildah Image Operations ==="); let buildah_available = is_buildah_available(); if !buildah_available { print("Buildah is not available. Skipping Buildah tests."); - // Exit gracefully without error - return; + throw err; } print("✓ Buildah is available"); diff --git a/rhai_tests/buildah/03_container_operations.rhai b/rhai_tests/buildah/03_container_operations.rhai index 8599ec7..6a37a03 100644 --- a/rhai_tests/buildah/03_container_operations.rhai +++ b/rhai_tests/buildah/03_container_operations.rhai @@ -21,13 +21,8 @@ fn assert_eq(actual, expected, message) { // Helper function to check if buildah is available fn is_buildah_available() { - try { - let command = run("which buildah"); - let result = command.execute(); - return result.success; - } catch(err) { - return false; - } + let command = run("which buildah"); + return command.silent().execute().success; } print("=== Testing Buildah Container Operations ==="); @@ -36,8 +31,7 @@ print("=== Testing Buildah Container Operations ==="); let buildah_available = is_buildah_available(); if !buildah_available { print("Buildah is not available. Skipping Buildah tests."); - // Exit gracefully without error - return; + throw err; } print("✓ Buildah is available"); diff --git a/rhai_tests/nerdctl/01_container_operations.rhai b/rhai_tests/nerdctl/01_container_operations.rhai index ce94bd0..ab14ef2 100644 --- a/rhai_tests/nerdctl/01_container_operations.rhai +++ b/rhai_tests/nerdctl/01_container_operations.rhai @@ -21,22 +21,31 @@ fn assert_eq(actual, expected, message) { // Helper function to check if nerdctl is available fn is_nerdctl_available() { - try { - let result = run("which nerdctl"); - return result.success; - } catch(err) { - return false; - } + let command = run("which nerdctl"); + return command.silent().execute().success; } // Helper function to check if a container exists fn container_exists(container_name) { - try { - let result = run(`nerdctl ps -a --format "{{.Names}}" | grep -w ${container_name}`); - return result.success; - } catch(err) { + // let command = run(`nerdctl ps -a --format "{{.Names}}" | grep -w ${container_name}`); + let command = run(`nerdctl ps -a --format "{{.Names}}"`); + let result = command.silent().execute(); + + // Check if the command was successful + if !result.success { + print(`Error executing 'nerdctl ps': ${result.stderr}`); return false; } + + // Split the output into individual lines (names) + // and check if any of them is an exact match for our container name. + for line in result.stdout.split('\n') { + if line.trim() == container_name { + return true; // Found the container + } + } + + return false; // Did not find the container } // Helper function to clean up a container if it exists @@ -49,6 +58,8 @@ fn cleanup_container(container_name) { } catch(err) { print(`Error cleaning up container ${container_name}: ${err}`); } + } else { + print!(`No container with name ${container_name} found. Nothing to clean up.`); } } @@ -58,8 +69,7 @@ print("=== Testing Nerdctl Container Operations ==="); let nerdctl_available = is_nerdctl_available(); if !nerdctl_available { print("nerdctl is not available. Skipping Nerdctl tests."); - // Exit gracefully without error - return; + throw err; } print("✓ nerdctl is available"); @@ -81,84 +91,127 @@ try { assert_eq(container.container_id, "", "Container ID should be empty initially"); // Test setting container image - print("Testing with_image()..."); - container.with_image("alpine:latest"); + print("Testing image setter..."); + container.image = "alpine:latest"; assert_eq(container.image, "alpine:latest", "Container image should match"); + + // Test setting container config + print("Testing config setter..."); + let config_options = #{"key1": "value1", "key2": "value2"}; + container.config = config_options; + assert_eq(container.config, config_options, "Container config options should match"); + + // Test container_id setter and getter + print("Testing container_id setter..."); + container.container_id = "test-id"; + assert_eq(container.container_id, "test-id", "Container ID should be 'test-id'"); - // Test setting detach mode - print("Testing with_detach()..."); - container.with_detach(true); - assert_true(container.detach, "Container detach mode should be true"); + // Test ports setter and getter + print("Testing ports setter and getter..."); + let ports_list = ["1234", "2345"]; + container.ports = ports_list; + assert_eq(container.ports, ports_list, "Container ports should match"); + + // Test volumes setter and getter + print("Testing volumes setter and getter..."); + let volumes_list = ["/tmp:/tmp"]; + container.volumes = volumes_list; + assert_eq(container.volumes, volumes_list, "Container volumes should match"); + + // Test env_vars setter and getter + print("Testing env_vars setter and getter..."); + let env_vars_map = #{"VAR1": "value1", "VAR2": "value2"}; + container.env_vars = env_vars_map; + assert_eq(container.env_vars, env_vars_map, "Container env_vars should match"); + + // Test network setter and getter + print("Testing network setter and getter..."); + container.network = "test-net"; + assert_eq(container.network, "test-net", "Container network should match"); + + // Test network_aliases setter and getter + print("Testing network_aliases setter and getter..."); + let aliases = ["alias1", "alias2"]; + container.network_aliases = aliases; + assert_eq(container.network_aliases, aliases, "Container network_aliases should match"); + + // Test cpu_limit setter and getter + print("Testing cpu_limit setter and getter..."); + container.cpu_limit = "0.5"; + assert_eq(container.cpu_limit, "0.5", "Container cpu_limit should match"); + + // Test memory_limit setter and getter + print("Testing memory_limit setter and getter..."); + container.memory_limit = "512m"; + assert_eq(container.memory_limit, "512m", "Container memory_limit should match"); + + // Test memory_swap_limit setter and getter + print("Testing memory_swap_limit setter and getter..."); + container.memory_swap_limit = "1g"; + assert_eq(container.memory_swap_limit, "1g", "Container memory_swap_limit should match"); + + // Test cpu_shares setter and getter + print("Testing cpu_shares setter and getter..."); + container.cpu_shares = "1024"; + assert_eq(container.cpu_shares, "1024", "Container cpu_shares should match"); + + // Test restart_policy setter and getter + print("Testing restart_policy setter and getter..."); + container.restart_policy = "always"; + assert_eq(container.restart_policy, "always", "Container restart_policy should match"); + + // Test detach setter and getter + print("Testing detach setter and getter..."); + container.detach = false; + assert_eq(container.detach, false, "Container detach should be false"); + container.detach = true; + assert_eq(container.detach, true, "Container detach should be true"); + + // TODO: Test health_check setter and getter + print("Testing health_check setter and getter..."); - // Test setting environment variables - print("Testing with_env()..."); - container.with_env("TEST_VAR", "test_value"); + // Test snapshotter setter and getter + print("Testing snapshotter setter and getter..."); + container.snapshotter = "stargz"; + assert_eq(container.snapshotter, "stargz", "Container snapshotter should match"); + + // // Test running the container + // print("Testing run()..."); + // let run_result = container.run(); + // assert_true(run_result.success, "Container run should succeed"); + // assert_true(container.container_id != "", "Container ID should not be empty after run"); + // print(`✓ run(): Container started with ID: ${container.container_id}`); - // Test setting multiple environment variables - print("Testing with_envs()..."); - let env_map = #{ - "VAR1": "value1", - "VAR2": "value2" - }; - container.with_envs(env_map); + // // Test executing a command in the container + // print("Testing exec()..."); + // let exec_result = container.exec("echo 'Hello from container'"); + // assert_true(exec_result.success, "Container exec should succeed"); + // assert_true(exec_result.stdout.contains("Hello from container"), "Exec output should contain expected text"); + // print("✓ exec(): Command executed successfully"); - // Test setting ports - print("Testing with_port()..."); - container.with_port("8080:80"); + // // Test getting container logs + // print("Testing logs()..."); + // let logs_result = container.logs(); + // assert_true(logs_result.success, "Container logs should succeed"); + // print("✓ logs(): Logs retrieved successfully"); - // Test setting multiple ports - print("Testing with_ports()..."); - container.with_ports(["9090:90", "7070:70"]); + // // Test stopping the container + // print("Testing stop()..."); + // let stop_result = container.stop(); + // assert_true(stop_result.success, "Container stop should succeed"); + // print("✓ stop(): Container stopped successfully"); - // Test setting volumes - print("Testing with_volume()..."); - // Create a test directory for volume mounting - let test_dir = "rhai_test_nerdctl_volume"; - mkdir(test_dir); - container.with_volume(`${test_dir}:/data`); + // // Test removing the container + // print("Testing remove()..."); + // let remove_result = container.remove(); + // assert_true(remove_result.success, "Container remove should succeed"); + // print("✓ remove(): Container removed successfully"); - // Test setting resource limits - print("Testing with_cpu_limit() and with_memory_limit()..."); - container.with_cpu_limit("0.5"); - container.with_memory_limit("256m"); + // // Clean up test directory + // delete(test_dir); + // print("✓ Cleanup: Test directory removed"); - // Test running the container - print("Testing run()..."); - let run_result = container.run(); - assert_true(run_result.success, "Container run should succeed"); - assert_true(container.container_id != "", "Container ID should not be empty after run"); - print(`✓ run(): Container started with ID: ${container.container_id}`); - - // Test executing a command in the container - print("Testing exec()..."); - let exec_result = container.exec("echo 'Hello from container'"); - assert_true(exec_result.success, "Container exec should succeed"); - assert_true(exec_result.stdout.contains("Hello from container"), "Exec output should contain expected text"); - print("✓ exec(): Command executed successfully"); - - // Test getting container logs - print("Testing logs()..."); - let logs_result = container.logs(); - assert_true(logs_result.success, "Container logs should succeed"); - print("✓ logs(): Logs retrieved successfully"); - - // Test stopping the container - print("Testing stop()..."); - let stop_result = container.stop(); - assert_true(stop_result.success, "Container stop should succeed"); - print("✓ stop(): Container stopped successfully"); - - // Test removing the container - print("Testing remove()..."); - let remove_result = container.remove(); - assert_true(remove_result.success, "Container remove should succeed"); - print("✓ remove(): Container removed successfully"); - - // Clean up test directory - delete(test_dir); - print("✓ Cleanup: Test directory removed"); - - print("All container operations tests completed successfully!"); + // print("All container operations tests completed successfully!"); } catch(err) { print(`Error: ${err}`); diff --git a/virt/src/nerdctl/container_builder.rs b/virt/src/nerdctl/container_builder.rs index 1ac39c7..fc3cb6a 100644 --- a/virt/src/nerdctl/container_builder.rs +++ b/virt/src/nerdctl/container_builder.rs @@ -52,6 +52,20 @@ impl Container { } } + /// Add an image + /// + /// # Arguments + /// + /// * `image` - Image to create the container from + /// + /// # Returns + /// + /// * `Self` - The container instance for method chaining + pub fn with_image(mut self, image: &str) -> Self { + self.image = Some(image.to_string()); + self + } + /// Add a port mapping /// /// # Arguments diff --git a/virt/src/rhai/nerdctl.rs b/virt/src/rhai/nerdctl.rs index f1d5033..78f6f3c 100644 --- a/virt/src/rhai/nerdctl.rs +++ b/virt/src/rhai/nerdctl.rs @@ -2,7 +2,7 @@ //! //! This module provides Rhai wrappers for the functions in the Nerdctl module. -use crate::nerdctl::{self, Container, Image, NerdctlError}; +use crate::nerdctl::{self, Container, HealthCheck, Image, NerdctlError}; use rhai::{Array, Dynamic, Engine, EvalAltResult, Map}; use sal_process::CommandResult; @@ -55,105 +55,7 @@ pub fn container_reset(container: Container) -> Container { container.reset() } -/// Add a port mapping to a Container -pub fn container_with_port(container: Container, port: &str) -> Container { - container.with_port(port) -} - -/// Add a volume mount to a Container -pub fn container_with_volume(container: Container, volume: &str) -> Container { - container.with_volume(volume) -} - -/// Add an environment variable to a Container -pub fn container_with_env(container: Container, key: &str, value: &str) -> Container { - container.with_env(key, value) -} - -/// Set the network for a Container -pub fn container_with_network(container: Container, network: &str) -> Container { - container.with_network(network) -} - -/// Add a network alias to a Container -pub fn container_with_network_alias(container: Container, alias: &str) -> Container { - container.with_network_alias(alias) -} - -/// Set CPU limit for a Container -pub fn container_with_cpu_limit(container: Container, cpus: &str) -> Container { - container.with_cpu_limit(cpus) -} - -/// Set memory limit for a Container -pub fn container_with_memory_limit(container: Container, memory: &str) -> Container { - container.with_memory_limit(memory) -} - -/// Set restart policy for a Container -pub fn container_with_restart_policy(container: Container, policy: &str) -> Container { - container.with_restart_policy(policy) -} - -/// Set health check for a Container -pub fn container_with_health_check(container: Container, cmd: &str) -> Container { - container.with_health_check(cmd) -} - -/// Add multiple port mappings to a Container -pub fn container_with_ports(mut container: Container, ports: Array) -> Container { - for port in ports.iter() { - if port.is_string() { - let port_str = port.clone().cast::(); - container = container.with_port(&port_str); - } - } - container -} - -/// Add multiple volume mounts to a Container -pub fn container_with_volumes(mut container: Container, volumes: Array) -> Container { - for volume in volumes.iter() { - if volume.is_string() { - let volume_str = volume.clone().cast::(); - container = container.with_volume(&volume_str); - } - } - container -} - -/// Add multiple environment variables to a Container -pub fn container_with_envs(mut container: Container, env_map: Map) -> Container { - for (key, value) in env_map.iter() { - if value.is_string() { - let value_str = value.clone().cast::(); - container = container.with_env(&key, &value_str); - } - } - container -} - -/// Add multiple network aliases to a Container -pub fn container_with_network_aliases(mut container: Container, aliases: Array) -> Container { - for alias in aliases.iter() { - if alias.is_string() { - let alias_str = alias.clone().cast::(); - container = container.with_network_alias(&alias_str); - } - } - container -} - -/// Set memory swap limit for a Container -pub fn container_with_memory_swap_limit(container: Container, memory_swap: &str) -> Container { - container.with_memory_swap_limit(memory_swap) -} - -/// Set CPU shares for a Container -pub fn container_with_cpu_shares(container: Container, shares: &str) -> Container { - container.with_cpu_shares(shares) -} - +// TODO: remove? /// Set health check with options for a Container pub fn container_with_health_check_options( container: Container, @@ -168,16 +70,6 @@ pub fn container_with_health_check_options( container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period) } -/// Set snapshotter for a Container -pub fn container_with_snapshotter(container: Container, snapshotter: &str) -> Container { - container.with_snapshotter(snapshotter) -} - -/// Set detach mode for a Container -pub fn container_with_detach(container: Container, detach: bool) -> Container { - container.with_detach(detach) -} - /// Build and run the Container /// /// This function builds and runs the container using the configured options. @@ -514,29 +406,34 @@ pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box Result<(), Box Result<(), Box> { // Register Container type engine.register_type_with_name::("NerdctlContainer"); + engine.register_type_with_name::("NerdctlHealthCheck"); - // Register getters for Container properties + // Register getters & setters for HealthCheck properties + engine.register_get("cmd", |hc: &mut HealthCheck| hc.cmd.clone()); + engine.register_set("cmd", |hc: &mut HealthCheck, cmd: &str| { + hc.cmd = cmd.to_string(); + }); + engine.register_get("interval", |hc: &mut HealthCheck| { + hc.interval.clone().unwrap_or_default() + }); + engine.register_set("interval", |hc: &mut HealthCheck, interval: &str| { + hc.interval = Some(interval.to_string()); + }); + engine.register_get("timeout", |hc: &mut HealthCheck| { + hc.timeout.clone().unwrap_or_default() + }); + engine.register_set("timeout", |hc: &mut HealthCheck, timeout: &str| { + hc.timeout = Some(timeout.to_string()); + }); + engine.register_get("retries", |hc: &mut HealthCheck| { + hc.retries.map_or(0, |r| r as i64) + }); + engine.register_set("retries", |hc: &mut HealthCheck, retries: i64| { + hc.retries = Some(retries as u32); + }); + engine.register_get("start_period", |hc: &mut HealthCheck| { + hc.start_period.clone().unwrap_or_default() + }); + engine.register_set("start_period", |hc: &mut HealthCheck, start_period: &str| { + hc.start_period = Some(start_period.to_string()); + }); + + // Register getters & setters for Container properties + + // -- name engine.register_get("name", |container: &mut Container| container.name.clone()); + engine.register_set("image", |container: &mut Container, image: &str| { + container.image = Some(image.to_string()); + }); + + // -- container_id engine.register_get( "container_id", |container: &mut Container| match &container.container_id { @@ -583,12 +518,37 @@ fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> None => "".to_string(), }, ); + engine.register_set("container_id", |container: &mut Container, container_id: &str| { + container.container_id = Some(container_id.to_string()); + }); + + // -- image engine.register_get("image", |container: &mut Container| { match &container.image { Some(img) => img.clone(), None => "".to_string(), } }); + engine.register_set("image", |container: &mut Container, image: &str| { + container.image = Some(image.to_string()); + }); + + // -- config + engine.register_get("config", |container: &mut Container| { + container + .config + .iter() + .map(|(k, v)| (k.clone().into(), v.clone().into())) + .collect::() + }); + engine.register_set("config", |container: &mut Container, config: Map| { + container.config = config + .into_iter() + .map(|(k, v)| (k.to_string(), v.into_string().unwrap_or_default())) + .collect(); + }); + + // -- ports engine.register_get("ports", |container: &mut Container| { let mut array = Array::new(); for port in &container.ports { @@ -596,6 +556,14 @@ fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> } array }); + engine.register_set("ports", |container: &mut Container, ports: Array| { + container.ports = ports + .into_iter() + .map(|v| v.into_string().unwrap_or_default()) + .collect(); + }); + + // -- volumes engine.register_get("volumes", |container: &mut Container| { let mut array = Array::new(); for volume in &container.volumes { @@ -603,7 +571,125 @@ fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> } array }); + engine.register_set("volumes", |container: &mut Container, volumes: Array| { + container.volumes = volumes + .into_iter() + .map(|v| v.into_string().unwrap_or_default()) + .collect(); + }); + + // -- env_vars + engine.register_get("env_vars", |container: &mut Container| { + container + .env_vars + .iter() + .map(|(k, v)| (k.clone().into(), v.clone().into())) + .collect::() + }); + engine.register_set("env_vars", |container: &mut Container, env_vars: Map| { + container.env_vars = env_vars + .into_iter() + .map(|(k, v)| (k.to_string(), v.into_string().unwrap_or_default())) + .collect(); + }); + + // -- network + engine.register_get("network", |container: &mut Container| { + container.network.clone().unwrap_or_default() + }); + engine.register_set("network", |container: &mut Container, network: &str| { + container.network = Some(network.to_string()); + }); + + // -- network_aliases + engine.register_get("network_aliases", |container: &mut Container| { + container + .network_aliases + .iter() + .map(|alias| Dynamic::from(alias.clone())) + .collect::() + }); + engine.register_set( + "network_aliases", + |container: &mut Container, aliases: Array| { + container.network_aliases = aliases + .into_iter() + .map(|a| a.into_string().unwrap_or_default()) + .collect(); + }, + ); + + // -- cpu_limit + engine.register_get("cpu_limit", |container: &mut Container| { + container.cpu_limit.clone().unwrap_or_default() + }); + engine.register_set("cpu_limit", |container: &mut Container, limit: &str| { + container.cpu_limit = Some(limit.to_string()); + }); + + // -- memory_limit + engine.register_get("memory_limit", |container: &mut Container| { + container.memory_limit.clone().unwrap_or_default() + }); + engine.register_set("memory_limit", |container: &mut Container, limit: &str| { + container.memory_limit = Some(limit.to_string()); + }); + + // -- memory_swap_limit + engine.register_get("memory_swap_limit", |container: &mut Container| { + container.memory_swap_limit.clone().unwrap_or_default() + }); + engine.register_set( + "memory_swap_limit", + |container: &mut Container, limit: &str| { + container.memory_swap_limit = Some(limit.to_string()); + }, + ); + + // -- cpu_shares + engine.register_get("cpu_shares", |container: &mut Container| { + container.cpu_shares.clone().unwrap_or_default() + }); + engine.register_set("cpu_shares", |container: &mut Container, shares: &str| { + container.cpu_shares = Some(shares.to_string()); + }); + + // -- restart_policy + engine.register_get("restart_policy", |container: &mut Container| { + container.restart_policy.clone().unwrap_or_default() + }); + engine.register_set( + "restart_policy", + |container: &mut Container, policy: &str| { + container.restart_policy = Some(policy.to_string()); + }, + ); + + // TODO: setters and getters for health_check + // -- health_check + // engine.register_get("health_check", |container: &mut Container| { + // container.health_check.clone() + // }); + // engine.register_set( + // "health_check", + // |container: &mut Container, health_check: HealthCheck| { + // container.health_check = Some(health_check); + // }, + // ); + + // -- detach engine.register_get("detach", |container: &mut Container| container.detach); + engine.register_set("detach", |container: &mut Container, detach: bool| { + container.detach = detach; + }); + + // -- snapshotter + engine.register_get("snapshotter", |container: &mut Container| { + container.snapshotter.clone().unwrap_or_default() + }); + engine.register_set("snapshotter", |container: &mut Container, snapshotter: &str| { + container.snapshotter = Some(snapshotter.to_string()); + }); // Register Image type and methods engine.register_type_with_name::("NerdctlImage");