diff --git a/Cargo.toml b/Cargo.toml index d263a2a..f3eb4a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ glob = "0.3.1" # For file pattern matching tempfile = "3.5" # For temporary file operations log = "0.4" # Logging facade rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language +clap = "2.33" # Command-line argument parsing # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] @@ -34,3 +35,7 @@ windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Thre [dev-dependencies] tempfile = "3.5" # For tests that need temporary files/directories + +[[bin]] +name = "herodo" +path = "src/bin/herodo.rs" diff --git a/created b/created new file mode 100644 index 0000000..e69de29 diff --git a/examples/rhai_buildah_example.rs b/examples/rhai_buildah_example.rs new file mode 100644 index 0000000..dfafeb3 --- /dev/null +++ b/examples/rhai_buildah_example.rs @@ -0,0 +1,74 @@ +//! 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_example.rs b/examples/rhai_example.rs index 4a3e954..8478c6f 100644 --- a/examples/rhai_example.rs +++ b/examples/rhai_example.rs @@ -2,9 +2,9 @@ //! //! This example demonstrates how to use the Rhai scripting language //! with the System Abstraction Layer (SAL) library. - use rhai::Engine; use sal::rhai; +use sal::rhai::{self, Engine}; use std::fs; fn main() -> Result<(), Box> { diff --git a/examples/rhai_process_example.rs b/examples/rhai_process_example.rs index ad01f11..8adbd81 100644 --- a/examples/rhai_process_example.rs +++ b/examples/rhai_process_example.rs @@ -3,65 +3,52 @@ //! This example demonstrates how to use the Rhai scripting language //! with the System Abstraction Layer (SAL) Process module. -use rhai::{Engine, Map, Dynamic}; -use sal::rhai; +use rhai::Engine; +use std::error::Error; -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { // Create a new Rhai engine let mut engine = Engine::new(); // Register SAL functions with the engine - rhai::register(&mut 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 a map with all the results - { - ls_exists: ls_exists, - echo_stdout: echo_result.stdout, - echo_success: echo_result.success, - silent_success: silent_result.success, - process_count: processes.len() - } + // Return success + true "#; - // Evaluate the script and get the results - let result = engine.eval::(script)?; - - // Print the results - println!("Script results:"); - - if let Some(ls_exists) = result.get("ls_exists") { - println!(" ls command exists: {}", ls_exists); - } - - if let Some(echo_stdout) = result.get("echo_stdout") { - println!(" Echo command output: {}", echo_stdout.clone().into_string().unwrap()); - } - - if let Some(echo_success) = result.get("echo_success") { - println!(" Echo command success: {}", echo_success.clone().as_bool().unwrap()); - } - - if let Some(silent_success) = result.get("silent_success") { - println!(" Silent command success: {}", silent_success.clone().as_bool().unwrap()); - } - - if let Some(process_count) = result.get("process_count") { - println!(" Number of processes: {}", process_count.clone().as_int().unwrap()); - } + // Evaluate the script + let result = engine.eval::(script)?; + println!("Script execution successful: {}", result); Ok(()) } \ No newline at end of file diff --git a/rhaiexamples/01_hello_world.rhai b/rhaiexamples/01_hello_world.rhai new file mode 100644 index 0000000..753fcf8 --- /dev/null +++ b/rhaiexamples/01_hello_world.rhai @@ -0,0 +1,39 @@ +// 01_hello_world.rhai +// A simple hello world script to demonstrate basic Rhai functionality + +// Print a message +println("Hello from Rhai!"); + +// Define a function +fn greet(name) { + "Hello, " + name + "!" +} + +// Call the function and print the result +let greeting = greet("SAL User"); +println(greeting); + +// Do some basic calculations +let a = 5; +let b = 7; +println(`${a} + ${b} = ${a + b}`); +println(`${a} * ${b} = ${a * b}`); + +// Create and use an array +let numbers = [1, 2, 3, 4, 5]; +println("Numbers: " + numbers); +println("Sum of numbers: " + numbers.reduce(|sum, n| sum + n, 0)); + +// Create and use a map +let person = #{ + name: "John Doe", + age: 30, + occupation: "Developer" +}; + +println("Person: " + person); +println("Name: " + person.name); +println("Age: " + person.age); + +// Return a success message +"Hello world script completed successfully!" \ No newline at end of file diff --git a/rhaiexamples/02_file_operations.rhai b/rhaiexamples/02_file_operations.rhai new file mode 100644 index 0000000..0c96e0d --- /dev/null +++ b/rhaiexamples/02_file_operations.rhai @@ -0,0 +1,61 @@ +// 02_file_operations.rhai +// Demonstrates file system operations using SAL + +// Create a test directory +let test_dir = "rhai_test_dir"; +println(`Creating directory: ${test_dir}`); +let mkdir_result = mkdir(test_dir); +println(`Directory creation result: ${mkdir_result}`); + +// Check if the directory exists +let dir_exists = exist(test_dir); +println(`Directory exists: ${dir_exists}`); + +// Create a test file +let test_file = test_dir + "/test_file.txt"; +let file_content = "This is a test file created by Rhai script."; + +// Create the file using a different approach +println(`Creating file: ${test_file}`); +// First ensure the directory exists +run_command(`mkdir -p ${test_dir}`); +// Then create the file using a different approach +// Use run_command with sh -c to properly handle shell features +let write_cmd = `sh -c 'touch ${test_file} && echo "${file_content}" > ${test_file}'`; +let write_result = run_command(write_cmd); +println(`File creation result: ${write_result.success}`); + +// Wait a moment to ensure the file is created +run_command("sleep 1"); + +// Check if the file exists +let file_exists = exist(test_file); +println(`File exists: ${file_exists}`); + +// Get file size +if file_exists { + let size = file_size(test_file); + println(`File size: ${size} bytes`); +} + +// Copy the file +let copied_file = test_dir + "/copied_file.txt"; +println(`Copying file to: ${copied_file}`); +let copy_result = copy(test_file, copied_file); +println(`File copy result: ${copy_result}`); + +// Find files in the directory +println("Finding files in the test directory:"); +let files = find_files(test_dir, "*.txt"); +for file in files { + println(` - ${file}`); +} + +// Clean up (uncomment to actually delete the files) +// println("Cleaning up..."); +// delete(copied_file); +// delete(test_file); +// delete(test_dir); +// println("Cleanup complete"); + +"File operations script completed successfully!" \ No newline at end of file diff --git a/rhaiexamples/03_process_management.rhai b/rhaiexamples/03_process_management.rhai new file mode 100644 index 0000000..152b1c7 --- /dev/null +++ b/rhaiexamples/03_process_management.rhai @@ -0,0 +1,64 @@ +// 03_process_management.rhai +// Demonstrates process management operations using SAL + +// Check if common commands exist +println("Checking if common commands exist:"); +let commands = ["ls", "echo", "cat", "grep"]; +for cmd in commands { + let exists = which(cmd); + println(` - ${cmd}: ${exists}`); +} + +// Run a simple command +println("\nRunning a simple echo command:"); +let echo_result = run_command("echo 'Hello from Rhai process management!'"); +println(`Command output: ${echo_result.stdout}`); +// The CommandResult type doesn't have an exit_code property +println(`Success: ${echo_result.success}`); + +// Run a command silently (no output to console) +println("\nRunning a command silently:"); +let silent_result = run_silent("ls -la"); +println(`Command success: ${silent_result.success}`); +println(`Command output length: ${silent_result.stdout.len()} characters`); + +// Create custom run options +println("\nRunning a command with custom options:"); +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 test'", options); +println(`Command success: ${custom_result.success}`); +println(`Command output: ${custom_result.stdout}`); + +// List processes +println("\nListing processes (limited to 5):"); +let processes = process_list(""); +let count = 0; +for proc in processes { + if count >= 5 { + break; + } + // Just print the PID since we're not sure what other properties are available + println(` - PID: ${proc.pid}`); + count += 1; +} +println(`Total processes: ${processes.len()}`); + +// Run a command that will create a background process +// Note: This is just for demonstration, the process will be short-lived +println("\nRunning a background process:"); +let bg_options = new_run_options(); +bg_options["async_exec"] = true; +// Fix the command to avoid issues with shell interpretation +let bg_result = run("sleep 1", bg_options); +println("Background process started"); + +// Wait a moment to let the background process run +run_command("sleep 0.5"); +println("Main script continuing while background process runs"); + +"Process management script completed successfully!" \ No newline at end of file diff --git a/rhaiexamples/04_buildah_operations.rhai b/rhaiexamples/04_buildah_operations.rhai new file mode 100644 index 0000000..02b6649 --- /dev/null +++ b/rhaiexamples/04_buildah_operations.rhai @@ -0,0 +1,112 @@ +// 04_buildah_operations.rhai +// Demonstrates container operations using SAL's buildah integration +// Note: This script requires buildah to be installed and may need root privileges + +// Check if buildah is installed +let buildah_exists = which("buildah"); +println(`Buildah exists: ${buildah_exists}`); + +// List available images (only if buildah is installed) +println("Listing available container images:"); +if buildah_exists != "" { + // This try/catch block will handle any errors that might occur + // when trying to use buildah functions + try { + let images = buildah_images(); + println(`Found ${images.len()} images`); + + // Print image details (limited to 3) + let count = 0; + for img in images { + if count >= 3 { + break; + } + println(` - ID: ${img.id}, Name: ${img.name}, Created: ${img.created}`); + count += 1; + } + } catch(err) { + println(`Error accessing buildah: ${err}`); + } +} else { + println("Buildah is not installed. Skipping container image operations."); +} + +// Remove the duplicate code that was causing the error + +// The following operations are commented out as they require buildah to be installed +// and may need root privileges. Uncomment them if you want to try them out. + +// Create a container from an image +// println("\nCreating a container from alpine image:"); +// let container = from("alpine:latest"); +// println(`Container ID: ${container.stdout.trim()}`); + +// Run a command in the container +// println("\nRunning a command in the container:"); +// let run_result = run(container.stdout.trim(), "echo 'Hello from container'"); +// println(`Command output: ${run_result.stdout}`); + +// Add a file to the container +// println("\nAdding a file to the container:"); +// let test_file = "test_file.txt"; +// run_command(`echo "Test content" > ${test_file}`); +// let add_result = add(container.stdout.trim(), test_file, "/"); +// println(`Add result: ${add_result.success}`); + +// Commit the container to create a new image +// println("\nCommitting the container to create a new image:"); +// let commit_result = commit(container.stdout.trim(), "my-custom-image:latest"); +// println(`Commit result: ${commit_result.success}`); + +// Remove the container +// println("\nRemoving the container:"); +// let remove_result = remove(container.stdout.trim()); +// println(`Remove result: ${remove_result.success}`); + +// Clean up the test file +// delete(test_file); + +// Only demonstrate buildah options if buildah is installed +if buildah_exists != "" { + try { + // Demonstrate build options + println("\nDemonstrating build options:"); + let build_options = buildah_new_build_options(); + build_options.tag = "example-image:latest"; + build_options.context_dir = "."; + build_options.file = "example_Dockerfile"; + + println("Build options configured:"); + println(` - Tag: ${build_options.tag}`); + println(` - Context: ${build_options.context_dir}`); + println(` - Dockerfile: ${build_options.file}`); + + // Demonstrate commit options + println("\nDemonstrating commit options:"); + let commit_options = buildah_new_commit_options(); + commit_options.format = "docker"; + commit_options.squash = true; + commit_options.rm = true; + + println("Commit options configured:"); + println(` - Format: ${commit_options.format}`); + println(` - Squash: ${commit_options.squash}`); + println(` - Remove container: ${commit_options.rm}`); + + // Demonstrate config options + println("\nDemonstrating config options:"); + let config_options = buildah_new_config_options(); + config_options.author = "Rhai Example"; + config_options.cmd = "/bin/sh -c 'echo Hello from Buildah'"; + + println("Config options configured:"); + println(` - Author: ${config_options.author}`); + println(` - Command: ${config_options.cmd}`); + } catch(err) { + println(`Error accessing buildah options: ${err}`); + } +} else { + println("\nSkipping buildah options demonstration since buildah is not installed."); +} + +"Buildah operations script completed successfully!" \ No newline at end of file diff --git a/src/bin/herodo.rs b/src/bin/herodo.rs new file mode 100644 index 0000000..8061af6 --- /dev/null +++ b/src/bin/herodo.rs @@ -0,0 +1,30 @@ +//! Herodo binary entry point +//! +//! This is the main entry point for the herodo binary. +//! It parses command line arguments and calls into the implementation in the cmd module. + +use clap::{App, Arg}; + +fn main() -> Result<(), Box> { + // Parse command line arguments + let matches = App::new("herodo") + .version("0.1.0") + .author("SAL Team") + .about("Executes Rhai scripts for SAL") + .arg( + Arg::with_name("path") + .short("p") + .long("path") + .value_name("PATH") + .help("Path to directory containing Rhai scripts") + .required(true) + .takes_value(true), + ) + .get_matches(); + + // Get the script path from arguments + let script_path = matches.value_of("path").unwrap(); + + // Call the run function from the cmd module + sal::cmd::herodo::run(script_path) +} \ No newline at end of file diff --git a/src/cmd/herodo.rs b/src/cmd/herodo.rs new file mode 100644 index 0000000..310cb31 --- /dev/null +++ b/src/cmd/herodo.rs @@ -0,0 +1,84 @@ +//! Herodo - A Rhai script executor for SAL +//! +//! This binary loads the Rhai engine, registers all SAL modules, +//! and executes Rhai scripts from a specified directory in sorted order. + +// Removed unused imports +use rhai::Engine; +use std::error::Error; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process; + +/// Run the herodo script executor with the given script path +/// +/// # Arguments +/// +/// * `script_path` - Path to the directory containing Rhai scripts +/// +/// # Returns +/// +/// Result indicating success or failure +pub fn run(script_path: &str) -> Result<(), Box> { + let script_dir = Path::new(script_path); + + // Check if the directory exists + if !script_dir.exists() || !script_dir.is_dir() { + eprintln!("Error: '{}' is not a valid directory", script_path); + process::exit(1); + } + + // Create a new Rhai engine + let mut engine = Engine::new(); + + // Register println function for output + engine.register_fn("println", |s: &str| println!("{}", s)); + + // Register all SAL modules with the engine + crate::rhai::register(&mut engine)?; + + // Find all .rhai files in the directory + let mut script_files: Vec = fs::read_dir(script_dir)? + .filter_map(Result::ok) + .filter(|entry| { + entry.path().is_file() && + entry.path().extension().map_or(false, |ext| ext == "rhai") + }) + .map(|entry| entry.path()) + .collect(); + + // Sort the script files by name + script_files.sort(); + + if script_files.is_empty() { + println!("No Rhai scripts found in '{}'", script_path); + return Ok(()); + } + + println!("Found {} Rhai scripts to execute:", script_files.len()); + + // Execute each script in sorted order + for script_file in script_files { + println!("\nExecuting: {}", script_file.display()); + + // Read the script content + let script = fs::read_to_string(&script_file)?; + + // Execute the script + match engine.eval::(&script) { + Ok(result) => { + println!("Script executed successfully"); + if !result.is_unit() { + println!("Result: {}", result); + } + }, + Err(err) => { + eprintln!("Error executing script: {}", err); + // Continue with the next script instead of stopping + } + } + } + + println!("\nAll scripts executed"); + Ok(()) +} \ No newline at end of file diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs new file mode 100644 index 0000000..08224aa --- /dev/null +++ b/src/cmd/mod.rs @@ -0,0 +1,5 @@ +//! Command-line tools for SAL +//! +//! This module contains command-line tools built on top of the SAL library. + +pub mod herodo; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 01d8efe..ceda467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub mod redisclient; pub mod text; pub mod virt; pub mod rhai; +pub mod cmd; // Version information /// Returns the version of the SAL library diff --git a/src/rhai/buildah.rs b/src/rhai/buildah.rs new file mode 100644 index 0000000..8b893de --- /dev/null +++ b/src/rhai/buildah.rs @@ -0,0 +1,391 @@ +//! Rhai wrappers for Buildah module functions +//! +//! This module provides Rhai wrappers for the functions in the Buildah module. + +use rhai::{Engine, EvalAltResult, Array, Dynamic, Map}; +use std::collections::HashMap; +use crate::virt::buildah::{self, BuildahError, Image}; +use crate::process::CommandResult; + +/// Register Buildah 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_buildah_module(engine: &mut Engine) -> Result<(), Box> { + // Register types + register_buildah_types(engine)?; + + // Register container functions + engine.register_fn("buildah_from", from); + engine.register_fn("buildah_run", run); + engine.register_fn("buildah_run_with_isolation", run_with_isolation); + engine.register_fn("buildah_copy", copy); + engine.register_fn("buildah_add", add); + engine.register_fn("buildah_commit", commit); + engine.register_fn("buildah_remove", remove); + engine.register_fn("buildah_list", list); + engine.register_fn("buildah_build", build_with_options); + engine.register_fn("buildah_new_build_options", new_build_options); + + // Register image functions + engine.register_fn("buildah_images", images); + engine.register_fn("buildah_image_remove", image_remove); + engine.register_fn("buildah_image_push", image_push); + engine.register_fn("buildah_image_tag", image_tag); + engine.register_fn("buildah_image_pull", image_pull); + engine.register_fn("buildah_image_commit", image_commit_with_options); + engine.register_fn("buildah_new_commit_options", new_commit_options); + engine.register_fn("buildah_config", config_with_options); + engine.register_fn("buildah_new_config_options", new_config_options); + + Ok(()) +} + +/// Register Buildah module types with the Rhai engine +fn register_buildah_types(engine: &mut Engine) -> Result<(), Box> { + // Register Image type and methods + engine.register_type_with_name::("BuildahImage"); + + // Register getters for Image properties + engine.register_get("id", |img: &mut Image| img.id.clone()); + engine.register_get("names", |img: &mut Image| { + let mut array = Array::new(); + for name in &img.names { + array.push(Dynamic::from(name.clone())); + } + array + }); + 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 buildah_error_to_rhai_error(result: Result) -> Result> { + result.map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Buildah error: {}", e).into(), + rhai::Position::NONE + )) + }) +} + +/// Create a new Map with default build options +pub fn new_build_options() -> Map { + let mut map = Map::new(); + map.insert("tag".into(), Dynamic::UNIT); + map.insert("context_dir".into(), Dynamic::from(".")); + map.insert("file".into(), Dynamic::from("Dockerfile")); + map.insert("isolation".into(), Dynamic::UNIT); + map +} + +/// Create a new Map with default commit options +pub fn new_commit_options() -> Map { + let mut map = Map::new(); + map.insert("format".into(), Dynamic::UNIT); + map.insert("squash".into(), Dynamic::from(false)); + map.insert("rm".into(), Dynamic::from(false)); + map +} + +/// Create a new Map for config options +pub fn new_config_options() -> Map { + Map::new() +} + +// +// Container Function Wrappers +// + +/// Wrapper for buildah::from +/// +/// Create a container from an image. +pub fn from(image: &str) -> Result> { + buildah_error_to_rhai_error(buildah::from(image)) +} + +/// Wrapper for buildah::run +/// +/// Run a command in a container. +pub fn run(container: &str, command: &str) -> Result> { + buildah_error_to_rhai_error(buildah::run(container, command)) +} + +/// Wrapper for buildah::run_with_isolation +/// +/// Run a command in a container with specified isolation. +pub fn run_with_isolation(container: &str, command: &str, isolation: &str) -> Result> { + buildah_error_to_rhai_error(buildah::run_with_isolation(container, command, isolation)) +} + +/// Wrapper for buildah::copy +/// +/// Copy files into a container. +pub fn copy(container: &str, source: &str, dest: &str) -> Result> { + buildah_error_to_rhai_error(buildah::copy(container, source, dest)) +} + +/// Wrapper for buildah::add +/// +/// Add files into a container. +pub fn add(container: &str, source: &str, dest: &str) -> Result> { + buildah_error_to_rhai_error(buildah::add(container, source, dest)) +} + +/// Wrapper for buildah::commit +/// +/// Commit a container to an image. +pub fn commit(container: &str, image_name: &str) -> Result> { + buildah_error_to_rhai_error(buildah::commit(container, image_name)) +} + +/// Wrapper for buildah::remove +/// +/// Remove a container. +pub fn remove(container: &str) -> Result> { + buildah_error_to_rhai_error(buildah::remove(container)) +} + +/// Wrapper for buildah::list +/// +/// List containers. +pub fn list() -> Result> { + buildah_error_to_rhai_error(buildah::list()) +} + +/// Build an image with options specified in a Map +/// +/// This provides a builder-style interface for Rhai scripts. +/// +/// # Example +/// +/// ```rhai +/// let options = buildah_new_build_options(); +/// options.tag = "my-image:latest"; +/// options.context_dir = "."; +/// options.file = "Dockerfile"; +/// options.isolation = "chroot"; +/// let result = buildah_build(options); +/// ``` +pub fn build_with_options(options: Map) -> Result> { + // Extract options from the map + let tag_option = match options.get("tag") { + Some(tag) => { + if tag.is_unit() { + None + } else if let Ok(tag_str) = tag.clone().into_string() { + Some(tag_str) + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "tag must be a string".into(), + rhai::Position::NONE + ))); + } + }, + None => None + }; + + let context_dir = match options.get("context_dir") { + Some(dir) => { + if let Ok(dir_str) = dir.clone().into_string() { + dir_str + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "context_dir must be a string".into(), + rhai::Position::NONE + ))); + } + }, + None => String::from(".") + }; + + let file = match options.get("file") { + Some(file) => { + if let Ok(file_str) = file.clone().into_string() { + file_str + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "file must be a string".into(), + rhai::Position::NONE + ))); + } + }, + None => String::from("Dockerfile") + }; + + let isolation_option = match options.get("isolation") { + Some(isolation) => { + if isolation.is_unit() { + None + } else if let Ok(isolation_str) = isolation.clone().into_string() { + Some(isolation_str) + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "isolation must be a string".into(), + rhai::Position::NONE + ))); + } + }, + None => None + }; + + // Convert String to &str for the function call + let tag_ref = tag_option.as_deref(); + let isolation_ref = isolation_option.as_deref(); + + // Call the buildah build function + buildah_error_to_rhai_error(buildah::build(tag_ref, &context_dir, &file, isolation_ref)) +} + +// +// Image Function Wrappers +// + +/// Wrapper for buildah::images +/// +/// List images in local storage. +pub fn images() -> Result> { + let images = buildah_error_to_rhai_error(buildah::images())?; + + // Convert Vec to Rhai Array + let mut array = Array::new(); + for image in images { + array.push(Dynamic::from(image)); + } + + Ok(array) +} + +/// Wrapper for buildah::image_remove +/// +/// Remove one or more images. +pub fn image_remove(image: &str) -> Result> { + buildah_error_to_rhai_error(buildah::image_remove(image)) +} + +/// Wrapper for buildah::image_push +/// +/// Push an image to a registry. +pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result> { + buildah_error_to_rhai_error(buildah::image_push(image, destination, tls_verify)) +} + +/// Wrapper for buildah::image_tag +/// +/// Add an additional name to a local image. +pub fn image_tag(image: &str, new_name: &str) -> Result> { + buildah_error_to_rhai_error(buildah::image_tag(image, new_name)) +} + +/// Wrapper for buildah::image_pull +/// +/// Pull an image from a registry. +pub fn image_pull(image: &str, tls_verify: bool) -> Result> { + buildah_error_to_rhai_error(buildah::image_pull(image, tls_verify)) +} + +/// Commit a container to an image with options specified in a Map +/// +/// This provides a builder-style interface for Rhai scripts. +/// +/// # Example +/// +/// ```rhai +/// let options = buildah_new_commit_options(); +/// options.format = "docker"; +/// options.squash = true; +/// options.rm = true; +/// let result = buildah_image_commit("my-container", "my-image:latest", options); +/// ``` +pub fn image_commit_with_options(container: &str, image_name: &str, options: Map) -> Result> { + // Extract options from the map + let format_option = match options.get("format") { + Some(format) => { + if format.is_unit() { + None + } else if let Ok(format_str) = format.clone().into_string() { + Some(format_str) + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "format must be a string".into(), + rhai::Position::NONE + ))); + } + }, + None => None + }; + + let squash = match options.get("squash") { + Some(squash) => { + if let Ok(squash_val) = squash.clone().as_bool() { + squash_val + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "squash must be a boolean".into(), + rhai::Position::NONE + ))); + } + }, + None => false + }; + + let rm = match options.get("rm") { + Some(rm) => { + if let Ok(rm_val) = rm.clone().as_bool() { + rm_val + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "rm must be a boolean".into(), + rhai::Position::NONE + ))); + } + }, + None => false + }; + + // Convert String to &str for the function call + let format_ref = format_option.as_deref(); + + // Call the buildah image_commit function + buildah_error_to_rhai_error(buildah::image_commit(container, image_name, format_ref, squash, rm)) +} + +/// Configure a container with options specified in a Map +/// +/// This provides a builder-style interface for Rhai scripts. +/// +/// # Example +/// +/// ```rhai +/// let options = buildah_new_config_options(); +/// options.author = "John Doe"; +/// options.cmd = "echo Hello"; +/// options.entrypoint = "/bin/sh -c"; +/// let result = buildah_config("my-container", options); +/// ``` +pub fn config_with_options(container: &str, options: Map) -> Result> { + // Convert Rhai Map to Rust HashMap + let mut config_options = HashMap::::new(); + + for (key, value) in options.iter() { + if let Ok(value_str) = value.clone().into_string() { + // Convert SmartString to String + config_options.insert(key.to_string(), value_str); + } else { + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Option '{}' must be a string", key).into(), + rhai::Position::NONE + ))); + } + } + + // Call the buildah config function + buildah_error_to_rhai_error(buildah::config(container, config_options)) +} \ No newline at end of file diff --git a/src/rhai/mod.rs b/src/rhai/mod.rs index dcfc87a..276fd42 100644 --- a/src/rhai/mod.rs +++ b/src/rhai/mod.rs @@ -6,15 +6,49 @@ mod error; mod os; mod process; +mod buildah; #[cfg(test)] mod tests; -use rhai::Engine; +// Re-export common Rhai types for convenience +pub use rhai::{Array, Dynamic, Map, EvalAltResult, Engine}; +// Re-export error module pub use error::*; -pub use os::*; -pub use process::*; + +// Re-export specific functions from modules to avoid name conflicts +pub use os::{ + register_os_module, + // File system functions + exist, find_file, find_files, find_dir, find_dirs, + delete, mkdir, file_size, rsync, + // Download functions + download, download_install +}; + +pub use process::{ + register_process_module, + // Run functions + run_command, run_silent, run_with_options, new_run_options, + // Process management functions + which, kill, process_list, process_get +}; + +pub use buildah::{ + register_buildah_module, + // Container functions + from, run, run_with_isolation, add, commit, remove, list, + build_with_options, new_build_options, + // Image functions + images, image_remove, image_push, image_tag, image_pull, + image_commit_with_options, new_commit_options, + config_with_options, new_config_options +}; + +// Rename copy functions to avoid conflicts +pub use os::copy as os_copy; +pub use buildah::copy as buildah_copy; /// Register all SAL modules with the Rhai engine /// @@ -41,6 +75,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box> { // Register Process module functions process::register_process_module(engine)?; + // Register Buildah module functions + buildah::register_buildah_module(engine)?; + // Future modules can be registered here Ok(()) diff --git a/src/rhai/process.rs b/src/rhai/process.rs index 38c55d9..a4bd58b 100644 --- a/src/rhai/process.rs +++ b/src/rhai/process.rs @@ -2,7 +2,7 @@ //! //! This module provides Rhai wrappers for the functions in the Process module. -use rhai::{Engine, EvalAltResult, Array, Dynamic}; +use rhai::{Engine, EvalAltResult, Array, Dynamic, Map}; use crate::process::{self, CommandResult, ProcessInfo, RunError, ProcessError}; /// Register Process module functions with the Rhai engine @@ -21,6 +21,8 @@ pub fn register_process_module(engine: &mut Engine) -> Result<(), Box Result<(), Box> engine.register_get("memory", |p: &mut ProcessInfo| p.memory); engine.register_get("cpu", |p: &mut ProcessInfo| p.cpu); - // Register error conversion functions - engine.register_fn("to_string", |err: &str| err.to_string()); - Ok(()) } @@ -76,6 +75,16 @@ fn process_error_to_rhai_error(result: Result) -> Result Map { + let mut map = Map::new(); + map.insert("die".into(), Dynamic::from(true)); + map.insert("silent".into(), Dynamic::from(false)); + map.insert("async_exec".into(), Dynamic::from(false)); + map.insert("log".into(), Dynamic::from(false)); + map +} + // // Run Function Wrappers // @@ -94,6 +103,50 @@ pub fn run_silent(command: &str) -> Result> { run_error_to_rhai_error(process::run_silent(command)) } +/// Run a command with options specified in a Map +/// +/// This provides a builder-style interface for Rhai scripts. +/// +/// # Example +/// +/// ```rhai +/// let options = new_run_options(); +/// options.die = false; +/// options.silent = true; +/// let result = run("echo Hello", options); +/// ``` +pub fn run_with_options(command: &str, options: Map) -> Result> { + let mut builder = process::run(command); + + // Apply options from the map + if let Some(die) = options.get("die") { + if let Ok(die_val) = die.clone().as_bool() { + builder = builder.die(die_val); + } + } + + if let Some(silent) = options.get("silent") { + if let Ok(silent_val) = silent.clone().as_bool() { + builder = builder.silent(silent_val); + } + } + + if let Some(async_exec) = options.get("async_exec") { + if let Ok(async_val) = async_exec.clone().as_bool() { + builder = builder.async_exec(async_val); + } + } + + if let Some(log) = options.get("log") { + if let Ok(log_val) = log.clone().as_bool() { + builder = builder.log(log_val); + } + } + + // Execute the command + run_error_to_rhai_error(builder.execute()) +} + // // Process Management Function Wrappers // diff --git a/src/rhai/tests.rs b/src/rhai/tests.rs index 07448b5..a91a5e0 100644 --- a/src/rhai/tests.rs +++ b/src/rhai/tests.rs @@ -117,6 +117,36 @@ mod tests { assert_eq!(result, ()); } + #[test] + fn test_run_with_options() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test running a command with custom options + #[cfg(target_os = "windows")] + let script = r#" + let options = new_run_options(); + options["die"] = true; + options["silent"] = false; + options["log"] = true; + let result = run("echo Hello World", options); + result.success && result.stdout.contains("Hello World") + "#; + + #[cfg(any(target_os = "macos", target_os = "linux"))] + let script = r#" + let options = new_run_options(); + options["die"] = true; + options["silent"] = false; + options["log"] = true; + let result = run("echo 'Hello World'", options); + result.success && result.stdout.contains("Hello World") + "#; + + let result = engine.eval::(script).unwrap(); + assert!(result); + } + #[test] fn test_run_command() { let mut engine = Engine::new(); diff --git a/src/virt/buildah/buildah-essentials.md b/src/virt/buildah/buildahdocs/buildah-essentials.md similarity index 100% rename from src/virt/buildah/buildah-essentials.md rename to src/virt/buildah/buildahdocs/buildah-essentials.md