From 168d90c6f9b8794d91e4af79493de255620c6947 Mon Sep 17 00:00:00 2001 From: connor rigby Date: Mon, 22 Jan 2018 10:12:43 -0800 Subject: [PATCH] Download/Upload settings from API. * `config_update` for the os is depricated. * Add new bootstrap task to download or upload settings. * Factory reset will `delete` settings from the API. (Or at least try too) * Add `DELETE` and `PUT` methods to HTTP adapter. * Download settings on `sync` --- lib/farmbot/bootstrap/settings_sync.ex | 78 +++++++++++++++++++ lib/farmbot/bootstrap/supervisor.ex | 3 +- lib/farmbot/bot_state/transport/amqp/amqp.ex | 13 ++++ .../celery_script/ast/node/config_update.ex | 59 +------------- .../celery_script/ast/node/factory_reset.ex | 1 + lib/farmbot/http/http.ex | 18 +++++ lib/farmbot/repo/repo.ex | 2 +- 7 files changed, 116 insertions(+), 58 deletions(-) create mode 100644 lib/farmbot/bootstrap/settings_sync.ex diff --git a/lib/farmbot/bootstrap/settings_sync.ex b/lib/farmbot/bootstrap/settings_sync.ex new file mode 100644 index 00000000..8983cd8f --- /dev/null +++ b/lib/farmbot/bootstrap/settings_sync.ex @@ -0,0 +1,78 @@ +defmodule Farmbot.Bootstrap.SettingsSync do + use Task, restart: :transient + use Farmbot.Logger + import Farmbot.System.ConfigStorage, only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0] + + def start_link() do + Task.start_link(__MODULE__, :run, []) + end + + def run() do + with {:ok, %{body: body, status_code: 200}} <- Farmbot.HTTP.get("/api/fbos_config"), + {:ok, data} <- Poison.decode(body) + do + do_sync_settings(data) + else + {:ok, status_code: code} -> + Logger.error 1, "HTTP error syncing settings: #{code}" + :ok + err -> + Logger.error 1, "Error syncing settings: #{inspect err}" + :ok + end + rescue + err -> + Logger.error 1, "Error syncing settings: #{Exception.message(err)}" + end + + def apply_map(old_map, new_map) do + Enum.all?(new_map, fn({key, new_value}) -> + if old_map[key] != new_value do + case new_value do + val when is_boolean(val) -> update_config_value(:bool, "settings", key, new_value) + val when is_binary(val) -> update_config_value(:string, "settings", key, new_value) + val when is_number(val) -> update_config_value(:float, "settings", key, new_value) + end + end + end) + end + + def do_sync_settings(api_data) do + Logger.info 3, "API is the source of truth; Downloading data." + old_config = get_config_as_map()["settings"] + apply_map(old_config, api_data) + :ok + end + + def do_sync_settings(_unimportant_data) do + Logger.info 3, "FBOS is the source of truth; Uploading data." + auto_sync = get_config_value(:bool, "settings", "auto_sync") + beta_opt_in = get_config_value(:bool, "settings", "beta_opt_in") + disable_factory_reset = get_config_value(:bool, "settings", "disable_factory_reset") + firmware_output_log = get_config_value(:bool, "settings", "firmware_output_log") + sequence_body_log = get_config_value(:bool, "settings", "sequence_body_log") + sequence_complete_log = get_config_value(:bool, "settings", "sequence_complete_log") + sequence_init_log = get_config_value(:bool, "settings", "sequence_init_log") + arduino_debug_messages = get_config_value(:bool, "settings", "arduino_debug_messages") + os_auto_update = get_config_value(:bool, "settings", "os_auto_update") + firmware_hardware = get_config_value(:string, "settings", "firmware_hardware") + network_not_found_timer = get_config_value(:float, "settings", "network_not_found_timer") + payload = %{ + # api_migrated: true, + auto_sync: auto_sync, + beta_opt_in: beta_opt_in, + disable_factory_reset: disable_factory_reset, + firmware_output_log: firmware_output_log, + sequence_body_log: sequence_body_log, + sequence_complete_log: sequence_complete_log, + sequence_init_log: sequence_init_log, + arduino_debug_messages: arduino_debug_messages, + os_auto_update: os_auto_update, + firmware_hardware: firmware_hardware, + network_not_found_timer: network_not_found_timer, + } |> Poison.encode!() + Farmbot.HTTP.delete!("/api/fbos_config") + Farmbot.HTTP.put!("/api/fbos_config", payload) + :ok + end +end diff --git a/lib/farmbot/bootstrap/supervisor.ex b/lib/farmbot/bootstrap/supervisor.ex index f444e49e..3a7b4179 100644 --- a/lib/farmbot/bootstrap/supervisor.ex +++ b/lib/farmbot/bootstrap/supervisor.ex @@ -125,11 +125,12 @@ defmodule Farmbot.Bootstrap.Supervisor do children = [ worker(Farmbot.Bootstrap.AuthTask, []), + supervisor(Farmbot.HTTP.Supervisor, []), + worker(Farmbot.Bootstrap.SettingsSync, [], [restart: :transient]), supervisor(Farmbot.Firmware.Supervisor, []), supervisor(Farmbot.BotState.Supervisor, []), supervisor(Farmbot.BotState.Transport.Supervisor, []), supervisor(Farmbot.FarmEvent.Supervisor, []), - supervisor(Farmbot.HTTP.Supervisor, []), supervisor(Farmbot.Repo.Supervisor, []), supervisor(Farmbot.Farmware.Supervisor, []), supervisor(Farmbot.Regimen.Supervisor, []), diff --git a/lib/farmbot/bot_state/transport/amqp/amqp.ex b/lib/farmbot/bot_state/transport/amqp/amqp.ex index 1ea1605c..83dea467 100644 --- a/lib/farmbot/bot_state/transport/amqp/amqp.ex +++ b/lib/farmbot/bot_state/transport/amqp/amqp.ex @@ -178,6 +178,8 @@ defmodule Farmbot.BotState.Transport.AMQP do ["bot", ^device, "sync", resource, _] when resource in ["Log", "User", "Image", "WebcamFeed"] -> {:noreply, [], state} + ["bot", ^device, "sync", "FbosConfig", id] -> + handle_fbos_config(id, payload, state) ["bot", ^device, "sync", resource, id] -> handle_sync_cmd(resource, id, payload, state) ["bot", ^device, "logs"] -> {:noreply, [], state} @@ -231,6 +233,17 @@ defmodule Farmbot.BotState.Transport.AMQP do {:noreply, [], state} end + def handle_fbos_config(_id, payload, state) do + case Poison.decode(payload) do + # TODO(Connor) What do I do with deletes? + {:ok, %{"body" => nil}} -> {:noreply, [], state} + {:ok, %{"body" => config}} -> + config = Map.drop(config, ["created_at", "updated_at", "id"]) + Farmbot.Bootstrap.SettingsSync.apply_map(state.state_cache.configuration, config) + {:noreply, [], state} + end + end + defp push_bot_log(chan, bot, log) do json = Poison.encode!(log) :ok = AMQP.Basic.publish chan, @exchange, "bot.#{bot}.logs", json diff --git a/lib/farmbot/celery_script/ast/node/config_update.ex b/lib/farmbot/celery_script/ast/node/config_update.ex index 02a8b2f2..e28c11a3 100644 --- a/lib/farmbot/celery_script/ast/node/config_update.ex +++ b/lib/farmbot/celery_script/ast/node/config_update.ex @@ -4,33 +4,17 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do use Farmbot.Logger allow_args [:package] - def execute(%{package: :farmbot_os}, body, env) do - Logger.warn 2, "`config_update` is a depricated RPC." + def execute(%{package: :farmbot_os}, _body, env) do + Logger.warn 2, "`config_update` for FBOS is depricated." env = mutate_env(env) - do_reduce_os(body, env) + {:ok, env} end def execute(%{package: :arduino_firmware}, body, env) do - Logger.warn 2, "`config_update` is a depricated RPC." env = mutate_env(env) do_reduce_fw(body, env) end - defp do_reduce_os([%{args: %{label: key, value: value}} | rest], env) do - case lookup_os_config(key, value) do - {:ok, {type, group, value}} -> - Farmbot.System.ConfigStorage.update_config_value(type, group, key, value) - Logger.success 3, "Updating: #{inspect key}: #{value}" - do_reduce_os(rest, env) - {:error, reason} -> - {:error, reason, env} - end - end - - defp do_reduce_os([], env) do - {:ok, env} - end - defp do_reduce_fw([%{args: %{label: key, value: value}} | rest], env) do case Farmbot.Firmware.update_param(:"#{key}", value) do :ok -> do_reduce_fw(rest, env) @@ -39,41 +23,4 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do end defp do_reduce_fw([], env), do: {:ok, env} - - defp lookup_os_config("os_auto_update", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("auto_sync", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("timezone", val), do: {:ok, {:string, "settings", val}} - - defp lookup_os_config("disable_factory_reset", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - - defp lookup_os_config("sequence_init_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("sequence_body_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("sequence_complete_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("arduino_debug_messages", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("firmware_input_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("firmware_output_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("beta_opt_in", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - defp lookup_os_config("api_migrated", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}} - - defp lookup_os_config("network_not_found_timer", val) when val > 0, do: {:ok, {:float, "settings", to_float(val)}} - defp lookup_os_config("network_not_found_timer", _val), do: {:error, "network_not_found_timer must be greater than zero"} - - defp lookup_os_config("firmware_hardware", "farmduino"), do: {:ok, {:string, "settings", "farmduino"}} - defp lookup_os_config("firmware_hardware", "arduino"), do: {:ok, {:string, "settings", "arduino"}} - defp lookup_os_config("firmware_hardware", unknown), do: {:error, "unknown hardware: #{unknown}"} - - defp lookup_os_config(unknown_config, _), do: {:error, "unknown config: #{unknown_config}"} - - defp format_bool_for_os(1), do: true - defp format_bool_for_os(0), do: false - defp format_bool_for_os(true), do: true - defp format_bool_for_os(false), do: false - - defp to_float(int) when is_integer(int) do - int / 1 - end - - defp to_float(float) when is_float(float) do - float - end end diff --git a/lib/farmbot/celery_script/ast/node/factory_reset.ex b/lib/farmbot/celery_script/ast/node/factory_reset.ex index 9bb00209..60daf30f 100644 --- a/lib/farmbot/celery_script/ast/node/factory_reset.ex +++ b/lib/farmbot/celery_script/ast/node/factory_reset.ex @@ -9,6 +9,7 @@ defmodule Farmbot.CeleryScript.AST.Node.FactoryReset do Farmbot.BotState.set_sync_status(:maintenance) Farmbot.BotState.force_state_push() Farmbot.System.ConfigStorage.update_config_value(:bool, "settings", "disable_factory_reset", false) + Farmbot.HTTP.delete("/api/fbos_config") Logger.warn 1, "Farmbot OS going down for factory reset!" Farmbot.System.factory_reset "CeleryScript request." {:ok, env} diff --git a/lib/farmbot/http/http.ex b/lib/farmbot/http/http.ex index a88899fe..03cfb5d8 100644 --- a/lib/farmbot/http/http.ex +++ b/lib/farmbot/http/http.ex @@ -88,6 +88,24 @@ defmodule Farmbot.HTTP do request(:put, url, body, headers, opts) end + def put!(url, body, headers \\ [], opts \\ []) + + def put!(url, body, headers, opts) do + request!(:put, url, body, headers, opts) + end + + def delete(url, headers \\ [], opts \\ []) + + def delete(url, headers, opts) do + request!(:delete, url, "", headers, opts) + end + + def delete!(url, headers \\ [], opts \\ []) + + def delete!(url, headers, opts) do + request!(:delete, url, "", headers, opts) + end + @doc "Download a file to the filesystem." def download_file(url, path, diff --git a/lib/farmbot/repo/repo.ex b/lib/farmbot/repo/repo.ex index 0cd620de..36d96b1c 100644 --- a/lib/farmbot/repo/repo.ex +++ b/lib/farmbot/repo/repo.ex @@ -370,7 +370,7 @@ defmodule Farmbot.Repo do case do_sync_all_resources(repo_a) do :ok -> do_sync_all_resources(repo_b) - + Farmbot.Bootstrap.SettingsSync.run() err -> err end