lmrc-docker 0.3.16

Docker client library for the LMRC Stack - ergonomic fluent APIs for containers, images, networks, volumes, and registry management
Documentation
//! Docker client configuration and connection management.

use crate::error::{DockerError, Result};
use bollard::Docker;
use std::path::PathBuf;
use tracing::{debug, info};

/// Configuration for connecting to Docker daemon.
#[derive(Debug, Clone)]
pub struct DockerClientConfig {
    /// Connection URI (unix:///var/run/docker.sock, tcp://host:port, etc.)
    pub uri: Option<String>,
    /// Connection timeout in seconds
    pub timeout: u64,
    /// API version to use
    pub api_version: Option<String>,
}

impl Default for DockerClientConfig {
    fn default() -> Self {
        Self {
            uri: None,
            timeout: 120,
            api_version: None,
        }
    }
}

impl DockerClientConfig {
    /// Create a new configuration with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the connection URI.
    pub fn uri(mut self, uri: impl Into<String>) -> Self {
        self.uri = Some(uri.into());
        self
    }

    /// Set the connection timeout in seconds.
    pub fn timeout(mut self, timeout: u64) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set the Docker API version.
    pub fn api_version(mut self, version: impl Into<String>) -> Self {
        self.api_version = Some(version.into());
        self
    }
}

/// Main Docker client for managing containers, images, networks, and volumes.
pub struct DockerClient {
    /// Underlying Bollard Docker client
    pub(crate) docker: Docker,
    /// Client configuration
    #[allow(dead_code)]
    pub(crate) config: DockerClientConfig,
}

impl DockerClient {
    /// Create a new Docker client with default configuration.
    ///
    /// This respects the `DOCKER_HOST` environment variable.
    ///
    /// # Errors
    ///
    /// Returns an error if unable to connect to the Docker daemon.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     Ok(())
    /// }
    /// ```
    pub fn new() -> Result<Self> {
        Self::with_config(DockerClientConfig::default())
    }

    /// Create a new Docker client with custom configuration.
    ///
    /// # Errors
    ///
    /// Returns an error if unable to connect to the Docker daemon.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::{DockerClient, DockerClientConfig};
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let config = DockerClientConfig::new()
    ///         .timeout(60)
    ///         .api_version("1.43");
    ///     let client = DockerClient::with_config(config)?;
    ///     Ok(())
    /// }
    /// ```
    pub fn with_config(config: DockerClientConfig) -> Result<Self> {
        debug!("Creating Docker client with config: {:?}", config);
        debug!("DOCKER_HOST = {:?}", std::env::var("DOCKER_HOST").ok());

        let docker =
            Docker::connect_with_defaults().map_err(|e| DockerError::Connection(e.to_string()))?;

        Ok(Self { docker, config })
    }

    /// Connect to Docker daemon using Unix socket.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::connect_with_unix("/var/run/docker.sock")?;
    ///     Ok(())
    /// }
    /// ```
    pub fn connect_with_unix(path: impl Into<PathBuf>) -> Result<Self> {
        let path = path.into();
        let config = DockerClientConfig::new().uri(format!("unix://{}", path.display()));
        Self::with_config(config)
    }

    /// Connect to Docker daemon using TCP.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::connect_with_tcp("localhost:2375")?;
    ///     Ok(())
    /// }
    /// ```
    pub fn connect_with_tcp(addr: impl Into<String>) -> Result<Self> {
        let config = DockerClientConfig::new().uri(format!("tcp://{}", addr.into()));
        Self::with_config(config)
    }

    /// Check if Docker daemon is accessible and get version info.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     let version = client.version().await?;
    ///     println!("Docker version: {}", version);
    ///     Ok(())
    /// }
    /// ```
    pub async fn version(&self) -> Result<String> {
        info!("Getting Docker version...");
        let version =
            self.docker.version().await.map_err(|e| {
                DockerError::Connection(format!("Failed to get Docker version: {}", e))
            })?;

        let version_string = version.version.unwrap_or_else(|| "unknown".to_string());
        info!("Docker version: {}", version_string);
        Ok(version_string)
    }

    /// Ping the Docker daemon to check connectivity.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     client.ping().await?;
    ///     println!("Docker daemon is accessible");
    ///     Ok(())
    /// }
    /// ```
    pub async fn ping(&self) -> Result<()> {
        self.docker
            .ping()
            .await
            .map_err(|e| DockerError::Connection(format!("Failed to ping Docker daemon: {}", e)))?;
        Ok(())
    }

    /// Get system information from Docker daemon.
    pub async fn info(&self) -> Result<bollard::models::SystemInfo> {
        self.docker
            .info()
            .await
            .map_err(|e| DockerError::Connection(format!("Failed to get Docker info: {}", e)))
    }

    /// Access the underlying Bollard Docker client for advanced operations.
    pub fn inner(&self) -> &Docker {
        &self.docker
    }

    /// Access registry operations.
    pub fn registry(&self) -> crate::registry::Registry<'_> {
        crate::registry::Registry::new(self)
    }
}

impl Default for DockerClient {
    fn default() -> Self {
        Self::new().expect("Failed to create default Docker client")
    }
}