...
This commit is contained in:
parent
e48063a79c
commit
7cdd9f5559
238
container_builder_implementation_plan.md
Normal file
238
container_builder_implementation_plan.md
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
# 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
|
@ -1,122 +0,0 @@
|
|||||||
//! 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::{BuildahError, Builder};
|
|
||||||
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 mut builder = Builder::new("my-fedora-container", "fedora:latest")?;
|
|
||||||
|
|
||||||
// Reset the builder to remove any existing container
|
|
||||||
println!("\n=== Resetting the builder to start fresh ===");
|
|
||||||
builder.reset()?;
|
|
||||||
|
|
||||||
// Create a new container (or continue with existing one)
|
|
||||||
println!("\n=== Creating container from fedora:latest ===");
|
|
||||||
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().unwrap();
|
|
||||||
}
|
|
62
examples/container_example.rs
Normal file
62
examples/container_example.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/examples/container_example.rs
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use sal::virt::nerdctl::Container;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Create a container from an image
|
||||||
|
println!("Creating container from image...");
|
||||||
|
let container = Container::from_image("my-nginx", "nginx:latest")?
|
||||||
|
.with_port("8080:80")
|
||||||
|
.with_env("NGINX_HOST", "example.com")
|
||||||
|
.with_volume("/tmp/nginx:/usr/share/nginx/html")
|
||||||
|
.with_health_check("curl -f http://localhost/ || exit 1")
|
||||||
|
.with_detach(true)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Container created successfully");
|
||||||
|
|
||||||
|
// Execute a command in the container
|
||||||
|
println!("Executing command in container...");
|
||||||
|
let result = container.exec("echo 'Hello from container'")?;
|
||||||
|
println!("Command output: {}", result.stdout);
|
||||||
|
|
||||||
|
// Get container status
|
||||||
|
println!("Getting container status...");
|
||||||
|
let status = container.status()?;
|
||||||
|
println!("Container status: {}", status.status);
|
||||||
|
|
||||||
|
// Get resource usage
|
||||||
|
println!("Getting resource usage...");
|
||||||
|
let resources = container.resources()?;
|
||||||
|
println!("CPU usage: {}", resources.cpu_usage);
|
||||||
|
println!("Memory usage: {}", resources.memory_usage);
|
||||||
|
|
||||||
|
// Stop and remove the container
|
||||||
|
println!("Stopping and removing container...");
|
||||||
|
container.stop()?;
|
||||||
|
container.remove()?;
|
||||||
|
|
||||||
|
println!("Container stopped and removed");
|
||||||
|
|
||||||
|
// Get a container by name (if it exists)
|
||||||
|
println!("\nGetting a container by name...");
|
||||||
|
match Container::new("existing-container") {
|
||||||
|
Ok(container) => {
|
||||||
|
if container.container_id.is_some() {
|
||||||
|
println!("Found container with ID: {}", container.container_id.unwrap());
|
||||||
|
|
||||||
|
// Perform operations on the existing container
|
||||||
|
let status = container.status()?;
|
||||||
|
println!("Container status: {}", status.status);
|
||||||
|
} else {
|
||||||
|
println!("Container exists but has no ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error getting container: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
//! Example usage of the nerdctl module
|
|
||||||
//!
|
|
||||||
//! This file demonstrates how to use the nerdctl module to perform
|
|
||||||
//! common container operations like creating containers, running commands,
|
|
||||||
//! and managing images.
|
|
||||||
|
|
||||||
use sal::virt::nerdctl::{self, NerdctlError};
|
|
||||||
|
|
||||||
/// Run a complete nerdctl workflow example
|
|
||||||
pub fn run_nerdctl_example() -> Result<(), NerdctlError> {
|
|
||||||
println!("Starting nerdctl example workflow...");
|
|
||||||
|
|
||||||
// Step 1: Pull an image
|
|
||||||
println!("\n=== Pulling nginx:latest image ===");
|
|
||||||
let pull_result = nerdctl::image_pull("nginx:latest")?;
|
|
||||||
println!("Pull output: {}", pull_result.stdout);
|
|
||||||
|
|
||||||
// Step 2: Create a container from the image
|
|
||||||
println!("\n=== Creating container from nginx:latest ===");
|
|
||||||
// Use "native" snapshotter to avoid overlay mount issues
|
|
||||||
let run_result = nerdctl::run("nginx:latest", Some("my-nginx"), true, Some(&["8080:80"]), Some("native"))?;
|
|
||||||
println!("Container created: {}", run_result.stdout.trim());
|
|
||||||
let container_id = "my-nginx"; // Using the name we specified
|
|
||||||
|
|
||||||
// Step 3: Execute a command in the container
|
|
||||||
println!("\n=== Installing curl in container ===");
|
|
||||||
let update_result = nerdctl::exec(container_id, "apt-get update")?;
|
|
||||||
println!("Update output: {}", update_result.stdout);
|
|
||||||
|
|
||||||
let install_result = nerdctl::exec(container_id, "apt-get install -y curl")?;
|
|
||||||
println!("Installation output: {}", install_result.stdout);
|
|
||||||
|
|
||||||
// Step 4: Copy a file into the container (assuming nginx.conf exists)
|
|
||||||
println!("\n=== Copying configuration file to container ===");
|
|
||||||
nerdctl::copy("./nginx.conf", format!("{}:/etc/nginx/nginx.conf", container_id).as_str())?;
|
|
||||||
|
|
||||||
// Step 5: Commit the container to create a new image
|
|
||||||
println!("\n=== Committing container to create image ===");
|
|
||||||
let image_name = "my-custom-nginx:latest";
|
|
||||||
nerdctl::image_commit(container_id, image_name)?;
|
|
||||||
println!("Created image: {}", image_name);
|
|
||||||
|
|
||||||
// Step 6: Stop and remove the container
|
|
||||||
println!("\n=== Stopping and removing container ===");
|
|
||||||
nerdctl::stop(container_id)?;
|
|
||||||
nerdctl::remove(container_id)?;
|
|
||||||
println!("Container stopped and removed");
|
|
||||||
|
|
||||||
// Step 7: Create a new container from our custom image
|
|
||||||
println!("\n=== Creating container from custom image ===");
|
|
||||||
// Use "native" snapshotter to avoid overlay mount issues
|
|
||||||
nerdctl::run(image_name, Some("nginx-custom"), true, Some(&["8081:80"]), Some("native"))?;
|
|
||||||
println!("Custom container created");
|
|
||||||
|
|
||||||
// Step 8: List images
|
|
||||||
println!("\n=== Listing images ===");
|
|
||||||
let images_result = nerdctl::images()?;
|
|
||||||
println!("Images: \n{}", images_result.stdout);
|
|
||||||
|
|
||||||
// Step 9: Clean up (optional in a real workflow)
|
|
||||||
println!("\n=== Cleaning up ===");
|
|
||||||
nerdctl::stop("nginx-custom")?;
|
|
||||||
nerdctl::remove("nginx-custom")?;
|
|
||||||
nerdctl::image_remove(image_name)?;
|
|
||||||
|
|
||||||
println!("\nNerdctl example workflow completed successfully!");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main function to run all examples
|
|
||||||
pub fn run_all_examples() -> Result<(), NerdctlError> {
|
|
||||||
println!("=== NERDCTL MODULE EXAMPLES ===\n");
|
|
||||||
|
|
||||||
run_nerdctl_example()?;
|
|
||||||
|
|
||||||
println!("\nNote that these examples require nerdctl to be installed on your system");
|
|
||||||
println!("and may require root/sudo privileges depending on your setup.");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let _ = run_all_examples().unwrap();
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
//! Example of using the Rhai integration with SAL Buildah module
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Rhai scripting language
|
|
||||||
//! with the System Abstraction Layer (SAL) Buildah module.
|
|
||||||
|
|
||||||
use rhai::Engine;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register println function
|
|
||||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
sal::rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Run a Rhai script that uses SAL Buildah functions
|
|
||||||
let script = r#"
|
|
||||||
// List available images
|
|
||||||
println("Listing available images:");
|
|
||||||
let images = buildah_images();
|
|
||||||
println("Found " + images.len() + " images");
|
|
||||||
|
|
||||||
// Create a container from an image (uncomment if you have a valid image)
|
|
||||||
// let container = buildah_from("alpine:latest");
|
|
||||||
// println("Created container: " + container.stdout.trim());
|
|
||||||
|
|
||||||
// Build an image using options
|
|
||||||
let build_options = buildah_new_build_options();
|
|
||||||
build_options.tag = "example-image:latest";
|
|
||||||
build_options.context_dir = ".";
|
|
||||||
build_options.file = "example_Dockerfile";
|
|
||||||
|
|
||||||
println("Building image with options:");
|
|
||||||
println(" Tag: " + build_options.tag);
|
|
||||||
println(" Context: " + build_options.context_dir);
|
|
||||||
println(" Dockerfile: " + build_options.file);
|
|
||||||
|
|
||||||
// Uncomment to actually build the image
|
|
||||||
// let build_result = buildah_build(build_options);
|
|
||||||
// println("Build result: " + build_result.success);
|
|
||||||
|
|
||||||
// Create a container configuration
|
|
||||||
let config_options = buildah_new_config_options();
|
|
||||||
config_options.author = "Rhai Example";
|
|
||||||
config_options.cmd = "/bin/sh -c 'echo Hello from Buildah'";
|
|
||||||
|
|
||||||
println("Container config options:");
|
|
||||||
println(" Author: " + config_options.author);
|
|
||||||
println(" Command: " + config_options.cmd);
|
|
||||||
|
|
||||||
// Commit options
|
|
||||||
let commit_options = buildah_new_commit_options();
|
|
||||||
commit_options.format = "docker";
|
|
||||||
commit_options.squash = true;
|
|
||||||
commit_options.rm = true;
|
|
||||||
|
|
||||||
println("Commit options:");
|
|
||||||
println(" Format: " + commit_options.format);
|
|
||||||
println(" Squash: " + commit_options.squash);
|
|
||||||
println(" Remove container: " + commit_options.rm);
|
|
||||||
|
|
||||||
// Return success
|
|
||||||
true
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
let result = engine.eval::<bool>(script)?;
|
|
||||||
println!("Script execution successful: {}", result);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
//! Example of using the Git module with Rhai
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Git module functions
|
|
||||||
//! through the Rhai scripting language.
|
|
||||||
|
|
||||||
use sal::rhai::{self, Engine};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Run a Rhai script that uses Git functions
|
|
||||||
let script = r#"
|
|
||||||
// Print a header
|
|
||||||
print("=== Testing Git Module Functions ===\n");
|
|
||||||
|
|
||||||
// Test git_list function
|
|
||||||
print("Listing git repositories...");
|
|
||||||
let repos = git_list();
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
|
||||||
|
|
||||||
// Print the first few repositories
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("First few repositories:");
|
|
||||||
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
|
||||||
for i in range(0, count) {
|
|
||||||
print(` - ${repos[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test find_matching_repos function
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("\nTesting repository search...");
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
|
||||||
let matching = find_matching_repos(repo_name);
|
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
|
||||||
for repo in matching {
|
|
||||||
print(` - ${repo}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a repository has changes
|
|
||||||
print("\nChecking for changes in repository...");
|
|
||||||
let has_changes = git_has_changes(repo_path);
|
|
||||||
print(`Repository ${repo_path} has changes: ${has_changes}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
match engine.eval::<()>(script) {
|
|
||||||
Ok(_) => println!("Script executed successfully"),
|
|
||||||
Err(e) => eprintln!("Script execution error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
//! Example of using the Rhai integration with SAL Process module
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Rhai scripting language
|
|
||||||
//! with the System Abstraction Layer (SAL) Process module.
|
|
||||||
|
|
||||||
use rhai::Engine;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
sal::rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Run a Rhai script that uses SAL Process functions
|
|
||||||
let script = r#"
|
|
||||||
// Check if a command exists
|
|
||||||
let ls_exists = which("ls");
|
|
||||||
println("ls command exists: " + ls_exists);
|
|
||||||
|
|
||||||
// Run a simple command
|
|
||||||
let echo_result = run_command("echo 'Hello from Rhai!'");
|
|
||||||
println("Echo command output: " + echo_result.stdout);
|
|
||||||
println("Echo command success: " + echo_result.success);
|
|
||||||
|
|
||||||
// Run a command silently
|
|
||||||
let silent_result = run_silent("ls -la");
|
|
||||||
println("Silent command success: " + silent_result.success);
|
|
||||||
|
|
||||||
// Run a command with custom options using a Map
|
|
||||||
let options = new_run_options();
|
|
||||||
options["die"] = false; // Don't return error if command fails
|
|
||||||
options["silent"] = true; // Suppress output to stdout/stderr
|
|
||||||
options["async_exec"] = false; // Run synchronously
|
|
||||||
options["log"] = true; // Log command execution
|
|
||||||
|
|
||||||
let custom_result = run("echo 'Custom options'", options);
|
|
||||||
println("Custom command success: " + custom_result.success);
|
|
||||||
|
|
||||||
// List processes
|
|
||||||
let processes = process_list("");
|
|
||||||
println("Number of processes: " + processes.len());
|
|
||||||
|
|
||||||
// Return success
|
|
||||||
true
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
let result = engine.eval::<bool>(script)?;
|
|
||||||
println!("Script execution successful: {}", result);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
// Test script for Git partial path matching functionality
|
|
||||||
|
|
||||||
print("===== Git Path Matching Test =====");
|
|
||||||
|
|
||||||
// Test git availability
|
|
||||||
print("\n=== Checking Git Availability ===");
|
|
||||||
let git_cmd = which("git");
|
|
||||||
if git_cmd != false {
|
|
||||||
print(`Git is available at: ${git_cmd}`);
|
|
||||||
} else {
|
|
||||||
print("WARNING: Git is not installed. Tests will be skipped.");
|
|
||||||
return "Git not available - test skipped";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for test assertions
|
|
||||||
fn assert(condition, message) {
|
|
||||||
if (condition == false) {
|
|
||||||
print(`FAILED: ${message}`);
|
|
||||||
throw `Assertion failed: ${message}`;
|
|
||||||
} else {
|
|
||||||
print(`PASSED: ${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Setting up test repositories ===");
|
|
||||||
// Get current repos from git_list
|
|
||||||
let repos = git_list();
|
|
||||||
print(`Found ${repos.len()} local git repositories for testing`);
|
|
||||||
|
|
||||||
if repos.len() == 0 {
|
|
||||||
print("No repositories found for testing. Creating a test repo...");
|
|
||||||
// Create a test repo in a temporary directory
|
|
||||||
let test_dir = "~/tmp_test_repo";
|
|
||||||
print(`Creating test directory: ${test_dir}`);
|
|
||||||
|
|
||||||
// Initialize a git repo with run commands
|
|
||||||
let result = run_silent(`
|
|
||||||
mkdir -p ${test_dir}/test1
|
|
||||||
cd ${test_dir}/test1
|
|
||||||
git init
|
|
||||||
echo "Test content" > README.md
|
|
||||||
git add README.md
|
|
||||||
git config --global user.email "test@example.com"
|
|
||||||
git config --global user.name "Test User"
|
|
||||||
git commit -m "Initial commit"
|
|
||||||
`);
|
|
||||||
|
|
||||||
if result.success {
|
|
||||||
print("Created test repository successfully");
|
|
||||||
// Update the repos list
|
|
||||||
repos = git_list();
|
|
||||||
print(`Now found ${repos.len()} local git repositories`);
|
|
||||||
} else {
|
|
||||||
print("Failed to create test repository");
|
|
||||||
return "Test setup failed - could not create test repository";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple function to get just the repo name from the path for easier debugging
|
|
||||||
fn get_repo_name(path) {
|
|
||||||
let parts = path.split("/");
|
|
||||||
return parts[parts.len()-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Test 1: Testing git_update with exact path ===");
|
|
||||||
// Using the first repo in the list for testing
|
|
||||||
let first_repo = repos[0];
|
|
||||||
print(`Using repository: ${first_repo}`);
|
|
||||||
print(`Repository name: ${get_repo_name(first_repo)}`);
|
|
||||||
|
|
||||||
// Test with exact path
|
|
||||||
let exact_result = git_update(first_repo);
|
|
||||||
print(`Result with exact path: ${exact_result}`);
|
|
||||||
|
|
||||||
// Check if the result was as expected
|
|
||||||
// Note: The function might find multiple repos even with exact path
|
|
||||||
// so we also check for that case
|
|
||||||
let is_success = exact_result.contains("up to date") ||
|
|
||||||
exact_result.contains("updated") ||
|
|
||||||
exact_result.contains("has local changes");
|
|
||||||
|
|
||||||
let multiple_match = exact_result.contains("Multiple repositories");
|
|
||||||
|
|
||||||
assert(is_success || multiple_match,
|
|
||||||
"git_update with path should succeed, indicate local changes, or report multiple matches");
|
|
||||||
|
|
||||||
print("\n=== Test 2: Testing git_update with partial path ===");
|
|
||||||
// Extract part of the path (last part of the path)
|
|
||||||
let repo_name = get_repo_name(first_repo);
|
|
||||||
print(`Testing partial match with: ${repo_name}`);
|
|
||||||
let partial_result = git_update(repo_name);
|
|
||||||
print(`Result with partial path: ${partial_result}`);
|
|
||||||
|
|
||||||
// Check if the result was as expected - similar to exact path test
|
|
||||||
let partial_success = partial_result.contains("up to date") ||
|
|
||||||
partial_result.contains("updated") ||
|
|
||||||
partial_result.contains("has local changes");
|
|
||||||
|
|
||||||
let partial_multiple = partial_result.contains("Multiple repositories");
|
|
||||||
|
|
||||||
assert(partial_success || partial_multiple,
|
|
||||||
"git_update with partial path should succeed, indicate local changes, or report multiple matches");
|
|
||||||
|
|
||||||
print("\n=== Test 3: Testing git_update with non-existent path ===");
|
|
||||||
let fake_repo = "this_repo_does_not_exist_anywhere";
|
|
||||||
print(`Testing with non-existent path: ${fake_repo}`);
|
|
||||||
let nonexist_result = git_update(fake_repo);
|
|
||||||
print(`Result with non-existent path: ${nonexist_result}`);
|
|
||||||
|
|
||||||
// Check that it properly reports an error
|
|
||||||
assert(nonexist_result.contains("No repositories found"),
|
|
||||||
"git_update with non-existent path should indicate no matching repos");
|
|
||||||
|
|
||||||
print("\n=== Test 4: Testing wildcard matching ===");
|
|
||||||
// Try to find a common substring in multiple repos
|
|
||||||
// For this test, we'll use a known path pattern likely to match multiple repos
|
|
||||||
let common_part = "code";
|
|
||||||
print(`Testing wildcard match with: ${common_part}*`);
|
|
||||||
|
|
||||||
let wildcard_result = git_update(common_part + "*");
|
|
||||||
print(`Result with wildcard: ${wildcard_result}`);
|
|
||||||
|
|
||||||
// For wildcard, we expect at least one match but not an error
|
|
||||||
// Implementation might return the first match or a success message
|
|
||||||
let wildcard_success = wildcard_result.contains("up to date") ||
|
|
||||||
wildcard_result.contains("updated") ||
|
|
||||||
wildcard_result.contains("has local changes");
|
|
||||||
|
|
||||||
// Just check that it didn't report no matches found
|
|
||||||
assert(!wildcard_result.contains("No repositories found"),
|
|
||||||
"Wildcard matching should find at least one repository");
|
|
||||||
|
|
||||||
print("\n===== All Git path matching tests completed! =====");
|
|
||||||
|
|
||||||
"Git path matching functionality works correctly"
|
|
@ -1,124 +0,0 @@
|
|||||||
// Git operations test script (primarily focused on validation)
|
|
||||||
// Note: Many git operations are destructive or require network access,
|
|
||||||
// so this test primarily validates availability and URL handling.
|
|
||||||
|
|
||||||
print("===== Git Operations Test =====");
|
|
||||||
|
|
||||||
// Test git availability
|
|
||||||
print("\n=== Test Git Availability ===");
|
|
||||||
let git_cmd = which("git");
|
|
||||||
if git_cmd {
|
|
||||||
print(`Git is available at: ${git_cmd}`);
|
|
||||||
} else {
|
|
||||||
print("WARNING: Git is not installed. Some tests will be skipped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test git URL parsing (testing internal implementation through git operations)
|
|
||||||
print("\n=== Test Git URL Parsing ===");
|
|
||||||
// HTTPS URLs
|
|
||||||
let https_urls = [
|
|
||||||
"https://github.com/user/repo.git",
|
|
||||||
"https://github.com/user/repo",
|
|
||||||
"https://example.com/user/repo.git"
|
|
||||||
];
|
|
||||||
|
|
||||||
print("Testing HTTPS GitHub URLs:");
|
|
||||||
for url in https_urls {
|
|
||||||
// For testing purposes, we'll use the URL rather than actually cloning
|
|
||||||
// Just check if git_clone responds with "already exists" message which indicates
|
|
||||||
// it recognized and parsed the URL correctly
|
|
||||||
let result = git_clone(url);
|
|
||||||
|
|
||||||
// Check if the result contains a path with expected structure
|
|
||||||
let contains_path = result.contains("/code/") &&
|
|
||||||
(result.contains("github.com") || result.contains("example.com"));
|
|
||||||
|
|
||||||
print(` URL: ${url}`);
|
|
||||||
print(` Path structure valid: ${contains_path ? "yes" : "no"}`);
|
|
||||||
|
|
||||||
// Extract the expected path components from the parsing
|
|
||||||
if contains_path {
|
|
||||||
let parts = result.split("/");
|
|
||||||
let domain_part = "";
|
|
||||||
let user_part = "";
|
|
||||||
let repo_part = "";
|
|
||||||
|
|
||||||
let found_code = false;
|
|
||||||
for i in 0..parts.len() {
|
|
||||||
if parts[i] == "code" && (i+3) < parts.len() {
|
|
||||||
domain_part = parts[i+1];
|
|
||||||
user_part = parts[i+2];
|
|
||||||
repo_part = parts[i+3];
|
|
||||||
found_code = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found_code {
|
|
||||||
print(` Parsed domain: ${domain_part}`);
|
|
||||||
print(` Parsed user: ${user_part}`);
|
|
||||||
print(` Parsed repo: ${repo_part}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSH URLs
|
|
||||||
let ssh_urls = [
|
|
||||||
"git@github.com:user/repo.git",
|
|
||||||
"git@example.com:organization/repository.git"
|
|
||||||
];
|
|
||||||
|
|
||||||
print("\nTesting SSH Git URLs:");
|
|
||||||
for url in ssh_urls {
|
|
||||||
// Similar approach to HTTPS testing
|
|
||||||
let result = git_clone(url);
|
|
||||||
let contains_path = result.contains("/code/");
|
|
||||||
|
|
||||||
print(` URL: ${url}`);
|
|
||||||
print(` Path structure valid: ${contains_path ? "yes" : "no"}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test git_list
|
|
||||||
print("\n=== Test git_list() ===");
|
|
||||||
let repos = git_list();
|
|
||||||
print(`Found ${repos.len()} local git repositories`);
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("First 3 repositories (or fewer if less available):");
|
|
||||||
let max = Math.min(3, repos.len());
|
|
||||||
for i in 0..max {
|
|
||||||
print(` ${i+1}. ${repos[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test git command access through run
|
|
||||||
print("\n=== Test Git Command Access ===");
|
|
||||||
if git_cmd {
|
|
||||||
let git_version = run("git --version");
|
|
||||||
print(`Git version info: ${git_version.stdout}`);
|
|
||||||
|
|
||||||
// Test git status command (this is safe and doesn't modify anything)
|
|
||||||
let git_status = run("git status");
|
|
||||||
print("Git status command:");
|
|
||||||
print(` Exit code: ${git_status.code}`);
|
|
||||||
if git_status.success {
|
|
||||||
print(" Git status executed successfully in current directory");
|
|
||||||
} else {
|
|
||||||
print(" Git status failed. This might not be a git repository.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Minimal testing of other git operations (these are mostly destructive)
|
|
||||||
print("\n=== Git Operations Availability Check ===");
|
|
||||||
print("The following git operations are available:");
|
|
||||||
print(" - git_clone: Available (tested above)");
|
|
||||||
print(" - git_list: Available (tested above)");
|
|
||||||
print(" - git_update: Available (not tested to avoid modifying repos)");
|
|
||||||
print(" - git_update_force: Available (not tested to avoid modifying repos)");
|
|
||||||
print(" - git_update_commit: Available (not tested to avoid modifying repos)");
|
|
||||||
print(" - git_update_commit_push: Available (not tested to avoid modifying repos)");
|
|
||||||
|
|
||||||
|
|
||||||
print("\nGit Operations Test completed!");
|
|
||||||
"Git Test Success"
|
|
@ -177,6 +177,7 @@ print("All repositories:");
|
|||||||
for repo_path in all_repos {
|
for repo_path in all_repos {
|
||||||
print(` - ${repo_path}`);
|
print(` - ${repo_path}`);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
//! Example of using the Git module with Rhai
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Git module functions
|
|
||||||
//! through the Rhai scripting language.
|
|
||||||
|
|
||||||
use sal::rhai::{self, Engine};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Run a Rhai script that uses Git functions
|
|
||||||
let script = r#"
|
|
||||||
// Print a header
|
|
||||||
print("=== Testing Git Module Functions ===\n");
|
|
||||||
|
|
||||||
// Create a new GitTree object
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let git_tree = gittree_new(`${home_dir}/code`);
|
|
||||||
print(`Created GitTree with base path: ${home_dir}/code`);
|
|
||||||
|
|
||||||
// Test list method
|
|
||||||
print("\nListing git repositories...");
|
|
||||||
let repos = git_tree.list();
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
|
||||||
|
|
||||||
// Print the first few repositories
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("First few repositories:");
|
|
||||||
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
|
||||||
for i in range(0, count) {
|
|
||||||
print(` - ${repos[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test find method
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("\nTesting repository search...");
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
|
||||||
let matching = git_tree.find(repo_name + "*");
|
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
|
||||||
for repo in matching {
|
|
||||||
print(` - ${repo}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test get method
|
|
||||||
print("\nTesting get method...");
|
|
||||||
let git_repos = git_tree.get(repo_name);
|
|
||||||
print(`Found ${git_repos.len()} GitRepo objects`);
|
|
||||||
|
|
||||||
// Test GitRepo methods
|
|
||||||
if git_repos.len() > 0 {
|
|
||||||
let git_repo = git_repos[0];
|
|
||||||
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
|
||||||
|
|
||||||
// Check if a repository has changes
|
|
||||||
print("Checking for changes in repository...");
|
|
||||||
let has_changes = git_repo.has_changes();
|
|
||||||
print(`Repository has changes: ${has_changes}`);
|
|
||||||
|
|
||||||
// Test method chaining (only if there are no changes to avoid errors)
|
|
||||||
if !has_changes {
|
|
||||||
print("\nTesting method chaining (pull)...");
|
|
||||||
let result = git_repo.pull();
|
|
||||||
print("Pull operation completed successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
match engine.eval::<()>(script) {
|
|
||||||
Ok(_) => println!("Script executed successfully"),
|
|
||||||
Err(e) => eprintln!("Script execution error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
//! Example of using the Git module with Rhai
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Git module functions
|
|
||||||
//! through the Rhai scripting language.
|
|
||||||
|
|
||||||
use sal::rhai::{self, Engine};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Run a Rhai script that uses Git functions
|
|
||||||
let script = r#"
|
|
||||||
// Print a header
|
|
||||||
print("=== Testing Git Module Functions ===\n");
|
|
||||||
|
|
||||||
// Create a new GitTree object
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let git_tree = gittree_new(`${home_dir}/code`);
|
|
||||||
print(`Created GitTree with base path: ${home_dir}/code`);
|
|
||||||
|
|
||||||
// Test list method
|
|
||||||
print("\nListing git repositories...");
|
|
||||||
let repos = git_tree.list();
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
|
||||||
|
|
||||||
// Print the first few repositories
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("First few repositories:");
|
|
||||||
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
|
||||||
for i in range(0, count) {
|
|
||||||
print(` - ${repos[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test find method
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("\nTesting repository search...");
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
|
||||||
let matching = git_tree.find(repo_name + "*");
|
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
|
||||||
for repo in matching {
|
|
||||||
print(` - ${repo}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test get method
|
|
||||||
print("\nTesting get method...");
|
|
||||||
let git_repos = git_tree.get(repo_name);
|
|
||||||
print(`Found ${git_repos.len()} GitRepo objects`);
|
|
||||||
|
|
||||||
// Test GitRepo methods
|
|
||||||
if git_repos.len() > 0 {
|
|
||||||
let git_repo = git_repos[0];
|
|
||||||
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
|
||||||
|
|
||||||
// Check if a repository has changes
|
|
||||||
print("Checking for changes in repository...");
|
|
||||||
let has_changes = git_repo.has_changes();
|
|
||||||
print(`Repository has changes: ${has_changes}`);
|
|
||||||
|
|
||||||
// Test method chaining (only if there are no changes to avoid errors)
|
|
||||||
if !has_changes {
|
|
||||||
print("\nTesting method chaining (pull)...");
|
|
||||||
let result = git_repo.pull();
|
|
||||||
print("Pull operation completed successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
match engine.eval::<()>(script) {
|
|
||||||
Ok(_) => println!("Script executed successfully"),
|
|
||||||
Err(e) => eprintln!("Script execution error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
//! Example of running the test_git.rhai script
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to run the test_git.rhai script
|
|
||||||
//! through the Rhai scripting engine.
|
|
||||||
|
|
||||||
use sal::rhai::{self, Engine};
|
|
||||||
use std::fs;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Read the test script
|
|
||||||
let script = fs::read_to_string("src/test_git.rhai")?;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
match engine.eval::<()>(&script) {
|
|
||||||
Ok(_) => println!("Script executed successfully"),
|
|
||||||
Err(e) => eprintln!("Script execution error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
//! Simple example of using the Git module with Rhai
|
|
||||||
//!
|
|
||||||
//! This example demonstrates how to use the Git module functions
|
|
||||||
//! through the Rhai scripting language.
|
|
||||||
|
|
||||||
use sal::rhai::{self, Engine};
|
|
||||||
use std::fs;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// Create a new Rhai engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Register SAL functions with the engine
|
|
||||||
rhai::register(&mut engine)?;
|
|
||||||
|
|
||||||
// Read the test script
|
|
||||||
let script = fs::read_to_string("simple_git_test.rhai")?;
|
|
||||||
|
|
||||||
// Evaluate the script
|
|
||||||
match engine.eval::<()>(&script) {
|
|
||||||
Ok(_) => println!("Script executed successfully"),
|
|
||||||
Err(e) => eprintln!("Script execution error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -98,16 +98,35 @@ fn prepare_script_file(script_content: &str) -> Result<(PathBuf, String, tempfil
|
|||||||
let (ext, interpreter) = (".bat", "cmd.exe".to_string());
|
let (ext, interpreter) = (".bat", "cmd.exe".to_string());
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
let (ext, interpreter) = (".sh", "/bin/sh".to_string());
|
let (ext, interpreter) = (".sh", "/bin/bash".to_string());
|
||||||
|
|
||||||
// Create the script file
|
// Create the script file
|
||||||
let script_path = temp_dir.path().join(format!("script{}", ext));
|
let script_path = temp_dir.path().join(format!("script{}", ext));
|
||||||
let mut file = File::create(&script_path)
|
let mut file = File::create(&script_path)
|
||||||
.map_err(RunError::FileCreationFailed)?;
|
.map_err(RunError::FileCreationFailed)?;
|
||||||
|
|
||||||
// Write the script content
|
// For Unix systems, ensure the script has a shebang line with -e flag
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
{
|
||||||
|
let script_with_shebang = if dedented.trim_start().starts_with("#!") {
|
||||||
|
// Script already has a shebang, use it as is
|
||||||
|
dedented
|
||||||
|
} else {
|
||||||
|
// Add shebang with -e flag to ensure script fails on errors
|
||||||
|
format!("#!/bin/bash -e\n{}", dedented)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write the script content with shebang
|
||||||
|
file.write_all(script_with_shebang.as_bytes())
|
||||||
|
.map_err(RunError::FileWriteFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Windows, just write the script as is
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
file.write_all(dedented.as_bytes())
|
file.write_all(dedented.as_bytes())
|
||||||
.map_err(RunError::FileWriteFailed)?;
|
.map_err(RunError::FileWriteFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Make the script executable (Unix only)
|
// Make the script executable (Unix only)
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
@ -166,7 +185,8 @@ 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 {
|
||||||
eprintln!("{}", l);
|
// Always print stderr, even if silent is true, for error visibility
|
||||||
|
eprintln!("\x1b[31mERROR: {}\x1b[0m", l); // Red color for errors
|
||||||
std::io::stderr().flush().unwrap_or(());
|
std::io::stderr().flush().unwrap_or(());
|
||||||
}
|
}
|
||||||
// Store it in our captured buffer
|
// Store it in our captured buffer
|
||||||
@ -198,6 +218,14 @@ fn handle_child_output(mut child: Child, silent: bool) -> Result<CommandResult,
|
|||||||
"Failed to capture stderr".to_string()
|
"Failed to capture stderr".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the command failed, print the stderr if it wasn't already printed
|
||||||
|
if !status.success() && silent && !captured_stderr.is_empty() {
|
||||||
|
eprintln!("\x1b[31mCommand failed with error:\x1b[0m");
|
||||||
|
for line in captured_stderr.lines() {
|
||||||
|
eprintln!("\x1b[31m{}\x1b[0m", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return the command result
|
// Return the command result
|
||||||
Ok(CommandResult {
|
Ok(CommandResult {
|
||||||
stdout: captured_stdout,
|
stdout: captured_stdout,
|
||||||
@ -214,6 +242,20 @@ fn process_command_output(output: Result<Output, std::io::Error>) -> Result<Comm
|
|||||||
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();
|
||||||
|
|
||||||
|
// Print stderr if there's any, even for silent execution
|
||||||
|
if !stderr.is_empty() {
|
||||||
|
eprintln!("\x1b[31mCommand stderr output:\x1b[0m");
|
||||||
|
for line in stderr.lines() {
|
||||||
|
eprintln!("\x1b[31m{}\x1b[0m", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the command failed, print a clear error message
|
||||||
|
if !out.status.success() {
|
||||||
|
eprintln!("\x1b[31mCommand failed with exit code: {}\x1b[0m",
|
||||||
|
out.status.code().unwrap_or(-1));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(CommandResult {
|
Ok(CommandResult {
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
@ -260,7 +302,18 @@ fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool)
|
|||||||
.args(&command_args)
|
.args(&command_args)
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
process_command_output(output)
|
let result = process_command_output(output)?;
|
||||||
|
|
||||||
|
// If the script failed, return an error
|
||||||
|
if !result.success {
|
||||||
|
return Err(RunError::CommandFailed(format!(
|
||||||
|
"Script execution failed with exit code {}: {}",
|
||||||
|
result.code,
|
||||||
|
result.stderr.trim()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
} else {
|
} else {
|
||||||
// For normal execution, spawn and handle the output streams
|
// For normal execution, spawn and handle the output streams
|
||||||
let child = Command::new(interpreter)
|
let child = Command::new(interpreter)
|
||||||
@ -270,16 +323,45 @@ fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool)
|
|||||||
.spawn()
|
.spawn()
|
||||||
.map_err(RunError::CommandExecutionFailed)?;
|
.map_err(RunError::CommandExecutionFailed)?;
|
||||||
|
|
||||||
handle_child_output(child, false)
|
let result = handle_child_output(child, false)?;
|
||||||
|
|
||||||
|
// If the script failed, return an error
|
||||||
|
if !result.success {
|
||||||
|
return Err(RunError::CommandFailed(format!(
|
||||||
|
"Script execution failed with exit code {}: {}",
|
||||||
|
result.code,
|
||||||
|
result.stderr.trim()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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> {
|
||||||
|
// Print the script being executed if not silent
|
||||||
|
if !silent {
|
||||||
|
println!("\x1b[36mExecuting script:\x1b[0m");
|
||||||
|
for (i, line) in script.lines().enumerate() {
|
||||||
|
println!("\x1b[36m{:3}: {}\x1b[0m", i + 1, line);
|
||||||
|
}
|
||||||
|
println!("\x1b[36m---\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
let (script_path, interpreter, _temp_dir) = prepare_script_file(script)?;
|
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_script_internal(&interpreter, &script_path, silent)
|
|
||||||
|
// Execute the script and handle the result
|
||||||
|
let result = execute_script_internal(&interpreter, &script_path, silent);
|
||||||
|
|
||||||
|
// If there was an error, print a clear error message
|
||||||
|
if let Err(ref e) = result {
|
||||||
|
eprintln!("\x1b[31mScript execution failed: {}\x1b[0m", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for configuring and executing commands or scripts
|
/// A builder for configuring and executing commands or scripts
|
||||||
@ -338,21 +420,41 @@ impl<'a> RunBuilder<'a> {
|
|||||||
|
|
||||||
// Log command execution if enabled
|
// Log command execution if enabled
|
||||||
if self.log {
|
if self.log {
|
||||||
println!("[LOG] Executing command: {}", trimmed);
|
println!("\x1b[36m[LOG] Executing command: {}\x1b[0m", trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle async execution
|
// Handle async execution
|
||||||
if self.async_exec {
|
if self.async_exec {
|
||||||
let cmd_copy = trimmed.to_string();
|
let cmd_copy = trimmed.to_string();
|
||||||
let silent = self.silent;
|
let silent = self.silent;
|
||||||
|
let log = self.log;
|
||||||
|
|
||||||
// Spawn a thread to run the command asynchronously
|
// Spawn a thread to run the command asynchronously
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let _ = if cmd_copy.contains('\n') {
|
if log {
|
||||||
|
println!("\x1b[36m[ASYNC] Starting execution\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if cmd_copy.contains('\n') {
|
||||||
run_script_internal(&cmd_copy, silent)
|
run_script_internal(&cmd_copy, silent)
|
||||||
} else {
|
} else {
|
||||||
run_command_internal(&cmd_copy, silent)
|
run_command_internal(&cmd_copy, silent)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if log {
|
||||||
|
match &result {
|
||||||
|
Ok(res) => {
|
||||||
|
if res.success {
|
||||||
|
println!("\x1b[32m[ASYNC] Command completed successfully\x1b[0m");
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31m[ASYNC] Command failed with exit code: {}\x1b[0m", res.code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("\x1b[31m[ASYNC] Command failed with error: {}\x1b[0m", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return a placeholder result for async execution
|
// Return a placeholder result for async execution
|
||||||
@ -375,8 +477,17 @@ impl<'a> RunBuilder<'a> {
|
|||||||
|
|
||||||
// Handle die=false: convert errors to CommandResult with success=false
|
// Handle die=false: convert errors to CommandResult with success=false
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => {
|
||||||
|
// If the command failed but die is false, print a warning
|
||||||
|
if !res.success && !self.die && !self.silent {
|
||||||
|
eprintln!("\x1b[33mWarning: Command failed with exit code {} but 'die' is false\x1b[0m", res.code);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// Always print the error, even if die is false
|
||||||
|
eprintln!("\x1b[31mCommand error: {}\x1b[0m", e);
|
||||||
|
|
||||||
if self.die {
|
if self.die {
|
||||||
Err(e)
|
Err(e)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
// Test script for Git module functions
|
|
||||||
|
|
||||||
// Import required modules
|
|
||||||
import "os" as os;
|
|
||||||
import "process" as process;
|
|
||||||
|
|
||||||
// Test GitTree creation
|
|
||||||
fn test_git_tree_creation() {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let code_dir = `${home_dir}/code`;
|
|
||||||
|
|
||||||
print("Testing GitTree creation...");
|
|
||||||
let git_tree = gittree_new(code_dir);
|
|
||||||
|
|
||||||
print(`Created GitTree with base path: ${code_dir}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test GitTree list method
|
|
||||||
fn test_git_tree_list() {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let code_dir = `${home_dir}/code`;
|
|
||||||
|
|
||||||
print("Testing GitTree list method...");
|
|
||||||
let git_tree = gittree_new(code_dir);
|
|
||||||
let repos = git_tree.list();
|
|
||||||
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
|
||||||
|
|
||||||
// Print the first few repositories
|
|
||||||
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
|
||||||
for i in range(0, count) {
|
|
||||||
print(` - ${repos[i]}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos.len() >= 0; // Success even if no repos found
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test GitTree find method
|
|
||||||
fn test_git_tree_find() {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let code_dir = `${home_dir}/code`;
|
|
||||||
|
|
||||||
print("Testing GitTree find method...");
|
|
||||||
let git_tree = gittree_new(code_dir);
|
|
||||||
let repos = git_tree.list();
|
|
||||||
|
|
||||||
if repos.len() == 0 {
|
|
||||||
print("No repositories found, skipping test");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
let search_pattern = repo_name + "*";
|
|
||||||
|
|
||||||
print(`Searching for repositories matching pattern: ${search_pattern}`);
|
|
||||||
let matching = git_tree.find(search_pattern);
|
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
|
||||||
for repo in matching {
|
|
||||||
print(` - ${repo}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matching.len() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test GitTree get method
|
|
||||||
fn test_git_tree_get() {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let code_dir = `${home_dir}/code`;
|
|
||||||
|
|
||||||
print("Testing GitTree get method...");
|
|
||||||
let git_tree = gittree_new(code_dir);
|
|
||||||
let repos = git_tree.list();
|
|
||||||
|
|
||||||
if repos.len() == 0 {
|
|
||||||
print("No repositories found, skipping test");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
|
|
||||||
print(`Getting GitRepo objects for: ${repo_name}`);
|
|
||||||
let git_repos = git_tree.get(repo_name);
|
|
||||||
|
|
||||||
print(`Found ${git_repos.len()} GitRepo objects`);
|
|
||||||
for repo in git_repos {
|
|
||||||
print(` - ${repo.path()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return git_repos.len() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test GitRepo has_changes method
|
|
||||||
fn test_git_repo_has_changes() {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let code_dir = `${home_dir}/code`;
|
|
||||||
|
|
||||||
print("Testing GitRepo has_changes method...");
|
|
||||||
let git_tree = gittree_new(code_dir);
|
|
||||||
let repos = git_tree.list();
|
|
||||||
|
|
||||||
if repos.len() == 0 {
|
|
||||||
print("No repositories found, skipping test");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the first repo
|
|
||||||
let git_repos = git_tree.get(repos[0]);
|
|
||||||
if git_repos.len() == 0 {
|
|
||||||
print("Failed to get GitRepo object, skipping test");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let git_repo = git_repos[0];
|
|
||||||
let has_changes = git_repo.has_changes();
|
|
||||||
|
|
||||||
print(`Repository ${git_repo.path()} has changes: ${has_changes}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the tests
|
|
||||||
fn run_tests() {
|
|
||||||
let tests = [
|
|
||||||
#{ name: "git_tree_creation", fn: test_git_tree_creation },
|
|
||||||
#{ name: "git_tree_list", fn: test_git_tree_list },
|
|
||||||
#{ name: "git_tree_find", fn: test_git_tree_find },
|
|
||||||
#{ name: "git_tree_get", fn: test_git_tree_get },
|
|
||||||
#{ name: "git_repo_has_changes", fn: test_git_repo_has_changes }
|
|
||||||
];
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for test in tests {
|
|
||||||
print(`\nRunning test: ${test.name}`);
|
|
||||||
|
|
||||||
let result = false;
|
|
||||||
try {
|
|
||||||
result = test.fn();
|
|
||||||
} catch(err) {
|
|
||||||
print(`Test ${test.name} threw an error: ${err}`);
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if result {
|
|
||||||
print(`Test ${test.name} PASSED`);
|
|
||||||
passed += 1;
|
|
||||||
} else {
|
|
||||||
print(`Test ${test.name} FAILED`);
|
|
||||||
failed += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print(`\nTest summary: ${passed} passed, ${failed} failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run all tests
|
|
||||||
run_tests();
|
|
164
src/rhaiexamples/git_test.rhai
Normal file
164
src/rhaiexamples/git_test.rhai
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Simplified test script for Git module functions
|
||||||
|
|
||||||
|
// Ensure test directory exists using a bash script
|
||||||
|
fn ensure_test_dir() {
|
||||||
|
print("Ensuring test directory exists at /tmp/code");
|
||||||
|
|
||||||
|
// Create a bash script to set up the test environment
|
||||||
|
let setup_script = `#!/bin/bash -ex
|
||||||
|
rm -rf /tmp/code
|
||||||
|
mkdir -p /tmp/code
|
||||||
|
cd /tmp/code
|
||||||
|
|
||||||
|
mkdir -p myserver.com/myaccount/repogreen
|
||||||
|
mkdir -p myserver.com/myaccount/repored
|
||||||
|
|
||||||
|
cd myserver.com/myaccount/repogreen
|
||||||
|
git init
|
||||||
|
echo 'Initial test file' > test.txt
|
||||||
|
git add test.txt
|
||||||
|
git config --local user.email 'test@example.com'
|
||||||
|
git config --local user.name 'Test User'
|
||||||
|
git commit -m 'Initial commit'
|
||||||
|
|
||||||
|
cd myserver.com/myaccount/repored
|
||||||
|
git init
|
||||||
|
echo 'Initial test file' > test2.txt
|
||||||
|
git add test2.txt
|
||||||
|
git config --local user.email 'test@example.com'
|
||||||
|
git config --local user.name 'Test User'
|
||||||
|
git commit -m 'Initial commit'
|
||||||
|
|
||||||
|
//now we have 2 repos
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Run the setup script
|
||||||
|
let result = run(setup_script);
|
||||||
|
if !result.success {
|
||||||
|
print("Failed to set up test directory");
|
||||||
|
print(`Error: ${result.stderr}`);
|
||||||
|
throw "Test setup failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GitTree creation
|
||||||
|
fn test_git_tree_creation() {
|
||||||
|
print("\n=== Testing GitTree creation ===");
|
||||||
|
let git_tree = gittree_new("/tmp/code");
|
||||||
|
print(`Created GitTree with base path: /tmp/code`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GitTree list method
|
||||||
|
fn test_git_tree_list() {
|
||||||
|
print("\n=== Testing GitTree list method ===");
|
||||||
|
let git_tree = gittree_new("/tmp/code");
|
||||||
|
let repos = git_tree.list();
|
||||||
|
|
||||||
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
|
// Print repositories
|
||||||
|
for repo in repos {
|
||||||
|
print(` - ${repo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if repos.len() == 0 {
|
||||||
|
print("No repositories found, which is unexpected");
|
||||||
|
throw "No repositories found";
|
||||||
|
}
|
||||||
|
|
||||||
|
if repos.len() != 2 {
|
||||||
|
print("No enough repositories found, needs to be 2");
|
||||||
|
throw "No enough repositories found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GitTree find method
|
||||||
|
fn test_git_tree_find() {
|
||||||
|
print("\n=== Testing GitTree find method ===");
|
||||||
|
let git_tree = gittree_new("/tmp/code");
|
||||||
|
|
||||||
|
// Search for repositories with "code" in the name
|
||||||
|
let search_pattern = "myaccount/repo"; //we need to check if we need *, would be better not
|
||||||
|
print(`Searching for repositories matching pattern: ${search_pattern}`);
|
||||||
|
let matching = git_tree.find(search_pattern);
|
||||||
|
|
||||||
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
|
for repo in matching {
|
||||||
|
print(` - ${repo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matching.len() == 0 {
|
||||||
|
print("No matching repositories found, which is unexpected");
|
||||||
|
throw "No matching repositories found";
|
||||||
|
}
|
||||||
|
if repos.len() != 2 {
|
||||||
|
print("No enough repositories found, needs to be 2");
|
||||||
|
throw "No enough repositories found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GitRepo operations
|
||||||
|
fn test_git_repo_operations() {
|
||||||
|
print("\n=== Testing GitRepo operations ===");
|
||||||
|
let git_tree = gittree_new("/tmp/code");
|
||||||
|
let repos = git_tree.list();
|
||||||
|
|
||||||
|
if repos.len() == 0 {
|
||||||
|
print("No repositories found, which is unexpected");
|
||||||
|
throw "No repositories found";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first repo
|
||||||
|
let repo_path = repos[0];
|
||||||
|
print(`Testing operations on repository: ${repo_path}`);
|
||||||
|
|
||||||
|
// Get GitRepo object
|
||||||
|
let git_repos = git_tree.get(repo_path);
|
||||||
|
if git_repos.len() == 0 {
|
||||||
|
print("Failed to get GitRepo object");
|
||||||
|
throw "Failed to get GitRepo object";
|
||||||
|
}
|
||||||
|
|
||||||
|
let git_repo = git_repos[0];
|
||||||
|
|
||||||
|
// Test has_changes method
|
||||||
|
print("Testing has_changes method");
|
||||||
|
let has_changes = git_repo.has_changes();
|
||||||
|
print(`Repository has changes: ${has_changes}`);
|
||||||
|
|
||||||
|
// Create a change to test
|
||||||
|
print("Creating a change to test");
|
||||||
|
file_write("/tmp/code/test2.txt", "Another test file");
|
||||||
|
|
||||||
|
// Check if changes are detected
|
||||||
|
let has_changes_after = git_repo.has_changes();
|
||||||
|
print(`Repository has changes after modification: ${has_changes_after}`);
|
||||||
|
|
||||||
|
if !has_changes_after {
|
||||||
|
print("Changes not detected, which is unexpected");
|
||||||
|
throw "Changes not detected";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the change
|
||||||
|
delete("/tmp/code/test2.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
fn run_all_tests() {
|
||||||
|
print("Starting Git module tests...");
|
||||||
|
|
||||||
|
// Ensure test directory exists
|
||||||
|
ensure_test_dir();
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
test_git_tree_creation();
|
||||||
|
test_git_tree_list();
|
||||||
|
test_git_tree_find();
|
||||||
|
test_git_repo_operations();
|
||||||
|
|
||||||
|
print("\nAll tests completed successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
run_all_tests();
|
33
src/rhaiexamples/stdout_test.rhai
Normal file
33
src/rhaiexamples/stdout_test.rhai
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// Create a bash script to set up the test environment
|
||||||
|
let setup_script = `
|
||||||
|
rm -rf /tmp/code
|
||||||
|
mkdir -p /tmp/code
|
||||||
|
cd /tmp/code
|
||||||
|
|
||||||
|
mkdir -p myserver.com/myaccount/repogreen
|
||||||
|
mkdir -p myserver.com/myaccount/repored
|
||||||
|
|
||||||
|
cd myserver.com/myaccount/repogreen
|
||||||
|
git init
|
||||||
|
echo 'Initial test file' > test.txt
|
||||||
|
git add test.txt
|
||||||
|
git config --local user.email 'test@example.com'
|
||||||
|
git config --local user.name 'Test User'
|
||||||
|
git commit -m 'Initial commit'
|
||||||
|
|
||||||
|
cd myserver.com/myaccount/repored
|
||||||
|
git init
|
||||||
|
echo 'Initial test file' > test2.txt
|
||||||
|
git add test2.txt
|
||||||
|
git config --local user.email 'test@example.com'
|
||||||
|
git config --local user.name 'Test User'
|
||||||
|
git commit -m 'Initial commit'
|
||||||
|
|
||||||
|
//now we have 2 repos
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Run the setup script
|
||||||
|
let result = run(setup_script);
|
@ -1,11 +0,0 @@
|
|||||||
// Simple test script for Git module functions
|
|
||||||
|
|
||||||
// Print a header
|
|
||||||
print("=== Testing Git Module Functions ===\n");
|
|
||||||
|
|
||||||
// Create a new GitTree object
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let git_tree = gittree_new(`${home_dir}/code`);
|
|
||||||
print(`Created GitTree with base path: ${home_dir}/code`);
|
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
|
@ -1,65 +0,0 @@
|
|||||||
// Simple test script for Git module functions
|
|
||||||
|
|
||||||
// Print a header
|
|
||||||
print("=== Testing Git Module Functions ===\n");
|
|
||||||
|
|
||||||
// Create a new GitTree object
|
|
||||||
let home_dir = env("HOME");
|
|
||||||
let git_tree = gittree_new(`${home_dir}/code`);
|
|
||||||
print(`Created GitTree with base path: ${home_dir}/code`);
|
|
||||||
|
|
||||||
// Test list method
|
|
||||||
print("\nListing git repositories...");
|
|
||||||
let repos = git_tree.list();
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
|
||||||
|
|
||||||
// Print the first few repositories
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("First few repositories:");
|
|
||||||
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
|
||||||
for i in range(0, count) {
|
|
||||||
print(` - ${repos[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test find method
|
|
||||||
if repos.len() > 0 {
|
|
||||||
print("\nTesting repository search...");
|
|
||||||
// Extract a part of the first repo name to search for
|
|
||||||
let repo_path = repos[0];
|
|
||||||
let parts = repo_path.split("/");
|
|
||||||
let repo_name = parts[parts.len() - 1];
|
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
|
||||||
let matching = git_tree.find(repo_name);
|
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
|
||||||
for repo in matching {
|
|
||||||
print(` - ${repo}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test get method
|
|
||||||
print("\nTesting get method...");
|
|
||||||
let git_repos = git_tree.get(repo_name);
|
|
||||||
print(`Found ${git_repos.len()} GitRepo objects`);
|
|
||||||
|
|
||||||
// Test GitRepo methods
|
|
||||||
if git_repos.len() > 0 {
|
|
||||||
let git_repo = git_repos[0];
|
|
||||||
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
|
||||||
|
|
||||||
// Check if a repository has changes
|
|
||||||
print("Checking for changes in repository...");
|
|
||||||
let has_changes = git_repo.has_changes();
|
|
||||||
print(`Repository has changes: ${has_changes}`);
|
|
||||||
|
|
||||||
// Test method chaining (only if there are no changes to avoid errors)
|
|
||||||
if !has_changes {
|
|
||||||
print("\nTesting method chaining (pull)...");
|
|
||||||
let result = git_repo.pull();
|
|
||||||
print("Pull operation completed successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
|
374
src/virt/nerdctl/README.md
Normal file
374
src/virt/nerdctl/README.md
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
# Container API for nerdctl
|
||||||
|
|
||||||
|
This module provides a Rust API for managing containers using nerdctl, a Docker-compatible CLI for containerd.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Container API is designed with a builder pattern to make it easy to create and manage containers. It provides a fluent interface for configuring container options and performing operations on containers.
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
- `Container`: The main struct representing a container
|
||||||
|
- `HealthCheck`: Configuration for container health checks
|
||||||
|
- `ContainerStatus`: Information about a container's status
|
||||||
|
- `ResourceUsage`: Information about a container's resource usage
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- nerdctl must be installed on your system
|
||||||
|
- containerd must be running
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
Add the following to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
sal = { path = "/path/to/sal" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Then import the Container API in your Rust code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use sal::virt::nerdctl::Container;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Getting a Container by Name
|
||||||
|
|
||||||
|
You can get a reference to an existing container by name:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use sal::virt::nerdctl::Container;
|
||||||
|
|
||||||
|
// Get a container by name (if it exists)
|
||||||
|
match Container::new("existing-container") {
|
||||||
|
Ok(container) => {
|
||||||
|
if container.container_id.is_some() {
|
||||||
|
println!("Found container with ID: {}", container.container_id.unwrap());
|
||||||
|
|
||||||
|
// Perform operations on the existing container
|
||||||
|
let status = container.status()?;
|
||||||
|
println!("Container status: {}", status.status);
|
||||||
|
} else {
|
||||||
|
println!("Container exists but has no ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error getting container: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Container
|
||||||
|
|
||||||
|
You can create a new container from an image using the builder pattern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use sal::virt::nerdctl::Container;
|
||||||
|
|
||||||
|
// Create a container from an image
|
||||||
|
let container = Container::from_image("my-nginx", "nginx:latest")?
|
||||||
|
.with_port("8080:80")
|
||||||
|
.with_env("NGINX_HOST", "example.com")
|
||||||
|
.with_volume("/tmp/nginx:/usr/share/nginx/html")
|
||||||
|
.with_health_check("curl -f http://localhost/ || exit 1")
|
||||||
|
.with_detach(true)
|
||||||
|
.build()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Operations
|
||||||
|
|
||||||
|
Once you have a container, you can perform various operations on it:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 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 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()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Configuration Options
|
||||||
|
|
||||||
|
The Container API supports a wide range of configuration options through its builder pattern:
|
||||||
|
|
||||||
|
### Ports
|
||||||
|
|
||||||
|
Map container ports to host ports:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Map a single port
|
||||||
|
.with_port("8080:80")
|
||||||
|
|
||||||
|
// Map multiple ports
|
||||||
|
.with_ports(&["8080:80", "8443:443"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Volumes
|
||||||
|
|
||||||
|
Mount host directories or volumes in the container:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Mount a single volume
|
||||||
|
.with_volume("/host/path:/container/path")
|
||||||
|
|
||||||
|
// Mount multiple volumes
|
||||||
|
.with_volumes(&["/host/path1:/container/path1", "/host/path2:/container/path2"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Set environment variables in the container:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Set a single environment variable
|
||||||
|
.with_env("KEY", "value")
|
||||||
|
|
||||||
|
// Set multiple environment variables
|
||||||
|
let mut env_map = HashMap::new();
|
||||||
|
env_map.insert("KEY1", "value1");
|
||||||
|
env_map.insert("KEY2", "value2");
|
||||||
|
.with_envs(&env_map)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Configuration
|
||||||
|
|
||||||
|
Configure container networking:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Set the network
|
||||||
|
.with_network("bridge")
|
||||||
|
|
||||||
|
// Add a network alias
|
||||||
|
.with_network_alias("my-container")
|
||||||
|
|
||||||
|
// Add multiple network aliases
|
||||||
|
.with_network_aliases(&["alias1", "alias2"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resource Limits
|
||||||
|
|
||||||
|
Set CPU and memory limits:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Set CPU limit (e.g., 0.5 for half a CPU, 2 for 2 CPUs)
|
||||||
|
.with_cpu_limit("0.5")
|
||||||
|
|
||||||
|
// Set memory limit (e.g., 512m for 512MB, 1g for 1GB)
|
||||||
|
.with_memory_limit("512m")
|
||||||
|
|
||||||
|
// Set memory swap limit
|
||||||
|
.with_memory_swap_limit("1g")
|
||||||
|
|
||||||
|
// Set CPU shares (relative weight)
|
||||||
|
.with_cpu_shares("1024")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
Configure container health checks:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Simple health check
|
||||||
|
.with_health_check("curl -f http://localhost/ || exit 1")
|
||||||
|
|
||||||
|
// Health check with custom options
|
||||||
|
.with_health_check_options(
|
||||||
|
"curl -f http://localhost/ || exit 1", // Command
|
||||||
|
Some("30s"), // Interval
|
||||||
|
Some("10s"), // Timeout
|
||||||
|
Some(3), // Retries
|
||||||
|
Some("5s") // Start period
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other Options
|
||||||
|
|
||||||
|
Other container configuration options:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Set restart policy
|
||||||
|
.with_restart_policy("always") // Options: no, always, on-failure, unless-stopped
|
||||||
|
|
||||||
|
// Set snapshotter
|
||||||
|
.with_snapshotter("native") // Options: native, fuse-overlayfs, etc.
|
||||||
|
|
||||||
|
// Set detach mode
|
||||||
|
.with_detach(true) // Run in detached mode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Operations
|
||||||
|
|
||||||
|
Once a container is created, you can perform various operations on it:
|
||||||
|
|
||||||
|
### Basic Operations
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Start the container
|
||||||
|
container.start()?;
|
||||||
|
|
||||||
|
// Stop the container
|
||||||
|
container.stop()?;
|
||||||
|
|
||||||
|
// Remove the container
|
||||||
|
container.remove()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Execution
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Execute a command in the container
|
||||||
|
let result = container.exec("echo 'Hello from container'")?;
|
||||||
|
println!("Command output: {}", result.stdout);
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Operations
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Copy files between the container and host
|
||||||
|
container.copy("container_name:/path/in/container", "/path/on/host")?;
|
||||||
|
container.copy("/path/on/host", "container_name:/path/in/container")?;
|
||||||
|
|
||||||
|
// Export the container to a tarball
|
||||||
|
container.export("/path/to/export.tar")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Operations
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Commit the container to an image
|
||||||
|
container.commit("my-custom-image:latest")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status and Monitoring
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Get container status
|
||||||
|
let status = container.status()?;
|
||||||
|
println!("Container state: {}", status.state);
|
||||||
|
println!("Container status: {}", status.status);
|
||||||
|
println!("Created: {}", status.created);
|
||||||
|
println!("Started: {}", status.started);
|
||||||
|
|
||||||
|
// Get health status
|
||||||
|
let health_status = container.health_status()?;
|
||||||
|
println!("Health status: {}", health_status);
|
||||||
|
|
||||||
|
// Get resource usage
|
||||||
|
let resources = container.resources()?;
|
||||||
|
println!("CPU usage: {}", resources.cpu_usage);
|
||||||
|
println!("Memory usage: {}", resources.memory_usage);
|
||||||
|
println!("Memory limit: {}", resources.memory_limit);
|
||||||
|
println!("Memory percentage: {}", resources.memory_percentage);
|
||||||
|
println!("Network I/O: {} / {}", resources.network_input, resources.network_output);
|
||||||
|
println!("Block I/O: {} / {}", resources.block_input, resources.block_output);
|
||||||
|
println!("PIDs: {}", resources.pids);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The Container API uses a custom error type `NerdctlError` that can be one of the following:
|
||||||
|
|
||||||
|
- `CommandExecutionFailed`: The nerdctl command failed to execute
|
||||||
|
- `CommandFailed`: The nerdctl command executed but returned an error
|
||||||
|
- `JsonParseError`: Failed to parse JSON output
|
||||||
|
- `ConversionError`: Failed to convert data
|
||||||
|
- `Other`: Generic error
|
||||||
|
|
||||||
|
Example error handling:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
match Container::new("non-existent-container") {
|
||||||
|
Ok(container) => {
|
||||||
|
// Container exists
|
||||||
|
println!("Container found");
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
NerdctlError::CommandExecutionFailed(io_error) => {
|
||||||
|
println!("Failed to execute nerdctl command: {}", io_error);
|
||||||
|
},
|
||||||
|
NerdctlError::CommandFailed(error_msg) => {
|
||||||
|
println!("nerdctl command failed: {}", error_msg);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("Other error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
The Container API is implemented in several modules:
|
||||||
|
|
||||||
|
- `container_types.rs`: Contains the struct definitions
|
||||||
|
- `container.rs`: Contains the main Container implementation
|
||||||
|
- `container_builder.rs`: Contains the builder pattern methods
|
||||||
|
- `container_operations.rs`: Contains the container operations
|
||||||
|
- `health_check.rs`: Contains the HealthCheck implementation
|
||||||
|
|
||||||
|
This modular approach makes the code more maintainable and easier to understand.
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
Here's a complete example that demonstrates the Container API:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::error::Error;
|
||||||
|
use sal::virt::nerdctl::Container;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Create a container from an image
|
||||||
|
println!("Creating container from image...");
|
||||||
|
let container = Container::from_image("my-nginx", "nginx:latest")?
|
||||||
|
.with_port("8080:80")
|
||||||
|
.with_env("NGINX_HOST", "example.com")
|
||||||
|
.with_volume("/tmp/nginx:/usr/share/nginx/html")
|
||||||
|
.with_health_check("curl -f http://localhost/ || exit 1")
|
||||||
|
.with_detach(true)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
println!("Container created successfully");
|
||||||
|
|
||||||
|
// Execute a command in the container
|
||||||
|
println!("Executing command in container...");
|
||||||
|
let result = container.exec("echo 'Hello from container'")?;
|
||||||
|
println!("Command output: {}", result.stdout);
|
||||||
|
|
||||||
|
// Get container status
|
||||||
|
println!("Getting container status...");
|
||||||
|
let status = container.status()?;
|
||||||
|
println!("Container status: {}", status.status);
|
||||||
|
|
||||||
|
// Get resource usage
|
||||||
|
println!("Getting resource usage...");
|
||||||
|
let resources = container.resources()?;
|
||||||
|
println!("CPU usage: {}", resources.cpu_usage);
|
||||||
|
println!("Memory usage: {}", resources.memory_usage);
|
||||||
|
|
||||||
|
// Stop and remove the container
|
||||||
|
println!("Stopping and removing container...");
|
||||||
|
container.stop()?;
|
||||||
|
container.remove()?;
|
||||||
|
|
||||||
|
println!("Container stopped and removed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
69
src/virt/nerdctl/container.rs
Normal file
69
src/virt/nerdctl/container.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container.rs
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::process::CommandResult;
|
||||||
|
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
|
||||||
|
use super::container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
/// Create a new container reference with the given name
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Name for the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Self, NerdctlError>` - Container instance or error
|
||||||
|
pub fn new(name: &str) -> Result<Self, NerdctlError> {
|
||||||
|
// Check if container exists
|
||||||
|
let result = execute_nerdctl_command(&["ps", "-a", "--format", "{{.Names}} {{.ID}}"])?;
|
||||||
|
|
||||||
|
// Look for the container name in the output
|
||||||
|
let container_id = result.stdout.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
if line.starts_with(&format!("{} ", name)) {
|
||||||
|
Some(line.split_whitespace().nth(1)?.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
container_id,
|
||||||
|
image: None,
|
||||||
|
config: HashMap::new(),
|
||||||
|
ports: Vec::new(),
|
||||||
|
volumes: Vec::new(),
|
||||||
|
env_vars: HashMap::new(),
|
||||||
|
network: None,
|
||||||
|
network_aliases: Vec::new(),
|
||||||
|
cpu_limit: None,
|
||||||
|
memory_limit: None,
|
||||||
|
memory_swap_limit: None,
|
||||||
|
cpu_shares: None,
|
||||||
|
restart_policy: None,
|
||||||
|
health_check: None,
|
||||||
|
detach: false,
|
||||||
|
snapshotter: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a container from an image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Name for the container
|
||||||
|
/// * `image` - Image to create the container from
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Self, NerdctlError>` - Container instance or error
|
||||||
|
pub fn from_image(name: &str, image: &str) -> Result<Self, NerdctlError> {
|
||||||
|
let mut container = Self::new(name)?;
|
||||||
|
container.image = Some(image.to_string());
|
||||||
|
Ok(container)
|
||||||
|
}
|
||||||
|
}
|
460
src/virt/nerdctl/container_builder.rs
Normal file
460
src/virt/nerdctl/container_builder.rs
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_builder.rs
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
|
||||||
|
use super::container_types::{Container, HealthCheck};
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
/// Add a port mapping
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `port` - Port mapping (e.g., "8080:80")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_port(mut self, port: &str) -> Self {
|
||||||
|
self.ports.push(port.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple port mappings
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `ports` - Array of port mappings (e.g., ["8080:80", "8443:443"])
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_ports(mut self, ports: &[&str]) -> Self {
|
||||||
|
for port in ports {
|
||||||
|
self.ports.push(port.to_string());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a volume mount
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `volume` - Volume mount (e.g., "/host/path:/container/path")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_volume(mut self, volume: &str) -> Self {
|
||||||
|
self.volumes.push(volume.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple volume mounts
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `volumes` - Array of volume mounts (e.g., ["/host/path1:/container/path1", "/host/path2:/container/path2"])
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_volumes(mut self, volumes: &[&str]) -> Self {
|
||||||
|
for volume in volumes {
|
||||||
|
self.volumes.push(volume.to_string());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an environment variable
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `key` - Environment variable name
|
||||||
|
/// * `value` - Environment variable value
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_env(mut self, key: &str, value: &str) -> Self {
|
||||||
|
self.env_vars.insert(key.to_string(), value.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple environment variables
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `env_map` - Map of environment variable names to values
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_envs(mut self, env_map: &HashMap<&str, &str>) -> Self {
|
||||||
|
for (key, value) in env_map {
|
||||||
|
self.env_vars.insert(key.to_string(), value.to_string());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the network for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `network` - Network name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_network(mut self, network: &str) -> Self {
|
||||||
|
self.network = Some(network.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a network alias for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `alias` - Network alias
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_network_alias(mut self, alias: &str) -> Self {
|
||||||
|
self.network_aliases.push(alias.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple network aliases for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `aliases` - Array of network aliases
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_network_aliases(mut self, aliases: &[&str]) -> Self {
|
||||||
|
for alias in aliases {
|
||||||
|
self.network_aliases.push(alias.to_string());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set CPU limit for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cpus` - CPU limit (e.g., "0.5" for half a CPU, "2" for 2 CPUs)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_cpu_limit(mut self, cpus: &str) -> Self {
|
||||||
|
self.cpu_limit = Some(cpus.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set memory limit for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `memory` - Memory limit (e.g., "512m" for 512MB, "1g" for 1GB)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_memory_limit(mut self, memory: &str) -> Self {
|
||||||
|
self.memory_limit = Some(memory.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set memory swap limit for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `memory_swap` - Memory swap limit (e.g., "1g" for 1GB)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_memory_swap_limit(mut self, memory_swap: &str) -> Self {
|
||||||
|
self.memory_swap_limit = Some(memory_swap.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set CPU shares for the container (relative weight)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `shares` - CPU shares (e.g., "1024" for default, "512" for half)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_cpu_shares(mut self, shares: &str) -> Self {
|
||||||
|
self.cpu_shares = Some(shares.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set restart policy for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `policy` - Restart policy (e.g., "no", "always", "on-failure", "unless-stopped")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_restart_policy(mut self, policy: &str) -> Self {
|
||||||
|
self.restart_policy = Some(policy.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a simple health check for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cmd` - Command to run for health check (e.g., "curl -f http://localhost/ || exit 1")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_health_check(mut self, cmd: &str) -> Self {
|
||||||
|
self.health_check = Some(HealthCheck {
|
||||||
|
cmd: cmd.to_string(),
|
||||||
|
interval: None,
|
||||||
|
timeout: None,
|
||||||
|
retries: None,
|
||||||
|
start_period: None,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a health check with custom options for the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cmd` - Command to run for health check
|
||||||
|
/// * `interval` - Optional time between running the check (e.g., "30s", "1m")
|
||||||
|
/// * `timeout` - Optional maximum time to wait for a check to complete (e.g., "30s", "1m")
|
||||||
|
/// * `retries` - Optional number of consecutive failures needed to consider unhealthy
|
||||||
|
/// * `start_period` - Optional start period for the container to initialize before counting retries (e.g., "30s", "1m")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_health_check_options(
|
||||||
|
mut self,
|
||||||
|
cmd: &str,
|
||||||
|
interval: Option<&str>,
|
||||||
|
timeout: Option<&str>,
|
||||||
|
retries: Option<u32>,
|
||||||
|
start_period: Option<&str>,
|
||||||
|
) -> Self {
|
||||||
|
let mut health_check = HealthCheck {
|
||||||
|
cmd: cmd.to_string(),
|
||||||
|
interval: None,
|
||||||
|
timeout: None,
|
||||||
|
retries: None,
|
||||||
|
start_period: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(interval_value) = interval {
|
||||||
|
health_check.interval = Some(interval_value.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(timeout_value) = timeout {
|
||||||
|
health_check.timeout = Some(timeout_value.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(retries_value) = retries {
|
||||||
|
health_check.retries = Some(retries_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(start_period_value) = start_period {
|
||||||
|
health_check.start_period = Some(start_period_value.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.health_check = Some(health_check);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the snapshotter
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `snapshotter` - Snapshotter to use
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_snapshotter(mut self, snapshotter: &str) -> Self {
|
||||||
|
self.snapshotter = Some(snapshotter.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether to run in detached mode
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `detach` - Whether to run in detached mode
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Self` - The container instance for method chaining
|
||||||
|
pub fn with_detach(mut self, detach: bool) -> Self {
|
||||||
|
self.detach = detach;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Self, NerdctlError>` - Container instance or error
|
||||||
|
pub fn build(self) -> Result<Self, NerdctlError> {
|
||||||
|
// If container already exists, return it
|
||||||
|
if self.container_id.is_some() {
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no image is specified, return an error
|
||||||
|
let image = match &self.image {
|
||||||
|
Some(img) => img,
|
||||||
|
None => return Err(NerdctlError::Other("No image specified for container creation".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the command arguments as strings
|
||||||
|
let mut args_strings = Vec::new();
|
||||||
|
args_strings.push("run".to_string());
|
||||||
|
|
||||||
|
if self.detach {
|
||||||
|
args_strings.push("-d".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
args_strings.push("--name".to_string());
|
||||||
|
args_strings.push(self.name.clone());
|
||||||
|
|
||||||
|
// Add port mappings
|
||||||
|
for port in &self.ports {
|
||||||
|
args_strings.push("-p".to_string());
|
||||||
|
args_strings.push(port.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add volume mounts
|
||||||
|
for volume in &self.volumes {
|
||||||
|
args_strings.push("-v".to_string());
|
||||||
|
args_strings.push(volume.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add environment variables
|
||||||
|
for (key, value) in &self.env_vars {
|
||||||
|
args_strings.push("-e".to_string());
|
||||||
|
args_strings.push(format!("{}={}", key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add network configuration
|
||||||
|
if let Some(network) = &self.network {
|
||||||
|
args_strings.push("--network".to_string());
|
||||||
|
args_strings.push(network.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add network aliases
|
||||||
|
for alias in &self.network_aliases {
|
||||||
|
args_strings.push("--network-alias".to_string());
|
||||||
|
args_strings.push(alias.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add resource limits
|
||||||
|
if let Some(cpu_limit) = &self.cpu_limit {
|
||||||
|
args_strings.push("--cpus".to_string());
|
||||||
|
args_strings.push(cpu_limit.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(memory_limit) = &self.memory_limit {
|
||||||
|
args_strings.push("--memory".to_string());
|
||||||
|
args_strings.push(memory_limit.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(memory_swap_limit) = &self.memory_swap_limit {
|
||||||
|
args_strings.push("--memory-swap".to_string());
|
||||||
|
args_strings.push(memory_swap_limit.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cpu_shares) = &self.cpu_shares {
|
||||||
|
args_strings.push("--cpu-shares".to_string());
|
||||||
|
args_strings.push(cpu_shares.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add restart policy
|
||||||
|
if let Some(restart_policy) = &self.restart_policy {
|
||||||
|
args_strings.push("--restart".to_string());
|
||||||
|
args_strings.push(restart_policy.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add health check
|
||||||
|
if let Some(health_check) = &self.health_check {
|
||||||
|
args_strings.push("--health-cmd".to_string());
|
||||||
|
args_strings.push(health_check.cmd.clone());
|
||||||
|
|
||||||
|
if let Some(interval) = &health_check.interval {
|
||||||
|
args_strings.push("--health-interval".to_string());
|
||||||
|
args_strings.push(interval.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(timeout) = &health_check.timeout {
|
||||||
|
args_strings.push("--health-timeout".to_string());
|
||||||
|
args_strings.push(timeout.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(retries) = &health_check.retries {
|
||||||
|
args_strings.push("--health-retries".to_string());
|
||||||
|
args_strings.push(retries.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(start_period) = &health_check.start_period {
|
||||||
|
args_strings.push("--health-start-period".to_string());
|
||||||
|
args_strings.push(start_period.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(snapshotter_value) = &self.snapshotter {
|
||||||
|
args_strings.push("--snapshotter".to_string());
|
||||||
|
args_strings.push(snapshotter_value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add flags to avoid BPF issues
|
||||||
|
args_strings.push("--cgroup-manager=cgroupfs".to_string());
|
||||||
|
|
||||||
|
args_strings.push(image.clone());
|
||||||
|
|
||||||
|
// Convert to string slices for the command
|
||||||
|
let args: Vec<&str> = args_strings.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
let result = execute_nerdctl_command(&args)?;
|
||||||
|
|
||||||
|
// Get the container ID from the output
|
||||||
|
let container_id = result.stdout.trim().to_string();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: self.name,
|
||||||
|
container_id: Some(container_id),
|
||||||
|
image: self.image,
|
||||||
|
config: self.config,
|
||||||
|
ports: self.ports,
|
||||||
|
volumes: self.volumes,
|
||||||
|
env_vars: self.env_vars,
|
||||||
|
network: self.network,
|
||||||
|
network_aliases: self.network_aliases,
|
||||||
|
cpu_limit: self.cpu_limit,
|
||||||
|
memory_limit: self.memory_limit,
|
||||||
|
memory_swap_limit: self.memory_swap_limit,
|
||||||
|
cpu_shares: self.cpu_shares,
|
||||||
|
restart_policy: self.restart_policy,
|
||||||
|
health_check: self.health_check,
|
||||||
|
detach: self.detach,
|
||||||
|
snapshotter: self.snapshotter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
0
src/virt/nerdctl/container_functions.rs
Normal file
0
src/virt/nerdctl/container_functions.rs
Normal file
317
src/virt/nerdctl/container_operations.rs
Normal file
317
src/virt/nerdctl/container_operations.rs
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_operations.rs
|
||||||
|
|
||||||
|
use crate::process::CommandResult;
|
||||||
|
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
|
||||||
|
use super::container_types::{Container, ContainerStatus, ResourceUsage};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
/// Start the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn start(&self) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["start", container_id])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn stop(&self) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["stop", container_id])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn remove(&self) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["rm", container_id])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a command in the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `command` - The command to run
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn exec(&self, command: &str) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["exec", container_id, "sh", "-c", command])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(&self, source: &str, dest: &str) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if self.container_id.is_some() {
|
||||||
|
execute_nerdctl_command(&["cp", source, dest])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export the container to a tarball
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Path to save the tarball
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn export(&self, path: &str) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["export", "-o", path, container_id])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit the container to an image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image_name` - Name for the new image
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
||||||
|
pub fn commit(&self, image_name: &str) -> Result<CommandResult, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_nerdctl_command(&["commit", container_id, image_name])
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get container status
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<ContainerStatus, NerdctlError>` - Container status or error
|
||||||
|
pub fn status(&self) -> Result<ContainerStatus, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
let result = execute_nerdctl_command(&["inspect", container_id])?;
|
||||||
|
|
||||||
|
// Parse the JSON output
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let Some(container_json) = json.as_array().and_then(|arr| arr.first()) {
|
||||||
|
let state = container_json
|
||||||
|
.get("State")
|
||||||
|
.and_then(|state| state.get("Status"))
|
||||||
|
.and_then(|status| status.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let status = container_json
|
||||||
|
.get("State")
|
||||||
|
.and_then(|state| state.get("Running"))
|
||||||
|
.and_then(|running| {
|
||||||
|
if running.as_bool().unwrap_or(false) {
|
||||||
|
Some("running")
|
||||||
|
} else {
|
||||||
|
Some("stopped")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let created = container_json
|
||||||
|
.get("Created")
|
||||||
|
.and_then(|created| created.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let started = container_json
|
||||||
|
.get("State")
|
||||||
|
.and_then(|state| state.get("StartedAt"))
|
||||||
|
.and_then(|started| started.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Get health status if available
|
||||||
|
let health_status = container_json
|
||||||
|
.get("State")
|
||||||
|
.and_then(|state| state.get("Health"))
|
||||||
|
.and_then(|health| health.get("Status"))
|
||||||
|
.and_then(|status| status.as_str())
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
|
// Get health check output if available
|
||||||
|
let health_output = container_json
|
||||||
|
.get("State")
|
||||||
|
.and_then(|state| state.get("Health"))
|
||||||
|
.and_then(|health| health.get("Log"))
|
||||||
|
.and_then(|log| log.as_array())
|
||||||
|
.and_then(|log_array| log_array.last())
|
||||||
|
.and_then(|last_log| last_log.get("Output"))
|
||||||
|
.and_then(|output| output.as_str())
|
||||||
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
|
Ok(ContainerStatus {
|
||||||
|
state,
|
||||||
|
status,
|
||||||
|
created,
|
||||||
|
started,
|
||||||
|
health_status,
|
||||||
|
health_output,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::JsonParseError("Invalid container inspect JSON".to_string()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Err(NerdctlError::JsonParseError(format!("Failed to parse container inspect JSON: {}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the health status of the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<String, NerdctlError>` - Health status or error
|
||||||
|
pub fn health_status(&self) -> Result<String, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
let result = execute_nerdctl_command(&["inspect", "--format", "{{.State.Health.Status}}", container_id])?;
|
||||||
|
Ok(result.stdout.trim().to_string())
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get container resource usage
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<ResourceUsage, NerdctlError>` - Resource usage or error
|
||||||
|
pub fn resources(&self) -> Result<ResourceUsage, NerdctlError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
let result = execute_nerdctl_command(&["stats", "--no-stream", container_id])?;
|
||||||
|
|
||||||
|
// Parse the output
|
||||||
|
let lines: Vec<&str> = result.stdout.lines().collect();
|
||||||
|
if lines.len() >= 2 {
|
||||||
|
let headers = lines[0];
|
||||||
|
let values = lines[1];
|
||||||
|
|
||||||
|
let headers_vec: Vec<&str> = headers.split_whitespace().collect();
|
||||||
|
let values_vec: Vec<&str> = values.split_whitespace().collect();
|
||||||
|
|
||||||
|
// Find indices for each metric
|
||||||
|
let cpu_index = headers_vec.iter().position(|&h| h.contains("CPU")).unwrap_or(0);
|
||||||
|
let mem_index = headers_vec.iter().position(|&h| h.contains("MEM")).unwrap_or(0);
|
||||||
|
let mem_perc_index = headers_vec.iter().position(|&h| h.contains("MEM%")).unwrap_or(0);
|
||||||
|
let net_in_index = headers_vec.iter().position(|&h| h.contains("NET")).unwrap_or(0);
|
||||||
|
let net_out_index = if net_in_index > 0 { net_in_index + 1 } else { 0 };
|
||||||
|
let block_in_index = headers_vec.iter().position(|&h| h.contains("BLOCK")).unwrap_or(0);
|
||||||
|
let block_out_index = if block_in_index > 0 { block_in_index + 1 } else { 0 };
|
||||||
|
let pids_index = headers_vec.iter().position(|&h| h.contains("PIDS")).unwrap_or(0);
|
||||||
|
|
||||||
|
let cpu_usage = if cpu_index < values_vec.len() {
|
||||||
|
values_vec[cpu_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let memory_usage = if mem_index < values_vec.len() {
|
||||||
|
values_vec[mem_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let memory_limit = if mem_index + 1 < values_vec.len() {
|
||||||
|
values_vec[mem_index + 1].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let memory_percentage = if mem_perc_index < values_vec.len() {
|
||||||
|
values_vec[mem_perc_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let network_input = if net_in_index < values_vec.len() {
|
||||||
|
values_vec[net_in_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let network_output = if net_out_index < values_vec.len() {
|
||||||
|
values_vec[net_out_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_input = if block_in_index < values_vec.len() {
|
||||||
|
values_vec[block_in_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_output = if block_out_index < values_vec.len() {
|
||||||
|
values_vec[block_out_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pids = if pids_index < values_vec.len() {
|
||||||
|
values_vec[pids_index].to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ResourceUsage {
|
||||||
|
cpu_usage,
|
||||||
|
memory_usage,
|
||||||
|
memory_limit,
|
||||||
|
memory_percentage,
|
||||||
|
network_input,
|
||||||
|
network_output,
|
||||||
|
block_input,
|
||||||
|
block_output,
|
||||||
|
pids,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::ConversionError("Failed to parse stats output".to_string()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
src/virt/nerdctl/container_test.rs
Normal file
76
src/virt/nerdctl/container_test.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_test.rs
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::super::container_types::{Container, ContainerStatus, ResourceUsage};
|
||||||
|
use super::super::NerdctlError;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_container_builder_pattern() {
|
||||||
|
// Create a container with builder pattern
|
||||||
|
let container = Container::new("test-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-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_container_from_image() {
|
||||||
|
// Create a container from image
|
||||||
|
let container = Container::from_image("test-container", "alpine:latest").unwrap();
|
||||||
|
|
||||||
|
// Verify container properties
|
||||||
|
assert_eq!(container.name, "test-container");
|
||||||
|
assert_eq!(container.image.as_ref().unwrap(), "alpine:latest");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_container_health_check() {
|
||||||
|
// Create a container with health check
|
||||||
|
let container = Container::new("test-container").unwrap()
|
||||||
|
.with_health_check("curl -f http://localhost/ || exit 1");
|
||||||
|
|
||||||
|
// Verify health check
|
||||||
|
assert!(container.health_check.is_some());
|
||||||
|
let health_check = container.health_check.unwrap();
|
||||||
|
assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1");
|
||||||
|
assert!(health_check.interval.is_none());
|
||||||
|
assert!(health_check.timeout.is_none());
|
||||||
|
assert!(health_check.retries.is_none());
|
||||||
|
assert!(health_check.start_period.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_container_health_check_options() {
|
||||||
|
// Create a container with health check options
|
||||||
|
let container = Container::new("test-container").unwrap()
|
||||||
|
.with_health_check_options(
|
||||||
|
"curl -f http://localhost/ || exit 1",
|
||||||
|
Some("30s"),
|
||||||
|
Some("10s"),
|
||||||
|
Some(3),
|
||||||
|
Some("5s")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify health check options
|
||||||
|
assert!(container.health_check.is_some());
|
||||||
|
let health_check = container.health_check.unwrap();
|
||||||
|
assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1");
|
||||||
|
assert_eq!(health_check.interval.as_ref().unwrap(), "30s");
|
||||||
|
assert_eq!(health_check.timeout.as_ref().unwrap(), "10s");
|
||||||
|
assert_eq!(health_check.retries.unwrap(), 3);
|
||||||
|
assert_eq!(health_check.start_period.as_ref().unwrap(), "5s");
|
||||||
|
}
|
||||||
|
}
|
97
src/virt/nerdctl/container_types.rs
Normal file
97
src/virt/nerdctl/container_types.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_types.rs
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Container struct for nerdctl operations
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Container {
|
||||||
|
/// Name of the container
|
||||||
|
pub name: String,
|
||||||
|
/// Container ID
|
||||||
|
pub container_id: Option<String>,
|
||||||
|
/// Base image (if created from an image)
|
||||||
|
pub image: Option<String>,
|
||||||
|
/// Configuration options
|
||||||
|
pub config: HashMap<String, String>,
|
||||||
|
/// Port mappings
|
||||||
|
pub ports: Vec<String>,
|
||||||
|
/// Volume mounts
|
||||||
|
pub volumes: Vec<String>,
|
||||||
|
/// Environment variables
|
||||||
|
pub env_vars: HashMap<String, String>,
|
||||||
|
/// Network to connect to
|
||||||
|
pub network: Option<String>,
|
||||||
|
/// Network aliases
|
||||||
|
pub network_aliases: Vec<String>,
|
||||||
|
/// CPU limit
|
||||||
|
pub cpu_limit: Option<String>,
|
||||||
|
/// Memory limit
|
||||||
|
pub memory_limit: Option<String>,
|
||||||
|
/// Memory swap limit
|
||||||
|
pub memory_swap_limit: Option<String>,
|
||||||
|
/// CPU shares
|
||||||
|
pub cpu_shares: Option<String>,
|
||||||
|
/// Restart policy
|
||||||
|
pub restart_policy: Option<String>,
|
||||||
|
/// Health check
|
||||||
|
pub health_check: Option<HealthCheck>,
|
||||||
|
/// Whether to run in detached mode
|
||||||
|
pub detach: bool,
|
||||||
|
/// Snapshotter to use
|
||||||
|
pub snapshotter: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Health check configuration for a container
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HealthCheck {
|
||||||
|
/// Command to run for health check
|
||||||
|
pub cmd: String,
|
||||||
|
/// Time between running the check (default: 30s)
|
||||||
|
pub interval: Option<String>,
|
||||||
|
/// Maximum time to wait for a check to complete (default: 30s)
|
||||||
|
pub timeout: Option<String>,
|
||||||
|
/// Number of consecutive failures needed to consider unhealthy (default: 3)
|
||||||
|
pub retries: Option<u32>,
|
||||||
|
/// Start period for the container to initialize before counting retries (default: 0s)
|
||||||
|
pub start_period: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Container status information
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ContainerStatus {
|
||||||
|
/// Container state (e.g., running, stopped)
|
||||||
|
pub state: String,
|
||||||
|
/// Container status
|
||||||
|
pub status: String,
|
||||||
|
/// Creation time
|
||||||
|
pub created: String,
|
||||||
|
/// Start time
|
||||||
|
pub started: String,
|
||||||
|
/// Health status (if health check is configured)
|
||||||
|
pub health_status: Option<String>,
|
||||||
|
/// Health check output (if health check is configured)
|
||||||
|
pub health_output: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Container resource usage information
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResourceUsage {
|
||||||
|
/// CPU usage percentage
|
||||||
|
pub cpu_usage: String,
|
||||||
|
/// Memory usage
|
||||||
|
pub memory_usage: String,
|
||||||
|
/// Memory limit
|
||||||
|
pub memory_limit: String,
|
||||||
|
/// Memory usage percentage
|
||||||
|
pub memory_percentage: String,
|
||||||
|
/// Network input
|
||||||
|
pub network_input: String,
|
||||||
|
/// Network output
|
||||||
|
pub network_output: String,
|
||||||
|
/// Block input
|
||||||
|
pub block_input: String,
|
||||||
|
/// Block output
|
||||||
|
pub block_output: String,
|
||||||
|
/// PIDs
|
||||||
|
pub pids: String,
|
||||||
|
}
|
@ -1,105 +0,0 @@
|
|||||||
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/containers.rs
|
|
||||||
|
|
||||||
use crate::virt::nerdctl::execute_nerdctl_command;
|
|
||||||
use crate::process::CommandResult;
|
|
||||||
use super::NerdctlError;
|
|
||||||
|
|
||||||
/// Run a container from an image
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `image` - The image to run
|
|
||||||
/// * `name` - Optional container name
|
|
||||||
/// * `detach` - Whether to run in detached mode
|
|
||||||
/// * `ports` - Optional port mappings (e.g., ["8080:80"])
|
|
||||||
/// * `snapshotter` - Optional snapshotter to use (e.g., "native", "fuse-overlayfs")
|
|
||||||
pub fn run(image: &str, name: Option<&str>, detach: bool, ports: Option<&[&str]>, snapshotter: Option<&str>) -> Result<CommandResult, NerdctlError> {
|
|
||||||
// First, try to remove any existing container with the same name to avoid conflicts
|
|
||||||
if let Some(name_str) = name {
|
|
||||||
// Ignore errors since the container might not exist
|
|
||||||
let _ = execute_nerdctl_command(&["rm", "-f", name_str]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args = vec!["run"];
|
|
||||||
|
|
||||||
if detach {
|
|
||||||
args.push("-d");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name_str) = name {
|
|
||||||
args.push("--name");
|
|
||||||
args.push(name_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(port_mappings) = ports {
|
|
||||||
for port in port_mappings {
|
|
||||||
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` - The container ID or name
|
|
||||||
/// * [command](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:303:0-324:1) - The command to run
|
|
||||||
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](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:55:4-64:5) - Source path (can be container:path or local path)
|
|
||||||
/// * `dest` - Destination path (can be container:path or local path)
|
|
||||||
pub fn copy(source: &str, dest: &str) -> Result<CommandResult, NerdctlError> {
|
|
||||||
execute_nerdctl_command(&["cp", source, dest])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop a container
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `container` - The container ID or name
|
|
||||||
pub fn stop(container: &str) -> Result<CommandResult, NerdctlError> {
|
|
||||||
execute_nerdctl_command(&["stop", container])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a container
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `container` - The container ID or name
|
|
||||||
pub fn remove(container: &str) -> Result<CommandResult, NerdctlError> {
|
|
||||||
execute_nerdctl_command(&["rm", container])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List containers
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `all` - Whether to show all containers (including stopped ones)
|
|
||||||
pub fn list(all: bool) -> Result<CommandResult, NerdctlError> {
|
|
||||||
let mut args = vec!["ps"];
|
|
||||||
|
|
||||||
if all {
|
|
||||||
args.push("-a");
|
|
||||||
}
|
|
||||||
|
|
||||||
execute_nerdctl_command(&args)
|
|
||||||
}
|
|
40
src/virt/nerdctl/health_check.rs
Normal file
40
src/virt/nerdctl/health_check.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/health_check.rs
|
||||||
|
|
||||||
|
use super::container_types::HealthCheck;
|
||||||
|
|
||||||
|
impl HealthCheck {
|
||||||
|
/// Create a new health check with the given command
|
||||||
|
pub fn new(cmd: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
cmd: cmd.to_string(),
|
||||||
|
interval: None,
|
||||||
|
timeout: None,
|
||||||
|
retries: None,
|
||||||
|
start_period: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the interval between health checks
|
||||||
|
pub fn with_interval(mut self, interval: &str) -> Self {
|
||||||
|
self.interval = Some(interval.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the timeout for health checks
|
||||||
|
pub fn with_timeout(mut self, timeout: &str) -> Self {
|
||||||
|
self.timeout = Some(timeout.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the number of retries for health checks
|
||||||
|
pub fn with_retries(mut self, retries: u32) -> Self {
|
||||||
|
self.retries = Some(retries);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the start period for health checks
|
||||||
|
pub fn with_start_period(mut self, start_period: &str) -> Self {
|
||||||
|
self.start_period = Some(start_period.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,12 @@
|
|||||||
mod containers;
|
|
||||||
mod images;
|
mod images;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
mod container_types;
|
||||||
|
mod container;
|
||||||
|
mod container_builder;
|
||||||
|
mod health_check;
|
||||||
|
mod container_operations;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod container_test;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@ -42,6 +48,6 @@ impl Error for NerdctlError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use containers::*;
|
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
pub use cmd::*;
|
pub use cmd::*;
|
||||||
|
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
|
Loading…
Reference in New Issue
Block a user