From 202ec4aea2fbaaba9eb180efccc9962071e84b8d Mon Sep 17 00:00:00 2001 From: Muhammad Mominul Huque Date: Sat, 31 Aug 2024 01:01:06 +0600 Subject: [PATCH] fix!: docker socket detection on unix (#721) Docker Desktop on macOS creates the socket in the user's home directory from version 4.13.0.[[1](https://github.com/docker/for-mac/issues/6529)][[2](https://github.com/testcontainers/testcontainers-java/issues/6165)] And the Docker team [has the intention to move away](https://github.com/docker/for-mac/issues/6529#issuecomment-1710091807) from the root-owned `/var/run/docker.sock` There is a option named `Allow the default Docket Socket to be used (requires password)` in the Docker Desktop, but the created symbolic link `/var/run/docker.sock` [doesn't persist](https://github.com/lando/lando/issues/3533#issuecomment-1464252377) between OS restart or OS upgrade. Without this patch or creating a symbolic link, macOS devs have to face the ```called `Result::unwrap()` on an `Err` value: Client(Init(SocketNotFoundError("/var/run/docker.sock")))``` error. I have used `Cow` to avoid the need of cloning the fields of `Config` struct. I am open to suggestion or other approaches. Thanks! --------- Co-authored-by: Artem Medvedev --- testcontainers/src/core/client.rs | 2 +- .../src/core/client/bollard_client.rs | 4 +- testcontainers/src/core/env/config.rs | 51 +++++++++++++++++-- testcontainers/src/lib.rs | 7 ++- 4 files changed, 57 insertions(+), 7 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 { + /// 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`. + /// 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()) - .unwrap_or(DEFAULT_DOCKER_HOST) + .map(Cow::Borrowed) + .unwrap_or_else(|| { + if cfg!(unix) { + validate_path("/var/run/docker.sock".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() + )) + }) + }) + .map(|p| format!("unix://{p}")) + .map(Cow::Owned) + .unwrap_or(DEFAULT_DOCKER_HOST.into()) + } else { + DEFAULT_DOCKER_HOST.into() + } + }) } pub(crate) fn tls_verify(&self) -> bool { @@ -150,6 +186,15 @@ impl Config { } } +/// Validate the path exists and return it if it does. +fn validate_path(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..76baa54f 100644 --- a/testcontainers/src/lib.rs +++ b/testcontainers/src/lib.rs @@ -40,7 +40,12 @@ //! 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`. +//! 6. The default Docker socket including schema will be returned if none of the above are set. //! //! ### Docker authentication //!