use std::{
ops::Deref,
path::Path,
sync::{Arc, OnceLock, Weak},
};
use dirs::{home_dir, runtime_dir};
use tokio::sync::Mutex;
use crate::ClientError;
static DOCKER_CLIENT: OnceLock<Mutex<Weak<Client>>> = OnceLock::new();
const DEFAULT_DOCKER_SOCKET: &str = "/var/run/docker.sock";
#[derive(Debug)]
pub struct Client {
bollard_client: bollard::Docker,
pub socket_path: Option<String>,
}
impl Client {
pub(crate) fn new() -> Result<Self, ClientError> {
let socket_path = get_socket_path();
let bollard_client = if let Some(socket_path) = &socket_path {
bollard::Docker::connect_with_socket(socket_path, 600, bollard::API_DEFAULT_VERSION)
.map_err(ClientError::Init)?
} else {
bollard::Docker::connect_with_defaults().map_err(ClientError::Init)?
};
Ok(Self {
bollard_client,
socket_path,
})
}
pub(crate) async fn lazy_client() -> Result<Arc<Client>, ClientError> {
let mut guard = DOCKER_CLIENT
.get_or_init(|| Mutex::new(Weak::new()))
.lock()
.await;
let maybe_client = guard.upgrade();
if let Some(client) = maybe_client {
Ok(client)
} else {
let client = Arc::new(Client::new()?);
*guard = Arc::downgrade(&client);
Ok(client)
}
}
}
impl Deref for Client {
type Target = bollard::Docker;
fn deref(&self) -> &Self::Target {
&self.bollard_client
}
}
fn get_socket_path() -> Option<String> {
validate_path(DEFAULT_DOCKER_SOCKET.into())
.or_else(|| {
runtime_dir()
.and_then(|dir| validate_path(format!("{}/.docker/run/docker.sock", dir.display())))
})
.or_else(|| {
home_dir()
.and_then(|dir| validate_path(format!("{}/.docker/run/docker.sock", dir.display())))
})
.or_else(|| {
home_dir().and_then(|dir| {
validate_path(format!("{}/.docker/desktop/docker.sock", dir.display()))
})
})
}
fn validate_path(path: String) -> Option<String> {
if Path::new(&path).exists() {
Some(path)
} else {
None
}
}