defmodule TestcontainerEx.EmqxContainer do
@moduledoc """
Provides functionality for creating and managing EMQX container configurations.
"""
alias TestcontainerEx.Container.Builder
alias TestcontainerEx.Container.Config
alias TestcontainerEx.EmqxContainer
alias TestcontainerEx.PortWaitStrategy
use TestcontainerEx.ContainerConfig
@default_image "emqx"
@default_tag "5.6.0"
@default_image_with_tag "#{@default_image}:#{@default_tag}"
@default_mqtt_port 1883
@default_mqtts_port 8883
@default_mqtt_over_ws_port 8083
@default_mqtt_over_wss_port 8084
@default_dashboard_port 18_083
@default_wait_timeout 60_000
@type t :: %__MODULE__{}
@enforce_keys [:image, :mqtt_port, :wait_timeout]
defstruct [
:image,
:mqtt_port,
:mqtts_port,
:mqtt_over_ws_port,
:mqtt_over_wss_port,
:dashboard_port,
:wait_timeout,
:name,
check_image: @default_image,
reuse: false
]
@doc """
Creates a new `EmqxContainer` struct with default configurations.
"""
def new do
%__MODULE__{
image: @default_image_with_tag,
wait_timeout: @default_wait_timeout,
mqtt_port: @default_mqtt_port,
mqtts_port: @default_mqtts_port,
mqtt_over_ws_port: @default_mqtt_over_ws_port,
mqtt_over_wss_port: @default_mqtt_over_wss_port,
dashboard_port: @default_dashboard_port
}
end
@doc """
Overrides the default image used for the Emqx container.
## Examples
iex> config = EmqxContainer.new()
iex> new_config = EmqxContainer.with_image(config, "emqx:xyz")
iex> new_config.image
"emqx:xyz"
"""
def with_image(%__MODULE__{} = config, image) when is_binary(image) do
%{config | image: image}
end
def with_ports(
%__MODULE__{} = config,
mqtt_port \\ @default_mqtt_port,
mqtts_port \\ @default_mqtts_port,
mqtt_over_ws_port \\ @default_mqtt_over_ws_port,
mqtt_over_wss_port \\ @default_mqtt_over_wss_port,
dashboard_port \\ @default_dashboard_port
) do
%{
config
| mqtt_port: mqtt_port,
mqtts_port: mqtts_port,
mqtt_over_ws_port: mqtt_over_ws_port,
mqtt_over_wss_port: mqtt_over_wss_port,
dashboard_port: dashboard_port
}
end
@doc """
Sets the container name.
"""
@spec with_name(t(), String.t()) :: t()
def with_name(%__MODULE__{} = config, name) when is_binary(name) do
%__MODULE__{config | name: name}
end
@doc """
Retrieves the default Docker image for the Emqx container.
"""
def default_image, do: @default_image
@doc """
Returns the address on the _host machine_ where the Emqx container is listening.
"""
def host, do: TestcontainerEx.get_host()
@doc """
Returns the port on the _host machine_ where the Emqx container is listening.
"""
def mqtt_port(%Config{} = container),
do: TestcontainerEx.get_port(container, @default_mqtt_port)
defimpl Builder do
@impl true
def build(%EmqxContainer{} = config) do
Config.new(config.image)
|> Config.with_exposed_ports(exposed_ports(config))
|> Config.with_waiting_strategies(waiting_strategies(config))
|> Config.with_check_image(config.check_image)
|> Config.with_reuse(config.reuse)
|> then(fn cfg ->
if config.name, do: Config.with_name(cfg, config.name), else: cfg
end)
|> Config.valid_image!()
end
defp exposed_ports(config),
do: [
config.mqtt_port,
config.mqtts_port,
config.mqtt_over_ws_port,
config.mqtt_over_wss_port,
config.dashboard_port
]
defp waiting_strategies(config),
do: [
PortWaitStrategy.new(EmqxContainer.host(), config.mqtt_port, config.wait_timeout, 1000),
PortWaitStrategy.new(EmqxContainer.host(), config.mqtts_port, config.wait_timeout, 1000),
PortWaitStrategy.new(
EmqxContainer.host(),
config.mqtt_over_ws_port,
config.wait_timeout,
1000
),
PortWaitStrategy.new(
EmqxContainer.host(),
config.mqtt_over_wss_port,
config.wait_timeout,
1000
),
PortWaitStrategy.new(
EmqxContainer.host(),
config.dashboard_port,
config.wait_timeout,
1000
)
]
@impl true
def after_start(_config, _container, _conn), do: :ok
end
end