Skip to main content

priv/templates/k8s/remote.exs

defmodule Remote do
  node_str = System.get_env("REMOTE")
  node = String.to_atom(node_str)
  [host, _] = String.split(node_str, "@")
  if not Node.connect(node), do: raise "Cannot connect to #{node}"
  IO.puts("Connected to #{inspect Node.list(:hidden)}")
  :persistent_term.put(:remote_node, node)
  :persistent_term.put(:remote_host, host)

  ebins = Path.join(System.get_env("ROOT"), "_build/dev/lib/**/ebin") |> Path.expand()
  IO.puts("Adding code paths in in #{ebins}")
  for dir <- Path.wildcard(ebins),
    do: :code.add_pathz(String.to_charlist(dir))

  deps = Path.join(System.get_env("ROOT"), "deps/*") |> Path.expand()
  deps = for dir <- Path.wildcard(deps), do: Path.basename(dir)

  IO.puts("Loading modules not in deps")
  for dir <- Path.wildcard(ebins) do
    ["ebin", base| _] = Path.split(dir) |> Enum.reverse()
    if not Enum.member?(deps, base) do
      {:ok, files} = File.ls(dir)

      for file <- files do
        if Path.extname(file) == ".beam" do
          module =
            Path.join(dir, file)
            |> String.to_charlist()
            |> :beam_lib.info()
            |> Keyword.fetch!(:module)

          Code.ensure_loaded!(module)
        end
      end
    end
  end

  def get_node(), do: :persistent_term.get(:remote_node)

  def get_all_nodes(), do: :rpc.call(get_node(), :erlang, :nodes, [])

  # get all nodes having the same host
  def get_all_hosts() do
    node = get_node()
    host = :persistent_term.get(:remote_host)

    get_all_nodes() |>
    Enum.reduce([node], fn node, acc ->
      case to_string(node) |> String.split("@") do
        [^host, _] -> [node | acc]
        _ -> acc
      end
    end)
  end

  def show(module) do
    show_code_local(module)
    show_code_remote(get_node(), module)
  end

  def show_all(module) do
    show_code_local(module)
    Enum.each(get_all_hosts(), fn node -> show_code_remote(node, module) end)
  end

  def load(module) do
    show(module)
    load_code(get_node(), module)
  end

  def load_all(module) do
    Enum.each(get_all_hosts(), fn node -> show_code_remote(node, module) end)
    show_code_local(module)
    Enum.each(get_all_hosts(), fn node -> load_code(node, module) end)
  end

  def revert(module) do
    show(module)
    revert_code(get_node(), module)
    show(module)
  end

  def revert_all(module) do
    show_all(module)
    Enum.each(get_all_hosts(), fn node -> revert_code(node, module) end)
    show_all(module)
  end

  defp show_code_local(module) do
    :code.purge(module)
    :code.load_file(module)
    IO.puts("LOCAL version is:  #{inspect :erlang.phash2(module.module_info(:md5))}")
  end

  defp show_code_remote(node, module) do
    remote = :rpc.call(node, module, :module_info, [:md5])
    IO.puts("REMOTE version is: #{inspect :erlang.phash2(remote)} (#{inspect node})")
  end

  defp load_code(node, module) do
    case :code.get_object_code(module) do
      {^module, bin, fname} ->
        case :rpc.call(node, :code, :load_binary, [module, fname, bin]) do
          {:module, ^module} ->
            show_code_remote(node, module)

          other ->
            other
        end

      other ->
        {:error, other}
    end
  end

  def revert_code(node, module) do
    :rpc.call(node, :code, :purge, [module])
    :rpc.call(node, :code, :get_object_code, [module])
  end

end