All checks were successful
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m2s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m18s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m25s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m0s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
204 lines
7.4 KiB
Rust
204 lines
7.4 KiB
Rust
/// # Docker Module
|
|
///
|
|
/// This module provides Docker integration for WatcherAgent, including container enumeration, statistics, and lifecycle management.
|
|
///
|
|
/// ## Responsibilities
|
|
/// - **Container Management:** Lists, inspects, and manages Docker containers relevant to the agent.
|
|
/// - **Statistics Aggregation:** Collects network and CPU statistics for all managed containers.
|
|
/// - **Lifecycle Operations:** Supports container restart and ID lookup for agent self-management.
|
|
///
|
|
pub mod container;
|
|
pub mod serverclientcomm;
|
|
pub mod stats;
|
|
|
|
use crate::models::{DockerContainerDto, DockerContainerMetricDto};
|
|
use bollard::{query_parameters::InspectContainerOptions, Docker};
|
|
use std::error::Error;
|
|
|
|
/// Main Docker manager that holds the Docker client and provides all operations
|
|
#[derive(Debug, Clone)]
|
|
pub struct DockerManager {
|
|
pub docker: Docker,
|
|
}
|
|
|
|
impl Default for DockerManager {
|
|
fn default() -> Self {
|
|
Self {
|
|
docker: Docker::connect_with_local_defaults()
|
|
.unwrap_or_else(|e| panic!("Failed to create default Docker connection: {}", e)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DockerManager {
|
|
/// Creates a new DockerManager instance
|
|
pub fn new() -> Result<Self, Box<dyn Error + Send + Sync>> {
|
|
let docker = Docker::connect_with_local_defaults()
|
|
.map_err(|e| format!("Failed to connect to Docker: {}", e))?;
|
|
|
|
Ok(Self { docker })
|
|
}
|
|
|
|
/// Creates a DockerManager instance with optional Docker connection
|
|
pub fn new_optional() -> Option<Self> {
|
|
Docker::connect_with_local_defaults()
|
|
.map(|docker| Self { docker })
|
|
.ok()
|
|
}
|
|
|
|
/// Finds the Docker container running the agent by image name
|
|
pub async fn get_client_container(
|
|
&self,
|
|
) -> Result<Option<DockerContainerDto>, Box<dyn Error + Send + Sync>> {
|
|
let containers = container::get_available_containers(&self.docker).await;
|
|
let client_image = "watcher-agent";
|
|
|
|
Ok(containers
|
|
.into_iter()
|
|
.find(|c| c.image == client_image)
|
|
.map(|container| DockerContainerDto {
|
|
id: container.id,
|
|
image: container.image,
|
|
name: container.name,
|
|
}))
|
|
}
|
|
|
|
/// Gets the current client version (image name) if running in Docker
|
|
pub async fn get_client_version(&self) -> String {
|
|
match self.get_client_container().await {
|
|
Ok(Some(container)) => container.image,
|
|
Ok(None) => {
|
|
eprintln!("Warning: No WatcherAgent container found");
|
|
"unknown".to_string()
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: Could not get current image version: {}", e);
|
|
"unknown".to_string()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks if Docker is available and the agent is running in a container
|
|
pub async fn is_dockerized(&self) -> bool {
|
|
self.get_client_container()
|
|
.await
|
|
.map(|c| c.is_some())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Gets all available containers as DTOs for registration
|
|
pub async fn get_containers_for_registration(
|
|
&self,
|
|
) -> Result<Vec<DockerContainerDto>, Box<dyn Error + Send + Sync>> {
|
|
let containers = container::get_available_containers(&self.docker).await;
|
|
|
|
Ok(containers
|
|
.into_iter()
|
|
.map(|container| DockerContainerDto {
|
|
id: container.id,
|
|
image: container.image,
|
|
name: container.name,
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
/// Gets container metrics for all containers
|
|
pub async fn get_container_metrics(
|
|
&self,
|
|
) -> Result<Vec<DockerContainerMetricDto>, Box<dyn Error + Send + Sync>> {
|
|
let containers = container::get_available_containers(&self.docker).await;
|
|
let mut metrics = Vec::new();
|
|
|
|
for container in containers {
|
|
// Get network stats (you'll need to implement this in container.rs)
|
|
let network_stats = container::get_network_stats(&self.docker, &container.id).await?;
|
|
// Get CPU stats (you'll need to implement this in container.rs)
|
|
let cpu_stats = container::get_cpu_stats(&self.docker, &container.id).await?;
|
|
|
|
// Get current status by inspecting the container
|
|
let status = match self
|
|
.docker
|
|
.inspect_container(&container.id, None::<InspectContainerOptions>)
|
|
.await
|
|
{
|
|
Ok(container_info) => {
|
|
// Extract status from container state and convert to string
|
|
container_info
|
|
.state
|
|
.and_then(|state| state.status)
|
|
.map(|status_enum| {
|
|
match status_enum {
|
|
bollard::models::ContainerStateStatusEnum::CREATED => "created",
|
|
bollard::models::ContainerStateStatusEnum::RUNNING => "running",
|
|
bollard::models::ContainerStateStatusEnum::PAUSED => "paused",
|
|
bollard::models::ContainerStateStatusEnum::RESTARTING => {
|
|
"restarting"
|
|
}
|
|
bollard::models::ContainerStateStatusEnum::REMOVING => "removing",
|
|
bollard::models::ContainerStateStatusEnum::EXITED => "exited",
|
|
bollard::models::ContainerStateStatusEnum::DEAD => "dead",
|
|
bollard::secret::ContainerStateStatusEnum::EMPTY => todo!(),
|
|
}
|
|
.to_string()
|
|
})
|
|
.unwrap_or_else(|| "unknown".to_string())
|
|
}
|
|
Err(_) => "unknown".to_string(),
|
|
};
|
|
|
|
metrics.push(DockerContainerMetricDto {
|
|
id: container.id,
|
|
status: status,
|
|
network: network_stats,
|
|
cpu: cpu_stats,
|
|
});
|
|
}
|
|
|
|
Ok(metrics)
|
|
}
|
|
|
|
/// Gets the number of running containers
|
|
pub async fn get_container_count(&self) -> Result<usize, Box<dyn Error + Send + Sync>> {
|
|
let containers = container::get_available_containers(&self.docker).await;
|
|
Ok(containers.len())
|
|
}
|
|
|
|
/// Restarts a specific container by ID
|
|
pub async fn restart_container(
|
|
&self,
|
|
container_id: &str,
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
container::restart_container(&self.docker, container_id).await
|
|
}
|
|
|
|
/// Gets total network statistics across all containers
|
|
pub async fn get_total_network_stats(
|
|
&self,
|
|
) -> Result<(u64, u64), Box<dyn Error + Send + Sync>> {
|
|
let metrics = self.get_container_metrics().await?;
|
|
|
|
let net_in_total: u64 = metrics.iter().map(|m| m.network.rx_bytes).sum();
|
|
let net_out_total: u64 = metrics.iter().map(|m| m.network.tx_bytes).sum();
|
|
|
|
Ok((net_in_total, net_out_total))
|
|
}
|
|
}
|
|
|
|
// Keep these as utility functions if needed, but they should use DockerManager internally
|
|
impl DockerContainerDto {
|
|
/// Returns the container ID
|
|
pub fn id(&self) -> &str {
|
|
&self.id
|
|
}
|
|
|
|
/// Returns the image name
|
|
pub fn image(&self) -> &str {
|
|
&self.image
|
|
}
|
|
|
|
/// Returns the container name
|
|
pub fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|