Current section
Files
Jump to
Current section
Files
lib/docker/docker_manager.ex
defmodule Illithid.Docker do
@moduledoc false
alias Illithid.Docker.Models.{Container, Image}
alias Illithid.Utils.BaseAPI
require Logger
@doc """
Handles listing all images found on the remote server
"""
@spec list_images(
server_ip :: String.t(),
all :: number(),
limit :: number(),
size :: boolean()
) :: {:ok, [Image.t()]} | {:error, HTTPoison.Error.t()}
def list_images(server_ip, all \\ 0, limit \\ 25, size \\ false) do
case BaseAPI.get(
server_url(server_ip) <> "/images/json?all=#{all}&limit=#{limit}&size=#{size}"
) do
{:ok, %HTTPoison.Response{body: body}} ->
images =
body
|> Jason.decode!()
|> Enum.map(&Image.from_map/1)
{:ok, images}
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
{:error, error}
end
end
@doc """
Finds a container's ID by its name
"""
@spec find_container_by_name(String.t(), String.t()) :: Container.t() | []
def find_container_by_name(server_ip, container_name) do
correct_container_name =
unless String.starts_with?(container_name, "/") do
"/" <> container_name
end
server_ip
|> list_containers()
|> case do
{:ok, containers} -> containers
_ -> []
end
|> Enum.filter(fn container -> container.names == [correct_container_name] end)
|> case do
[%Container{} = container] -> container
[] -> nil
end
end
@doc """
Handles pulling the image locally
"""
@spec pull_image(server_ip :: String.t(), image_name :: String.t()) ::
{:ok, map()} | {:error, HTTPoison.Error.t()}
def pull_image(server_ip, image_name \\ "loicmahieu/wait-for-it") do
# TODO(ian): Worth determining if there's a better image to be made
request_data = %{
fromImage: image_name,
fromSrc: "https://hub.docker.com/r",
repo: "wait-for-it",
tag: "latest"
}
case BaseAPI.post(server_url(server_ip) <> "/images/create", Jason.encode!(request_data), [
{"Content-Type", "application/json"}
]) do
{:ok, %HTTPoison.Response{body: body}} ->
containers =
body
|> Jason.decode!()
{:ok, containers}
{:error, %HTTPoison.Error{} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
{:error, error}
end
end
# TODO(ian): Ensure limit is being used or remove it from here.
@doc """
Handles listing all containers found on the remote server
"""
@spec list_containers(
server_ip :: String.t(),
all :: number(),
limit :: number(),
size :: boolean()
) :: {:ok, [Container.t()]} | {:error, HTTPoison.Error.t()}
def list_containers(server_ip, all \\ 1, _limit \\ 25, size \\ false) do
case BaseAPI.get(server_url(server_ip) <> "/containers/json?all=#{all}&size=#{size}") do
{:ok, %HTTPoison.Response{body: body}} ->
containers =
body
|> Jason.decode!()
|> Enum.map(&Container.from_map/1)
{:ok, containers}
{:error, %HTTPoison.Error{} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
{:error, error}
end
end
@doc """
Creates a container to run
"""
@spec create_container(String.t(), String.t(), String.t()) :: :ok | :error
def create_container(server_ip, image_name, container_name) do
create_request = %{
image: image_name,
Tty: true,
OpenStdin: true,
AttachStdin: false,
AttachStdout: true,
AttachStderr: false
}
case BaseAPI.post(
server_url(server_ip) <> "/containers/create?name=#{container_name}",
Jason.encode!(create_request),
[{"Content-Type", "application/json"}]
) do
{:ok, %HTTPoison.Response{status_code: 201}} ->
:ok
{:ok, %HTTPoison.Response{body: body, status_code: status_code}} ->
Logger.debug(
"Got error #{inspect(body)} with status code #{inspect(status_code)} from #{__MODULE__} @ #{
inspect(__ENV__)
}"
)
:error
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
end
@doc """
Starts a container in the local server.
"""
@spec start_container(server_ip :: String.t(), container_id :: String.t()) :: :ok | :error
def start_container(server_ip, container_id) do
case BaseAPI.post(server_url(server_ip) <> "/containers/#{container_id}/start", "") do
{:ok, %HTTPoison.Response{status_code: 204}} ->
:ok
{:ok, %HTTPoison.Response{body: body, status_code: status_code}} ->
Logger.debug(
"Got error #{inspect(body)} with status code #{inspect(status_code)} from #{__MODULE__} @ #{
inspect(__ENV__)
}"
)
:error
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
end
@doc """
Stops the container in the local server.
"""
@spec stop_container(server_ip :: String.t(), container_id :: String.t()) :: :ok | :error
def stop_container(server_ip, container_id) do
case BaseAPI.post(server_url(server_ip) <> "/containers/#{container_id}/stop", "") do
{:ok, %HTTPoison.Response{status_code: 204}} ->
:ok
{:ok, %HTTPoison.Response{body: body, status_code: status_code}} ->
Logger.debug(
"Got error #{inspect(body)} with status code #{inspect(status_code)} from #{__MODULE__} @ #{
inspect(__ENV__)
}"
)
:error
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
end
@doc """
Retrieves logs from a container
"""
@spec retrieve_container_logs(
server_ip :: String.t(),
container_id :: String.t(),
timestamps :: boolean(),
stdout :: boolean(),
stderr :: boolean()
) :: {:ok, String.t()} | :error
def retrieve_container_logs(
server_ip,
container_id,
timestamps \\ false,
stdout \\ true,
stderr \\ false
) do
case BaseAPI.get(
server_url(server_ip) <>
"/containers/#{container_id}/logs?timestamps=#{timestamps}&stdout=#{stdout}&stderr=#{
stderr
}"
) do
{:ok, %HTTPoison.Response{body: body}} ->
{:ok, body}
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
end
@doc """
Executes commands inside a running container
"""
@spec exec_command(String.t(), String.t(), [String.t()], list) ::
{:ok, String.t()} | {:error, String.t()}
def exec_command(server_ip, container_id, commands, env_args \\ []) when is_list(commands) do
request_data = %{
AttachStdin: false,
AttachStdout: true,
AttachStderr: false,
Cmd: commands,
Env: env_args,
Tty: true
}
case BaseAPI.post(
server_url(server_ip) <> "/containers/#{container_id}/exec",
Jason.encode!(request_data),
[{"Content-Type", "application/json"}]
) do
{:ok, %HTTPoison.Response{body: body}} ->
response =
body
|> Jason.decode!()
inter_request = %{
Detach: false,
Tty: false
}
case BaseAPI.post(
server_url(server_ip) <> "/exec/#{response["Id"]}/start",
Jason.encode!(inter_request),
[{"Content-Type", "application/json"}]
) do
{:ok, %HTTPoison.Response{body: body}} ->
output = parse_exec_output(body)
{:ok, output}
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
{:error, %HTTPoison.Error{reason: _reason} = error} ->
Logger.debug("Got error #{inspect(error)} from #{__MODULE__} @ #{inspect(__ENV__)}")
:error
end
end
@spec server_url(String.t(), String.t(), String.t()) :: String.t()
defp server_url(server_ip, scheme \\ "http", port \\ "2375") do
"#{scheme}://#{server_ip}:#{port}"
end
@spec parse_exec_output(exec_output :: String.t()) :: String.t()
defp parse_exec_output(exec_output) when is_binary(exec_output) do
exec_output
|> String.codepoints()
|> Enum.filter(fn x -> String.printable?(x) end)
|> Enum.join()
end
end