Skip to content

Commit

Permalink
[chore] windows support (kind of) (#155)
Browse files Browse the repository at this point in the history
Support for windows and environments where localhost is not the correct hostname 
now uses docker host as found by docker context
  • Loading branch information
jarlah authored Dec 3, 2024
1 parent 3f8aba5 commit 85e2847
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 24 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,33 @@ Testcontainers use the standard Logger, see https://hexdocs.pm/logger/Logger.htm

For more detailed information about the API, different container configurations, and advanced usage scenarios, please refer to the [API documentation](https://hexdocs.pm/testcontainers/api-reference.html).

## Windows support

### Native
You can run on windows natively with elixir and erlang.

First install Visual Studio 2022 with Desktop development with C++.

Open visual studio dev shell. I do it by just opening an empty c++ project, then View -> Terminal.

Enable "Expose daemon on tcp://localhost:2375 without TLS" in Docker settings.

for powershell:

`$Env:DOCKER_HOST = "tcp://localhost:2375"`

for cmd:

`set DOCKER_HOST=tcp://localhost:2375`

Compile and run tests:

`mix deps.get`

`mix deps.compile`

`mix test`

## Contributing

We welcome your contributions! Please see our contributing guidelines (TBD) for more details on how to submit patches and the contribution workflow.
Expand Down
11 changes: 10 additions & 1 deletion lib/container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,22 @@ defmodule Testcontainers.Container do
defguard is_os(name)
when is_atom(name) and name == @os_type

@dialyzer {:nowarn_function, os_type: 0}
def os_type() do
cond do
is_os(:linux) -> :linux
is_os(:macos) -> :macos
is_os(:windows) -> :windows
true -> :unknown
end
end

@doc """
A constructor function to make it easier to construct a container
"""
def new(image) when is_binary(image) do
%__MODULE__{image: image}
end

@doc """
Sets a _waiting strategy_ for the _container_.
"""
Expand Down
69 changes: 47 additions & 22 deletions lib/testcontainers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Testcontainers do
alias Testcontainers.Util.PropertiesParser

import Testcontainers.Constants
import Testcontainers.Container, only: [os_type: 0]

@timeout 300_000

Expand All @@ -42,28 +43,16 @@ defmodule Testcontainers do
ryuk_config =
Container.new("testcontainers/ryuk:#{Constants.ryuk_version()}")
|> Container.with_exposed_port(8080)
|> then(fn config ->
with %URI{scheme: "unix", path: docker_socket_path} <- URI.parse(docker_host) do
Container.with_bind_mount(
config,
docker_socket_path,
"/var/run/docker.sock",
"rw"
)
else
_ ->
config
end
end)
|> then(&apply_docker_socket_volume_binding(&1, docker_host))
|> Container.with_auto_remove(true)

with {:ok, _} <- Api.pull_image(ryuk_config.image, conn),
{:ok, docker_hostname} <- get_docker_hostname(docker_host_url, conn),
{:ok, ryuk_container_id} <- Api.create_container(ryuk_config, conn),
:ok <- Api.start_container(ryuk_container_id, conn),
{:ok, container} <- Api.get_container(ryuk_container_id, conn),
{:ok, socket} <- create_ryuk_socket(container),
{:ok, socket} <- create_ryuk_socket(container, docker_hostname),
:ok <- register_ryuk_filter(session_id, socket),
{:ok, docker_hostname} <- get_docker_hostname(docker_host_url, conn),
{:ok, properties} <- PropertiesParser.read_property_file() do
Logger.info("Testcontainers initialized")

Expand Down Expand Up @@ -191,13 +180,13 @@ defmodule Testcontainers do
GenServer.call(name, call, @timeout)
end

defp create_ryuk_socket(container, reattempt_count \\ 0)
defp create_ryuk_socket(container, docker_hostname, reattempt_count \\ 0)

defp create_ryuk_socket(%Container{} = container, reattempt_count)
defp create_ryuk_socket(%Container{} = container, docker_hostname, reattempt_count)
when reattempt_count < 3 do
host_port = Container.mapped_port(container, 8080)

case :gen_tcp.connect(~c"localhost", host_port, [
case :gen_tcp.connect(~c"#{docker_hostname}", host_port, [
:binary,
active: false,
packet: :line,
Expand All @@ -207,17 +196,17 @@ defmodule Testcontainers do
{:ok, connected}

{:error, :econnrefused} ->
Logger.debug("Connection refused. Retrying... Attempt #{reattempt_count + 1}/3")
Logger.info("Connection refused. Retrying... Attempt #{reattempt_count + 1}/3")
:timer.sleep(5000)
create_ryuk_socket(container, reattempt_count + 1)
create_ryuk_socket(container, docker_hostname, reattempt_count + 1)

{:error, error} ->
{:error, error}
end
end

defp create_ryuk_socket(%Container{} = _container, _reattempt_count) do
Logger.debug("Ryuk host refused to connect")
defp create_ryuk_socket(%Container{} = _container, _docker_hostname, _reattempt_count) do
Logger.info("Ryuk host refused to connect")
{:error, :econnrefused}
end

Expand Down Expand Up @@ -291,4 +280,40 @@ defmodule Testcontainers do
error
end)
end

defp apply_docker_socket_volume_binding(config, docker_host) do
case {os_type(), URI.parse(docker_host)} do
{os, uri} -> handle_docker_socket_binding(config, os, uri)
end
end

@dialyzer {:nowarn_function, handle_docker_socket_binding: 3}
defp handle_docker_socket_binding(config, :linux, %URI{scheme: "unix", path: docker_socket_path}) do
Container.with_bind_mount(
config,
docker_socket_path,
"/var/run/docker.sock",
"rw"
)
end

defp handle_docker_socket_binding(config, :macos, %URI{scheme: "unix", path: docker_socket_path}) do
Container.with_bind_mount(
config,
docker_socket_path,
"/var/run/docker.sock",
"rw"
)
end

defp handle_docker_socket_binding(config, :windows, _) do
Container.with_bind_mount(
config,
"//var/run/docker.sock",
"/var/run/docker.sock",
"rw"
)
end

defp handle_docker_socket_binding(config, _, _), do: config
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowlib": {:hex, :cowlib, "2.6.0", "8aa629f81a0fc189f261dc98a42243fa842625feea3c7ec56c48f4ccdb55490f", [:rebar3], [], "hexpm", "45a1a08e05e4c66f2af665295955e337d52c2d33b1f1cf24d353cadeddf34992"},
"crc32cer": {:hex, :crc32cer, "0.1.10", "fb87abbf34b72f180f8c3a908cd1826c6cb9a59787d156a29e05de9e98be385e", [:rebar3], [], "hexpm", "5b1f47efd0a1b4b7411f1f35e14d3c8c6da6e6a2a725ec8f2cf1ab13703e5f38"},
"crc32cer": {:hex, :crc32cer, "0.1.11", "b550da6d615feb72a882d15d020f8f7dee72dfb2cb1bcdf3b1ee8dc2afd68cfc", [:rebar3], [], "hexpm", "a39b8f0b1990ac1bf06c3a247fc6a178b740cdfc33c3b53688dc7dd6b1855942"},
"credentials_obfuscation": {:hex, :credentials_obfuscation, "3.4.0", "34e18b126b3aefd6e8143776fbe1ceceea6792307c99ac5ee8687911f048cfd7", [:rebar3], [], "hexpm", "738ace0ed5545d2710d3f7383906fc6f6b582d019036e5269c4dbd85dbced566"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
Expand Down

0 comments on commit 85e2847

Please sign in to comment.