Current section

Files

Jump to
testcontainers lib connection.ex
Raw

lib/connection.ex

# SPDX-License-Identifier: MIT
defmodule Testcontainers.Connection do
use GenServer
require Logger
alias Testcontainers.Connection.DockerHostStrategyEvaluator
alias Testcontainers.Connection.DockerHostStrategy.DockerSocketPath
alias Testcontainers.Connection.DockerHostStrategy.DockerHostFromProperties
alias Testcontainers.Connection.DockerHostStrategy.DockerHostFromEnv
alias Testcontainers.Connection.DockerHostStrategy.DockerHostFromProperties
alias Testcontainers.Container
alias Testcontainers.Docker.Api
alias DockerEngineAPI.Connection
@api_version "v1.41"
@timeout 300_000
@doc """
Eagerly starts this genserver unlinked and waits until it is registered
"""
def start_eager(options \\ []) do
case GenServer.whereis(__MODULE__) do
nil ->
start_unlinked(options)
pid when is_pid(pid) ->
{:ok, pid}
end
end
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def pull_image(image) when is_binary(image) do
GenServer.call(__MODULE__, {:pull_image, image}, 300_000)
end
def create_container(%Container{} = container) do
GenServer.call(__MODULE__, {:create_container, container}, 300_000)
end
def start_container(container_id) when is_binary(container_id) do
GenServer.call(__MODULE__, {:start_container, container_id}, 300_000)
end
def stop_container(container_id) when is_binary(container_id) do
GenServer.call(__MODULE__, {:stop_container, container_id}, 300_000)
end
def get_container(container_id) when is_binary(container_id) do
GenServer.call(__MODULE__, {:get_container, container_id}, 300_000)
end
def stdout_logs(container_id) when is_binary(container_id) do
GenServer.call(__MODULE__, {:stdout_logs, container_id}, 300_000)
end
def exec_create(container_id, command) when is_binary(container_id) and is_list(command) do
GenServer.call(__MODULE__, {:exec_create, command, container_id}, 300_000)
end
def exec_start(exec_id) when is_binary(exec_id) do
GenServer.call(__MODULE__, {:exec_start, exec_id}, 300_000)
end
def exec_inspect(exec_id) when is_binary(exec_id) do
GenServer.call(__MODULE__, {:exec_inspect, exec_id}, 300_000)
end
@impl true
def init(options) do
{:ok, get_connection(options)}
end
@impl true
def handle_call({:pull_image, image}, _from, connection) do
{:reply, Api.pull_image(image, connection), connection}
end
@impl true
def handle_call({:get_container, container_id}, _from, connection) do
{:reply, Api.get_container(container_id, connection), connection}
end
@impl true
def handle_call({:start_container, container_id}, _from, connection) do
{:reply, Api.start_container(container_id, connection), connection}
end
@impl true
def handle_call({:create_container, %Container{} = container}, _from, connection) do
{:reply, Api.create_container(container, connection), connection}
end
@impl true
def handle_call({:stop_container, container_id}, _from, connection) do
{:reply, Api.stop_container(container_id, connection), connection}
end
@impl true
def handle_call({:stdout_logs, container_id}, _from, connection) do
{:reply, Api.stdout_logs(container_id, connection), connection}
end
@impl true
def handle_call({:exec_create, command, container_id}, _from, connection) do
{:reply, Api.create_exec(container_id, command, connection), connection}
end
@impl true
def handle_call({:exec_start, exec_id}, _from, connection) do
{:reply, Api.start_exec(exec_id, connection), connection}
end
@impl true
def handle_call({:exec_inspect, exec_id}, _from, connection) do
{:reply, Api.inspect_exec(exec_id, connection), connection}
end
def get_connection(options \\ []) do
docker_host_url = docker_base_url()
Logger.log(
Testcontainers.Constants.get_log_level(),
"Using docker host url: #{docker_host_url}"
)
options = Keyword.merge(options, base_url: docker_host_url, recv_timeout: @timeout)
Connection.new(options)
end
defp docker_base_url do
strategies = [
%DockerHostFromProperties{key: "tc.host"},
%DockerHostFromEnv{},
%DockerSocketPath{socket_paths: ["/var/run/docker.sock"]},
%DockerHostFromProperties{key: "docker.host"},
%DockerSocketPath{}
]
case DockerHostStrategyEvaluator.run_strategies(strategies, []) do
{:ok, "unix://" <> path} ->
"http+unix://#{:uri_string.quote(path)}/#{@api_version}"
{:ok, docker_host} ->
construct_url_from_docker_host(docker_host)
:error ->
exit("Failed to find docker host")
end
end
defp construct_url_from_docker_host(docker_host) do
uri = URI.parse(docker_host)
case uri do
%URI{scheme: "tcp"} ->
URI.to_string(%{uri | scheme: "http", path: "/#{@api_version}"})
%URI{scheme: _, authority: _} = uri ->
URI.to_string(%{uri | path: "/#{@api_version}"})
end
end
defp start_unlinked(options) do
spawn(fn -> GenServer.start(__MODULE__, options, name: __MODULE__) end)
wait_for_start(__MODULE__, 10_000)
end
defp wait_for_start(name, timeout) do
wait_for_start(name, timeout, :timer.seconds(1))
end
defp wait_for_start(_name, 0, _interval), do: {:error, :timeout}
defp wait_for_start(name, remaining_time, interval) do
case GenServer.whereis(name) do
nil ->
Process.sleep(interval)
wait_for_start(name, remaining_time - interval, interval)
pid when is_pid(pid) ->
{:ok, pid}
end
end
end