defmodule TestcontainerEx.Connection.Strategies.Env do
@moduledoc """
Resolves the container engine host from environment variables.
Checks `CONTAINER_ENGINE_HOST` first, then `DOCKER_HOST` for backward
compatibility.
"""
@behaviour TestcontainerEx.Connection.Strategies.Behaviour
alias TestcontainerEx.Connection.Url
@primary_key "CONTAINER_ENGINE_HOST"
@fallback_key "DOCKER_HOST"
@impl true
def resolve do
case {System.get_env(@primary_key), System.get_env(@fallback_key)} do
{nil, nil} ->
{:error, {:not_found, @primary_key}}
{"", _} ->
{:error, {:empty, @primary_key}}
{nil, ""} ->
{:error, {:empty, @fallback_key}}
{url, _} when is_binary(url) and url != "" ->
probe(url)
{nil, url} when is_binary(url) and url != "" ->
probe(url)
_ ->
{:error, {:empty, @primary_key}}
end
end
defp probe(url) do
case URI.parse(url) do
%URI{scheme: "unix", path: path} ->
if socket_accessible?(path) do
require Logger
Logger.info("Container engine host detected via #{@primary_key}: #{url}")
{:ok, url}
else
{:error, {:socket_not_found, path}}
end
_ ->
case Req.get("#{Url.construct(url)}/_ping") do
{:ok, %{status: 200}} -> {:ok, url}
{:error, reason} -> {:error, {:ping_failed, url, reason}}
end
end
end
# Check if a path is a readable Unix socket.
# Uses file mode bits (not File.stat type field) because some
# filesystems (e.g. virtiofs on macOS) report sockets as :other.
# The Unix socket type is indicated by mode bits 0o140000.
defp socket_accessible?(path) do
case File.stat(path) do
{:ok, stat} -> :erlang.band(stat.mode, 0o170000) == 0o140000
_ -> false
end
end
end