Compare commits

...

4 Commits

Author SHA1 Message Date
6de7bf9b56 ... 2025-04-05 06:34:16 +02:00
245aee12bf ... 2025-04-05 06:21:50 +02:00
d336153247 ... 2025-04-05 06:20:19 +02:00
3803a54529 ... 2025-04-05 05:59:51 +02:00
27 changed files with 1188 additions and 1383 deletions

View File

@ -1,752 +0,0 @@
# Buildah Builder Implementation Plan
## Introduction
This document outlines the plan for changing the buildah interface in the `@src/virt/buildah` module to use a builder object pattern. The current implementation uses standalone functions, which makes the interface less clear and harder to use. The new implementation will use a builder object with methods, which will make the interface more intuitive and easier to use.
## Current Architecture
The current buildah implementation has:
- Standalone functions in the buildah module (from, run, images, etc.)
- Functions that operate on container IDs passed as parameters
- No state maintained between function calls
- Rhai wrappers that expose these functions to Rhai scripts
Example of current usage:
```rust
// Create a container
let result = buildah::from("fedora:latest")?;
let container_id = result.stdout.trim();
// Run a command in the container
buildah::run(container_id, "dnf install -y nginx")?;
// Copy a file into the container
buildah::bah_copy(container_id, "./example.conf", "/etc/example.conf")?;
// Commit the container to create a new image
buildah::bah_commit(container_id, "my-nginx:latest")?;
```
## Proposed Architecture
We'll change to a builder object pattern where:
- A `Builder` struct is created with a `new()` method that takes a name and image
- All operations (including those not specific to a container) are methods on the Builder
- The Builder maintains state (like container ID) between method calls
- Methods return operation results (CommandResult or other types) for error handling
- Rhai wrappers expose the Builder and its methods to Rhai scripts
Example of proposed usage:
```rust
// Create a builder
let builder = Builder::new("my-container", "fedora:latest")?;
// Run a command in the container
builder.run("dnf install -y nginx")?;
// Copy a file into the container
builder.copy("./example.conf", "/etc/example.conf")?;
// Commit the container to create a new image
builder.commit("my-nginx:latest")?;
```
## Class Diagram
```mermaid
classDiagram
class Builder {
-String name
-String container_id
-String image
+new(name: String, image: String) -> Result<Builder, BuildahError>
+run(command: String) -> Result<CommandResult, BuildahError>
+run_with_isolation(command: String, isolation: String) -> Result<CommandResult, BuildahError>
+add(source: String, dest: String) -> Result<CommandResult, BuildahError>
+copy(source: String, dest: String) -> Result<CommandResult, BuildahError>
+commit(image_name: String) -> Result<CommandResult, BuildahError>
+remove() -> Result<CommandResult, BuildahError>
+config(options: HashMap<String, String>) -> Result<CommandResult, BuildahError>
}
class BuilderStatic {
+images() -> Result<Vec<Image>, BuildahError>
+image_remove(image: String) -> Result<CommandResult, BuildahError>
+image_pull(image: String, tls_verify: bool) -> Result<CommandResult, BuildahError>
+image_push(image: String, destination: String, tls_verify: bool) -> Result<CommandResult, BuildahError>
+image_tag(image: String, new_name: String) -> Result<CommandResult, BuildahError>
+image_commit(container: String, image_name: String, format: Option<String>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError>
+build(tag: Option<String>, context_dir: String, file: String, isolation: Option<String>) -> Result<CommandResult, BuildahError>
}
Builder --|> BuilderStatic : Static methods
```
## Implementation Plan
### Step 1: Create the Builder Struct
1. Create a new file `src/virt/buildah/builder.rs`
2. Define the Builder struct with fields for name, container_id, and image
3. Implement the new() method that creates a container from the image and returns a Builder instance
4. Implement methods for all container operations (run, add, copy, commit, etc.)
5. Implement methods for all image operations (images, image_remove, image_pull, etc.)
### Step 2: Update the Module Structure
1. Update `src/virt/buildah/mod.rs` to include the new builder module
2. Re-export the Builder struct and its methods
3. Keep the existing functions for backward compatibility (marked as deprecated)
### Step 3: Update the Rhai Wrapper
1. Update `src/rhai/buildah.rs` to register the Builder type with the Rhai engine
2. Register all Builder methods with the Rhai engine
3. Create Rhai-friendly constructor for the Builder
4. Update the existing function wrappers to use the new Builder (or keep them for backward compatibility)
### Step 4: Update Examples and Tests
1. Update `examples/buildah.rs` to use the new Builder pattern
2. Update `rhaiexamples/04_buildah_operations.rhai` to use the new Builder pattern
3. Update any tests to use the new Builder pattern
## Detailed Implementation
### 1. Builder Struct Definition
```rust
// src/virt/buildah/builder.rs
pub struct Builder {
name: String,
container_id: Option<String>,
image: String,
}
impl Builder {
pub fn new(name: &str, image: &str) -> Result<Self, BuildahError> {
let result = execute_buildah_command(&["from", "--name", name, image])?;
let container_id = result.stdout.trim().to_string();
Ok(Self {
name: name.to_string(),
container_id: Some(container_id),
image: image.to_string(),
})
}
// Container methods
pub fn run(&self, command: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["run", container_id, "sh", "-c", command])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn run_with_isolation(&self, command: &str, isolation: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["run", "--isolation", isolation, container_id, "sh", "-c", command])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["copy", container_id, source, dest])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn add(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["add", container_id, source, dest])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn commit(&self, image_name: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["commit", container_id, image_name])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn remove(&self) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
execute_buildah_command(&["rm", container_id])
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
pub fn config(&self, options: HashMap<String, String>) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
let mut args_owned: Vec<String> = Vec::new();
args_owned.push("config".to_string());
// Process options map
for (key, value) in options.iter() {
let option_name = format!("--{}", key);
args_owned.push(option_name);
args_owned.push(value.clone());
}
args_owned.push(container_id.clone());
// Convert Vec<String> to Vec<&str> for execute_buildah_command
let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect();
execute_buildah_command(&args)
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
// Static methods
pub fn images() -> Result<Vec<Image>, BuildahError> {
// Implementation from current images() function
let result = execute_buildah_command(&["images", "--json"])?;
// Try to parse the JSON output
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
Ok(json) => {
if let serde_json::Value::Array(images_json) = json {
let mut images = Vec::new();
for image_json in images_json {
// Extract image ID
let id = match image_json.get("id").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return Err(BuildahError::ConversionError("Missing image ID".to_string())),
};
// Extract image names
let names = match image_json.get("names").and_then(|v| v.as_array()) {
Some(names_array) => {
let mut names_vec = Vec::new();
for name_value in names_array {
if let Some(name_str) = name_value.as_str() {
names_vec.push(name_str.to_string());
}
}
names_vec
},
None => Vec::new(), // Empty vector if no names found
};
// Extract image size
let size = match image_json.get("size").and_then(|v| v.as_str()) {
Some(size) => size.to_string(),
None => "Unknown".to_string(), // Default value if size not found
};
// Extract creation timestamp
let created = match image_json.get("created").and_then(|v| v.as_str()) {
Some(created) => created.to_string(),
None => "Unknown".to_string(), // Default value if created not found
};
// Create Image struct and add to vector
images.push(Image {
id,
names,
size,
created,
});
}
Ok(images)
} else {
Err(BuildahError::JsonParseError("Expected JSON array".to_string()))
}
},
Err(e) => {
Err(BuildahError::JsonParseError(format!("Failed to parse image list JSON: {}", e)))
}
}
}
pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
execute_buildah_command(&["rmi", image])
}
pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
let mut args = vec!["pull"];
if !tls_verify {
args.push("--tls-verify=false");
}
args.push(image);
execute_buildah_command(&args)
}
pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
let mut args = vec!["push"];
if !tls_verify {
args.push("--tls-verify=false");
}
args.push(image);
args.push(destination);
execute_buildah_command(&args)
}
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
execute_buildah_command(&["tag", image, new_name])
}
pub fn image_commit(container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError> {
let mut args = vec!["commit"];
if let Some(format_str) = format {
args.push("--format");
args.push(format_str);
}
if squash {
args.push("--squash");
}
if rm {
args.push("--rm");
}
args.push(container);
args.push(image_name);
execute_buildah_command(&args)
}
pub fn build(tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>) -> Result<CommandResult, BuildahError> {
let mut args = Vec::new();
args.push("build");
if let Some(tag_value) = tag {
args.push("-t");
args.push(tag_value);
}
if let Some(isolation_value) = isolation {
args.push("--isolation");
args.push(isolation_value);
}
args.push("-f");
args.push(file);
args.push(context_dir);
execute_buildah_command(&args)
}
}
```
### 2. Updated Module Structure
```rust
// src/virt/buildah/mod.rs
mod containers;
mod images;
mod cmd;
mod builder;
#[cfg(test)]
mod containers_test;
use std::fmt;
use std::error::Error;
use std::io;
/// Error type for buildah operations
#[derive(Debug)]
pub enum BuildahError {
/// The buildah command failed to execute
CommandExecutionFailed(io::Error),
/// The buildah command executed but returned an error
CommandFailed(String),
/// Failed to parse JSON output
JsonParseError(String),
/// Failed to convert data
ConversionError(String),
/// Generic error
Other(String),
}
impl fmt::Display for BuildahError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildahError::CommandExecutionFailed(e) => write!(f, "Failed to execute buildah command: {}", e),
BuildahError::CommandFailed(e) => write!(f, "Buildah command failed: {}", e),
BuildahError::JsonParseError(e) => write!(f, "Failed to parse JSON: {}", e),
BuildahError::ConversionError(e) => write!(f, "Conversion error: {}", e),
BuildahError::Other(e) => write!(f, "{}", e),
}
}
}
impl Error for BuildahError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
BuildahError::CommandExecutionFailed(e) => Some(e),
_ => None,
}
}
}
// Re-export the Builder
pub use builder::Builder;
// Re-export existing functions for backward compatibility
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
pub use containers::*;
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
pub use images::*;
pub use cmd::*;
```
### 3. Rhai Wrapper Changes
```rust
// src/rhai/buildah.rs
//! 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, Builder};
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<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register types
register_bah_types(engine)?;
// Register Builder constructor
engine.register_fn("bah_new", bah_new);
// Register Builder instance methods
engine.register_fn("run", |builder: &mut Builder, command: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.run(command))
});
engine.register_fn("run_with_isolation", |builder: &mut Builder, command: &str, isolation: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.run_with_isolation(command, isolation))
});
engine.register_fn("copy", |builder: &mut Builder, source: &str, dest: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.copy(source, dest))
});
engine.register_fn("add", |builder: &mut Builder, source: &str, dest: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.add(source, dest))
});
engine.register_fn("commit", |builder: &mut Builder, image_name: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.commit(image_name))
});
engine.register_fn("remove", |builder: &mut Builder| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(builder.remove())
});
engine.register_fn("config", |builder: &mut Builder, options: Map| -> Result<CommandResult, Box<EvalAltResult>> {
// Convert Rhai Map to Rust HashMap
let config_options = convert_map_to_hashmap(options)?;
bah_error_to_rhai_error(builder.config(config_options))
});
// Register Builder static methods
engine.register_fn("images", |_: &mut Builder| -> Result<Array, Box<EvalAltResult>> {
let images = bah_error_to_rhai_error(Builder::images())?;
// Convert Vec<Image> to Rhai Array
let mut array = Array::new();
for image in images {
array.push(Dynamic::from(image));
}
Ok(array)
});
engine.register_fn("image_remove", |_: &mut Builder, image: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_remove(image))
});
engine.register_fn("image_pull", |_: &mut Builder, image: &str, tls_verify: bool| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_pull(image, tls_verify))
});
engine.register_fn("image_push", |_: &mut Builder, image: &str, destination: &str, tls_verify: bool| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify))
});
engine.register_fn("image_tag", |_: &mut Builder, image: &str, new_name: &str| -> Result<CommandResult, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::image_tag(image, new_name))
});
// Register legacy functions for backward compatibility
register_legacy_functions(engine)?;
Ok(())
}
/// Register Buildah module types with the Rhai engine
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register Builder type
engine.register_type_with_name::<Builder>("BuildahBuilder");
// Register getters for Builder properties
engine.register_get("container_id", |builder: &mut Builder| {
match builder.container_id() {
Some(id) => id.clone(),
None => "".to_string(),
}
});
engine.register_get("name", |builder: &mut Builder| builder.name().to_string());
engine.register_get("image", |builder: &mut Builder| builder.image().to_string());
// Register Image type and methods (same as before)
engine.register_type_with_name::<Image>("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
});
// Add a 'name' getter that returns the first name or a default
engine.register_get("name", |img: &mut Image| {
if img.names.is_empty() {
"<none>".to_string()
} else {
img.names[0].clone()
}
});
engine.register_get("size", |img: &mut Image| img.size.clone());
engine.register_get("created", |img: &mut Image| img.created.clone());
Ok(())
}
/// Register legacy functions for backward compatibility
fn register_legacy_functions(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register container functions
engine.register_fn("bah_from", bah_from_legacy);
engine.register_fn("bah_run", bah_run_legacy);
engine.register_fn("bah_run_with_isolation", bah_run_with_isolation_legacy);
engine.register_fn("bah_copy", bah_copy_legacy);
engine.register_fn("bah_add", bah_add_legacy);
engine.register_fn("bah_commit", bah_commit_legacy);
engine.register_fn("bah_remove", bah_remove_legacy);
engine.register_fn("bah_list", bah_list_legacy);
engine.register_fn("bah_build", bah_build_with_options_legacy);
engine.register_fn("bah_new_build_options", new_build_options);
// Register image functions
engine.register_fn("bah_images", images_legacy);
engine.register_fn("bah_image_remove", image_remove_legacy);
engine.register_fn("bah_image_push", image_push_legacy);
engine.register_fn("bah_image_tag", image_tag_legacy);
engine.register_fn("bah_image_pull", image_pull_legacy);
engine.register_fn("bah_image_commit", image_commit_with_options_legacy);
engine.register_fn("bah_new_commit_options", new_commit_options);
engine.register_fn("bah_config", config_with_options_legacy);
engine.register_fn("bah_new_config_options", new_config_options);
Ok(())
}
// Helper functions for error conversion
fn bah_error_to_rhai_error<T>(result: Result<T, BuildahError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Buildah error: {}", e).into(),
rhai::Position::NONE
))
})
}
// Helper function to convert Rhai Map to Rust HashMap
fn convert_map_to_hashmap(options: Map) -> Result<HashMap<String, String>, Box<EvalAltResult>> {
let mut config_options = HashMap::<String, String>::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
)));
}
}
Ok(config_options)
}
/// Create a new Builder
pub fn bah_new(name: &str, image: &str) -> Result<Builder, Box<EvalAltResult>> {
bah_error_to_rhai_error(Builder::new(name, image))
}
// Legacy function implementations (for backward compatibility)
// These would call the new Builder methods internally
// ...
```
### 4. Example Updates
#### Rust Example
```rust
// examples/buildah.rs
//! Example usage of the buildah module
//!
//! This file demonstrates how to use the buildah module to perform
//! common container operations like creating containers, running commands,
//! and managing images.
use sal::virt::buildah::{Builder, BuildahError};
use std::collections::HashMap;
/// Run a complete buildah workflow example
pub fn run_buildah_example() -> Result<(), BuildahError> {
println!("Starting buildah example workflow...");
// Step 1: Create a container from an image using the Builder
println!("\n=== Creating container from fedora:latest ===");
let builder = Builder::new("my-fedora-container", "fedora:latest")?;
println!("Created container: {}", builder.container_id().unwrap_or(&"unknown".to_string()));
// Step 2: Run a command in the container
println!("\n=== Installing nginx in container ===");
// Use chroot isolation to avoid BPF issues
let install_result = builder.run_with_isolation("dnf install -y nginx", "chroot")?;
println!("{:#?}", install_result);
println!("Installation output: {}", install_result.stdout);
// Step 3: Copy a file into the container
println!("\n=== Copying configuration file to container ===");
builder.copy("./example.conf", "/etc/example.conf")?;
// Step 4: Configure container metadata
println!("\n=== Configuring container metadata ===");
let mut config_options = HashMap::new();
config_options.insert("port".to_string(), "80".to_string());
config_options.insert("label".to_string(), "maintainer=example@example.com".to_string());
config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string());
builder.config(config_options)?;
println!("Container configured");
// Step 5: Commit the container to create a new image
println!("\n=== Committing container to create image ===");
let image_name = "my-nginx:latest";
builder.commit(image_name)?;
println!("Created image: {}", image_name);
// Step 6: List images to verify our new image exists
println!("\n=== Listing images ===");
let images = Builder::images()?;
println!("Found {} images:", images.len());
for image in images {
println!(" ID: {}", image.id);
println!(" Names: {}", image.names.join(", "));
println!(" Size: {}", image.size);
println!(" Created: {}", image.created);
println!();
}
// Step 7: Clean up (optional in a real workflow)
println!("\n=== Cleaning up ===");
Builder::image_remove(image_name)?;
builder.remove()?;
println!("\nBuildah example workflow completed successfully!");
Ok(())
}
/// Demonstrate how to build an image from a Containerfile/Dockerfile
pub fn build_image_example() -> Result<(), BuildahError> {
println!("Building an image from a Containerfile...");
// Use the build function with tag, context directory, and isolation to avoid BPF issues
let result = Builder::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?;
println!("Build output: {}", result.stdout);
println!("Image built successfully!");
Ok(())
}
/// Example of pulling and pushing images
pub fn registry_operations_example() -> Result<(), BuildahError> {
println!("Demonstrating registry operations...");
// Pull an image
println!("\n=== Pulling an image ===");
Builder::image_pull("docker.io/library/alpine:latest", true)?;
println!("Image pulled successfully");
// Tag the image
println!("\n=== Tagging the image ===");
Builder::image_tag("alpine:latest", "my-alpine:v1.0")?;
println!("Image tagged successfully");
// Push an image (this would typically go to a real registry)
// println!("\n=== Pushing an image (example only) ===");
// println!("In a real scenario, you would push to a registry with:");
// println!("Builder::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)");
Ok(())
}
/// Main function to run all examples
pub fn run_all_examples() -> Result<(), BuildahError> {
println!("=== BUILDAH MODULE EXAMPLES ===\n");
run_buildah_example()?;
build_image_example()?;
registry_operations_example()?;
Ok(())
}
fn main() {
let _ = run_all_examples();
}
```
#### Rhai Example
```rhai
// rhaiexamples/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}`);

View File

@ -1,238 +0,0 @@
# Container Builder Implementation Plan
## Overview
This document outlines the plan for redesigning the nerdctl interface in the `src/virt/nerdctl` directory to use an object-oriented approach with a Container struct that supports method chaining for the builder pattern. This will replace the existing function-based approach while maintaining all current functionality.
## Architecture
```mermaid
classDiagram
class Container {
-String name
-String container_id
-String? image
-HashMap~String, String~ config
-Vec~String~ ports
-Vec~String~ volumes
-HashMap~String, String~ env_vars
-Option~String~ network
-Vec~String~ network_aliases
-Option~String~ cpu_limit
-Option~String~ memory_limit
-Option~String~ memory_swap_limit
-Option~String~ cpu_shares
-Option~String~ restart_policy
-Option~HealthCheck~ health_check
+new(name: &str) -> Result~Container, NerdctlError~
+from_image(name: &str, image: &str) -> Result~Container, NerdctlError~
+with_port(port: &str) -> Container
+with_ports(ports: &[&str]) -> Container
+with_volume(volume: &str) -> Container
+with_volumes(volumes: &[&str]) -> Container
+with_env(key: &str, value: &str) -> Container
+with_envs(env_map: &HashMap<&str, &str>) -> Container
+with_network(network: &str) -> Container
+with_network_alias(alias: &str) -> Container
+with_network_aliases(aliases: &[&str]) -> Container
+with_cpu_limit(cpus: &str) -> Container
+with_memory_limit(memory: &str) -> Container
+with_memory_swap_limit(memory_swap: &str) -> Container
+with_cpu_shares(shares: &str) -> Container
+with_restart_policy(policy: &str) -> Container
+with_health_check(cmd: &str) -> Container
+with_health_check_options(cmd: &str, interval: Option<&str>, timeout: Option<&str>, retries: Option<u32>, start_period: Option<&str>) -> Container
+with_snapshotter(snapshotter: &str) -> Container
+with_detach(detach: bool) -> Container
+build() -> Result~Container, NerdctlError~
+start() -> Result~CommandResult, NerdctlError~
+stop() -> Result~CommandResult, NerdctlError~
+remove() -> Result~CommandResult, NerdctlError~
+exec(command: &str) -> Result~CommandResult, NerdctlError~
+copy(source: &str, dest: &str) -> Result~CommandResult, NerdctlError~
+export(path: &str) -> Result~CommandResult, NerdctlError~
+commit(image_name: &str) -> Result~CommandResult, NerdctlError~
+status() -> Result~ContainerStatus, NerdctlError~
+health_status() -> Result~String, NerdctlError~
+resources() -> Result~ResourceUsage, NerdctlError~
}
class HealthCheck {
+String cmd
+Option~String~ interval
+Option~String~ timeout
+Option~u32~ retries
+Option~String~ start_period
}
class ContainerStatus {
+String state
+String status
+String created
+String started
+Option~String~ health_status
+Option~String~ health_output
}
class ResourceUsage {
+String cpu_usage
+String memory_usage
+String memory_limit
+String memory_percentage
+String network_input
+String network_output
+String block_input
+String block_output
+String pids
}
class NerdctlError {
+CommandExecutionFailed(io::Error)
+CommandFailed(String)
+JsonParseError(String)
+ConversionError(String)
+Other(String)
}
Container --> ContainerStatus : returns
Container --> ResourceUsage : returns
Container --> HealthCheck : contains
Container --> NerdctlError : may throw
```
## Implementation Steps
### 1. Create Container Struct and Basic Methods
Create a new file `src/virt/nerdctl/container.rs` with the Container struct and basic methods.
### 2. Implement Builder Pattern Methods
Add builder pattern methods to the Container struct for configuration.
### 3. Implement Container Operations
Add methods for container operations like start, stop, exec, etc.
### 4. Implement Status and Resource Usage Methods
Add methods for getting container status and resource usage information.
### 5. Update mod.rs to Export the New Container Struct
Update `src/virt/nerdctl/mod.rs` to include the new container module.
### 6. Create Example Usage
Create an example file to demonstrate the new Container API.
## Key Features
### Container Creation and Configuration
- Method chaining for the builder pattern
- Support for multiple ports and volumes
- Environment variable configuration
- Network configuration and aliases
- Resource limits (CPU, memory)
- Restart policies
- Health checks
### Container Operations
- Start, stop, and remove containers
- Execute commands in containers
- Copy files between container and host
- Export containers to tarballs
- Commit containers to images
### Container Monitoring
- Get container status information
- Get container health status
- Get resource usage information
## Example Usage
```rust
// Create a container with various configuration options
let container = Container::from_image("my-web-app", "nginx:latest")?
.with_ports(&["8080:80", "8443:443"])
.with_volumes(&[
"./html:/usr/share/nginx/html",
"./config/nginx.conf:/etc/nginx/nginx.conf"
])
.with_env("NGINX_HOST", "example.com")
.with_env("NGINX_PORT", "80")
.with_network("app-network")
.with_network_alias("web")
.with_cpu_limit("0.5")
.with_memory_limit("512m")
.with_restart_policy("always")
.with_health_check_options(
"curl -f http://localhost/ || exit 1",
Some("10s"),
Some("5s"),
Some(3),
Some("30s")
)
.with_detach(true)
.build()?;
// Start the container
container.start()?;
// Execute a command in the container
let result = container.exec("echo 'Hello from container'")?;
println!("Command output: {}", result.stdout);
// Get container status
let status = container.status()?;
println!("Container state: {}", status.state);
println!("Container status: {}", status.status);
// Get resource usage
let resources = container.resources()?;
println!("CPU usage: {}", resources.cpu_usage);
println!("Memory usage: {}", resources.memory_usage);
// Stop and remove the container
container.stop()?;
container.remove()?;
```
## Network Management
```rust
// Create a network
Container::create_network("app-network", Some("bridge"))?;
// Create containers in the network
let db = Container::from_image("db", "postgres:latest")?
.with_network("app-network")
.with_network_alias("database")
.with_env("POSTGRES_PASSWORD", "example")
.build()?;
let app = Container::from_image("app", "my-app:latest")?
.with_network("app-network")
.with_env("DATABASE_URL", "postgres://postgres:example@database:5432/postgres")
.build()?;
// Remove the network when done
Container::remove_network("app-network")?;
```
## Migration Strategy
1. Create the new Container struct and its methods
2. Update the mod.rs file to export the new Container struct
3. Create example usage to demonstrate the new API
4. Deprecate the old function-based API (but keep it for backward compatibility)
5. Update documentation to reflect the new API
## Testing Strategy
1. Unit tests for the Container struct and its methods
2. Integration tests for the Container API
3. Manual testing with real containers

View File

34
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
bun.lockb
bun.lock
yarn.lock
build.sh
build_dev.sh
develop.sh
docusaurus.config.ts
sidebars.ts
tsconfig.json

72
docs/cfg/footer.json Normal file
View File

@ -0,0 +1,72 @@
{
"style": "dark",
"links": [
{
"title": "Docs",
"items": [
{
"label": "Introduction",
"to": "/docs/introduction"
},
{
"label": "Litepaper",
"to": "/docs/litepaper"
},
{
"label": "Roadmap",
"to": "/docs/roadmap"
},
{
"label": "Manual",
"href": "https://manual.grid.tf/"
}
]
},
{
"title": "Features",
"items": [
{
"label": "Become a Farmer",
"to": "/docs/category/become-a-farmer"
},
{
"label": "Components",
"to": "/docs/category/components"
},
{
"label": "Tokenomics",
"to": "/docs/tokens/tokenomics"
},
{
"label": "Technology",
"to": "/docs/tech"
}
]
},
{
"title": "Web",
"items": [
{
"label": "ThreeFold.io",
"href": "https://threefold.io"
},
{
"label": "Dashboard",
"href": "https://dashboard.grid.tf"
},
{
"label": "GitHub",
"href": "https://github.com/threefoldtech/home"
},
{
"href": "https://mycelium.threefold.io/",
"label": "Mycelium Network"
},
{
"href": "https://aibox.threefold.io/",
"label": "AI Box"
}
]
}
]
}

17
docs/cfg/main.json Normal file
View File

@ -0,0 +1,17 @@
{
"title": "ThreeFold DePIN",
"tagline": "ThreeFold DePIN",
"favicon": "img/favicon.png",
"url": "https://docs.threefold.io",
"url_home": "docs/introduction",
"baseUrl": "/",
"image": "img/tf_graph.png",
"metadata": {
"description": "Internet Infrastructur for Everyone by Everyone, Everywhere.",
"image": "https://threefold.info/tfgrid4/img/tf_graph.png",
"title": "ThreeFold DePIN"
},
"buildDest":["root@info.ourworld.tf:/root/hero/www/info/tfgrid4"],
"buildDestDev":["root@info.ourworld.tf:/root/hero/www/infodev/tfgrid4"],
"copyright": "ThreeFold"
}

25
docs/cfg/navbar.json Normal file
View File

@ -0,0 +1,25 @@
{
"title": "",
"logo": {
"alt": "ThreeFold Logo",
"src": "img/logo.svg",
"srcDark": "img/new_logo_tft.png"
},
"items": [
{
"href": "https://threefold.io",
"label": "ThreeFold.io",
"position": "right"
},
{
"href": "https://mycelium.threefold.io/",
"label": "Mycelium Network",
"position": "right"
},
{
"href": "https://aibox.threefold.io/",
"label": "AI Box",
"position": "right"
}
]
}

View File

@ -1 +0,0 @@
EXAMPLE FILE TO TEST

View File

@ -1,8 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000

View File

@ -1,121 +0,0 @@
# Implementation Plan: Rhai Integration for OS Module Functions
## 1. Project Structure Changes
We'll create a new directory structure for the Rhai integration:
```
src/
├── rhai/
│ ├── mod.rs # Main module file for Rhai integration
│ ├── os.rs # OS module wrappers
│ └── error.rs # Error type conversions
```
## 2. Dependencies
Add Rhai as a dependency in Cargo.toml:
```toml
[dependencies]
# Existing dependencies...
rhai = { version = "1.12.0", features = ["sync"] }
```
## 3. Implementation Steps
### 3.1. Create Basic Module Structure
1. Create the `src/rhai/mod.rs` file with:
- Module declarations
- A modular registration system
- Public exports
2. Create the `src/rhai/error.rs` file with:
- Conversions from our custom error types to Rhai's `EvalAltResult`
- Helper functions for error handling
### 3.2. Implement OS Module Wrappers
Create the `src/rhai/os.rs` file with:
1. Import necessary modules and types
2. Create wrapper functions for each function in `src/os/fs.rs` and `src/os/download.rs`
3. Implement a registration function specific to the OS module
4. Expose error types to Rhai
### 3.3. Update Main Library File
Update `src/lib.rs` to expose the new Rhai module.
## 4. Detailed Implementation
### 4.1. Error Handling
For each function that returns a `Result<T, E>` where `E` is one of our custom error types:
1. Create a wrapper function that converts our error type to Rhai's `EvalAltResult`
2. Register the error types with Rhai to allow for proper error handling in scripts
### 4.2. Function Wrappers
For each function in the OS module:
1. Create a wrapper function with the same name
2. Handle any necessary type conversions
3. Convert error types to Rhai's error system
4. Register the function with the Rhai engine
### 4.3. Registration System
Create a modular registration system:
1. Implement a general `register` function that takes a Rhai engine
2. Implement module-specific registration functions (e.g., `register_os_module`)
3. Design the system to be extensible for future modules
## 5. Implementation Flow Diagram
```mermaid
flowchart TD
A[Add Rhai dependency] --> B[Create directory structure]
B --> C[Implement error conversions]
C --> D[Implement OS module wrappers]
D --> E[Create registration system]
E --> F[Update lib.rs]
F --> G[Test the implementation]
```
## 6. Function Mapping
Here's a mapping of the OS module functions to their Rhai wrappers:
### File System Functions (from fs.rs)
- `copy` → Wrap with error conversion
- `exist` → Direct wrapper (returns bool)
- `find_file` → Wrap with error conversion
- `find_files` → Wrap with error conversion
- `find_dir` → Wrap with error conversion
- `find_dirs` → Wrap with error conversion
- `delete` → Wrap with error conversion
- `mkdir` → Wrap with error conversion
- `file_size` → Wrap with error conversion
- `rsync` → Wrap with error conversion
### Download Functions (from download.rs)
- `download` → Wrap with error conversion
- `download_install` → Wrap with error conversion
## 7. Error Type Handling
We'll expose our custom error types to Rhai:
1. Register `FsError` and `DownloadError` as custom types
2. Implement proper error conversion to allow for detailed error handling in Rhai scripts
3. Create helper functions to extract error information
## 8. Testing Strategy
1. Create unit tests for each wrapper function
2. Create integration tests with sample Rhai scripts
3. Test error handling scenarios

View File

@ -1,179 +0,0 @@
# Run Builder Implementation Plan
This document outlines the plan for refactoring the `run.rs` module to use the builder pattern.
## Current Implementation Analysis
The current implementation has several functions for running commands and scripts:
- `run_command` and `run_command_silent` for single commands
- `run_script` and `run_script_silent` for multiline scripts
- `run` and `run_silent` as convenience functions that detect whether the input is a command or script
These functions don't support all the options we want (die, async, log), and they don't follow the builder pattern.
## Builder Pattern Implementation Plan
### 1. Create a `RunBuilder` struct
```rust
pub struct RunBuilder<'a> {
cmd: &'a str,
die: bool,
silent: bool,
async_exec: bool,
log: bool,
}
```
### 2. Implement Default Values and Builder Methods
```rust
impl<'a> RunBuilder<'a> {
pub fn new(cmd: &'a str) -> Self {
Self {
cmd,
die: true, // Default: true
silent: false, // Default: false
async_exec: false, // Default: false
log: false, // Default: false
}
}
pub fn die(mut self, die: bool) -> Self {
self.die = die;
self
}
pub fn silent(mut self, silent: bool) -> Self {
self.silent = silent;
self
}
pub fn async_exec(mut self, async_exec: bool) -> Self {
self.async_exec = async_exec;
self
}
pub fn log(mut self, log: bool) -> Self {
self.log = log;
self
}
pub fn execute(self) -> Result<CommandResult, RunError> {
// Implementation will go here
}
}
```
### 3. Implement the `execute` Method
The `execute` method will:
1. Determine if the command is a script or a single command
2. Handle the `async_exec` option by spawning a process without waiting
3. Handle the `log` option by logging command execution if enabled
4. Handle the `die` option by returning a CommandResult instead of an Err when die=false
5. Use the existing internal functions for the actual execution
### 4. Create a Public Function to Start the Builder
```rust
pub fn run(cmd: &str) -> RunBuilder {
RunBuilder::new(cmd)
}
```
### 5. Update Existing Functions for Backward Compatibility
Update the existing functions to use the new builder pattern internally for backward compatibility.
## Structure Diagram
```mermaid
classDiagram
class RunBuilder {
+String cmd
+bool die
+bool silent
+bool async_exec
+bool log
+new(cmd: &str) RunBuilder
+die(bool) RunBuilder
+silent(bool) RunBuilder
+async_exec(bool) RunBuilder
+log(bool) RunBuilder
+execute() Result<CommandResult, RunError>
}
class CommandResult {
+String stdout
+String stderr
+bool success
+int code
}
RunBuilder ..> CommandResult : produces
note for RunBuilder "Builder pattern implementation\nfor command execution"
```
## Implementation Details
### Handling the `async_exec` Option
When `async_exec` is true, we'll spawn the process but not wait for it to complete. We'll return a CommandResult with:
- Empty stdout and stderr
- success = true (since we don't know the outcome)
- code = 0 (since we don't know the exit code)
### Handling the `log` Option
When `log` is true, we'll log the command execution with a "[LOG]" prefix. For example:
```
[LOG] Executing command: ls -la
```
### Handling the `die` Option
When `die` is false and a command fails, instead of returning an Err, we'll return a CommandResult with:
- success = false
- The appropriate error message in stderr
- code = -1 or the actual exit code if available
## Usage Examples
After implementation, users will be able to use the builder pattern like this:
```rust
// Simple usage with defaults
let result = run("ls -la").execute()?;
// With options
let result = run("ls -la")
.silent(true)
.die(false)
.execute()?;
// Async execution
run("long_running_command")
.async_exec(true)
.execute()?;
// With logging
let result = run("important_command")
.log(true)
.execute()?;
// Script execution
let result = run("echo 'Hello'\necho 'World'")
.silent(true)
.execute()?;
```
## Implementation Steps
1. Add the `RunBuilder` struct and its methods
2. Implement the `execute` method
3. Create the public `run` function
4. Update the existing functions to use the builder pattern internally
5. Add tests for the new functionality
6. Update documentation

View File

@ -0,0 +1,8 @@
{
"label": "Become a Farmer",
"position": 6,
"link": {
"type": "generated-index",
"description": "Learn how to become a farmer. Let's together build Web4 everywhere for everyone."
}
}

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
## 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)`

54
src/docs/tech.md Normal file
View File

@ -0,0 +1,54 @@
---
sidebar_position: 10
---
# Technology
![](img/threefold_parts.png)
ThreeFold delivers the plumbing layer for a better Internet which has the potential to achieve Augmented Collective Intelligence[^1]. We call such a system **Web4**.
> *ThreeFold might be the only platform in the world providing Web4 network, data and cloud capabilities in one system.*
## 3 Required Levels
Together with our partners, we have all the required parts to make it happen on 3 major levels:
### Personal Level
- **Agent Layer**: Every person owns a Personal Digital Assistant (PDA), managing their digital life.
- **Identity Layer**: Strong reputation management, proof of authenticity, a global name system.
- **Intelligence Layer**: Decentralized, personal AI systems for collaboration & augmented intelligence.
- **Transaction Layer**: Fully integrated with Web3 systems and beyond, e.g. mutual credit, etc.
### Infrastructure Level
- **Network Layer**: Redesign of how communication happens with a private and more scalable network layer.
- **Data Layer**: Redesign of how we share, distribute and store data.
- **Serverless Compute Layer**: Allow code to run close to where participants and data are.
- **Cloud Layer**: Run VMs and containers as part of the ecosystem with Web2 compatibility layer.
### Physical Level
- **Routers**: Route between old and new web, and create new secure communication channels.
- **Nodes**: Deliver AI, Data, Compute to the ecosystem.
- **Phones**: Our personal device, capable of building a meshed network, offline support with catchup.
- **Computers**: Any current Linux, Windows, macOS computer seamlessly integrates.
![](img/zos.png)
The following are the required components to make all this possible:
- **Zero-OS**: Custom lightweight operating system for nodes built on the Linux kernel.
- Self-healing and automated resource management via bare metal ThreeFold nodes.
- **Mycelium**: End-to-end encrypted network always using the shortest path.
- **Quantum Safe Storage**: Technology resistant to quantum computer attacks where data can never be lost.
- **Advanced AI Agent**: Creation of apps fully compatibility with Web3.
- **Smart Contract for IT**: Blockchain-based resource allocation with signed contracts.
- Secure, transparent transaction mechanisms for deployment of solutions on the ThreeFold Grid.
> For more information, read the [ThreeFold Tech ebook](https://threefold.info/tech).
[^1]: Augmented Collective Intelligence - Supermind [Link](https://www.supermind.design/)

View File

@ -185,7 +185,7 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result<CommandResult,
if let Ok(l) = line {
// Print the line if not silent and flush immediately
if !silent_clone {
// Always print stderr, even if silent is true, for error visibility
// Print all stderr messages
eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors
std::io::stderr().flush().unwrap_or(());
}
@ -241,14 +241,8 @@ fn process_command_output(output: Result<Output, std::io::Error>) -> Result<Comm
Ok(out) => {
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
// Print stderr if there's any, even for silent execution
if !stderr.is_empty() {
eprintln!("\x1b[31mCommand stderr output:\x1b[0m");
for line in stderr.lines() {
eprintln!("\x1b[31m{}\x1b[0m", line);
}
}
// We'll collect stderr but not print it here
// It will be included in the error message if the command fails
// If the command failed, print a clear error message
if !out.status.success() {
@ -294,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("")];
#[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 {
// For silent execution, use output() which captures but doesn't display
@ -340,25 +334,40 @@ fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool)
/// Run a multiline script with optional silent mode
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
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
// Execute the script and handle the result
let result = execute_script_internal(&interpreter, &script_path, silent);
// If there was an error, print a clear error message
// If there was an error, print a clear error message only if it's not a CommandFailed error
// (which would already have printed the stderr)
if let Err(ref e) = result {
eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e);
if !matches!(e, RunError::CommandFailed(_)) {
eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e);
}
}
result
@ -485,8 +494,11 @@ impl<'a> RunBuilder<'a> {
Ok(res)
},
Err(e) => {
// Always print the error, even if die is false
eprintln!("\x1b[31mCommand error: {}\x1b[0m", e);
// Print the error only if it's not a CommandFailed error
// (which would already have printed the stderr)
if !matches!(e, RunError::CommandFailed(_)) {
eprintln!("\x1b[31mCommand error: {}\x1b[0m", e);
}
if self.die {
Err(e)

View File

@ -3,70 +3,261 @@
//! 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<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 with improved context
fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> {
result.map_err(|e| {
// Create a more detailed error message based on the error type
let error_message = match &e {
NerdctlError::CommandExecutionFailed(io_err) => {
format!("Failed to execute nerdctl command: {}. This may indicate nerdctl is not installed or not in PATH.", io_err)
},
NerdctlError::CommandFailed(msg) => {
format!("Nerdctl command failed: {}. Check container status and logs for more details.", msg)
},
NerdctlError::JsonParseError(msg) => {
format!("Failed to parse nerdctl JSON output: {}. This may indicate an incompatible nerdctl version.", msg)
},
NerdctlError::ConversionError(msg) => {
format!("Data conversion error: {}. This may indicate unexpected output format from nerdctl.", msg)
},
NerdctlError::Other(msg) => {
format!("Nerdctl error: {}. This is an unexpected error.", msg)
},
};
Box::new(EvalAltResult::ErrorRuntime(
format!("Nerdctl error: {}", e).into(),
error_message.into(),
rhai::Position::NONE
))
})
}
//
// 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
///
/// This function builds and runs the container using the configured options.
/// It provides detailed error information if the build fails.
pub fn container_build(container: Container) -> Result<Container, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let image = container.image.clone().unwrap_or_else(|| "none".to_string());
let ports = container.ports.clone();
let volumes = container.volumes.clone();
let env_vars = container.env_vars.clone();
// Try to build the container
let build_result = container.build();
// Handle the result with improved error context
match build_result {
Ok(built_container) => {
// Container built successfully
Ok(built_container)
},
Err(err) => {
// Add more context to the error
let enhanced_error = match err {
NerdctlError::CommandFailed(msg) => {
// Provide more detailed error information
let mut enhanced_msg = format!("Failed to build container '{}' from image '{}': {}",
container_name, image, msg);
// Add information about configured options that might be relevant
if !ports.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports));
}
if !volumes.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes));
}
if !env_vars.is_empty() {
enhanced_msg.push_str(&format!("\nConfigured environment variables: {:?}", env_vars));
}
// Add suggestions for common issues
if msg.contains("not found") || msg.contains("no such image") {
enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull().");
} else if msg.contains("port is already allocated") {
enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port.");
} else if msg.contains("permission denied") {
enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes.");
}
NerdctlError::CommandFailed(enhanced_msg)
},
_ => err
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// Start the Container and verify it's running
///
/// This function starts the container and verifies that it's actually running.
/// It returns detailed error information if the container fails to start or
/// if it starts but stops immediately.
pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
// Try to start the container
let start_result = container.start();
// Handle the result with improved error context
match start_result {
Ok(result) => {
// Container started successfully
Ok(result)
},
Err(err) => {
// Add more context to the error
let enhanced_error = match err {
NerdctlError::CommandFailed(msg) => {
// Check if this is a "container already running" error, which is not really an error
if msg.contains("already running") {
return Ok(CommandResult {
stdout: format!("Container {} is already running", container_name),
stderr: "".to_string(),
success: true,
code: 0,
});
}
// Try to get more information about why the container might have failed to start
let mut enhanced_msg = format!("Failed to start container '{}' (ID: {}): {}",
container_name, container_id, msg);
// Try to check if the image exists
if let Some(image) = &container.image {
enhanced_msg.push_str(&format!("\nContainer was using image: {}", image));
}
NerdctlError::CommandFailed(enhanced_msg)
},
_ => err
};
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// 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))
}
/// Get container logs
pub fn container_logs(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
// Get container details for better error reporting
let container_name = container.name.clone();
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
// Use the nerdctl::logs function
let logs_result = nerdctl::logs(&container_id);
match logs_result {
Ok(result) => {
Ok(result)
},
Err(err) => {
// Add more context to the error
let enhanced_error = NerdctlError::CommandFailed(
format!("Failed to get logs for container '{}' (ID: {}): {}",
container_name, container_id, err)
);
nerdctl_error_to_rhai_error(Err(enhanced_error))
}
}
}
/// 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
pub fn new_run_options() -> Map {
let mut map = Map::new();
@ -134,6 +325,13 @@ pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::list(all))
}
/// Wrapper for nerdctl::logs
///
/// Get container logs.
pub fn nerdctl_logs(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
nerdctl_error_to_rhai_error(nerdctl::logs(container))
}
//
// Image Function Wrappers
//
@ -185,4 +383,112 @@ pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result<Command
/// Build an image using a Dockerfile.
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))
}
/// 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("logs", container_logs);
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);
engine.register_fn("nerdctl_logs", nerdctl_logs);
// 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

@ -14,7 +14,7 @@ fn nerdctl_download(){
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
delete(`/tmp/${name}`);
run("apt-get -y install buildah")
run("apt-get -y install buildah runc")
}

View File

@ -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,108 @@ 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();
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 exec_result = container.exec("echo 'Hello from Alpine container!'");
println(`Command output: ${exec_result.stdout}`);
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 +186,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
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
`;

View File

@ -0,0 +1,141 @@
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_functions.rs
use crate::process::CommandResult;
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
/// Run a container from an image
///
/// # Arguments
///
/// * `image` - Image to run
/// * `name` - Optional name for the container
/// * `detach` - Whether to run in detached mode
/// * `ports` - Optional port mappings
/// * `snapshotter` - Optional snapshotter to use
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn run(
image: &str,
name: Option<&str>,
detach: bool,
ports: Option<&[&str]>,
snapshotter: Option<&str>,
) -> Result<CommandResult, NerdctlError> {
let mut args = vec!["run"];
if detach {
args.push("-d");
}
if let Some(name_value) = name {
args.push("--name");
args.push(name_value);
}
if let Some(ports_value) = ports {
for port in ports_value {
args.push("-p");
args.push(port);
}
}
if let Some(snapshotter_value) = snapshotter {
args.push("--snapshotter");
args.push(snapshotter_value);
}
// Add flags to avoid BPF issues
args.push("--cgroup-manager=cgroupfs");
args.push(image);
execute_nerdctl_command(&args)
}
/// Execute a command in a container
///
/// # Arguments
///
/// * `container` - Container name or ID
/// * `command` - Command to execute
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn exec(container: &str, command: &str) -> Result<CommandResult, NerdctlError> {
execute_nerdctl_command(&["exec", container, "sh", "-c", command])
}
/// Copy files between container and local filesystem
///
/// # Arguments
///
/// * `source` - Source path (can be container:path or local path)
/// * `dest` - Destination path (can be container:path or local path)
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn copy(source: &str, dest: &str) -> Result<CommandResult, NerdctlError> {
execute_nerdctl_command(&["cp", source, dest])
}
/// Stop a container
///
/// # Arguments
///
/// * `container` - Container name or ID
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn stop(container: &str) -> Result<CommandResult, NerdctlError> {
execute_nerdctl_command(&["stop", container])
}
/// Remove a container
///
/// # Arguments
///
/// * `container` - Container name or ID
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn remove(container: &str) -> Result<CommandResult, NerdctlError> {
execute_nerdctl_command(&["rm", container])
}
/// List containers
///
/// # Arguments
///
/// * `all` - Whether to list all containers (including stopped ones)
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn list(all: bool) -> Result<CommandResult, NerdctlError> {
let mut args = vec!["ps"];
if all {
args.push("-a");
}
execute_nerdctl_command(&args)
}
/// Get container logs
///
/// # Arguments
///
/// * `container` - Container name or ID
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn logs(container: &str) -> Result<CommandResult, NerdctlError> {
execute_nerdctl_command(&["logs", container])
}

View File

@ -6,14 +6,90 @@ use super::container_types::{Container, ContainerStatus, ResourceUsage};
use serde_json;
impl Container {
/// Start the container
/// Start the container and verify it's running
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
/// * `Result<CommandResult, NerdctlError>` - Command result or error with detailed information
pub fn start(&self) -> Result<CommandResult, NerdctlError> {
if let Some(container_id) = &self.container_id {
execute_nerdctl_command(&["start", container_id])
// First, try to start the container
let start_result = execute_nerdctl_command(&["start", container_id]);
// If the start command failed, return the error with details
if let Err(err) = &start_result {
return Err(NerdctlError::CommandFailed(
format!("Failed to start container {}: {}", container_id, err)
));
}
// Verify the container is actually running
match self.verify_running() {
Ok(true) => start_result,
Ok(false) => {
// Container started but isn't running - get detailed information
let mut error_message = format!("Container {} started but is not running.", container_id);
// Get container status
if let Ok(status) = self.status() {
error_message.push_str(&format!("\nStatus: {}, State: {}, Health: {}",
status.status,
status.state,
status.health_status.unwrap_or_else(|| "N/A".to_string())
));
}
// Get container logs
if let Ok(logs) = execute_nerdctl_command(&["logs", container_id]) {
if !logs.stdout.trim().is_empty() {
error_message.push_str(&format!("\nContainer logs (stdout):\n{}", logs.stdout.trim()));
}
if !logs.stderr.trim().is_empty() {
error_message.push_str(&format!("\nContainer logs (stderr):\n{}", logs.stderr.trim()));
}
}
// Get container exit code if available
if let Ok(inspect_result) = execute_nerdctl_command(&["inspect", "--format", "{{.State.ExitCode}}", container_id]) {
let exit_code = inspect_result.stdout.trim();
if !exit_code.is_empty() && exit_code != "0" {
error_message.push_str(&format!("\nContainer exit code: {}", exit_code));
}
}
Err(NerdctlError::CommandFailed(error_message))
},
Err(err) => {
// Failed to verify if container is running
Err(NerdctlError::CommandFailed(
format!("Container {} may have started, but verification failed: {}",
container_id, err
)
))
}
}
} else {
Err(NerdctlError::Other("No container ID available".to_string()))
}
}
/// Verify if the container is running
///
/// # Returns
///
/// * `Result<bool, NerdctlError>` - True if running, false if not running, error if verification failed
fn verify_running(&self) -> Result<bool, NerdctlError> {
if let Some(container_id) = &self.container_id {
// Use inspect to check if the container is running
let inspect_result = execute_nerdctl_command(&["inspect", "--format", "{{.State.Running}}", container_id]);
match inspect_result {
Ok(result) => {
let running = result.stdout.trim().to_lowercase() == "true";
Ok(running)
},
Err(err) => Err(err)
}
} else {
Err(NerdctlError::Other("No container ID available".to_string()))
}
@ -212,6 +288,19 @@ impl Container {
} else {
Err(NerdctlError::Other("No container ID available".to_string()))
}
/// Get container logs
///
/// # Returns
///
/// * `Result<CommandResult, NerdctlError>` - Command result or error
pub fn logs(&self) -> Result<CommandResult, NerdctlError> {
if let Some(container_id) = &self.container_id {
execute_nerdctl_command(&["logs", container_id])
} else {
Err(NerdctlError::Other("No container ID available".to_string()))
}
}
}
/// Get container resource usage

View File

@ -5,6 +5,8 @@ mod tests {
use super::super::container_types::{Container, ContainerStatus, ResourceUsage};
use super::super::NerdctlError;
use std::error::Error;
use std::thread;
use std::time::Duration;
#[test]
fn test_container_builder_pattern() {
@ -73,4 +75,181 @@ mod tests {
assert_eq!(health_check.retries.unwrap(), 3);
assert_eq!(health_check.start_period.as_ref().unwrap(), "5s");
}
#[test]
#[ignore] // Ignore by default as it requires nerdctl to be installed and running
fn test_container_runtime_and_resources() {
// Check if nerdctl is available and properly configured
let nerdctl_check = super::super::execute_nerdctl_command(&["info"]);
if nerdctl_check.is_err() {
println!("Skipping test: nerdctl is not available or properly configured");
println!("Error: {:?}", nerdctl_check.err());
return;
}
// Create a unique container name for this test
let container_name = format!("test-runtime-{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs());
// Create and build a container that will use resources
// Use a simple container with a basic command to avoid dependency on external images
let container_result = Container::from_image(&container_name, "busybox:latest").unwrap()
.with_detach(true)
.build();
// Check if the build was successful
if container_result.is_err() {
println!("Failed to build container: {:?}", container_result.err());
return;
}
let container = container_result.unwrap();
println!("Container created successfully: {}", container_name);
// Start the container with a simple command
let start_result = container.exec("sh -c 'for i in $(seq 1 10); do echo $i; sleep 1; done'");
if start_result.is_err() {
println!("Failed to start container: {:?}", start_result.err());
// Try to clean up
let _ = container.remove();
return;
}
println!("Container started successfully");
// Wait for the container to start and consume resources
thread::sleep(Duration::from_secs(3));
// Check container status
let status_result = container.status();
if status_result.is_err() {
println!("Failed to get container status: {:?}", status_result.err());
// Try to clean up
let _ = container.stop();
let _ = container.remove();
return;
}
let status = status_result.unwrap();
println!("Container status: {:?}", status);
// Verify the container is running
if status.status != "running" {
println!("Container is not running, status: {}", status.status);
// Try to clean up
let _ = container.remove();
return;
}
// Check resource usage
let resources_result = container.resources();
if resources_result.is_err() {
println!("Failed to get resource usage: {:?}", resources_result.err());
// Try to clean up
let _ = container.stop();
let _ = container.remove();
return;
}
let resources = resources_result.unwrap();
println!("Container resources: {:?}", resources);
// Verify the container is using memory (if we can get the information)
if resources.memory_usage == "0B" || resources.memory_usage == "unknown" {
println!("Warning: Container memory usage is {}", resources.memory_usage);
} else {
println!("Container is using memory: {}", resources.memory_usage);
}
// Clean up - stop and remove the container
println!("Stopping container...");
let stop_result = container.stop();
if stop_result.is_err() {
println!("Warning: Failed to stop container: {:?}", stop_result.err());
}
println!("Removing container...");
let remove_result = container.remove();
if remove_result.is_err() {
println!("Warning: Failed to remove container: {:?}", remove_result.err());
}
println!("Test completed successfully");
}
#[test]
fn test_container_with_custom_command() {
// Create a container with a custom command
let container = Container::new("test-command-container").unwrap()
.with_port("8080:80")
.with_volume("/tmp:/data")
.with_env("TEST_ENV", "test_value")
.with_detach(true);
// Verify container properties
assert_eq!(container.name, "test-command-container");
assert_eq!(container.ports.len(), 1);
assert_eq!(container.ports[0], "8080:80");
assert_eq!(container.volumes.len(), 1);
assert_eq!(container.volumes[0], "/tmp:/data");
assert_eq!(container.env_vars.len(), 1);
assert_eq!(container.env_vars.get("TEST_ENV").unwrap(), "test_value");
assert_eq!(container.detach, true);
// Convert the container to a command string that would be used to run it
let command_args = container_to_command_args(&container);
// Verify the command arguments contain all the expected options
assert!(command_args.contains(&"--name".to_string()));
assert!(command_args.contains(&"test-command-container".to_string()));
assert!(command_args.contains(&"-p".to_string()));
assert!(command_args.contains(&"8080:80".to_string()));
assert!(command_args.contains(&"-v".to_string()));
assert!(command_args.contains(&"/tmp:/data".to_string()));
assert!(command_args.contains(&"-e".to_string()));
assert!(command_args.contains(&"TEST_ENV=test_value".to_string()));
assert!(command_args.contains(&"-d".to_string()));
println!("Command args: {:?}", command_args);
}
// Helper function to convert a container to command arguments
fn container_to_command_args(container: &Container) -> Vec<String> {
let mut args = Vec::new();
args.push("run".to_string());
if container.detach {
args.push("-d".to_string());
}
args.push("--name".to_string());
args.push(container.name.clone());
// Add port mappings
for port in &container.ports {
args.push("-p".to_string());
args.push(port.clone());
}
// Add volume mounts
for volume in &container.volumes {
args.push("-v".to_string());
args.push(volume.clone());
}
// Add environment variables
for (key, value) in &container.env_vars {
args.push("-e".to_string());
args.push(format!("{}={}", key, value));
}
// Add image if available
if let Some(image) = &container.image {
args.push(image.clone());
}
args
}
}

View File

@ -5,6 +5,7 @@ mod container;
mod container_builder;
mod health_check;
mod container_operations;
mod container_functions;
#[cfg(test)]
mod container_test;
@ -50,4 +51,5 @@ impl Error for NerdctlError {
pub use images::*;
pub use cmd::*;
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
pub use container_functions::*;