448 lines
18 KiB
Rust
448 lines
18 KiB
Rust
// 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 and verify it's running
|
|
/// If the container hasn't been created yet, it will be created automatically.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error with detailed information
|
|
pub fn start(&self) -> Result<CommandResult, NerdctlError> {
|
|
// If container_id is None, we need to create the container first
|
|
let container = if self.container_id.is_none() {
|
|
// Check if we have an image specified
|
|
if self.image.is_none() {
|
|
return Err(NerdctlError::Other("No image specified for container creation".to_string()));
|
|
}
|
|
|
|
// Clone self and create the container
|
|
println!("Container not created yet. Creating container from image...");
|
|
|
|
// First, try to pull the image if it doesn't exist locally
|
|
let image = self.image.as_ref().unwrap();
|
|
match execute_nerdctl_command(&["image", "inspect", image]) {
|
|
Err(_) => {
|
|
println!("Image '{}' not found locally. Pulling image...", image);
|
|
if let Err(e) = execute_nerdctl_command(&["pull", image]) {
|
|
return Err(NerdctlError::CommandFailed(
|
|
format!("Failed to pull image '{}': {}", image, e)
|
|
));
|
|
}
|
|
println!("Image '{}' pulled successfully.", image);
|
|
},
|
|
Ok(_) => {
|
|
println!("Image '{}' found locally.", image);
|
|
}
|
|
}
|
|
|
|
// Now create the container
|
|
match self.clone().build() {
|
|
Ok(built) => built,
|
|
Err(e) => {
|
|
return Err(NerdctlError::CommandFailed(
|
|
format!("Failed to create container from image '{}': {}", image, e)
|
|
));
|
|
}
|
|
}
|
|
} else {
|
|
// Container already has an ID, use it as is
|
|
self.clone()
|
|
};
|
|
|
|
if let Some(container_id) = &container.container_id {
|
|
// First, try to start the container
|
|
let start_result = execute_nerdctl_command(&["start", container_id]);
|
|
|
|
// If the start command failed, return the error with details
|
|
if let Err(err) = &start_result {
|
|
return Err(NerdctlError::CommandFailed(
|
|
format!("Failed to start container {}: {}", container_id, err)
|
|
));
|
|
}
|
|
|
|
// Verify the container is actually running
|
|
match container.verify_running() {
|
|
Ok(true) => start_result,
|
|
Ok(false) => {
|
|
// Container started but isn't running - get detailed information
|
|
let mut error_message = format!("Container {} started but is not running.", container_id);
|
|
|
|
// Get container status
|
|
if let Ok(status) = container.status() {
|
|
error_message.push_str(&format!("\nStatus: {}, State: {}, Health: {}",
|
|
status.status,
|
|
status.state,
|
|
status.health_status.unwrap_or_else(|| "N/A".to_string())
|
|
));
|
|
}
|
|
|
|
// Get container logs
|
|
if let Ok(logs) = execute_nerdctl_command(&["logs", container_id]) {
|
|
if !logs.stdout.trim().is_empty() {
|
|
error_message.push_str(&format!("\nContainer logs (stdout):\n{}", logs.stdout.trim()));
|
|
}
|
|
if !logs.stderr.trim().is_empty() {
|
|
error_message.push_str(&format!("\nContainer logs (stderr):\n{}", logs.stderr.trim()));
|
|
}
|
|
}
|
|
|
|
// Get container exit code if available
|
|
if let Ok(inspect_result) = execute_nerdctl_command(&["inspect", "--format", "{{.State.ExitCode}}", container_id]) {
|
|
let exit_code = inspect_result.stdout.trim();
|
|
if !exit_code.is_empty() && exit_code != "0" {
|
|
error_message.push_str(&format!("\nContainer exit code: {}", exit_code));
|
|
}
|
|
}
|
|
|
|
Err(NerdctlError::CommandFailed(error_message))
|
|
},
|
|
Err(err) => {
|
|
// Failed to verify if container is running
|
|
Err(NerdctlError::CommandFailed(
|
|
format!("Container {} may have started, but verification failed: {}",
|
|
container_id, err
|
|
)
|
|
))
|
|
}
|
|
}
|
|
} else {
|
|
Err(NerdctlError::Other("Failed to create container. No container ID available.".to_string()))
|
|
}
|
|
}
|
|
|
|
/// Verify if the container is running
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Result<bool, NerdctlError>` - True if running, false if not running, error if verification failed
|
|
fn verify_running(&self) -> Result<bool, NerdctlError> {
|
|
if let Some(container_id) = &self.container_id {
|
|
// Use inspect to check if the container is running
|
|
let inspect_result = execute_nerdctl_command(&["inspect", "--format", "{{.State.Running}}", container_id]);
|
|
|
|
match inspect_result {
|
|
Ok(result) => {
|
|
let running = result.stdout.trim().to_lowercase() == "true";
|
|
Ok(running)
|
|
},
|
|
Err(err) => Err(err)
|
|
}
|
|
} else {
|
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
|
}
|
|
}
|
|
|
|
/// 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 logs
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Result<CommandResult, NerdctlError>` - Command result or error
|
|
pub fn logs(&self) -> Result<CommandResult, NerdctlError> {
|
|
if let Some(container_id) = &self.container_id {
|
|
execute_nerdctl_command(&["logs", container_id])
|
|
} else {
|
|
Err(NerdctlError::Other("No container ID available".to_string()))
|
|
}
|
|
}
|
|
|
|
/// Get container resource usage
|
|
///
|
|
/// # 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()))
|
|
}
|
|
}
|
|
} |