/// # 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> { 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 { 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, Box> { 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, Box> { 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, Box> { 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::) .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> { 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> { 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> { 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 } }