From 06ab5bcc51e1ab134edd55b9aed2657c17960590 Mon Sep 17 00:00:00 2001 From: Muhammad Mominul Huque Date: Fri, 16 Aug 2024 23:11:59 +0600 Subject: [PATCH 1/5] Fix the socket not found error on macOS --- testcontainers/src/core/client.rs | 2 +- testcontainers/src/core/client/bollard_client.rs | 4 ++-- testcontainers/src/core/env/config.rs | 16 ++++++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/testcontainers/src/core/client.rs b/testcontainers/src/core/client.rs index 4efc2ce4..be1c3599 100644 --- a/testcontainers/src/core/client.rs +++ b/testcontainers/src/core/client.rs @@ -312,7 +312,7 @@ impl Client { } pub(crate) async fn docker_hostname(&self) -> Result { - let docker_host = self.config.docker_host(); + let docker_host = &self.config.docker_host(); let docker_host_url = Url::from_str(docker_host) .map_err(|e| ConfigurationError::InvalidDockerHost(e.to_string()))?; diff --git a/testcontainers/src/core/client/bollard_client.rs b/testcontainers/src/core/client/bollard_client.rs index e185c4cc..0362544d 100644 --- a/testcontainers/src/core/client/bollard_client.rs +++ b/testcontainers/src/core/client/bollard_client.rs @@ -8,7 +8,7 @@ use crate::core::env; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2 * 60); pub(super) fn init(config: &env::Config) -> Result { - let host = config.docker_host(); + let host = &config.docker_host(); let host_url = Url::from_str(host)?; match host_url.scheme() { @@ -36,7 +36,7 @@ fn connect_with_ssl(config: &env::Config) -> Result &str { + pub(crate) fn docker_host(&self) -> Cow<'_, str> { self.tc_host .as_deref() .or(self.host.as_deref()) - .unwrap_or(DEFAULT_DOCKER_HOST) + .map(Cow::Borrowed) + .unwrap_or_else(|| { + // Docker Desktop on macOS creates the socket in the user's home directory + // from version 4.13.0. + // https://github.com/docker/for-mac/issues/6529 + if cfg!(target_os = "macos") { + format!("unix://{}/.docker/run/docker.sock", var("HOME").unwrap()).into() + } else { + DEFAULT_DOCKER_HOST.into() + } + }) } pub(crate) fn tls_verify(&self) -> bool { From 4e1b8cae1a9491f64db9fb1ddc4f83dd918381ee Mon Sep 17 00:00:00 2001 From: Muhammad Mominul Huque Date: Fri, 30 Aug 2024 21:39:38 +0600 Subject: [PATCH 2/5] Try rootless Docker socket paths before defaulting to the root docker socket path in unix platforms --- testcontainers/src/core/env/config.rs | 60 +++++++++++++++++++++++---- testcontainers/src/lib.rs | 8 +++- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/testcontainers/src/core/env/config.rs b/testcontainers/src/core/env/config.rs index c7a75ac3..08cf007b 100644 --- a/testcontainers/src/core/env/config.rs +++ b/testcontainers/src/core/env/config.rs @@ -2,7 +2,8 @@ use std::{ borrow::Cow, env::var, path::{Path, PathBuf}, - str::FromStr, + process::Command as StdCommand, + str::{from_utf8, FromStr}, }; use crate::core::env::GetEnvValue; @@ -127,18 +128,54 @@ impl Config { /// 1. Docker host from the `tc.host` property in the `~/.testcontainers.properties` file. /// 2. `DOCKER_HOST` environment variable. /// 3. Docker host from the `docker.host` property in the `~/.testcontainers.properties` file. - /// 4. Else, the default Docker socket will be returned. + /// 4. Read the default Docker socket path, without the unix schema. E.g. `/var/run/docker.sock`. + /// 5. Read the rootless Docker socket path, checking in the following alternative locations: + /// 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. + /// 2. `${HOME}/.docker/run/docker.sock`. + /// 3. `${HOME}/.docker/desktop/docker.sock`. + /// 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user. + /// 6. The default Docker socket including schema will be returned if none of the above are set. pub(crate) fn docker_host(&self) -> Cow<'_, str> { self.tc_host .as_deref() .or(self.host.as_deref()) .map(Cow::Borrowed) .unwrap_or_else(|| { - // Docker Desktop on macOS creates the socket in the user's home directory - // from version 4.13.0. - // https://github.com/docker/for-mac/issues/6529 - if cfg!(target_os = "macos") { - format!("unix://{}/.docker/run/docker.sock", var("HOME").unwrap()).into() + if cfg!(unix) { + check_path_exists("/var/run/docker.sock".into()) + .or_else(|| { + var("XDG_RUNTIME_DIR").ok().and_then(|dir| { + check_path_exists(format!("{dir}/.docker/run/docker.sock",)) + }) + }) + .or_else(|| { + check_path_exists(format!( + "{}/.docker/run/docker.sock", + var("HOME").unwrap() + )) + }) + .or_else(|| { + check_path_exists(format!( + "{}/.docker/desktop/docker.sock", + var("HOME").unwrap() + )) + }) + .or_else(|| { + // Get the user ID of the current user by running `id -u` + StdCommand::new("id") + .arg("-u") + .output() + .ok() + .and_then(|output| { + from_utf8(&output.stdout).map(|s| s.trim().to_string()).ok() + }) + .and_then(|uid| { + check_path_exists(format!("/run/user/${uid}/docker.sock")) + }) + }) + .map(|p| format!("unix://{p}")) + .map(Cow::Owned) + .unwrap_or(DEFAULT_DOCKER_HOST.into()) } else { DEFAULT_DOCKER_HOST.into() } @@ -162,6 +199,15 @@ impl Config { } } +/// Checks if the path exists and returns it if it does. +fn check_path_exists(path: String) -> Option { + if Path::new(&path).exists() { + Some(path) + } else { + None + } +} + /// Read the Docker authentication configuration in the following order: /// /// 1. `DOCKER_AUTH_CONFIG` environment variable, unmarshalling the string value from its JSON representation and using it as the Docker config. diff --git a/testcontainers/src/lib.rs b/testcontainers/src/lib.rs index bdefce47..eae57a49 100644 --- a/testcontainers/src/lib.rs +++ b/testcontainers/src/lib.rs @@ -40,7 +40,13 @@ //! 1. Docker host from the `tc.host` property in the `~/.testcontainers.properties` file. //! 2. `DOCKER_HOST` environment variable. //! 3. Docker host from the "docker.host" property in the `~/.testcontainers.properties` file. -//! 4. Else, the default Docker socket will be returned. +//! 4. Read the default Docker socket path, without the unix schema. E.g. `/var/run/docker.sock`. +//! 5. Read the rootless Docker socket path, checking in the following alternative locations: +//! 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. +//! 2. `${HOME}/.docker/run/docker.sock`. +//! 3. `${HOME}/.docker/desktop/docker.sock`. +//! 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user. +//! 6. The default Docker socket including schema will be returned if none of the above are set. //! //! ### Docker authentication //! From 2d6953c9daea8fa9ed277504e8aefac477cac4df Mon Sep 17 00:00:00 2001 From: Muhammad Mominul Huque Date: Sat, 31 Aug 2024 00:21:50 +0600 Subject: [PATCH 3/5] Use dirs crate to retrieve system paths Remove the user id dependent fallback socket path --- testcontainers/src/core/env/config.rs | 46 +++++++++++---------------- testcontainers/src/lib.rs | 1 - 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/testcontainers/src/core/env/config.rs b/testcontainers/src/core/env/config.rs index 08cf007b..7f552a4f 100644 --- a/testcontainers/src/core/env/config.rs +++ b/testcontainers/src/core/env/config.rs @@ -1,9 +1,8 @@ +use dirs::{home_dir, runtime_dir}; use std::{ borrow::Cow, - env::var, path::{Path, PathBuf}, - process::Command as StdCommand, - str::{from_utf8, FromStr}, + str::FromStr, }; use crate::core::env::GetEnvValue; @@ -133,7 +132,6 @@ impl Config { /// 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. /// 2. `${HOME}/.docker/run/docker.sock`. /// 3. `${HOME}/.docker/desktop/docker.sock`. - /// 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user. /// 6. The default Docker socket including schema will be returned if none of the above are set. pub(crate) fn docker_host(&self) -> Cow<'_, str> { self.tc_host @@ -144,34 +142,28 @@ impl Config { if cfg!(unix) { check_path_exists("/var/run/docker.sock".into()) .or_else(|| { - var("XDG_RUNTIME_DIR").ok().and_then(|dir| { - check_path_exists(format!("{dir}/.docker/run/docker.sock",)) + runtime_dir().and_then(|dir| { + check_path_exists(format!( + "{}/.docker/run/docker.sock", + dir.display() + )) }) }) .or_else(|| { - check_path_exists(format!( - "{}/.docker/run/docker.sock", - var("HOME").unwrap() - )) - }) - .or_else(|| { - check_path_exists(format!( - "{}/.docker/desktop/docker.sock", - var("HOME").unwrap() - )) + home_dir().and_then(|dir| { + check_path_exists(format!( + "{}/.docker/run/docker.sock", + dir.display() + )) + }) }) .or_else(|| { - // Get the user ID of the current user by running `id -u` - StdCommand::new("id") - .arg("-u") - .output() - .ok() - .and_then(|output| { - from_utf8(&output.stdout).map(|s| s.trim().to_string()).ok() - }) - .and_then(|uid| { - check_path_exists(format!("/run/user/${uid}/docker.sock")) - }) + home_dir().and_then(|dir| { + check_path_exists(format!( + "{}/.docker/desktop/docker.sock", + dir.display() + )) + }) }) .map(|p| format!("unix://{p}")) .map(Cow::Owned) diff --git a/testcontainers/src/lib.rs b/testcontainers/src/lib.rs index eae57a49..76baa54f 100644 --- a/testcontainers/src/lib.rs +++ b/testcontainers/src/lib.rs @@ -45,7 +45,6 @@ //! 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. //! 2. `${HOME}/.docker/run/docker.sock`. //! 3. `${HOME}/.docker/desktop/docker.sock`. -//! 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user. //! 6. The default Docker socket including schema will be returned if none of the above are set. //! //! ### Docker authentication From 34b31a1c62e575d3e29a3c9214e030076d3bcaad Mon Sep 17 00:00:00 2001 From: Muhammad Mominul Huque Date: Sat, 31 Aug 2024 00:47:49 +0600 Subject: [PATCH 4/5] Rename the function --- testcontainers/src/core/env/config.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/testcontainers/src/core/env/config.rs b/testcontainers/src/core/env/config.rs index 7f552a4f..86751b93 100644 --- a/testcontainers/src/core/env/config.rs +++ b/testcontainers/src/core/env/config.rs @@ -140,26 +140,20 @@ impl Config { .map(Cow::Borrowed) .unwrap_or_else(|| { if cfg!(unix) { - check_path_exists("/var/run/docker.sock".into()) + validate_path("/var/run/docker.sock".into()) .or_else(|| { runtime_dir().and_then(|dir| { - check_path_exists(format!( - "{}/.docker/run/docker.sock", - dir.display() - )) + validate_path(format!("{}/.docker/run/docker.sock", dir.display())) }) }) .or_else(|| { home_dir().and_then(|dir| { - check_path_exists(format!( - "{}/.docker/run/docker.sock", - dir.display() - )) + validate_path(format!("{}/.docker/run/docker.sock", dir.display())) }) }) .or_else(|| { home_dir().and_then(|dir| { - check_path_exists(format!( + validate_path(format!( "{}/.docker/desktop/docker.sock", dir.display() )) @@ -191,8 +185,8 @@ impl Config { } } -/// Checks if the path exists and returns it if it does. -fn check_path_exists(path: String) -> Option { +/// Validate the path exists and return it if it does. +fn validate_path(path: String) -> Option { if Path::new(&path).exists() { Some(path) } else { From 41bb721fb56bdadfd3149908294c7101db53109f Mon Sep 17 00:00:00 2001 From: Artem Medvedev Date: Fri, 30 Aug 2024 20:55:03 +0200 Subject: [PATCH 5/5] style: apply fmt changes --- testcontainers/src/core/env/config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcontainers/src/core/env/config.rs b/testcontainers/src/core/env/config.rs index 86751b93..488b348e 100644 --- a/testcontainers/src/core/env/config.rs +++ b/testcontainers/src/core/env/config.rs @@ -1,10 +1,11 @@ -use dirs::{home_dir, runtime_dir}; use std::{ borrow::Cow, path::{Path, PathBuf}, str::FromStr, }; +use dirs::{home_dir, runtime_dir}; + use crate::core::env::GetEnvValue; #[derive(Debug, thiserror::Error)]