Packages
testcontainers
0.9.3
2.3.1
2.3.0
2.2.0
2.1.0
2.1.0-rc1
2.0.0
2.0.0-rc3
2.0.0-rc2
2.0.0-rc
1.14.1
1.14.0
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-beta.1
1.0.0-beta
0.9.3
0.9.2
0.9.1
0.9.0
Testcontainers is an Elixir library that supports ExUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
Current section
Files
Jump to
Current section
Files
lib/reaper.ex
# SPDX-License-Identifier: MIT
# Original by: Marco Dallagiacoma @ 2023 in https://github.com/dallagi/excontainers
# Modified by: Jarl André Hübenthal @ 2023
defmodule Testcontainers.Reaper do
use GenServer
require Logger
alias Testcontainers.Container
@ryuk_image "testcontainers/ryuk:0.5.1"
@ryuk_port 8080
@doc """
Eagerly starts this genserver unlinked and waits until it is registered
"""
def start_eager(opts \\ []) do
case GenServer.whereis(__MODULE__) do
nil ->
start_unlinked(opts)
pid when is_pid(pid) ->
{:ok, pid}
end
end
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def register(filter) do
GenServer.cast(__MODULE__, {:register, filter})
end
@impl true
def init(_) do
Process.flag(:trap_exit, true)
with {:ok, container} <- create_ryuk_container(),
{:ok, socket} <- create_ryuk_socket(container) do
Logger.log(
Testcontainers.Constants.get_log_level(),
"Reaper initialized with containerId #{container.container_id}"
)
{:ok, %{socket: socket, container: container}}
end
end
@impl true
def handle_cast({:register, filter}, %{socket: socket} = state) do
case register(socket, filter) do
:ok ->
{:noreply, state}
{:error, _reason} ->
{:stop, :error_reason, state}
end
end
defp register(socket, {filter_key, filter_value}) do
:gen_tcp.send(
socket,
"#{:uri_string.quote(filter_key)}=#{:uri_string.quote(filter_value)}" <> "\n"
)
case :gen_tcp.recv(socket, 0, 1_000) do
{:ok, "ACK\n"} ->
:ok
{:error, reason} ->
{:error, reason}
end
end
defp create_ryuk_container do
%Container{image: @ryuk_image}
|> Container.with_exposed_port(@ryuk_port)
|> Container.with_environment("RYUK_PORT", "#{@ryuk_port}")
|> Container.with_environment("RYUK_CONNECTION_TIMEOUT", "120s")
|> Container.with_bind_mount("/var/run/docker.sock", "/var/run/docker.sock", "rw")
|> Container.run(reap: false)
end
defp create_ryuk_socket(%Container{} = container) do
host_port = Container.mapped_port(container, @ryuk_port)
:gen_tcp.connect(~c"localhost", host_port, [
:binary,
active: false,
packet: :line
])
end
defp start_unlinked(opts) do
spawn(fn -> GenServer.start(__MODULE__, opts, 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