Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to access images from private registries #90

Merged
merged 2 commits into from
May 15, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
jarlah marked this conversation as resolved.
Show resolved Hide resolved
{: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