From b6d832261d41b2a2df23fee75eda5457be245866 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 16:43:21 +0800 Subject: [PATCH 01/10] copied relevant files --- lib/docker/container_manager.ex | 23 +++++ lib/docker/container_registry.ex | 61 +++++++++++++ lib/docker/container_worker.ex | 152 +++++++++++++++++++++++++++++++ lib/docker/request_map.ex | 42 +++++++++ 4 files changed, 278 insertions(+) create mode 100644 lib/docker/container_manager.ex create mode 100644 lib/docker/container_registry.ex create mode 100644 lib/docker/container_worker.ex create mode 100644 lib/docker/request_map.ex diff --git a/lib/docker/container_manager.ex b/lib/docker/container_manager.ex new file mode 100644 index 0000000..384dba1 --- /dev/null +++ b/lib/docker/container_manager.ex @@ -0,0 +1,23 @@ +defmodule Docker.ContainerManager do + use Supervisor + + def start_link do + Supervisor.start_link(__MODULE__, :ok, name: :container_manager) + end + + def start_container(image_name) do + Supervisor.start_child(:container_manager, [%{"Image" => image_name}]) + end + + def start_container do + Supervisor.start_child(:container_manager, []) + end + + def init(:ok) do + children = [ + worker(Docker.ContainerWorker, [], restart: :temporary) + ] + + supervise(children, startegy: :simple_one_for_one) + end +end diff --git a/lib/docker/container_registry.ex b/lib/docker/container_registry.ex new file mode 100644 index 0000000..741b086 --- /dev/null +++ b/lib/docker/container_registry.ex @@ -0,0 +1,61 @@ +defmodule Docker.ContainerRegistry do + use GenServer + alias Docker.Container + + def start_link(name) do + GenServer.start_link(__MODULE__, :ok, name: name) + end + + def register_worker(worker_pid, container_id) do + Genserver.cast( + __MODULE__, + {:register_worker, worker_pid, container_id} + ) + end + + def list_workers do + GenServer.call(__MODULE__, {:list_workers}) + end + + def init(:ok) do + case Docker.Container.list(host) do + {:error, _reason} -> + {:ok, %{}} + ids -> + Enum.each(ids, &Docker.Container.kill(host, &1)) + {:ok, %{}} + end + end + + def handle_cast({:register_worker, worker_pid, container_id}, state) do + ref = Process.monitor(worker_pid) + new_state = state |> Map.put(worker_pid, { container_id, ref }) + {:noreply, new_state} + end + + def handle_call({:list_workers}, _from, state) do + {:reply, {:ok, state}, state} + end + + def handle_info({:DOWN, ref, _type, pid, _reason}, state) do + case Map.get(state, pid) do + {container_id, ^ref} -> + try do + Container.kill(host, container_id) + rescue + reason -> + IO.puts "Error Killing Container" + IO.inspect reason + end + Process.demonitor(ref) + new_state = state |> Map.delte(pid) + {:noreply, new_state} + _ -> + {:noreply, state} + end + end + + defp host do + "127.0.0.1:2375" + end +end diff --git a/lib/docker/container_worker.ex b/lib/docker/container_worker.ex new file mode 100644 index 0000000..fcd1edc --- /dev/null +++ b/lib/docker/container_worker.ex @@ -0,0 +1,152 @@ +defmodule Docker.ContainerWorker do + alias Docker.ContainerRegistry + alias Docker.Container + use GenServer + + @default_timeout 300_000 + @default_container_opts %{ + "NetworkDisabled" => true, + "HostConfig" => %{ + "Memory" => 50_000_000, + "MemorySwap" => 50_000_000, + "KernelMemory" => 50_000_000, + "CpuQuota" => 5_000 + } + } + + def start_link(opts \\ %{}) do + GenServer.start_link(__MODULE__, opts) + end + + def exec(pid, commands) do + GenServer.call(pid, {:exec, commands}, 15_000) + end + + def stdin(pid, stdin) do + GenServer.cast(pid, {:stdin, stdin}) + end + + def send_files(pid, blobs) when is_list(blobs) do + Enum.each(blobs, fn blob -> + send_file(pid, blob.name, blob.contents) + end) + end + + def send_file(pid, filename, file_contents) do + commands = save_file_commands(filename, file_contents) + GenServer.cast(pid, {:exec_detached, commands}) + end + + def stop(pid) do + GenServer.stop(pid, :shutdown) + end + + ### Server Callbacks + def init(opts \\ %{}) do + {:ok, timer_ref} = set_keep_alive_timer + case create_container(opts) do + {:ok, container_id} -> + {:ok, %{timer: timer_ref, container_id: container_id}} + {:error, _reason} -> + {:stop, :error_creating_container} + end + end + + def handle_call({:exec, commands}, _from, state) do + try do + container_id = Map.get(state, :container_id) + json = Container.exec_stream(host, container_id, + %{ + "Cmd" => commands, + "AttachStdout" => true, + "AttachStderr" => true + } + ) + {:reply, {:ok, json}, reset_timer(state)} + rescue + reason -> + {:reply, {:error, reason}, state} + end + end + + def handle_cast({:exec_detached, commands}, state) do + container_id = Map.get(state, :container_id) + Container.exec_detached(host, container_id, + %{ + "Cmd" => commands + } + ) + {:noreply, reset_timer(state)} + end + + def handle_cast({:stdin, stdin}, state) do + case state do + %{persistent_conn: connRef} -> + Container.stream_stdin(stdin, connRef) + {:noreply, reset_timer(state)} + %{container_id: container_id} -> + {:ok, %{connRef: connRef}} = Container.attach(host, container_id, self) + Container.stream_stdin(stdin, connRef) + new_state = Map.put(state, :persistent_conn, connRef) + {:noreply, reset_timer(new_state)} + end + end + + def terminate(:shutdown, state), do: kill_container(state) + def terminate(:shutdown, _exit_reason}, state), do: kill_container(state) + + + def handle_info({:hackney_response, _conn, message}, state) when is_binary(message) do + Docker.RequestMap.whereis_request(self) + |> send({:stdout, message}) + {:noreply, state} + end + + def handle_info(_msg, state) do + {:noreply, state} + end + + defp reset_timer(state = %{timer: timer_ref}) do + :timer.cancel(timer_ref) + {:ok, new_timer_ref} = set_keep_alive_timer + %{state | timer: new_timer_ref} + end + + defp set_keep_alive_timer do + :timer.apply_after(@default_timeout, GenServer, :stop, [self, {:shutdown, :timeout}]) + end + + defp host do + "127.0.0.1:2375" + end + + defp create_container(opts \\ %{}) do + try do + merge_opts = Map.merge(@default_container_opts, opts) + {:ok, %{id: container_id}} = Container.run host, merged_opts + ContainerRegistry.register_worker(self, container_id) + {:ok, container_id} + rescue + reason -> + IO.puts "Error creating container" + {:error, reason} + end + end + + defp save_file_commands(filename, file_contents) do + ["bash", "-c", ~s(cat << 'EOF' > #{filename}\n#{file_contents}\nEOF)] + end + + defp kill_container(%{container_id: container_id}) do + case Container.kill(host, container_id) do + {:ok, _ } -> + IO.puts "Container #{container_id} shutdown" + %{"message" => message} -> + IO.puts message + {:error, reason} -> + IO.puts "Error killing container" + IO.inspect reason + end + end +end + diff --git a/lib/docker/request_map.ex b/lib/docker/request_map.ex new file mode 100644 index 0000000..c9e8230 --- /dev/null +++ b/lib/docker/request_map.ex @@ -0,0 +1,42 @@ +defmodule Docker.RequestMap do + def start_link do + Agent.start_link(fn -> %{} end, name: __MODULE__) + end + + def register(request_pid, worker_pid) do + Agent.update(__MODULE__, fn(current) -> + Map.put( current, worker_pid, %{ request: request_pid }) + end) + end + + def unregister(worker_pid) do + Agent.update(__MODULE__, fn(current) -> + Map.drop(current, [worker_pid]) + end) + end + + def whereis_request(worker_pid) do + Agent.get(__MODULE__, fn(current) -> + current + |> Map.get(worker_pid) + |> Map.get(:request) + end) + end + + def find_by_request_pid(request_pid) do + Agent.get(__MODULE__, fn(current) -> + current + |> Enum.find(fn {_key, val} -> val == %{request: request_pid} end) + |> case do + {worker_pid. _val} -> worker_pid + nil -> nil + end + end) + end + + def list_all do + Agent.get(__MODULE__, fn(current) -> + current + end) + end +end From a638658987b69614df83e4ebaac41983733d5900 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 16:51:27 +0800 Subject: [PATCH 02/10] changed link for package --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 81dafee..30f3964 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule Docker.Mixfile do [ maintainers: ["Ming Xiang Chan"], licenses: ["GPL"], - links: %{"Github" => "https://github.com/NextAcademy/docker_elixir"} + links: %{"Github" => "https://github.com/Hinsei/docker_elixir"} ] end # Configuration for the OTP application From 450e0f92597cec2b0026926b9a27a8a3de3da958 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 16:58:30 +0800 Subject: [PATCH 03/10] changed link to branch instead of master --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 30f3964..bc06bc4 100644 --- a/mix.exs +++ b/mix.exs @@ -14,9 +14,9 @@ defmodule Docker.Mixfile do def package do [ - maintainers: ["Ming Xiang Chan"], + maintainers: ["Aiman"], licenses: ["GPL"], - links: %{"Github" => "https://github.com/Hinsei/docker_elixir"} + links: %{"Github" => "https://github.com/Hinsei/docker_elixir/tree/feat/mass_moving"} ] end # Configuration for the OTP application From 545f35d26a6cea2c919dc9202ea1c270938c4a01 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 17:48:32 +0800 Subject: [PATCH 04/10] used branch instead of master in mix.exs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index bc06bc4..977d79b 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule Docker.Mixfile do [ maintainers: ["Aiman"], licenses: ["GPL"], - links: %{"Github" => "https://github.com/Hinsei/docker_elixir/tree/feat/mass_moving"} + links: %{"Github" => "https://github.com/Hinsei/docker_elixir.git#feat/mass_moving"} ] end # Configuration for the OTP application From c835db7ea30a07669e57fa47a32a656e55b9af7a Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 17:59:06 +0800 Subject: [PATCH 05/10] branch specification error --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 977d79b..1c80d8b 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule Docker.Mixfile do [ maintainers: ["Aiman"], licenses: ["GPL"], - links: %{"Github" => "https://github.com/Hinsei/docker_elixir.git#feat/mass_moving"} + links: %{"Github" => "https://github.com/Hinsei/docker_elixir.git", branch: "feat/mass_moving"} ] end # Configuration for the OTP application From a367a4797dc25fafd62a4c3ef817965ec70f43ba Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 18:26:49 +0800 Subject: [PATCH 06/10] fixed spelling errors --- lib/docker/container_manager.ex | 2 +- lib/docker/container_worker.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/docker/container_manager.ex b/lib/docker/container_manager.ex index 384dba1..771a2c3 100644 --- a/lib/docker/container_manager.ex +++ b/lib/docker/container_manager.ex @@ -18,6 +18,6 @@ defmodule Docker.ContainerManager do worker(Docker.ContainerWorker, [], restart: :temporary) ] - supervise(children, startegy: :simple_one_for_one) + supervise(children, strategy: :simple_one_for_one) end end diff --git a/lib/docker/container_worker.ex b/lib/docker/container_worker.ex index fcd1edc..f9c8d70 100644 --- a/lib/docker/container_worker.ex +++ b/lib/docker/container_worker.ex @@ -93,7 +93,7 @@ defmodule Docker.ContainerWorker do end def terminate(:shutdown, state), do: kill_container(state) - def terminate(:shutdown, _exit_reason}, state), do: kill_container(state) + def terminate({:shutdown, _exit_reason}, state), do: kill_container(state) def handle_info({:hackney_response, _conn, message}, state) when is_binary(message) do From 214f06ccbd52b02f40a67de21427346c7b22b798 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 18:33:03 +0800 Subject: [PATCH 07/10] fixed multiple spelling errors --- lib/docker/container_worker.ex | 2 +- lib/docker/request_map.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/docker/container_worker.ex b/lib/docker/container_worker.ex index f9c8d70..99efb39 100644 --- a/lib/docker/container_worker.ex +++ b/lib/docker/container_worker.ex @@ -122,7 +122,7 @@ defmodule Docker.ContainerWorker do defp create_container(opts \\ %{}) do try do - merge_opts = Map.merge(@default_container_opts, opts) + merged_opts = Map.merge(@default_container_opts, opts) {:ok, %{id: container_id}} = Container.run host, merged_opts ContainerRegistry.register_worker(self, container_id) {:ok, container_id} diff --git a/lib/docker/request_map.ex b/lib/docker/request_map.ex index c9e8230..01ae44d 100644 --- a/lib/docker/request_map.ex +++ b/lib/docker/request_map.ex @@ -28,7 +28,7 @@ defmodule Docker.RequestMap do current |> Enum.find(fn {_key, val} -> val == %{request: request_pid} end) |> case do - {worker_pid. _val} -> worker_pid + {worker_pid, _val} -> worker_pid nil -> nil end end) From 010d39aab74d0cad1ffdbea47eeff22ec64b3970 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Mon, 19 Jun 2017 18:41:52 +0800 Subject: [PATCH 08/10] fixed warnings --- lib/docker/client.ex | 2 +- lib/docker/container.ex | 10 +++++----- lib/docker/container_registry.ex | 8 ++++---- lib/docker/container_worker.ex | 20 ++++++++++---------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/docker/client.ex b/lib/docker/client.ex index 81d1043..7ccd276 100644 --- a/lib/docker/client.ex +++ b/lib/docker/client.ex @@ -7,7 +7,7 @@ defmodule Docker.Client do def send_request(url, method, body \\ "", headers \\ [], opts \\ []) do json_body = Poison.encode!(body) json_headers = headers ++ [{"Content-Type", "application/json"}] - merged_opts = opts ++ default_options + merged_opts = opts ++ default_options() request!(method, url, json_body, json_headers, merged_opts) end diff --git a/lib/docker/container.ex b/lib/docker/container.ex index a67258a..b6a3390 100644 --- a/lib/docker/container.ex +++ b/lib/docker/container.ex @@ -50,7 +50,7 @@ defmodule Docker.Container do {:ok, %{id: id}} -> case start(host, id) do {:ok, _} -> {:ok, %{id: id}} - {:error, reason} -> {:error, :reason} + {:error, reason} -> {:error, reason} end {:error, reason} -> {:error, reason} end @@ -95,7 +95,7 @@ defmodule Docker.Container do def exec_start(host, exec_id, opts) do "#{host}/exec/#{exec_id}/start" - |> Client.send_request(:post, opts, [], [stream_to: self]) + |> Client.send_request(:post, opts, [], [stream_to: self()]) |> Response.parse(:exec_start) end @@ -111,7 +111,7 @@ defmodule Docker.Container do case exec_create(host, container_id, create_opts) do {:ok, %{id: exec_id}} -> case exec_start(host, exec_id, %{}) do - {:ok, _} -> stream_response + {:ok, _} -> stream_response() {:error, reason} -> {:error, reason} end {:error, reason} -> {:error, reason} @@ -130,11 +130,11 @@ defmodule Docker.Container do def logs_stream(host, container_id, opts \\ %{}) do "#{host}/containers/#{container_id}/logs" |> Client.add_query_params(opts) - |> Client.send_request(:get, [], [stream_to: self]) + |> Client.send_request(:get, [], [stream_to: self()]) |> parse_logs_stream_response end - defp parse_logs_stream_response(%HTTPoison.AsyncResponse{id: _ref}), do: stream_response + defp parse_logs_stream_response(%HTTPoison.AsyncResponse{id: _ref}), do: stream_response() @default_attach_params %{ stream: 1, diff --git a/lib/docker/container_registry.ex b/lib/docker/container_registry.ex index 741b086..af6da30 100644 --- a/lib/docker/container_registry.ex +++ b/lib/docker/container_registry.ex @@ -7,7 +7,7 @@ defmodule Docker.ContainerRegistry do end def register_worker(worker_pid, container_id) do - Genserver.cast( + GenServer.cast( __MODULE__, {:register_worker, worker_pid, container_id} ) @@ -18,11 +18,11 @@ defmodule Docker.ContainerRegistry do end def init(:ok) do - case Docker.Container.list(host) do + case Docker.Container.list(host()) do {:error, _reason} -> {:ok, %{}} ids -> - Enum.each(ids, &Docker.Container.kill(host, &1)) + Enum.each(ids, &Docker.Container.kill(host(), &1)) {:ok, %{}} end end @@ -48,7 +48,7 @@ defmodule Docker.ContainerRegistry do IO.inspect reason end Process.demonitor(ref) - new_state = state |> Map.delte(pid) + new_state = state |> Map.delete(pid) {:noreply, new_state} _ -> {:noreply, state} diff --git a/lib/docker/container_worker.ex b/lib/docker/container_worker.ex index 99efb39..8aa2219 100644 --- a/lib/docker/container_worker.ex +++ b/lib/docker/container_worker.ex @@ -43,7 +43,7 @@ defmodule Docker.ContainerWorker do ### Server Callbacks def init(opts \\ %{}) do - {:ok, timer_ref} = set_keep_alive_timer + {:ok, timer_ref} = set_keep_alive_timer() case create_container(opts) do {:ok, container_id} -> {:ok, %{timer: timer_ref, container_id: container_id}} @@ -55,7 +55,7 @@ defmodule Docker.ContainerWorker do def handle_call({:exec, commands}, _from, state) do try do container_id = Map.get(state, :container_id) - json = Container.exec_stream(host, container_id, + json = Container.exec_stream(host(), container_id, %{ "Cmd" => commands, "AttachStdout" => true, @@ -71,7 +71,7 @@ defmodule Docker.ContainerWorker do def handle_cast({:exec_detached, commands}, state) do container_id = Map.get(state, :container_id) - Container.exec_detached(host, container_id, + Container.exec_detached(host(), container_id, %{ "Cmd" => commands } @@ -85,7 +85,7 @@ defmodule Docker.ContainerWorker do Container.stream_stdin(stdin, connRef) {:noreply, reset_timer(state)} %{container_id: container_id} -> - {:ok, %{connRef: connRef}} = Container.attach(host, container_id, self) + {:ok, %{connRef: connRef}} = Container.attach(host(), container_id, self()) Container.stream_stdin(stdin, connRef) new_state = Map.put(state, :persistent_conn, connRef) {:noreply, reset_timer(new_state)} @@ -97,7 +97,7 @@ defmodule Docker.ContainerWorker do def handle_info({:hackney_response, _conn, message}, state) when is_binary(message) do - Docker.RequestMap.whereis_request(self) + Docker.RequestMap.whereis_request(self()) |> send({:stdout, message}) {:noreply, state} end @@ -108,12 +108,12 @@ defmodule Docker.ContainerWorker do defp reset_timer(state = %{timer: timer_ref}) do :timer.cancel(timer_ref) - {:ok, new_timer_ref} = set_keep_alive_timer + {:ok, new_timer_ref} = set_keep_alive_timer() %{state | timer: new_timer_ref} end defp set_keep_alive_timer do - :timer.apply_after(@default_timeout, GenServer, :stop, [self, {:shutdown, :timeout}]) + :timer.apply_after(@default_timeout, GenServer, :stop, [self(), {:shutdown, :timeout}]) end defp host do @@ -123,8 +123,8 @@ defmodule Docker.ContainerWorker do defp create_container(opts \\ %{}) do try do merged_opts = Map.merge(@default_container_opts, opts) - {:ok, %{id: container_id}} = Container.run host, merged_opts - ContainerRegistry.register_worker(self, container_id) + {:ok, %{id: container_id}} = Container.run host(), merged_opts + ContainerRegistry.register_worker(self(), container_id) {:ok, container_id} rescue reason -> @@ -138,7 +138,7 @@ defmodule Docker.ContainerWorker do end defp kill_container(%{container_id: container_id}) do - case Container.kill(host, container_id) do + case Container.kill(host(), container_id) do {:ok, _ } -> IO.puts "Container #{container_id} shutdown" %{"message" => message} -> From bcaeaddeb588f8979d5d92e89336a6245b5a7f24 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Tue, 20 Jun 2017 14:48:29 +0800 Subject: [PATCH 09/10] fixed warnings --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 1c80d8b..a55661f 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule Docker.Mixfile do build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, description: "Api wrapper for Docker API in Elixir", - package: package, + package: package(), deps: deps()] end From 475361dc4b553a9f9848744e639d274fa154c1a2 Mon Sep 17 00:00:00 2001 From: Hinsei Date: Thu, 22 Jun 2017 17:29:14 +0800 Subject: [PATCH 10/10] return maintainer to rightful person --- lib/docker/container_registry.ex | 2 +- mix.exs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/docker/container_registry.ex b/lib/docker/container_registry.ex index af6da30..5206763 100644 --- a/lib/docker/container_registry.ex +++ b/lib/docker/container_registry.ex @@ -41,7 +41,7 @@ defmodule Docker.ContainerRegistry do case Map.get(state, pid) do {container_id, ^ref} -> try do - Container.kill(host, container_id) + Container.kill(host(), container_id) rescue reason -> IO.puts "Error Killing Container" diff --git a/mix.exs b/mix.exs index a55661f..a4fe31d 100644 --- a/mix.exs +++ b/mix.exs @@ -14,9 +14,9 @@ defmodule Docker.Mixfile do def package do [ - maintainers: ["Aiman"], + maintainers: ["Ming Xiang Chan"], licenses: ["GPL"], - links: %{"Github" => "https://github.com/Hinsei/docker_elixir.git", branch: "feat/mass_moving"} + links: %{"Github" => "https://github.com/NextAcademy/docker_elixir"} ] end # Configuration for the OTP application