defmodule Testcontainers.Reaper do
use Supervisor
def start_link(opts \\ []) do
Supervisor.start_link(__MODULE__, :ok, opts)
end
@impl true
def init(:ok) do
Process.flag(:trap_exit, true)
children = [
{Testcontainers.ReaperWorker, []}
]
Supervisor.init(children, strategy: :one_for_one)
end
end
defmodule Testcontainers.ReaperWorker do
use GenServer
require Logger
alias Testcontainers.Docker
alias Testcontainers.Container
@ryuk_image "testcontainers/ryuk:0.5.1"
@ryuk_port 8080
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def register(filter) do
if Process.whereis(__MODULE__) do
GenServer.cast(__MODULE__, {:register, filter})
else
Logger.warning("""
Reaper is not running! Ensure that Testcontainers.Reaper
is started in your test_helper.exs.
e.g.,
Testcontainers.Reaper.start_link()
""")
end
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.info("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} ->
Logger.warning("Error receiving data: #{inspect(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")
|> Docker.Api.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
end