Compare commits
4 Commits
7cdd9f5559
...
6de7bf9b56
Author | SHA1 | Date | |
---|---|---|---|
6de7bf9b56 | |||
245aee12bf | |||
d336153247 | |||
3803a54529 |
@ -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}`);
|
|
||||||
|
|
@ -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
|
|
34
docs/.gitignore
vendored
Normal file
34
docs/.gitignore
vendored
Normal 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
72
docs/cfg/footer.json
Normal 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
17
docs/cfg/main.json
Normal 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
25
docs/cfg/navbar.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
EXAMPLE FILE TO TEST
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
8
src/docs/subdir/_category_.json
Normal file
8
src/docs/subdir/_category_.json
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
@ -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)`
|
54
src/docs/tech.md
Normal file
54
src/docs/tech.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
# Technology
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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/)
|
@ -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 {
|
||||||
// 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
|
eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors
|
||||||
std::io::stderr().flush().unwrap_or(());
|
std::io::stderr().flush().unwrap_or(());
|
||||||
}
|
}
|
||||||
@ -241,14 +241,8 @@ fn process_command_output(output: Result<Output, std::io::Error>) -> Result<Comm
|
|||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
|
||||||
|
// We'll collect stderr but not print it here
|
||||||
// Print stderr if there's any, even for silent execution
|
// It will be included in the error message if the command fails
|
||||||
if !stderr.is_empty() {
|
|
||||||
eprintln!("\x1b[31mCommand stderr output:\x1b[0m");
|
|
||||||
for line in stderr.lines() {
|
|
||||||
eprintln!("\x1b[31m{}\x1b[0m", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the command failed, print a clear error message
|
// If the command failed, print a clear error message
|
||||||
if !out.status.success() {
|
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("")];
|
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
|
||||||
@ -340,26 +334,41 @@ 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
|
||||||
|
|
||||||
// Execute the script and handle the result
|
// Execute the script and handle the result
|
||||||
let result = execute_script_internal(&interpreter, &script_path, silent);
|
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 {
|
if let Err(ref e) = result {
|
||||||
|
if !matches!(e, RunError::CommandFailed(_)) {
|
||||||
eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e);
|
eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -485,8 +494,11 @@ impl<'a> RunBuilder<'a> {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Always print the error, even if die is false
|
// 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);
|
eprintln!("\x1b[31mCommand error: {}\x1b[0m", e);
|
||||||
|
}
|
||||||
|
|
||||||
if self.die {
|
if self.die {
|
||||||
Err(e)
|
Err(e)
|
||||||
|
@ -3,70 +3,261 @@
|
|||||||
//! 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
|
// Helper functions for error conversion with improved context
|
||||||
///
|
|
||||||
/// # 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
|
|
||||||
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| {
|
||||||
|
// 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(
|
Box::new(EvalAltResult::ErrorRuntime(
|
||||||
format!("Nerdctl error: {}", e).into(),
|
error_message.into(),
|
||||||
rhai::Position::NONE
|
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
|
/// 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();
|
||||||
@ -134,6 +325,13 @@ pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
|||||||
nerdctl_error_to_rhai_error(nerdctl::list(all))
|
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
|
// Image Function Wrappers
|
||||||
//
|
//
|
||||||
@ -186,3 +384,111 @@ 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("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(())
|
||||||
|
}
|
@ -14,7 +14,7 @@ fn nerdctl_download(){
|
|||||||
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
|
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
|
||||||
delete(`/tmp/${name}`);
|
delete(`/tmp/${name}`);
|
||||||
|
|
||||||
run("apt-get -y install buildah")
|
run("apt-get -y install buildah runc")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,108 @@ 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();
|
||||||
|
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
|
// Helper function to check if a function is registered
|
||||||
fn is_function_registered(name) {
|
fn is_function_registered(name) {
|
||||||
try {
|
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
|
@ -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
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
}
|
@ -6,14 +6,90 @@ use super::container_types::{Container, ContainerStatus, ResourceUsage};
|
|||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
/// Start the container
|
/// Start the container and verify it's running
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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> {
|
pub fn start(&self) -> Result<CommandResult, NerdctlError> {
|
||||||
if let Some(container_id) = &self.container_id {
|
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 {
|
} else {
|
||||||
Err(NerdctlError::Other("No container ID available".to_string()))
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
}
|
}
|
||||||
@ -212,6 +288,19 @@ impl Container {
|
|||||||
} else {
|
} else {
|
||||||
Err(NerdctlError::Other("No container ID available".to_string()))
|
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
|
/// Get container resource usage
|
||||||
|
@ -5,6 +5,8 @@ mod tests {
|
|||||||
use super::super::container_types::{Container, ContainerStatus, ResourceUsage};
|
use super::super::container_types::{Container, ContainerStatus, ResourceUsage};
|
||||||
use super::super::NerdctlError;
|
use super::super::NerdctlError;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_container_builder_pattern() {
|
fn test_container_builder_pattern() {
|
||||||
@ -73,4 +75,181 @@ mod tests {
|
|||||||
assert_eq!(health_check.retries.unwrap(), 3);
|
assert_eq!(health_check.retries.unwrap(), 3);
|
||||||
assert_eq!(health_check.start_period.as_ref().unwrap(), "5s");
|
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
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ mod container;
|
|||||||
mod container_builder;
|
mod container_builder;
|
||||||
mod health_check;
|
mod health_check;
|
||||||
mod container_operations;
|
mod container_operations;
|
||||||
|
mod container_functions;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod container_test;
|
mod container_test;
|
||||||
|
|
||||||
@ -51,3 +52,4 @@ impl Error for NerdctlError {
|
|||||||
pub use images::*;
|
pub use images::*;
|
||||||
pub use cmd::*;
|
pub use cmd::*;
|
||||||
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
|
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
|
||||||
|
pub use container_functions::*;
|
Loading…
Reference in New Issue
Block a user