Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run credo
run: mix credo --strict
- name: Run tests
run: MIX_ENV=test mix citest
- name: Verify version
Expand Down
15 changes: 8 additions & 7 deletions lib/connection/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ defmodule Testcontainers.Connection do

require Logger

alias DockerEngineAPI.Connection
alias Testcontainers.Constants
alias Testcontainers.DockerUrl
alias Testcontainers.DockerHostFromEnvStrategy
alias Testcontainers.DockerHostFromPropertiesStrategy
alias Testcontainers.DockerHostStrategyEvaluator
alias Testcontainers.DockerSocketPathStrategy
alias Testcontainers.DockerHostFromPropertiesStrategy
alias Testcontainers.DockerHostFromEnvStrategy
alias DockerEngineAPI.Connection
alias Testcontainers.DockerUrl

@timeout 300_000

Expand All @@ -29,9 +29,10 @@ defmodule Testcontainers.Connection do
end

defp get_docker_host_url do
with {:ok, docker_host} <- get_docker_host() do
{DockerUrl.construct(docker_host), docker_host}
else
case get_docker_host() do
{:ok, docker_host} ->
{DockerUrl.construct(docker_host), docker_host}

{:error, error} ->
exit(error)
end
Expand Down
50 changes: 27 additions & 23 deletions lib/connection/docker_host_strategy/docker_socket_path.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,34 @@ defmodule Testcontainers.DockerSocketPathStrategy do
end

def execute(strategy, _input) do
Enum.reduce_while(
if length(strategy.socket_paths) == 0 do
default_socket_paths()
else
strategy.socket_paths
end,
{:error, {:docker_socket_not_found, []}},
fn path, {:error, {:docker_socket_not_found, tried_paths}} ->
if path != nil && File.exists?(path) do
path_with_scheme = "unix://" <> path

case DockerUrl.test_docker_host(path_with_scheme) do
:ok ->
{:halt, {:ok, path_with_scheme}}

{:error, reason} ->
Logger.debug("Docker socket path #{path} failed: #{reason}")
{:cont, {:error, {:docker_socket_not_found, tried_paths ++ [path]}}}
end
else
{:cont, {:error, {:docker_socket_not_found, tried_paths ++ [path]}}}
end
paths =
case strategy.socket_paths do
[] -> default_socket_paths()
paths -> paths
end
)

Enum.reduce_while(paths, {:error, {:docker_socket_not_found, []}}, &try_socket_path/2)
end

defp try_socket_path(path, {:error, {:docker_socket_not_found, tried_paths}}) do
if path != nil && File.exists?(path) do
probe_socket(path, tried_paths)
else
{:cont, {:error, {:docker_socket_not_found, tried_paths ++ [path]}}}
end
end

defp probe_socket(path, tried_paths) do
path_with_scheme = "unix://" <> path

case DockerUrl.test_docker_host(path_with_scheme) do
:ok ->
{:halt, {:ok, path_with_scheme}}

{:error, reason} ->
Logger.debug("Docker socket path #{path} failed: #{reason}")
{:cont, {:error, {:docker_socket_not_found, tried_paths ++ [path]}}}
end
end
end
end
3 changes: 1 addition & 2 deletions lib/connection/docker_host_strategy_evaluator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ defmodule Testcontainers.DockerHostStrategyEvaluator do
defp format_errors(errors) do
errors
|> Enum.reverse()
|> Enum.map(fn {:error, error} -> inspect(error) end)
|> Enum.join(", ")
|> Enum.map_join(", ", fn {:error, error} -> inspect(error) end)
end
end
9 changes: 6 additions & 3 deletions lib/container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule Testcontainers.Container do

require Logger

@type t :: %__MODULE__{}

@enforce_keys [:image]
defstruct [
:image,
Expand Down Expand Up @@ -51,7 +53,7 @@ defmodule Testcontainers.Container do
when is_atom(name) and name == @os_type

@dialyzer {:nowarn_function, os_type: 0}
def os_type() do
def os_type do
cond do
is_os(:linux) -> :linux
is_os(:macos) -> :macos
Expand Down Expand Up @@ -335,7 +337,8 @@ defmodule Testcontainers.Container do

Available options:

* `TestContainers.PullPolicy.always_pull()` - default, always pulls image from the remote repository
* `TestContainers.PullPolicy.pull_if_missing()` - default, pulls image only if not already present locally (avoids registry rate limits)
* `TestContainers.PullPolicy.always_pull()` - always pulls image from the remote repository
* `TestContainers.PullPolicy.never_pull()` - does not pull images, use when working with local images
* `TestContainers.PullPolicy.pull_condition(expr)` - pulls image if expression returns `true`
"""
Expand All @@ -353,7 +356,7 @@ defmodule Testcontainers.Container do
Do stuff after container has started.
"""
@impl true
@spec after_start(%Testcontainers.Container{}, %Testcontainers.Container{}, %Tesla.Env{}) ::
@spec after_start(Testcontainers.Container.t(), Testcontainers.Container.t(), Tesla.Env.t()) ::
:ok
def after_start(_config, _container, _conn), do: :ok
end
Expand Down
6 changes: 4 additions & 2 deletions lib/container/cassandra_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ defmodule Testcontainers.CassandraContainer do

alias Testcontainers.CassandraContainer
alias Testcontainers.CommandWaitStrategy
alias Testcontainers.ContainerBuilder
alias Testcontainers.Container
alias Testcontainers.ContainerBuilder

import Testcontainers.Container, only: [is_valid_image: 1]

Expand All @@ -19,6 +19,8 @@ defmodule Testcontainers.CassandraContainer do
@default_port 9042
@default_wait_timeout 60_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :wait_timeout]
defstruct [
:image,
Expand Down Expand Up @@ -75,7 +77,7 @@ defmodule Testcontainers.CassandraContainer do
import Container

@impl true
@spec build(%CassandraContainer{}) :: %Container{}
@spec build(CassandraContainer.t()) :: Container.t()
def build(%CassandraContainer{} = config) do
new(config.image)
|> with_exposed_port(CassandraContainer.default_port())
Expand Down
8 changes: 5 additions & 3 deletions lib/container/ceph_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ defmodule Testcontainers.CephContainer do
Provides functionality for creating and managing Ceph container configurations.
"""

alias Testcontainers.LogWaitStrategy
alias Testcontainers.CephContainer
alias Testcontainers.ContainerBuilder
alias Testcontainers.Container
alias Testcontainers.ContainerBuilder
alias Testcontainers.LogWaitStrategy

import Testcontainers.Container, only: [is_valid_image: 1]

Expand All @@ -20,6 +20,8 @@ defmodule Testcontainers.CephContainer do
@default_port 8080
@default_wait_timeout 300_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :access_key, :secret_key, :bucket, :port, :wait_timeout]
defstruct [
:image,
Expand Down Expand Up @@ -230,7 +232,7 @@ defmodule Testcontainers.CephContainer do

- Raises `ArgumentError` if the provided image is not compatible with the default Ceph image.
"""
@spec build(%CephContainer{}) :: %Container{}
@spec build(CephContainer.t()) :: Container.t()
@impl true
def build(%CephContainer{} = config) do
new(config.image)
Expand Down
7 changes: 3 additions & 4 deletions lib/container/emqx_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ defmodule Testcontainers.EmqxContainer do
Provides functionality for creating and managing EMQX container configurations.
"""

alias Testcontainers.ContainerBuilder
alias Testcontainers.Container
alias Testcontainers.PortWaitStrategy
alias Testcontainers.ContainerBuilder
alias Testcontainers.EmqxContainer
alias Testcontainers.PortWaitStrategy

import Testcontainers.Container, only: [is_valid_image: 1]

Expand All @@ -17,7 +17,7 @@ defmodule Testcontainers.EmqxContainer do
@default_mqtts_port 8883
@default_mqtt_over_ws_port 8083
@default_mqtt_over_wss_port 8084
@default_dashboard_port 18083
@default_dashboard_port 18_083
@default_wait_timeout 60_000

@enforce_keys [:image, :mqtt_port, :wait_timeout]
Expand Down Expand Up @@ -160,7 +160,6 @@ defmodule Testcontainers.EmqxContainer do
]

@impl true
# TODO Implement the `after_start/3` function for the `ContainerBuilder` protocol.
def after_start(_config, _container, _conn), do: :ok
end
end
6 changes: 4 additions & 2 deletions lib/container/kafka_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ defmodule Testcontainers.KafkaContainer do
@default_wait_timeout 60_000
@default_cluster_id "4L6g3nShT-eMCtK--X86sw"

@type t :: %__MODULE__{}

@enforce_keys [
:image,
:kafka_port,
Expand Down Expand Up @@ -73,7 +75,7 @@ defmodule Testcontainers.KafkaContainer do
"""
def new do
# Select a random port in a high range to minimize conflicts
kafka_port = Enum.random(29000..29999)
kafka_port = Enum.random(29_000..29_999)

%__MODULE__{
image: @default_image_with_tag,
Expand Down Expand Up @@ -170,7 +172,7 @@ defmodule Testcontainers.KafkaContainer do
import Container

@impl true
@spec build(%KafkaContainer{}) :: %Container{}
@spec build(KafkaContainer.t()) :: Container.t()
def build(%KafkaContainer{} = config) do
host = Testcontainers.get_host()

Expand Down
6 changes: 4 additions & 2 deletions lib/container/minio_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ defmodule Testcontainers.MinioContainer do
"""

alias Testcontainers.Container
alias Testcontainers.MinioContainer
alias Testcontainers.ContainerBuilder
alias Testcontainers.LogWaitStrategy
alias Testcontainers.MinioContainer

@default_image "minio/minio"
@default_tag "RELEASE.2023-11-11T08-14-41Z"
Expand All @@ -17,6 +17,8 @@ defmodule Testcontainers.MinioContainer do
@default_ui_port 9001
@default_wait_timeout 60_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :username, :password, :wait_timeout]
defstruct [
:image,
Expand Down Expand Up @@ -75,7 +77,7 @@ defmodule Testcontainers.MinioContainer do
defimpl ContainerBuilder do
import Container

@spec build(%MinioContainer{}) :: %Container{}
@spec build(MinioContainer.t()) :: Container.t()
@impl true
def build(%MinioContainer{} = config) do
new(config.image)
Expand Down
6 changes: 4 additions & 2 deletions lib/container/mongo_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ defmodule Testcontainers.MongoContainer do
@default_user "test"
@default_password "test"
@default_database "test"
@default_port 27017
@default_port 27_017
@default_wait_timeout 180_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :user, :password, :database, :port, :wait_timeout, :persistent_volume]
defstruct [
:image,
Expand Down Expand Up @@ -251,7 +253,7 @@ defmodule Testcontainers.MongoContainer do

- Raises `ArgumentError` if the provided image is not compatible with the default Mongo image.
"""
@spec build(%MongoContainer{}) :: %Container{}
@spec build(MongoContainer.t()) :: Container.t()
@impl true
def build(%MongoContainer{} = config) do
new(config.image)
Expand Down
6 changes: 4 additions & 2 deletions lib/container/mysql_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ defmodule Testcontainers.MySqlContainer do

alias Testcontainers.Container
alias Testcontainers.ContainerBuilder
alias Testcontainers.MySqlContainer
alias Testcontainers.LogWaitStrategy
alias Testcontainers.MySqlContainer

import Testcontainers.Container, only: [is_valid_image: 1]

Expand All @@ -23,6 +23,8 @@ defmodule Testcontainers.MySqlContainer do
@default_port 3306
@default_wait_timeout 180_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :user, :password, :database, :port, :wait_timeout, :persistent_volume]
defstruct [
:image,
Expand Down Expand Up @@ -211,7 +213,7 @@ defmodule Testcontainers.MySqlContainer do

- Raises `ArgumentError` if the provided image is not compatible with the default MySql image.
"""
@spec build(%MySqlContainer{}) :: %Container{}
@spec build(MySqlContainer.t()) :: Container.t()
@impl true
def build(%MySqlContainer{} = config) do
new(config.image)
Expand Down
6 changes: 4 additions & 2 deletions lib/container/postgres_container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ defmodule Testcontainers.PostgresContainer do
"""

alias Testcontainers.CommandWaitStrategy
alias Testcontainers.PostgresContainer
alias Testcontainers.Container
alias Testcontainers.ContainerBuilder
alias Testcontainers.PostgresContainer

import Testcontainers.Container, only: [is_valid_image: 1]

Expand All @@ -23,6 +23,8 @@ defmodule Testcontainers.PostgresContainer do
@default_port 5432
@default_wait_timeout 60_000

@type t :: %__MODULE__{}

@enforce_keys [:image, :user, :password, :database, :port, :wait_timeout, :persistent_volume]
defstruct [
:image,
Expand Down Expand Up @@ -211,7 +213,7 @@ defmodule Testcontainers.PostgresContainer do

- Raises `ArgumentError` if the provided image is not compatible with the default Postgres image.
"""
@spec build(%PostgresContainer{}) :: %Container{}
@spec build(PostgresContainer.t()) :: Container.t()
@impl true
def build(%PostgresContainer{} = config) do
new(config.image)
Expand Down
4 changes: 2 additions & 2 deletions lib/container/protocols/container_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ defprotocol Testcontainers.ContainerBuilder do
@moduledoc """
All types of predefined containers must implement this protocol.
"""
@spec build(t()) :: %Testcontainers.Container{}
@spec build(t()) :: Testcontainers.Container.t()
def build(builder)

@doc """
Do stuff after container has started.
"""
@spec after_start(t(), %Testcontainers.Container{}, %Tesla.Env{}) :: :ok | {:error, term()}
@spec after_start(t(), Testcontainers.Container.t(), Tesla.Env.t()) :: :ok | {:error, term()}
def after_start(builder, container, connection)
end
Loading
Loading