110 lines
3.3 KiB
Elixir
110 lines
3.3 KiB
Elixir
defmodule Farmbot.Farmware.Runtime do
|
|
@moduledoc "Handles execution of a Farmware."
|
|
|
|
alias Farmbot.Farmware
|
|
alias Farmware.{RuntimeError, Installer}
|
|
use Farmbot.Logger
|
|
|
|
@fbos_version Mix.Project.config()[:version]
|
|
|
|
defstruct [:farmware, :env, :port, :exit_status, :working_dir, :return_dir]
|
|
|
|
@doc "Execute a Farmware struct."
|
|
def execute(%Farmware{} = farmware, env) when is_list(env) do
|
|
Logger.busy(2, "Beginning execution of #{inspect(farmware)}")
|
|
fw_path = Installer.install_path(farmware) |> Path.absname("#{:code.priv_dir(:farmbot)}/..")
|
|
cwd = File.cwd!()
|
|
with :ok <- File.cd(fw_path),
|
|
env <- build_env(farmware, env) do
|
|
exec = farmware.executable
|
|
|
|
opts = [
|
|
:stream,
|
|
:binary,
|
|
:exit_status,
|
|
:hide,
|
|
:use_stdio,
|
|
:stderr_to_stdout,
|
|
args: farmware.args,
|
|
env: env
|
|
]
|
|
|
|
port = Port.open({:spawn_executable, exec}, opts)
|
|
|
|
handle_port(
|
|
struct(
|
|
__MODULE__,
|
|
port: port,
|
|
env: env,
|
|
farmware: farmware,
|
|
working_dir: fw_path,
|
|
return_dir: cwd
|
|
)
|
|
)
|
|
else
|
|
{:error, err} ->
|
|
File.cd(cwd)
|
|
raise RuntimeError, state: nil, message: err
|
|
end
|
|
|> do_cleanup()
|
|
end
|
|
|
|
defp do_cleanup(%__MODULE__{return_dir: return_dir} = state) do
|
|
File.cd(return_dir)
|
|
state
|
|
end
|
|
|
|
defp handle_port(%__MODULE__{port: port, farmware: farmware} = state) do
|
|
receive do
|
|
{^port, {:exit_status, 0}} ->
|
|
Logger.success(2, "#{inspect(farmware)} completed without errors.")
|
|
%{state | exit_status: 0}
|
|
|
|
{^port, {:exit_status, status}} ->
|
|
Logger.warn(2, "#{inspect(farmware)} completed with exit status: #{status}")
|
|
%{state | exit_status: status}
|
|
|
|
{^port, {:data, data}} ->
|
|
msg = ["[#{inspect(farmware)}] sent data:",
|
|
"\r\n\=\=\=\=\=\=\=\=\=\=\=\r\n\r\n",
|
|
data,
|
|
"\r\n\=\=\=\=\=\=\=\=\=\=\=\r\n\r\n"] |> Enum.join()
|
|
# Logger.info(3, msg, color: :NC)
|
|
IO.puts msg
|
|
handle_port(state)
|
|
end
|
|
end
|
|
|
|
def build_env(%Farmware{config: config, name: fw_name} = _farmware, env) do
|
|
token = Farmbot.System.ConfigStorage.get_config_value(:string, "authorization", "token")
|
|
images_dir = "/tmp/images"
|
|
|
|
config
|
|
|> Enum.filter(&match?(%{"label" => _, "name" => _, "value" => _}, &1))
|
|
|> Map.new(&format_config(fw_name, &1))
|
|
|> Map.put("API_TOKEN", token)
|
|
|> Map.put("FARMWARE_TOKEN", token)
|
|
|> Map.put("IMAGES_DIR", images_dir)
|
|
|> Map.put("FARMWARE_URL", "http://localhost:27347/")
|
|
|> Map.put("FARMBOT_OS_VERSION", @fbos_version)
|
|
|> Map.merge(Farmbot.BotState.get_user_env())
|
|
|> Map.merge(Map.new(env))
|
|
|> Enum.map(fn {key, val} -> {to_erl_safe(key), to_erl_safe(val)} end)
|
|
end
|
|
|
|
defp format_config(fw_name, %{"label" => _, "name" => name, "value" => val}) do
|
|
sep =
|
|
case String.contains?(fw_name, "-") do
|
|
true -> "-"
|
|
false -> " "
|
|
end
|
|
|
|
ns = String.split(fw_name, sep) |> Enum.join() |> Macro.underscore()
|
|
{"#{ns}_#{name}", val}
|
|
end
|
|
|
|
defp to_erl_safe(binary) when is_binary(binary), do: to_charlist(binary)
|
|
defp to_erl_safe(map) when is_map(map), do: map |> Poison.encode!() |> to_erl_safe()
|
|
defp to_erl_safe(number) when is_number(number), do: number
|
|
end
|