Skip to content

Commit

Permalink
Add support to access images from private registries (#90)
Browse files Browse the repository at this point in the history
* added with_auth

* added container tests + fixed bug in with_fixed_port
  • Loading branch information
tw00 authored May 15, 2024
1 parent afa7aca commit 81718ba
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 13 deletions.
41 changes: 31 additions & 10 deletions lib/container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Testcontainers.Container do
:image,
cmd: nil,
environment: %{},
auth: nil,
exposed_ports: [],
ip_address: nil,
wait_strategies: [],
Expand Down Expand Up @@ -60,14 +61,28 @@ defmodule Testcontainers.Container do
%__MODULE__{config | exposed_ports: [port | filtered_ports]}
end

@doc """
Adds multiple _ports_ to be exposed on the _container_.
"""
def with_exposed_ports(%__MODULE__{} = config, ports) when is_list(ports) do
filtered_ports = config.exposed_ports |> Enum.reject(fn port -> port in ports end)

%__MODULE__{config | exposed_ports: ports ++ filtered_ports}
end

@doc """
Adds a fixed _port_ to be exposed on the _container_.
This approach to managing ports is not recommended by Testcontainers.
Use at your own risk.
"""
def with_fixed_port(%__MODULE__{} = config, port, host_port \\ nil)
when is_integer(port) and (is_nil(host_port) or is_integer(host_port)) do
filtered_ports = config.exposed_ports |> Enum.reject(fn p -> p == port end)
filtered_ports =
config.exposed_ports
|> Enum.reject(fn
{p, _} -> p == port
p -> p == port
end)

%__MODULE__{
config
Expand All @@ -77,15 +92,6 @@ defmodule Testcontainers.Container do
}
end

@doc """
Adds multiple _ports_ to be exposed on the _container_.
"""
def with_exposed_ports(%__MODULE__{} = config, ports) when is_list(ports) do
filtered_ports = config.exposed_ports |> Enum.reject(fn port -> port in ports end)

%__MODULE__{config | exposed_ports: ports ++ filtered_ports}
end

@doc """
Sets a file or the directory on the _host machine_ to be mounted into a _container_.
"""
Expand Down Expand Up @@ -130,6 +136,21 @@ defmodule Testcontainers.Container do
%__MODULE__{config | auto_remove: auto_remove}
end

@doc """
Adds authentication token for registries that require a login.
"""
def with_auth(%__MODULE__{} = config, username, password)
when is_binary(username) and is_binary(password) do
registry_auth_token =
Jason.encode!(%{
username: username,
password: password
})
|> Base.encode64()

%__MODULE__{config | auth: registry_auth_token}
end

@doc """
Gets the host port on the container for the given exposed port.
"""
Expand Down
6 changes: 4 additions & 2 deletions lib/docker/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ defmodule Testcontainers.Docker.Api do
end
end

def pull_image(image, conn) when is_binary(image) do
case Api.Image.image_create(conn, fromImage: image) do
def pull_image(image, conn, opts \\ []) when is_binary(image) do
auth = Keyword.get(opts, :auth, nil)

case Api.Image.image_create(conn, fromImage: image, "X-Registry-Auth": auth) do
{:ok, %Tesla.Env{status: 200}} ->
{:ok, nil}

Expand Down
2 changes: 1 addition & 1 deletion lib/testcontainers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ defmodule Testcontainers do
|> Container.with_label(container_lang_label(), container_lang_value())
|> Container.with_label(container_label(), "#{true}")

with {:ok, _} <- Api.pull_image(config.image, state.conn),
with {:ok, _} <- Api.pull_image(config.image, state.conn, auth: config.auth),
{:ok, id} <- Api.create_container(config, state.conn),
:ok <- Api.start_container(id, state.conn),
{:ok, container} <- Api.get_container(id, state.conn),
Expand Down
87 changes: 87 additions & 0 deletions test/container_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Testcontainers.ContainerTest do
use ExUnit.Case, async: true

alias Testcontainers.Container

describe "with_exposed_port/2" do
test "adds an exposed port to the container" do
container =
Container.new("my-image")
|> Container.with_exposed_port(80)

assert container.exposed_ports == [80]
end

test "removes duplicate exposed ports" do
container =
Container.new("my-image")
|> Container.with_exposed_port(80)
|> Container.with_exposed_port(80)

assert container.exposed_ports == [80]
end
end

describe "with_exposed_ports/2" do
test "adds multiple exposed ports to the container" do
container =
Container.new("my-image")
|> Container.with_exposed_ports([80, 443])

assert container.exposed_ports == [80, 443]
end

test "removes duplicate exposed ports" do
container =
Container.new("my-image")
|> Container.with_exposed_ports([80, 443])
|> Container.with_exposed_ports([80])

assert container.exposed_ports == [80, 443]
end
end

describe "with_fixed_port/3" do
test "adds a fixed exposed port to the container" do
container =
Container.new("my-image")
|> Container.with_fixed_port(80, 8080)

assert container.exposed_ports == [{80, 8080}]
end

test "removes and overwrites duplicate fixed ports" do
container =
Container.new("my-image")
|> Container.with_fixed_port(80)
|> Container.with_fixed_port(80, 8080)
|> Container.with_fixed_port(80, 8081)

assert container.exposed_ports == [{80, 8081}]
end
end

describe "mapped_port/2" do
test "returns the mapped host port for the given exposed port" do
container = Container.new("my-image") |> Container.with_fixed_port(80, 8080)
assert Container.mapped_port(container, 80) == 8080
end

test "returns nil if the exposed port is not found" do
container = Container.new("my-image")
assert Container.mapped_port(container, 80) == nil
end
end

describe "with_auth/3" do
test "sets the authentication token for the container" do
container = Container.new("my-image")
assert container.auth == nil

updated_container = Container.with_auth(container, "username", "password")

assert updated_container.auth ==
"eyJwYXNzd29yZCI6InBhc3N3b3JkIiwidXNlcm5hbWUiOiJ1c2VybmFtZSJ9"
end
end
end

0 comments on commit 81718ba

Please sign in to comment.