This commit is contained in:
despiegk 2025-04-05 06:20:19 +02:00
parent 3803a54529
commit d336153247
5 changed files with 391 additions and 65 deletions

View File

@ -23,17 +23,16 @@ When you get information about a running process, you can see:
- `cpu`: How much CPU the process is using - `cpu`: How much CPU the process is using
## Run Functions ## Run Functions
### `run(command)` ### `run(command)`
Runs a command or multiline script with arguments. Runs a command or multiline script with arguments.
**Parameters:** **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. **Returns:** The result of the command, including output and whether it succeeded.
**Example:** **Example 1: Running a simple command**
```rhai ```rhai
// Run a simple command // Run a simple command
let result = run("ls -la"); 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)` ### `run_silent(command)`
@ -110,6 +131,24 @@ options.log = true; // Log the command execution
let result = run_with_options("npm install", options); 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 ## Process Management Functions
### `which(cmd)` ### `which(cmd)`

View File

@ -185,7 +185,7 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result<CommandResult,
if let Ok(l) = line { if let Ok(l) = line {
// Print the line if not silent and flush immediately // Print the line if not silent and flush immediately
if !silent_clone { if !silent_clone {
// Print stderr with error prefix // Print all stderr messages
eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors
std::io::stderr().flush().unwrap_or(()); std::io::stderr().flush().unwrap_or(());
} }
@ -288,7 +288,7 @@ fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool)
let command_args = vec!["/c", script_path.to_str().unwrap_or("")]; let command_args = vec!["/c", script_path.to_str().unwrap_or("")];
#[cfg(any(target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "macos", target_os = "linux"))]
let command_args = vec![script_path.to_str().unwrap_or("")]; let command_args = vec!["-e", script_path.to_str().unwrap_or("")];
if silent { if silent {
// For silent execution, use output() which captures but doesn't display // For silent execution, use output() which captures but doesn't display
@ -334,16 +334,28 @@ fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool)
/// Run a multiline script with optional silent mode /// Run a multiline script with optional silent mode
fn run_script_internal(script: &str, silent: bool) -> Result<CommandResult, RunError> { fn run_script_internal(script: &str, silent: bool) -> Result<CommandResult, RunError> {
// 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 // Print the script being executed if not silent
if !silent { if !silent {
println!("\x1b[36mExecuting script:\x1b[0m"); println!("\x1b[36mExecuting script:\x1b[0m");
// 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() { for (i, line) in script.lines().enumerate() {
println!("\x1b[36m{:3}: {}\x1b[0m", i + 1, line); println!("\x1b[36m{:3}: {}\x1b[0m", i + 1, line);
} }
}
println!("\x1b[36m---\x1b[0m"); 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 // _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 // it's not dropped prematurely, which would clean up the directory

View File

@ -3,60 +3,10 @@
//! This module provides Rhai wrappers for the functions in the Nerdctl module. //! This module provides Rhai wrappers for the functions in the Nerdctl module.
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map}; 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; 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<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// 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<EvalAltResult>> {
// Register Image type and methods
engine.register_type_with_name::<Image>("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 // Helper functions for error conversion
fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> { fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| { result.map_err(|e| {
@ -67,6 +17,100 @@ fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T,
}) })
} }
//
// Container Builder Pattern Implementation
//
/// Create a new Container
pub fn container_new(name: &str) -> Result<Container, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(Container::new(name))
}
/// Create a Container from an image
pub fn container_from_image(name: &str, image: &str) -> Result<Container, Box<EvalAltResult>> {
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<Container, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.build())
}
/// Start the Container
pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.start())
}
/// Stop the Container
pub fn container_stop(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.stop())
}
/// Remove the Container
pub fn container_remove(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.remove())
}
/// Execute a command in the Container
pub fn container_exec(container: &mut Container, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
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<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(container.copy(source, dest))
}
/// Create a new Map with default run options /// Create a new Map with default run options
pub fn new_run_options() -> Map { pub fn new_run_options() -> Map {
let mut map = Map::new(); let mut map = Map::new();
@ -186,3 +230,109 @@ pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result<Command
pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResult, Box<EvalAltResult>> { pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path)) 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<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// 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<EvalAltResult>> {
// Register Container type
engine.register_type_with_name::<Container>("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::<Image>("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(())
}

View File

@ -29,6 +29,7 @@ println(`- snapshotter: ${run_options.snapshotter}`);
// Test function availability // Test function availability
println("\nTesting function availability:"); println("\nTesting function availability:");
let functions = [ let functions = [
// Legacy functions
"nerdctl_run", "nerdctl_run",
"nerdctl_run_with_name", "nerdctl_run_with_name",
"nerdctl_run_with_port", "nerdctl_run_with_port",
@ -43,7 +44,27 @@ let functions = [
"nerdctl_image_tag", "nerdctl_image_tag",
"nerdctl_image_pull", "nerdctl_image_pull",
"nerdctl_image_commit", "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) // 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}`); 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 // Helper function to check if a function is registered
fn is_function_registered(name) { fn is_function_registered(name) {
try { try {
@ -63,4 +185,4 @@ fn is_function_registered(name) {
} }
} }
"Nerdctl wrapper test completed successfully!" // The final result depends on the outcome of the container test

View File

@ -2,6 +2,9 @@
// Create a bash script to set up the test environment // Create a bash script to set up the test environment
let setup_script = ` let setup_script = `
# Configure git to suppress the default branch name warning
git config --global advice.initDefaultBranch false
rm -rf /tmp/code rm -rf /tmp/code
mkdir -p /tmp/code mkdir -p /tmp/code
cd /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 config --local user.name 'Test User'
git commit -m 'Initial commit' git commit -m 'Initial commit'
cd myserver.com/myaccount/repored cd /tmp/code/myserver.com/myaccount/repored
git init git init
echo 'Initial test file' > test2.txt echo 'Initial test file' > test2.txt
git add 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 config --local user.name 'Test User'
git commit -m 'Initial commit' git commit -m 'Initial commit'
//now we have 2 repos # now we have 2 repos
`; `;