Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Example Elixir Pods

EXAMPLES.livemd

Example Elixir Pods

Mix.install([
  {:pods, path: "./pods"},
  {:pod_lispyclouds_sqlite, path: "./pods/example"},
  {:erlexec, "~> 2.0"},
  {:jason, "~> 1.4"},
  {:bento, "~> 1.0"}
])

require Logger

Example Client

The example process manager uses https://github.com/saleyn/erlexec/ but you can implement the pod services using System.cmd or Erlang Ports or any other solution if you want.

The only requirement is that it can allow stdin and stdout interactions.

defmodule Decoder do
  use Pods.Decoder
  def decode(message, "bencode"), do: Bento.decode(message)
  def decode(message, "json"), do: Jason.decode(message)
  def decode!(message, "bencode"), do: Bento.decode!(message)
  def decode!(message, "json"), do: Jason.decode!(message)
end

defmodule Encoder do
  use Pods.Encoder
  def encode(message, "bencode"), do: Bento.encode(message)
  def encode(message, "json"), do: Jason.encode(message)
  def encode!(message, "bencode"), do: Bento.encode!(message)
  def encode!(message, "json"), do: Jason.encode!(message)
end

defmodule Handler do
  use Pods.Handler

  def event(payload, type) do
    Logger.info("Got Pod Event #{type}")
    Logger.debug(payload)
  end

  def success(payload) do
    Logger.info("Got Pod Success Response")
    Logger.debug(payload)
  end

  def error(payload) do
    Logger.info("Got Pod Error Response")
    Logger.debug(payload)
  end

  def exception(payload) do
    Logger.info("Got Pod Exception")
    Logger.debug(payload)
  end
end

defmodule Manager do
  use Pods.Manager

  def init() do
    Logger.info("Manager started")
    :exec.start()
  end

  def stop(pod) do
    Logger.info("Stopping Pod #{pod.module}")
    :exec.stop(pod.pid)
  end

  def send(pod, message) do
    Logger.info("Sending Message to Pod #{pod.module}")
    :exec.send(pod.pid, message)
  end

  def start(executable, decoder, out_handler, exception_handler, opts \\ []) do
    temp_dir = System.tmp_dir!()
    temp_file_stdout = Path.join([temp_dir, Path.basename(executable) <> ".stdout"])

    # Some processes may write a lot of text (streams), so we try to decode a cache
    # until it works and then send the data to the handler
    # maybe a better solution exists, but it works for now.
    # can be a genserver? or maybe mnesia?
    stdout_watcher = fn origin, pid, response ->
      File.write!(temp_file_stdout, response, [:append])

      case decoder.decode(String.trim(File.read!(temp_file_stdout)), "bencode") do
        {:ok, _data} ->
          out_handler.(%{origin: origin, pid: pid, response: response})
          File.rm_rf!(temp_file_stdout)

        _ ->
          nil
      end
    end

    # exceptions may not be in bencode, so we send the raw response
    stderr_watcher = fn origin, pid, response ->
      exception_handler.(%{origin: origin, pid: pid, response: response})
    end

    # start the process with ELIXIR_POD in the env
    case :exec.run_link(
           executable,
           [
             :stdin,
             {:stdout, stdout_watcher},
             {:stderr, stderr_watcher},
             :monitor,
             {:env, [{"ELIXIR_POD", "1"}]}
           ] ++ opts
         ) do
      {:ok, _, pid} -> {:ok, pid}
      _ = error -> {:error, error}
    end
  end
end
{:module, Manager, <<70, 79, 82, 49, 0, 0, 20, ...>>, {:start, 5}}

Example Pod Usage

# Start the Pods Services
pods =
  Pods.start(
    # Available Pods List
    [Pod.LispyClouds.SQLite],
    # Pod Manager
    Manager,
    # Message Encoder
    Encoder,
    # Message Decoder
    Decoder,
    # stdout and stderr handler
    Handler
  )

# Use the Pods
pods
|> Pod.LispyClouds.SQLite.execute!("create table if not exists foo ( int foo )")
|> Pod.LispyClouds.SQLite.execute!("delete from foo")
|> Pod.LispyClouds.SQLite.execute!("insert into foo values (1), (2)")
|> Pod.LispyClouds.SQLite.execute!("select * from foo")
|> then(fn pods ->
  # Give a little time to complete the operations
  receive do
  after
    2000 ->
      Pods.stop(pods, :all)
  end
end)

16:01:26.921 [info] Manager started

16:01:26.925 [info] Got Pod Event before_op

16:01:26.925 [debug] [args: [], command: "", id: "018f35bf-658d-744e-90dd-36a92005b5f7", message: "d2:id36:018f35bf-658d-744e-90dd-36a92005b5f72:op8:describee", op: "describe", result: %{}, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.925 [info] Sending Message to Pod Elixir.Pod.LispyClouds.SQLite

16:01:26.925 [info] Got Pod Event ready

16:01:26.925 [debug] [args: [], command: "", id: "018f35bf-658d-744e-90dd-36a92005b5f7", message: "d2:id36:018f35bf-658d-744e-90dd-36a92005b5f72:op8:describee", op: "describe", result: :ok, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.926 [info] Invoke command in Pod

16:01:26.926 [debug] [args: ["create table if not exists foo ( int foo )"], command: "execute!", module: Pod.LispyClouds.SQLite]

16:01:26.926 [info] Got Pod Event before_op

16:01:26.926 [debug] [args: ["create table if not exists foo ( int foo )"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-658e-7ac6-ad20-3eaadd97065d", message: "d4:args46:[\"create table if not exists foo ( int foo )\"]2:id36:018f35bf-658e-7ac6-ad20-3eaadd97065d2:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: %{}, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.926 [info] Sending Message to Pod Elixir.Pod.LispyClouds.SQLite

16:01:26.927 [info] Got Pod Event after_op

16:01:26.927 [debug] [args: ["create table if not exists foo ( int foo )"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-658e-7ac6-ad20-3eaadd97065d", message: "d4:args46:[\"create table if not exists foo ( int foo )\"]2:id36:018f35bf-658e-7ac6-ad20-3eaadd97065d2:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: :ok, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.927 [info] Invoke command in Pod

16:01:26.927 [debug] [args: ["delete from foo"], command: "execute!", module: Pod.LispyClouds.SQLite]

16:01:26.927 [info] Got Pod Event before_op

16:01:26.927 [debug] [args: ["delete from foo"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-658f-761a-ad35-f9da6d1a0384", message: "d4:args19:[\"delete from foo\"]2:id36:018f35bf-658f-761a-ad35-f9da6d1a03842:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: %{}, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.927 [info] Sending Message to Pod Elixir.Pod.LispyClouds.SQLite

16:01:26.928 [info] Got Pod Event after_op

16:01:26.928 [debug] [args: ["delete from foo"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-658f-761a-ad35-f9da6d1a0384", message: "d4:args19:[\"delete from foo\"]2:id36:018f35bf-658f-761a-ad35-f9da6d1a03842:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: :ok, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.928 [info] Invoke command in Pod

16:01:26.928 [debug] [args: ["insert into foo values (1), (2)"], command: "execute!", module: Pod.LispyClouds.SQLite]

16:01:26.928 [info] Got Pod Event before_op

16:01:26.928 [debug] [args: ["insert into foo values (1), (2)"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-6590-7694-a2f5-b45d6c1f3396", message: "d4:args35:[\"insert into foo values (1), (2)\"]2:id36:018f35bf-6590-7694-a2f5-b45d6c1f33962:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: %{}, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.928 [info] Sending Message to Pod Elixir.Pod.LispyClouds.SQLite

16:01:26.928 [info] Got Pod Event after_op

16:01:26.928 [debug] [args: ["insert into foo values (1), (2)"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-6590-7694-a2f5-b45d6c1f3396", message: "d4:args35:[\"insert into foo values (1), (2)\"]2:id36:018f35bf-6590-7694-a2f5-b45d6c1f33962:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: :ok, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.929 [info] Invoke command in Pod

16:01:26.938 [debug] [args: ["select * from foo"], command: "execute!", module: Pod.LispyClouds.SQLite]

16:01:26.938 [info] Got Pod Event before_op

16:01:26.938 [debug] [args: ["select * from foo"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-659a-7bf8-b912-098a0b14c8dc", message: "d4:args21:[\"select * from foo\"]2:id36:018f35bf-659a-7bf8-b912-098a0b14c8dc2:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: %{}, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:26.939 [info] Sending Message to Pod Elixir.Pod.LispyClouds.SQLite

16:01:26.939 [info] Got Pod Event after_op

16:01:26.939 [debug] [args: ["select * from foo"], command: "pod.lispyclouds.sqlite/execute!", id: "018f35bf-659a-7bf8-b912-098a0b14c8dc", message: "d4:args21:[\"select * from foo\"]2:id36:018f35bf-659a-7bf8-b912-098a0b14c8dc2:op6:invoke3:var31:pod.lispyclouds.sqlite/execute!e", op: "invoke", result: :ok, pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:27.047 [info] Got Pod Success Response

16:01:27.048 [debug] [id: 7915, raw: %{pid: 7915, origin: :stdout, response: "d6:format4:json10:namespacesld4:name22:pod.lispyclouds.sqlite4:varsld4:name8:execute!eeeee"}, status: :ok, result: [], pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:27.050 [info] Got Pod Success Response

16:01:27.050 [debug] [id: "018f35bf-658e-7ac6-ad20-3eaadd97065d", raw: %{pid: 7915, origin: :stdout, response: "d2:id36:018f35bf-658e-7ac6-ad20-3eaadd97065d6:statusl4:donee5:value2:[]e"}, status: :ok, result: [], pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:27.052 [info] Got Pod Success Response

16:01:27.052 [debug] [id: "018f35bf-658f-761a-ad35-f9da6d1a0384", raw: %{pid: 7915, origin: :stdout, response: "d2:id36:018f35bf-658f-761a-ad35-f9da6d1a03846:statusl4:donee5:value2:[]e"}, status: :ok, result: [], pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:27.055 [info] Got Pod Success Response

16:01:27.055 [debug] [id: "018f35bf-6590-7694-a2f5-b45d6c1f3396", raw: %{pid: 7915, origin: :stdout, response: "d2:id36:018f35bf-6590-7694-a2f5-b45d6c1f33966:statusl4:donee5:value2:[]e"}, status: :ok, result: [], pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:27.058 [info] Got Pod Success Response

16:01:27.058 [debug] [id: "018f35bf-659a-7bf8-b912-098a0b14c8dc", raw: %{pid: 7915, origin: :stdout, response: "d2:id36:018f35bf-659a-7bf8-b912-098a0b14c8dc6:statusl4:donee5:value10:[[1], [2]]e"}, status: :ok, result: [[1], [2]], pod: %{module: Pod.LispyClouds.SQLite, pid: 7915, manifest: Pod.LispyClouds.SQLite.Manifest}]

16:01:28.940 [info] Stopping all pods

16:01:28.940 [info] Stopping Pod Elixir.Pod.LispyClouds.SQLite
[%{pid: 7915, stop: :ok, pod: Pod.LispyClouds.SQLite}]