Packages

TestcontainerEx is an Elixir library for integration testing with containerized services. Start, stop, and monitor Docker, Podman, Minikube, Colima, or Apple Container containers with a unified API. Supports custom containers.

Current section

Files

Jump to
testcontainer_ex lib testcontainer_ex.ex
Raw

lib/testcontainer_ex.ex

defmodule TestcontainerEx do
@moduledoc """
Public API for TestcontainerEx.
This module is a thin facade that delegates to the GenServer
(`TestcontainerEx.Server`) and domain modules.
"""
alias TestcontainerEx.{
Container.Config,
Docker.Engine,
Server
}
@timeout 300_000
# ── Lifecycle ─────────────────────────────────────────────────────
def start_link(options \\ []), do: Server.start_link(options)
def start(options \\ []), do: Server.start(options)
# ── Container operations ──────────────────────────────────────────
def start_container(config_builder, name \\ __MODULE__) do
GenServer.call(name, {:start_container, config_builder}, @timeout)
end
def stop_container(container_id, name \\ __MODULE__) when is_binary(container_id) do
GenServer.call(name, {:stop_container, container_id}, @timeout)
end
# ── Host/port resolution ──────────────────────────────────────────
def get_host, do: GenServer.call(__MODULE__, :get_host, @timeout)
def get_host(%Config{} = container), do: get_host(container, __MODULE__)
def get_host(name) when is_atom(name), do: GenServer.call(name, :get_host, @timeout)
def get_host(%Config{} = container, name) do
mode = GenServer.call(name, :get_connection_mode, @timeout)
if mode == :container_ip and is_binary(container.ip_address) and container.ip_address != "" and
is_nil(container.network) do
container.ip_address
else
GenServer.call(name, :get_host, @timeout)
end
end
def get_port(%Config{} = container, port), do: get_port(container, port, __MODULE__)
def get_port(%Config{} = container, port, name) do
mode = GenServer.call(name, :get_connection_mode, @timeout)
if mode == :container_ip and is_binary(container.ip_address) and container.ip_address != "" and
is_nil(container.network) do
port
else
Config.mapped_port(container, port)
end
end
# ── Network operations ────────────────────────────────────────────
def create_network(network_name, name \\ __MODULE__) do
GenServer.call(name, {:create_network, network_name}, @timeout)
end
def remove_network(network_name, name \\ __MODULE__) do
GenServer.call(name, {:remove_network, network_name}, @timeout)
end
# ── Compose operations ────────────────────────────────────────────
def start_compose(config, name \\ __MODULE__) do
GenServer.call(name, {:start_compose, config}, @timeout)
end
def stop_compose(compose_env, name \\ __MODULE__) do
GenServer.call(name, {:stop_compose, compose_env}, @timeout)
end
# ── Engine detection ──────────────────────────────────────────────
def container_engine, do: Engine.detect()
def running_in_container?, do: Config.running_in_container?()
# ── Ryuk ──────────────────────────────────────────────────────────
defdelegate ryuk_privileged?(properties), to: TestcontainerEx.Ryuk, as: :privileged?
# ── Connection ────────────────────────────────────────────────────
def connected?(name \\ __MODULE__), do: Server.connected?(name)
def stop(name \\ __MODULE__), do: Server.stop(name)
@doc """
Returns `true` when running inside a container (Docker, Podman, Kubernetes).
Accepts optional overrides for the `.dockerenv` path, cgroup path,
Kubernetes secrets path, and Podman containerenv path
(useful for testing on non-Linux hosts).
"""
def running_in_container?(dockerenv_path, cgroup_path, k8s_secrets_path \\ "/var/run/secrets/kubernetes.io", containerenv_path \\ "/.containerenv") do
Config.running_in_container?(dockerenv_path, cgroup_path, k8s_secrets_path, containerenv_path)
end
@doc """
Parses the default gateway IP from `/proc/net/route` content.
Returns `{:ok, ip_string}` or `{:error, :no_default_route}`.
"""
def parse_gateway_from_proc_route(content) when is_binary(content) do
content
|> String.split("\n")
|> Enum.map(&String.split(&1, "\t"))
|> Enum.find(fn
[_, "00000000", gateway | _] when gateway != "00000000" -> true
_ -> false
end)
|> case do
[_, "00000000", gateway | _] ->
ip =
gateway
|> String.to_integer(16)
|> :binary.encode_unsigned(:little)
|> :binary.bin_to_list()
{:ok, Enum.join(ip, ".")}
_ ->
{:error, :no_default_route}
end
end
end