Current section

Files

Jump to
nerves_hub_cli lib mix nerves_hub shell.ex
Raw

lib/mix/nerves_hub/shell.ex

defmodule Mix.NervesHubCLI.Shell do
alias Mix.NervesHubCLI.Utils
@spec info(IO.ANSI.ansidata()) :: :ok
def info(output) do
Mix.shell().info(output)
end
@spec error(IO.ANSI.ansidata()) :: :ok
def error(output) do
Mix.shell().error(output)
end
@spec raise(String.t()) :: no_return()
def raise(output) do
Mix.raise(output)
end
@spec prompt(String.t()) :: String.t()
def prompt(output) do
Mix.shell().prompt(output) |> String.trim()
end
@spec yes?(String.t()) :: boolean()
def yes?(message) do
System.get_env("NERVES_HUB_NON_INTERACTIVE") || Mix.shell().yes?(message)
end
@spec request_auth() :: NervesHubCLI.API.Auth.t() | nil
def request_auth() do
if token = Utils.token() do
%NervesHubCLI.API.Auth{token: token}
end
end
def request_password(_prompt, 0) do
{:error, :failed_password}
end
def request_keys(org, name) do
request_keys(org, name, "Local signing key password for '#{name}': ")
end
def request_keys(org, name, prompt) do
env_pub_key = System.get_env("NERVES_HUB_FW_PUBLIC_KEY")
env_priv_key = System.get_env("NERVES_HUB_FW_PRIVATE_KEY")
if env_pub_key != nil and env_priv_key != nil do
{:ok, env_pub_key, env_priv_key}
else
key_password = password_get(prompt)
NervesHubCLI.Key.get(org, name, key_password)
end
end
# Password prompt that hides input by every 1ms
# clearing the line with stderr
@spec password_get(String.t()) :: String.t()
def password_get(prompt) do
password_clean(prompt)
|> String.trim()
end
@dialyzer [{:no_return, render_error: 1}, {:no_fail_call, render_error: 1}]
@spec render_error([{:error, any()}] | {:error, any()}, boolean() | nil) ::
:ok | no_return()
def render_error(errors, halt? \\ true) do
_ = do_render_error(errors)
if halt? do
System.halt(1)
else
:ok
end
end
@spec do_render_error(any()) :: :ok
def do_render_error(errors) when is_list(errors) do
Enum.each(errors, &do_render_error/1)
end
def do_render_error({error, reasons}) when is_list(reasons) do
error("#{error}")
for reason <- reasons, do: error(" #{reason}")
:ok
end
def do_render_error({:error, reason}) when is_binary(reason) do
error(reason)
end
def do_render_error({:error, %{"status" => "forbidden"}}) do
error("Invalid credentials")
error("Your user token has either expired or has been revoked.")
error("Please authenticate again:")
error(" mix nerves_hub.user auth")
end
def do_render_error({:error, %{"status" => reason}}) do
error(reason)
end
def do_render_error({:error, %{"errors" => reason}}) when is_binary(reason) do
error(reason)
end
def do_render_error({:error, %{"errors" => reasons}}) when is_list(reasons) do
error("HTTP error")
for {key, reason} <- reasons, do: error(" #{key}: #{reason}")
:ok
end
def do_render_error(error) do
error("Unhandled error: #{inspect(error)}")
end
defp password_clean(prompt) do
pid = spawn_link(fn -> loop(prompt) end)
ref = make_ref()
value = IO.gets(prompt <> " ")
send(pid, {:done, self(), ref})
receive do: ({:done, ^pid, ^ref} -> :ok)
value
end
defp loop(prompt) do
receive do
{:done, parent, ref} ->
send(parent, {:done, self(), ref})
IO.write(:standard_error, "\e[2K\r")
after
1 ->
IO.write(:standard_error, "\e[2K\r#{prompt} ")
loop(prompt)
end
end
end