...
This commit is contained in:
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 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::error::Error;
|
||||
@@ -42,6 +48,6 @@ impl Error for NerdctlError {
|
||||
}
|
||||
}
|
||||
|
||||
pub use containers::*;
|
||||
pub use images::*;
|
||||
pub use cmd::*;
|
||||
pub use cmd::*;
|
||||
pub use container_types::{Container, HealthCheck, ContainerStatus, ResourceUsage};
|
Reference in New Issue
Block a user