Start refactoring update handler. [WIP]
parent
0413855d74
commit
40d1d9ecde
|
@ -5,11 +5,11 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
|
||||||
|
|
||||||
def execute(%{package: :farmbot_os}, _, env) do
|
def execute(%{package: :farmbot_os}, _, env) do
|
||||||
env = mutate_env(env)
|
env = mutate_env(env)
|
||||||
case Farmbot.System.Updates.check_updates(true) do
|
# case Farmbot.System.Updates.check_updates(true) do
|
||||||
:ok -> {:ok, env}
|
# :ok -> {:ok, env}
|
||||||
:no_update -> {:ok, env}
|
# :no_update -> {:ok, env}
|
||||||
_ -> {:error, "Failed to check updates", env}
|
# _ -> {:error, "Failed to check updates", env}
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(%{package: :arduino_firmware}, _, env) do
|
def execute(%{package: :arduino_firmware}, _, env) do
|
||||||
|
|
|
@ -21,7 +21,6 @@ defmodule Farmbot.System.Debug do
|
||||||
]
|
]
|
||||||
children = [
|
children = [
|
||||||
Plug.Adapters.Cowboy.child_spec(:http, DebugRouter, [], options),
|
Plug.Adapters.Cowboy.child_spec(:http, DebugRouter, [], options),
|
||||||
worker(Farmbot.System.Updates.SlackUpdater, []),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
opts = [strategy: :one_for_one]
|
opts = [strategy: :one_for_one]
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
defmodule Farmbot.System.Updates.SlackUpdater do
|
|
||||||
@moduledoc """
|
|
||||||
Development module for auto flashing fw updates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@token System.get_env("SLACK_TOKEN")
|
|
||||||
@target Farmbot.Project.target()
|
|
||||||
@data_path Application.get_env(:farmbot, :data_path)
|
|
||||||
|
|
||||||
use Farmbot.Logger
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
defmodule RTMSocket do
|
|
||||||
@moduledoc false
|
|
||||||
def start_link(url, cb) do
|
|
||||||
pid = spawn(__MODULE__, :socket_init, [url, cb])
|
|
||||||
{:ok, pid}
|
|
||||||
end
|
|
||||||
|
|
||||||
def socket_init("wss://" <> url, cb) do
|
|
||||||
[domain | rest] = String.split(url, "/")
|
|
||||||
|
|
||||||
domain
|
|
||||||
|> Socket.Web.connect!(secure: true, path: "/" <> Enum.join(rest, "/"))
|
|
||||||
|> socket_loop(cb)
|
|
||||||
end
|
|
||||||
|
|
||||||
def socket_init("ws://" <> url, cb) do
|
|
||||||
[domain | rest] = String.split(url, "/")
|
|
||||||
|
|
||||||
domain
|
|
||||||
|> Socket.Web.connect!(secure: false, path: "/" <> Enum.join(rest, "/"))
|
|
||||||
|> socket_loop(cb)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp socket_loop(socket, cb) do
|
|
||||||
case socket |> Socket.Web.recv!() do
|
|
||||||
{:text, data} -> data |> Poison.decode!() |> handle_data(socket, cb)
|
|
||||||
{:ping, _} -> Socket.Web.send!(socket, {:pong, ""})
|
|
||||||
end
|
|
||||||
|
|
||||||
receive do
|
|
||||||
{:stop, reason} ->
|
|
||||||
term_msg =
|
|
||||||
%{
|
|
||||||
type: "message",
|
|
||||||
id: 2,
|
|
||||||
channel: "C58DCU4A3",
|
|
||||||
text: ":farmbot-genesis: #{node()} Disconnected (#{inspect(reason)})"
|
|
||||||
}
|
|
||||||
|> Poison.encode!()
|
|
||||||
|
|
||||||
Socket.Web.send!(socket, {:text, term_msg})
|
|
||||||
Socket.Web.close(socket)
|
|
||||||
|
|
||||||
data ->
|
|
||||||
Socket.Web.send!(socket, {:text, Poison.encode!(data)})
|
|
||||||
socket_loop(socket, cb)
|
|
||||||
after
|
|
||||||
500 -> socket_loop(socket, cb)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_data(%{"type" => "hello"}, socket, _) do
|
|
||||||
Logger.success(3, "Connected to slack!")
|
|
||||||
|
|
||||||
msg =
|
|
||||||
%{
|
|
||||||
type: "message",
|
|
||||||
id: 1,
|
|
||||||
channel: "C58DCU4A3",
|
|
||||||
text: ":farmbot-genesis: #{node()} Connected!"
|
|
||||||
}
|
|
||||||
|> Poison.encode!()
|
|
||||||
|
|
||||||
Socket.Web.send!(socket, {:text, msg})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_data(msg, _socket, cb), do: send(cb, {:socket, msg})
|
|
||||||
end
|
|
||||||
|
|
||||||
def upload_file(file, channels \\ "C58DCU4A3") do
|
|
||||||
GenServer.call(__MODULE__, {:upload_file, file, channels}, :infinity)
|
|
||||||
end
|
|
||||||
|
|
||||||
def start_link() do
|
|
||||||
GenServer.start_link(__MODULE__, @token, name: __MODULE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
def init(nil) do
|
|
||||||
Logger.warn(3, "Not setting up slack (No slack token)")
|
|
||||||
:ignore
|
|
||||||
end
|
|
||||||
|
|
||||||
def init(token) do
|
|
||||||
url = "https://slack.com/api/rtm.connect"
|
|
||||||
payload = {:multipart, [{"token", token}]}
|
|
||||||
headers = [{'User-Agent', 'Farmbot HTTP Adapter'}]
|
|
||||||
|
|
||||||
with {:ok, %{status_code: 200, body: body}} <- HTTPoison.post(url, payload, headers),
|
|
||||||
{:ok, %{"ok" => true} = results} <- Poison.decode(body),
|
|
||||||
{:ok, url} <- Map.fetch(results, "url"),
|
|
||||||
{:ok, pid} <- RTMSocket.start_link(url, self()) do
|
|
||||||
Process.link(pid)
|
|
||||||
{:ok, %{rtm_socket: pid, token: token, updating: false}}
|
|
||||||
else
|
|
||||||
{:error, :invalid, _} ->
|
|
||||||
init(token)
|
|
||||||
|
|
||||||
{:ok, %{status_code: code}} ->
|
|
||||||
Logger.error(2, "Failed get RTM Auth: #{code}")
|
|
||||||
:ignore
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.error(2, "Failed to get RTM Auth: #{inspect(reason)}")
|
|
||||||
:ignore
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_call({:upload_file, file, channels}, _from, state) do
|
|
||||||
file = to_string(file)
|
|
||||||
|
|
||||||
payload =
|
|
||||||
%{
|
|
||||||
:file => file,
|
|
||||||
"token" => state.token,
|
|
||||||
"channels" => channels,
|
|
||||||
"title" => file,
|
|
||||||
"initial_comment" => ""
|
|
||||||
}
|
|
||||||
|> Map.to_list()
|
|
||||||
|
|
||||||
real_payload = {:multipart, payload}
|
|
||||||
url = "https://slack.com/api/files.upload"
|
|
||||||
headers = [{'User-Agent', 'Farmbot HTTP Adapter'}]
|
|
||||||
|
|
||||||
case HTTPoison.post(url, real_payload, headers, follow_redirect: true) do
|
|
||||||
{:ok, %{status_code: code, body: body}} when code > 199 and code < 300 ->
|
|
||||||
if Poison.decode!(body) |> Map.get("ok", false) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
Logger.error(3, "#{inspect(Poison.decode!(body, pretty: true))}")
|
|
||||||
end
|
|
||||||
|
|
||||||
other ->
|
|
||||||
Logger.error(3, "#{inspect(other)}")
|
|
||||||
end
|
|
||||||
|
|
||||||
{:reply, :ok, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(
|
|
||||||
{:socket, %{"file" => %{"url_private_download" => dl_url, "name" => name}}},
|
|
||||||
state
|
|
||||||
) do
|
|
||||||
if Path.extname(name) == ".fw" do
|
|
||||||
if match?(<<(<<"farmbot-">>), @target, <<"-">>, _rest::binary>>, name) do
|
|
||||||
Logger.warn(3, "Downloading and applying an image from slack!")
|
|
||||||
|
|
||||||
if Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "os_auto_update") do
|
|
||||||
dl_fun = Farmbot.BotState.download_progress_fun("FBOS_OTA")
|
|
||||||
|
|
||||||
case Farmbot.HTTP.download_file(dl_url, Path.join(@data_path, name), dl_fun, "", [
|
|
||||||
{'Authorization', 'Bearer #{state.token}'}
|
|
||||||
]) do
|
|
||||||
{:ok, path} ->
|
|
||||||
Farmbot.System.Updates.apply_firmware(path, true)
|
|
||||||
{:stop, :normal, %{state | updating: true}}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.error(3, "Failed to download update file: #{inspect(reason)}")
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Logger.warn(3, "Not downloading debug update because auto updates are disabled.")
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Logger.debug(3, "Not downloading #{name} (wrong target)")
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:socket, _msg}, state), do: {:noreply, state}
|
|
||||||
|
|
||||||
def terminate(reason, state) do
|
|
||||||
if Process.alive?(state.rtm_socket) do
|
|
||||||
send(state.rtm_socket, {:stop, reason})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -31,7 +31,7 @@ defmodule Farmbot.System.UpdateTimer do
|
||||||
|
|
||||||
def handle_info(:checkup, state) do
|
def handle_info(:checkup, state) do
|
||||||
osau = Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
|
osau = Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
|
||||||
Farmbot.System.Updates.check_updates(osau)
|
# Farmbot.System.Updates.check_updates(osau)
|
||||||
Process.send_after(self(), :checkup, @twelve_hours)
|
Process.send_after(self(), :checkup, @twelve_hours)
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,6 @@ defmodule Farmbot.System.Updates do
|
||||||
use Supervisor
|
use Supervisor
|
||||||
|
|
||||||
@data_path Application.get_env(:farmbot, :data_path)
|
@data_path Application.get_env(:farmbot, :data_path)
|
||||||
@current_version Farmbot.Project.version()
|
|
||||||
@target Farmbot.Project.target()
|
|
||||||
@current_commit Farmbot.Project.commit()
|
|
||||||
@env Farmbot.Project.env()
|
|
||||||
use Farmbot.Logger
|
use Farmbot.Logger
|
||||||
|
|
||||||
@handler Application.get_env(:farmbot, :behaviour)[:update_handler]
|
@handler Application.get_env(:farmbot, :behaviour)[:update_handler]
|
||||||
|
@ -16,152 +12,206 @@ defmodule Farmbot.System.Updates do
|
||||||
|
|
||||||
@doc "Overwrite os update server field"
|
@doc "Overwrite os update server field"
|
||||||
def override_update_server(url) do
|
def override_update_server(url) do
|
||||||
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", true)
|
|
||||||
ConfigStorage.update_config_value(:string, "settings", "os_update_server_overwrite", url)
|
ConfigStorage.update_config_value(:string, "settings", "os_update_server_overwrite", url)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Force check updates."
|
defmodule Release do
|
||||||
def check_updates(reboot) do
|
defmodule Asset do
|
||||||
if @handler.requires_reboot? do
|
defstruct [
|
||||||
if reboot do
|
:name,
|
||||||
Logger.info 1, "Farmbot applied an update. Rebooting."
|
:browser_download_url
|
||||||
Farmbot.System.reboot("Update reboot required")
|
]
|
||||||
else
|
end
|
||||||
Logger.info 1, "Farmbot already applied an update. Please reboot."
|
|
||||||
:ok
|
defstruct [
|
||||||
end
|
tag_name: nil,
|
||||||
else
|
target_commitish: nil,
|
||||||
token = ConfigStorage.get_config_value(:string, "authorization", "token")
|
name: nil,
|
||||||
if token do
|
draft: false,
|
||||||
case Farmbot.Jwt.decode(token) do
|
prerelease: true,
|
||||||
{:ok, %Farmbot.Jwt{os_update_server: normal_update_server, beta_os_update_server: beta_update_server}} ->
|
body: nil,
|
||||||
override = ConfigStorage.get_config_value(:string, "settings", "os_update_server_overwrite")
|
assets: []
|
||||||
if ConfigStorage.get_config_value(:bool, "settings", "beta_opt_in") do
|
]
|
||||||
do_check_updates_http(override || beta_update_server, reboot)
|
end
|
||||||
else
|
|
||||||
do_check_updates_http(override || normal_update_server, reboot)
|
|
||||||
end
|
defmodule CurrentStuff do
|
||||||
_ -> no_token()
|
import Farmbot.Project
|
||||||
end
|
defstruct [
|
||||||
else
|
:token,
|
||||||
no_token()
|
:beta_opt_in,
|
||||||
end
|
:os_update_server_overwrite,
|
||||||
|
:env,
|
||||||
|
:commit,
|
||||||
|
:target,
|
||||||
|
:version
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(replace \\ %{}) do
|
||||||
|
os_update_server_overwrite = ConfigStorage.get_config_value(:string, "settings", "os_update_server_overwrite")
|
||||||
|
beta_opt_in? = is_binary(os_update_server_overwrite) || ConfigStorage.get_config_value(:bool, "settings", "beta_opt_in")
|
||||||
|
token_bin = ConfigStorage.get_config_value(:string, "authorization", "token")
|
||||||
|
token = if token_bin, do: Farmbot.Jwt.decode!(token_bin), else: nil
|
||||||
|
opts = %{
|
||||||
|
token: token,
|
||||||
|
beta_opt_in: beta_opt_in?,
|
||||||
|
os_update_server_overwrite: os_update_server_overwrite,
|
||||||
|
env: env(),
|
||||||
|
commit: commit(),
|
||||||
|
target: target(),
|
||||||
|
version: version()
|
||||||
|
} |> Map.merge(Map.new(replace))
|
||||||
|
struct(__MODULE__, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp no_token do
|
@doc """
|
||||||
Logger.debug 3, "Not checking for updates. (No token)"
|
Force check for updates.
|
||||||
:ok
|
Does _NOT_ download or apply update.
|
||||||
|
"""
|
||||||
|
# @spec check_updates(Release.t | nil) ::
|
||||||
|
def check_updates(release \\ nil, current_stuff \\ nil)
|
||||||
|
|
||||||
|
def check_updates(nil, current_stuff) do
|
||||||
|
current_stuff_mut = %{
|
||||||
|
token: token,
|
||||||
|
beta_opt_in: beta_opt_in,
|
||||||
|
os_update_server_overwrite: server_override,
|
||||||
|
env: env,
|
||||||
|
} = current_stuff || CurrentStuff.get()
|
||||||
|
cond do
|
||||||
|
env != :prod -> {:error, :wrong_env}
|
||||||
|
is_nil(token) -> {:error, :no_token}
|
||||||
|
is_binary(server_override) ->
|
||||||
|
Logger.debug 3, "Update server override: #{server_override}"
|
||||||
|
get_release_from_url(server_override)
|
||||||
|
beta_opt_in ->
|
||||||
|
Logger.debug 3, "Checking for beta updates."
|
||||||
|
token
|
||||||
|
|> Map.get(:beta_os_update_server)
|
||||||
|
|> get_release_from_url()
|
||||||
|
true ->
|
||||||
|
Logger.debug 3, "Checking for production updates."
|
||||||
|
token
|
||||||
|
|> Map.get(:os_update_server)
|
||||||
|
|> get_release_from_url()
|
||||||
|
end
|
||||||
|
|> case do
|
||||||
|
%Release{} = release when beta_opt_in ->
|
||||||
|
do_check_production_release = fn() ->
|
||||||
|
token
|
||||||
|
|> Map.get(:os_update_server)
|
||||||
|
|> get_release_from_url()
|
||||||
|
|> case do
|
||||||
|
%Release{} = prod_release -> check_updates(prod_release, current_stuff_mut)
|
||||||
|
err -> err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
check_updates(release, current_stuff_mut) || do_check_production_release.()
|
||||||
|
%Release{} = release -> check_updates(release, current_stuff_mut)
|
||||||
|
err -> err
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_check_updates_http(url, reboot) do
|
def check_updates(%Release{} = rel, %CurrentStuff{} = current_stuff) do
|
||||||
Logger.info 3, "Checking: #{url} for updates."
|
%{
|
||||||
with {:ok, %{body: body, status_code: 200}} <- Farmbot.HTTP.get(url),
|
beta_opt_in: beta_opt_in,
|
||||||
{:ok, data} <- Poison.decode(body),
|
commit: current_commit,
|
||||||
{:ok, prerelease} <- Map.fetch(data, "prerelease"),
|
version: current_version
|
||||||
{:ok, new_commit} <- Map.fetch(data, "target_commitish"),
|
} = current_stuff
|
||||||
{:ok, cl} <- Map.fetch(data, "body"),
|
|
||||||
{:ok, false} <- Map.fetch(data, "draft"),
|
release_version = String.trim(rel.tag_name, "v") |> Version.parse!()
|
||||||
{:ok, "v" <> new_version_str} <- Map.fetch(data, "tag_name"),
|
is_beta_release? = "beta" in (release_version.pre || [])
|
||||||
{:ok, new_version} <- Version.parse(new_version_str),
|
version_comp = Version.compare(current_version, release_version)
|
||||||
{:ok, current_version} <- Version.parse(@current_version),
|
|
||||||
{:ok, fw_url} <- find_fw_url(data, new_version) do
|
release_commit = rel.target_commitish
|
||||||
needs_update = if prerelease do
|
commits_equal? = current_commit == release_commit
|
||||||
val = new_commit == @current_commit
|
|
||||||
Logger.info 1, "Checking prerelease commits: current_commit: #{@current_commit} new_commit: #{new_commit} #{val}"
|
prerelease = rel.prerelease
|
||||||
!val
|
cond do
|
||||||
else
|
# Don't bother if the release is a draft. Not sure how/if this can happen.
|
||||||
case Version.compare(current_version, new_version) do
|
rel.draft ->
|
||||||
s when s in [:gt, :eq] ->
|
Logger.warn 1, "Not checking draft release."
|
||||||
Logger.success 2, "Farmbot is up to date."
|
nil
|
||||||
false
|
|
||||||
:lt ->
|
# Only check prerelease if
|
||||||
Logger.busy 1, "New Farmbot firmware update: #{new_version}"
|
# current_version is less than or equal to release_version
|
||||||
true
|
# AND
|
||||||
|
# the commits are not equal.
|
||||||
|
prerelease and is_beta_release? and beta_opt_in and !commits_equal? ->
|
||||||
|
# beta release get marked as greater than non beta release, so we need
|
||||||
|
# to manually check the versions by removing the pre part.
|
||||||
|
case Version.compare(current_version, %{release_version | pre: nil}) do
|
||||||
|
c when c in [:lt, :eq] ->
|
||||||
|
Logger.debug 3, "Current version (#{current_version}) is less than or equal to beta release (#{release_version})"
|
||||||
|
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
|
||||||
|
:gt ->
|
||||||
|
Logger.debug 3, "Current version (#{current_version}) is greater than latest beta release (#{release_version})"
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
# if the current version is less than the release version.
|
||||||
|
!prerelease and version_comp == :lt ->
|
||||||
|
Logger.debug 3, "Current version is less than release."
|
||||||
|
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
|
||||||
|
|
||||||
if should_apply_update(@env, prerelease, needs_update) do
|
# If the version isn't different, but the commits are different,
|
||||||
Logger.busy 1, "Downloading FarmbotOS over the air update"
|
# This happens for beta releases.
|
||||||
IO.puts cl
|
!prerelease and version_comp == :eq and !commits_equal? ->
|
||||||
# Logger.info 1, "Downloading update. Here is the release notes"
|
Logger.debug 3, "Current version is equal to release, but commits are not equal."
|
||||||
# Logger.info 1, cl
|
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
|
||||||
do_download_and_apply(fw_url, new_version, reboot)
|
|
||||||
else
|
true ->
|
||||||
:no_update
|
comparison_str = "version check: current version: #{current_version} #{version_comp} latest release version: #{release_version} \n"<>
|
||||||
end
|
"commit check: current commit: #{current_commit} latest release commit: #{release_commit}: (equal: #{commits_equal?})"
|
||||||
|
|
||||||
|
Logger.debug 3, "No updates available: \ntarget: #{Farmbot.Project.target()}: \nprerelease: #{prerelease} \n#{comparison_str}"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_find_dl_url_in_asset(assets, version, current_stuff)
|
||||||
|
|
||||||
|
def try_find_dl_url_in_asset([%Release.Asset{name: name, browser_download_url: bdurl} | rest], release_version, current_stuff) do
|
||||||
|
release_version = to_string(release_version)
|
||||||
|
current_target = to_string(current_stuff.target)
|
||||||
|
expected_name = "farmbot-#{current_target}-#{release_version}.fw"
|
||||||
|
if match?(^expected_name, name) do
|
||||||
|
bdurl
|
||||||
else
|
else
|
||||||
:error ->
|
Logger.debug 3, "Incorrect asset name for target: #{current_target}: #{name}"
|
||||||
msg = "Unexpected release HTTP response or wrong formated `tag_name`"
|
try_find_dl_url_in_asset(rest, release_version, current_stuff)
|
||||||
Logger.error 2, msg
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{:error, :no_fw_url} ->
|
def try_find_dl_url_in_asset([], release_version, current_stuff) do
|
||||||
Logger.error 2, "No firmware in update asssets."
|
Logger.warn 2, "No update in assets for #{current_stuff.target()} for #{release_version}"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
{:error, reason} ->
|
def get_release_from_url(url) when is_binary(url) do
|
||||||
Logger.error 1, "Failed to fetch update data: #{inspect reason}"
|
Logger.debug 3, "Checking for updates: #{url}"
|
||||||
|
case http_adapter().get(url) do
|
||||||
{:ok, %{status_code: 400}} ->
|
{:ok, %{status_code: 404}} ->
|
||||||
Logger.info 2, "Had out of date token. Try that again."
|
Logger.warn 1, "Got a 404 checking for updates: #{url}. Fetching a new token. Try that again"
|
||||||
Farmbot.Bootstrap.AuthTask.force_refresh()
|
Farmbot.Bootstrap.AuthTask.force_refresh()
|
||||||
|
{:error, :token_refresh}
|
||||||
{:ok, %{body: body, status_code: code}} ->
|
{:ok, %{status_code: 200, body: body}} ->
|
||||||
reason = case Poison.decode(body) do
|
pattern = struct(Release, [assets: [struct(Release.Asset)]])
|
||||||
{:ok, res} -> res
|
case Poison.decode(body, as: pattern) do
|
||||||
_ -> body
|
{:ok, %Release{} = rel} -> rel
|
||||||
|
_err -> {:error, :bad_release_body}
|
||||||
end
|
end
|
||||||
Logger.error 1, "OS Update HTTP error: #{code}: #{inspect reason}"
|
{:ok, %{status_code: _code, body: body}} ->
|
||||||
|
{:error, body}
|
||||||
|
err -> err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp find_fw_url(%{"assets" => assets}, version) do
|
def http_adapter do
|
||||||
expected_name = "farmbot-#{@target}-#{version}.fw"
|
# adapter = Application.get_env(:farmbot, :behaviour)[:http_adapter]
|
||||||
res = Enum.find_value(assets, fn(asset) ->
|
# adapter || raise "No http adapter!"
|
||||||
case asset do
|
Farmbot.HTTP
|
||||||
%{"browser_download_url" => fw_url, "name" => ^expected_name} -> fw_url
|
|
||||||
_ -> nil
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
if res do
|
|
||||||
{:ok, res}
|
|
||||||
else
|
|
||||||
{:error, :no_fw_url}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp should_apply_update(env, prerelease?, needs_update?)
|
|
||||||
defp should_apply_update(_, _, false), do: false
|
|
||||||
defp should_apply_update(:prod, true, _) do
|
|
||||||
if ConfigStorage.get_config_value(:bool, "settings", "beta_opt_in") do
|
|
||||||
Logger.info 3, "Applying beta update for production firmware"
|
|
||||||
true
|
|
||||||
else
|
|
||||||
Logger.info 3, "Not applying prerelease update for production firmware"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp should_apply_update(_env, true, _) do
|
|
||||||
Logger.info 3, "Applying prerelease firmware."
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
defp should_apply_update(_, _, true) do
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_download_and_apply(dl_url, new_version, reboot) do
|
|
||||||
dl_fun = Farmbot.BotState.download_progress_fun("FBOS_OTA")
|
|
||||||
dl_path = Path.join(@data_path, "#{new_version}.fw")
|
|
||||||
case Farmbot.HTTP.download_file(dl_url, dl_path, dl_fun, "", []) do
|
|
||||||
{:ok, path} ->
|
|
||||||
apply_firmware(path, reboot)
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.error 1, "Failed to download update file: #{inspect reason}"
|
|
||||||
{:error, reason}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Apply an OS (fwup) firmware."
|
@doc "Apply an OS (fwup) firmware."
|
||||||
|
@ -204,7 +254,8 @@ defmodule Farmbot.System.Updates do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def start_link do
|
def start_link do
|
||||||
Supervisor.start_link(__MODULE__, [], [name: __MODULE__])
|
:ignore
|
||||||
|
# Supervisor.start_link(__MODULE__, [], [name: __MODULE__])
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Farmbot.System.UpdatesTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
alias Farmbot.System.Updates
|
||||||
|
alias Farmbot.System.Updates.{Release, CurrentStuff}
|
||||||
|
@old_version Farmbot.Project.version |> Version.parse! |> Map.update(:major, nil, &Kernel.-(&1, 1)) |> to_string()
|
||||||
|
|
||||||
|
describe "CurrentStuff replacement" do
|
||||||
|
test "replaces valid things in the current stuff struct" do
|
||||||
|
r = CurrentStuff.get(env: :almost_prod)
|
||||||
|
assert r.env == :almost_prod
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Allows and igrnores arbitry data" do
|
||||||
|
r = CurrentStuff.get(some_key: :some_val)
|
||||||
|
refute Map.get(r, :some_key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "checks for updates for prod rpi3 no beta combo" do
|
||||||
|
# updating from old to new version should work
|
||||||
|
current = CurrentStuff.get(os_update_server_overwrite: nil, beta_opt_in: false, env: :prod, target: :rpi3, version: @old_version)
|
||||||
|
assert Updates.check_updates(nil, current)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "checks for updates for prod rpi3 with beta combo" do
|
||||||
|
# old version to later beta
|
||||||
|
current = CurrentStuff.get(os_update_server_overwrite: nil, env: :prod, target: :rpi3, version: @old_version, beta_opt_in: true)
|
||||||
|
assert Updates.check_updates(nil, current)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
unless '--exclude farmbot_api' in :init.get_plain_arguments() do
|
unless '--exclude farmbot_api' in :init.get_plain_arguments() do
|
||||||
FarmbotTestSupport.preflight_checks()
|
FarmbotTestSupport.preflight_checks()
|
||||||
end
|
end
|
||||||
Farmbot.Logger.Console.set_verbosity_level(0)
|
# Farmbot.Logger.Console.set_verbosity_level(0)
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Repo.A, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Repo.A, :manual)
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Repo.B, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Repo.B, :manual)
|
||||||
|
|
Loading…
Reference in New Issue