diff --git a/src/docs/rhai/process.md b/src/docs/rhai/process.md index 4208928..8f49ff2 100644 --- a/src/docs/rhai/process.md +++ b/src/docs/rhai/process.md @@ -23,17 +23,16 @@ When you get information about a running process, you can see: - `cpu`: How much CPU the process is using ## Run Functions - ### `run(command)` Runs a command or multiline script with arguments. **Parameters:** -- `command` (string): The command to run +- `command` (string): The command to run (can be a single command or a multiline script) **Returns:** The result of the command, including output and whether it succeeded. -**Example:** +**Example 1: Running a simple command** ```rhai // Run a simple command let result = run("ls -la"); @@ -46,6 +45,28 @@ if result.success { } ``` +**Example 2: Running a multiline script** +```rhai +// Create a multiline script using backtick string literals +let setup_script = ` +# Create directories +mkdir -p /tmp/test_project +cd /tmp/test_project + +# Initialize git repository +git init +echo 'Initial content' > README.md +git add README.md +git config --local user.email 'test@example.com' +git config --local user.name 'Test User' +git commit -m 'Initial commit' +`; + +// Execute the multiline script +let result = run(setup_script); + +``` + ### `run_silent(command)` @@ -110,6 +131,24 @@ options.log = true; // Log the command execution let result = run_with_options("npm install", options); ``` +## Working with Multiline Scripts + +The Process module allows you to execute multiline scripts, which is particularly useful for complex operations that require multiple commands to be executed in sequence. + +### Creating Multiline Scripts + +Multiline scripts can be created using backtick (`) string literals in Rhai: + +```rhai +let my_script = ` +# This is a multiline bash script +echo "Hello, World!" +mkdir -p /tmp/my_project +cd /tmp/my_project +touch example.txt +`; +``` + ## Process Management Functions ### `which(cmd)` diff --git a/src/process/run.rs b/src/process/run.rs index f0953fb..afd4782 100644 --- a/src/process/run.rs +++ b/src/process/run.rs @@ -185,7 +185,7 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result Result { + // Prepare the script file first to get the content with shebang + let (script_path, interpreter, _temp_dir) = prepare_script_file(script)?; + // 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); + + // Read the script file to get the content with shebang + if let Ok(script_content) = fs::read_to_string(&script_path) { + for (i, line) in script_content.lines().enumerate() { + println!("\x1b[36m{:3}: {}\x1b[0m", i + 1, line); + } + } else { + // Fallback to original script if reading fails + 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 diff --git a/src/rhai/nerdctl.rs b/src/rhai/nerdctl.rs index 5e48991..fb4df45 100644 --- a/src/rhai/nerdctl.rs +++ b/src/rhai/nerdctl.rs @@ -3,60 +3,10 @@ //! This module provides Rhai wrappers for the functions in the Nerdctl module. use rhai::{Engine, EvalAltResult, Array, Dynamic, Map}; -use crate::virt::nerdctl::{self, NerdctlError, Image}; +use std::collections::HashMap; +use crate::virt::nerdctl::{self, NerdctlError, Image, Container}; use crate::process::CommandResult; -/// Register Nerdctl module functions with the Rhai engine -/// -/// # Arguments -/// -/// * `engine` - The Rhai engine to register the functions with -/// -/// # Returns -/// -/// * `Result<(), Box>` - Ok if registration was successful, Err otherwise -pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box> { - // Register types - register_nerdctl_types(engine)?; - - // Register container functions - engine.register_fn("nerdctl_run", nerdctl_run); - engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name); - engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port); - engine.register_fn("new_run_options", new_run_options); - engine.register_fn("nerdctl_exec", nerdctl_exec); - engine.register_fn("nerdctl_copy", nerdctl_copy); - engine.register_fn("nerdctl_stop", nerdctl_stop); - engine.register_fn("nerdctl_remove", nerdctl_remove); - engine.register_fn("nerdctl_list", nerdctl_list); - - // Register image functions - engine.register_fn("nerdctl_images", nerdctl_images); - engine.register_fn("nerdctl_image_remove", nerdctl_image_remove); - engine.register_fn("nerdctl_image_push", nerdctl_image_push); - engine.register_fn("nerdctl_image_tag", nerdctl_image_tag); - engine.register_fn("nerdctl_image_pull", nerdctl_image_pull); - engine.register_fn("nerdctl_image_commit", nerdctl_image_commit); - engine.register_fn("nerdctl_image_build", nerdctl_image_build); - - Ok(()) -} - -/// Register Nerdctl module types with the Rhai engine -fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> { - // Register Image type and methods - engine.register_type_with_name::("NerdctlImage"); - - // Register getters for Image properties - engine.register_get("id", |img: &mut Image| img.id.clone()); - engine.register_get("repository", |img: &mut Image| img.repository.clone()); - engine.register_get("tag", |img: &mut Image| img.tag.clone()); - engine.register_get("size", |img: &mut Image| img.size.clone()); - engine.register_get("created", |img: &mut Image| img.created.clone()); - - Ok(()) -} - // Helper functions for error conversion fn nerdctl_error_to_rhai_error(result: Result) -> Result> { result.map_err(|e| { @@ -67,6 +17,100 @@ fn nerdctl_error_to_rhai_error(result: Result) -> Result Result> { + nerdctl_error_to_rhai_error(Container::new(name)) +} + +/// Create a Container from an image +pub fn container_from_image(name: &str, image: &str) -> Result> { + nerdctl_error_to_rhai_error(Container::from_image(name, image)) +} + +/// Add a port mapping to a Container +pub fn container_with_port(mut container: Container, port: &str) -> Container { + container.with_port(port) +} + +/// Add a volume mount to a Container +pub fn container_with_volume(mut container: Container, volume: &str) -> Container { + container.with_volume(volume) +} + +/// Add an environment variable to a Container +pub fn container_with_env(mut container: Container, key: &str, value: &str) -> Container { + container.with_env(key, value) +} + +/// Set the network for a Container +pub fn container_with_network(mut container: Container, network: &str) -> Container { + container.with_network(network) +} + +/// Add a network alias to a Container +pub fn container_with_network_alias(mut container: Container, alias: &str) -> Container { + container.with_network_alias(alias) +} + +/// Set CPU limit for a Container +pub fn container_with_cpu_limit(mut container: Container, cpus: &str) -> Container { + container.with_cpu_limit(cpus) +} + +/// Set memory limit for a Container +pub fn container_with_memory_limit(mut container: Container, memory: &str) -> Container { + container.with_memory_limit(memory) +} + +/// Set restart policy for a Container +pub fn container_with_restart_policy(mut container: Container, policy: &str) -> Container { + container.with_restart_policy(policy) +} + +/// Set health check for a Container +pub fn container_with_health_check(mut container: Container, cmd: &str) -> Container { + container.with_health_check(cmd) +} + +/// Set detach mode for a Container +pub fn container_with_detach(mut container: Container, detach: bool) -> Container { + container.with_detach(detach) +} + +/// Build and run the Container +pub fn container_build(container: Container) -> Result> { + nerdctl_error_to_rhai_error(container.build()) +} + +/// Start the Container +pub fn container_start(container: &mut Container) -> Result> { + nerdctl_error_to_rhai_error(container.start()) +} + +/// Stop the Container +pub fn container_stop(container: &mut Container) -> Result> { + nerdctl_error_to_rhai_error(container.stop()) +} + +/// Remove the Container +pub fn container_remove(container: &mut Container) -> Result> { + nerdctl_error_to_rhai_error(container.remove()) +} + +/// Execute a command in the Container +pub fn container_exec(container: &mut Container, command: &str) -> Result> { + nerdctl_error_to_rhai_error(container.exec(command)) +} + +/// Copy files between the Container and local filesystem +pub fn container_copy(container: &mut Container, source: &str, dest: &str) -> Result> { + nerdctl_error_to_rhai_error(container.copy(source, dest)) +} + /// Create a new Map with default run options pub fn new_run_options() -> Map { let mut map = Map::new(); @@ -185,4 +229,110 @@ pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result Result> { nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path)) +} + +/// Register Nerdctl module functions with the Rhai engine +/// +/// # Arguments +/// +/// * `engine` - The Rhai engine to register the functions with +/// +/// # Returns +/// +/// * `Result<(), Box>` - Ok if registration was successful, Err otherwise +pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box> { + // Register types + register_nerdctl_types(engine)?; + + // Register Container constructor + engine.register_fn("nerdctl_container_new", container_new); + engine.register_fn("nerdctl_container_from_image", container_from_image); + + // Register Container instance methods + engine.register_fn("with_port", container_with_port); + engine.register_fn("with_volume", container_with_volume); + engine.register_fn("with_env", container_with_env); + engine.register_fn("with_network", container_with_network); + engine.register_fn("with_network_alias", container_with_network_alias); + engine.register_fn("with_cpu_limit", container_with_cpu_limit); + engine.register_fn("with_memory_limit", container_with_memory_limit); + engine.register_fn("with_restart_policy", container_with_restart_policy); + engine.register_fn("with_health_check", container_with_health_check); + engine.register_fn("with_detach", container_with_detach); + engine.register_fn("build", container_build); + engine.register_fn("start", container_start); + engine.register_fn("stop", container_stop); + engine.register_fn("remove", container_remove); + engine.register_fn("exec", container_exec); + engine.register_fn("copy", container_copy); + + // Register legacy container functions (for backward compatibility) + engine.register_fn("nerdctl_run", nerdctl_run); + engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name); + engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port); + engine.register_fn("new_run_options", new_run_options); + engine.register_fn("nerdctl_exec", nerdctl_exec); + engine.register_fn("nerdctl_copy", nerdctl_copy); + engine.register_fn("nerdctl_stop", nerdctl_stop); + engine.register_fn("nerdctl_remove", nerdctl_remove); + engine.register_fn("nerdctl_list", nerdctl_list); + + // Register image functions + engine.register_fn("nerdctl_images", nerdctl_images); + engine.register_fn("nerdctl_image_remove", nerdctl_image_remove); + engine.register_fn("nerdctl_image_push", nerdctl_image_push); + engine.register_fn("nerdctl_image_tag", nerdctl_image_tag); + engine.register_fn("nerdctl_image_pull", nerdctl_image_pull); + engine.register_fn("nerdctl_image_commit", nerdctl_image_commit); + engine.register_fn("nerdctl_image_build", nerdctl_image_build); + + Ok(()) +} + +/// Register Nerdctl module types with the Rhai engine +fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box> { + // Register Container type + engine.register_type_with_name::("NerdctlContainer"); + + // Register getters for Container properties + engine.register_get("name", |container: &mut Container| container.name.clone()); + engine.register_get("container_id", |container: &mut Container| { + match &container.container_id { + Some(id) => id.clone(), + None => "".to_string(), + } + }); + engine.register_get("image", |container: &mut Container| { + match &container.image { + Some(img) => img.clone(), + None => "".to_string(), + } + }); + engine.register_get("ports", |container: &mut Container| { + let mut array = Array::new(); + for port in &container.ports { + array.push(Dynamic::from(port.clone())); + } + array + }); + engine.register_get("volumes", |container: &mut Container| { + let mut array = Array::new(); + for volume in &container.volumes { + array.push(Dynamic::from(volume.clone())); + } + array + }); + engine.register_get("detach", |container: &mut Container| container.detach); + + // Register Image type and methods + engine.register_type_with_name::("NerdctlImage"); + + // Register getters for Image properties + engine.register_get("id", |img: &mut Image| img.id.clone()); + engine.register_get("repository", |img: &mut Image| img.repository.clone()); + engine.register_get("tag", |img: &mut Image| img.tag.clone()); + engine.register_get("size", |img: &mut Image| img.size.clone()); + engine.register_get("created", |img: &mut Image| img.created.clone()); + + Ok(()) } \ No newline at end of file diff --git a/src/rhaiexamples/nerdctl_test.rhai b/src/rhaiexamples/nerdctl_test.rhai index 0295862..a913600 100644 --- a/src/rhaiexamples/nerdctl_test.rhai +++ b/src/rhaiexamples/nerdctl_test.rhai @@ -29,7 +29,8 @@ println(`- snapshotter: ${run_options.snapshotter}`); // Test function availability println("\nTesting function availability:"); let functions = [ - "nerdctl_run", + // Legacy functions + "nerdctl_run", "nerdctl_run_with_name", "nerdctl_run_with_port", "nerdctl_exec", @@ -43,7 +44,27 @@ let functions = [ "nerdctl_image_tag", "nerdctl_image_pull", "nerdctl_image_commit", - "nerdctl_image_build" + "nerdctl_image_build", + + // Builder pattern functions + "nerdctl_container_new", + "nerdctl_container_from_image", + "with_port", + "with_volume", + "with_env", + "with_network", + "with_network_alias", + "with_cpu_limit", + "with_memory_limit", + "with_restart_policy", + "with_health_check", + "with_detach", + "build", + "start", + "stop", + "remove", + "exec", + "copy" ]; // Try to access each function (this will throw an error if the function doesn't exist) @@ -52,6 +73,107 @@ for func in functions { println(`Function ${func} registered: ${exists}`); } +// Helper function to get current timestamp in seconds +fn timestamp() { + // Use the current time in seconds since epoch as a unique identifier + return now(); +} + +// Test the builder pattern with actual container creation and execution +println("\nTesting container builder pattern with actual container:"); +try { + // Generate a unique container name based on timestamp + let container_name = "test-alpine-container"; + + // First, try to remove any existing container with this name + println(`Cleaning up any existing container named '${container_name}'...`); + try { + // Try to stop the container first (in case it's running) + nerdctl_stop(container_name); + println("Stopped existing container"); + } catch(e) { + println("No running container to stop"); + } + + try { + // Try to remove the container + nerdctl_remove(container_name); + println("Removed existing container"); + } catch(e) { + println("No container to remove"); + } + + // Create a container with builder pattern using Alpine image with a command that keeps it running + println("\nCreating new container from Alpine image..."); + let container = nerdctl_container_from_image(container_name, "alpine:latest"); + println(`Created container from image: ${container.name} (${container.image})`); + + // Configure the container + container = container + .with_port("8080:80") + .with_volume("/tmp:/data") + .with_env("TEST_ENV", "test_value") + .with_detach(true); + + // Print container properties before building + println("Container properties before building:"); + println(`- name: ${container.name}`); + println(`- image: ${container.image}`); + println(`- ports: ${container.ports}`); + println(`- volumes: ${container.volumes}`); + println(`- detach: ${container.detach}`); + + // Build the container + println("\nBuilding the container..."); + container = container.build(); + + // Print container ID after building + println(`Container built successfully with ID: ${container.container_id}`); + + // Start the container + println("\nStarting the container..."); + let start_result = container.start(); + println(`Start result: ${start_result.success}`); + + // Execute a command in the running container + println("\nExecuting command in the container..."); + let run_cmd = container.exec("echo 'Hello from Alpine container'"); + println(`Command output: ${run_cmd.stdout}`); + + // List all containers to verify it's running + println("\nListing all containers:"); + let list_result = nerdctl_list(true); + println(`Container list: ${list_result.stdout}`); + + // Stop and remove the container + println("\nStopping and removing the container..."); + let stop_result = container.stop(); + println(`Stop result: ${stop_result.success}`); + + let remove_result = container.remove(); + println(`Remove result: ${remove_result.success}`); + println("Container stopped and removed successfully"); + + // Return success message only if everything worked + return "Container builder pattern test completed successfully!"; +} catch(e) { + // Print the error and return a failure message + println(`ERROR: ${e}`); + + // Try to clean up if possible + try { + println("Attempting to clean up after error..."); + nerdctl_stop("test-alpine-container"); + nerdctl_remove("test-alpine-container"); + println("Cleanup completed"); + } catch(cleanup_error) { + println(`Cleanup failed: ${cleanup_error}`); + } + + // Return failure message + return "Container builder pattern test FAILED!"; +} + // Helper function to check if a function is registered fn is_function_registered(name) { try { @@ -63,4 +185,4 @@ fn is_function_registered(name) { } } -"Nerdctl wrapper test completed successfully!" \ No newline at end of file +// The final result depends on the outcome of the container test \ No newline at end of file diff --git a/src/rhaiexamples/stdout_test.rhai b/src/rhaiexamples/stdout_test.rhai index 0954f96..d8f54bb 100644 --- a/src/rhaiexamples/stdout_test.rhai +++ b/src/rhaiexamples/stdout_test.rhai @@ -2,6 +2,9 @@ // Create a bash script to set up the test environment let setup_script = ` +# Configure git to suppress the default branch name warning +git config --global advice.initDefaultBranch false + rm -rf /tmp/code mkdir -p /tmp/code cd /tmp/code @@ -17,7 +20,7 @@ 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 +cd /tmp/code/myserver.com/myaccount/repored git init echo 'Initial test file' > test2.txt git add test2.txt @@ -25,7 +28,7 @@ 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 +# now we have 2 repos `;