defmodule Mix.Tasks.TestcontainerEx.Run do
use Mix.Task
alias TestcontainerEx.MySqlContainer
alias TestcontainerEx.PostgresContainer
@shortdoc "Runs a Mix sub-task (test, phx.server, etc) with a database container"
@moduledoc """
Usage:
mix testcontainer_ex.run [sub_task] [--database DB] [--db-volume VOLUME] [sub_task_args...]
Examples:
mix testcontainer_ex.run test --database postgres
mix testcontainer_ex.run phx.server --database mysql
mix testcontainer_ex.run test --database postgres --db-volume my_postgres_data
mix testcontainer_ex.run phx.server --db-volume my_postgres_data
mix testcontainer_ex.run some.custom.server
"""
def run(args) do
Enum.each([:req, :fs, :logger], fn app ->
{:ok, _} = Application.ensure_all_started(app)
end)
{:ok, _} = TestcontainerEx.start()
{opts, rest_args, _} =
OptionParser.parse(args,
switches: [
database: :string,
db_volume: :string,
host_port: :integer
]
)
database = opts[:database] || "postgres"
db_volume = opts[:db_volume]
host_port = opts[:host_port]
# Determine sub_task and its args
{sub_task, sub_task_args} =
case rest_args do
[task | tail] -> {task, tail}
[] -> {"test", []}
end
IO.puts("Starting database container: #{database}")
IO.puts("Using database volume: #{db_volume || "none"}")
{_container, env} = setup_container(database, db_volume, host_port)
run_sub_task_and_exit(sub_task, sub_task_args, env)
end
@spec run_sub_task_and_exit(String.t(), list(String.t()), list({String.t(), String.t()})) ::
no_return()
defp run_sub_task_and_exit(sub_task, sub_task_args, env) do
IO.puts("Starting mix task: #{sub_task} #{Enum.join(sub_task_args, " ")}")
Enum.each(env, fn {k, v} -> System.put_env(k, v) end)
:ok = Mix.Task.run(sub_task, sub_task_args)
IO.puts("Task finished: #{sub_task} #{Enum.join(sub_task_args, " ")}")
end
defp setup_container(database, db_volume, host_port) do
case database do
"postgres" ->
container_def =
PostgresContainer.new()
|> PostgresContainer.with_user("test")
|> PostgresContainer.with_password("test")
|> PostgresContainer.with_reuse(true)
|> maybe_with_host_port(host_port, PostgresContainer.default_port(), PostgresContainer)
|> maybe_with_persistent_volume(db_volume, PostgresContainer)
{:ok, container} = TestcontainerEx.start_container(container_def)
port = PostgresContainer.port(container)
{container, create_env(container, port)}
"mysql" ->
container_def =
MySqlContainer.new()
|> MySqlContainer.with_user("test")
|> MySqlContainer.with_password("test")
|> MySqlContainer.with_reuse(true)
|> maybe_with_host_port(host_port, MySqlContainer.default_port(), MySqlContainer)
|> maybe_with_persistent_volume(db_volume, MySqlContainer)
{:ok, container} = TestcontainerEx.start_container(container_def)
port = MySqlContainer.port(container)
{container, create_env(container, port)}
_ ->
raise("Unsupported database: #{database}")
end
end
defp maybe_with_host_port(config, nil, _exposed_port, _module), do: config
defp maybe_with_host_port(config, host_port, exposed_port, module) do
module.with_port(config, {exposed_port, host_port})
end
defp maybe_with_persistent_volume(config, nil, _module), do: config
defp maybe_with_persistent_volume(config, db_volume, module) do
module.with_persistent_volume(config, db_volume)
end
defp create_env(container, port) do
[
{"DATABASE_URL", "ecto://test:test@#{TestcontainerEx.get_host(container)}:#{port}/test"}
]
end
end