defmodule Mix.Tasks.PromEx.Dashboard.Publish do
@moduledoc """
This mix task will publish dashboards to Grafana for a PromEx module. It is
recommended that you use the functionality that is part of the PromEx supervision
tree in order to upload dashboards as opposed to this, given that mix may not
always be available (like in a mix release). This is more so a convenience for
testing and validating dashboards without starting the whole application.
The following CLI flags are supported:
```md
-m, --module The PromEx module which will be used to render the dashboards.
This is needed to fetch any relevant assigns from the
`c:PromEx.dashboard_assigns/0` callback and to get the Grafana
configuration from app config.
-t, --timeout The timeout value defines how long the mix task will wait while
uploading dashboards.
```
"""
@shortdoc "Upload dashboards to Grafana"
use Mix.Task
alias Mix.Shell.IO
alias PromEx.DashboardUploader
@impl true
def run(args) do
# Compile the project
Mix.Task.run("compile")
# Get CLI args and set up uploader
%{module: prom_ex_module, timeout: timeout} = parse_options(args)
uploader_process_name = Mix.Tasks.PromEx.Publish.Uploader
"Elixir.#{prom_ex_module}"
|> String.to_atom()
|> Code.ensure_compiled()
|> case do
{:module, module} ->
module
{:error, reason} ->
raise "#{prom_ex_module} is not a valid PromEx module because #{inspect(reason)}"
end
|> check_grafana_configuration()
|> start_finch()
|> upload_dashboards(uploader_process_name, timeout)
end
defp parse_options(args) do
cli_options = [module: :string, timeout: :integer]
cli_aliases = [m: :module, t: :timeout]
# Parse out the arguments and put defaults where necessary
args
|> OptionParser.parse(aliases: cli_aliases, strict: cli_options)
|> case do
{options, _remaining_args, [] = _errors} ->
Map.new(options)
{_options, _remaining_args, errors} ->
raise "Invalid CLI args were provided: #{inspect(errors)}"
end
|> Map.put_new(:timeout, 10_000)
|> Map.put_new_lazy(:module, fn ->
Mix.Project.config()
|> Keyword.get(:app)
|> Atom.to_string()
|> Macro.camelize()
|> Kernel.<>(".PromEx")
end)
end
defp check_grafana_configuration(prom_ex_module) do
if prom_ex_module.init_opts().grafana_config == :disabled do
raise "#{prom_ex_module} has the Grafana option disabled. Please update your configuration and rerun."
end
prom_ex_module
end
defp start_finch(prom_ex_module) do
client_name = Module.concat([prom_ex_module, GrafanaClient])
Supervisor.start_link(
[
{PromEx.GrafanaClient, name: client_name}
],
strategy: :one_for_one
)
prom_ex_module
end
defp upload_dashboards(prom_ex_module, uploader_process_name, timeout) do
# We don't want errors in DashboardUploader to kill the mix task
Process.flag(:trap_exit, true)
# Start the DashboardUploader
default_dashboard_opts = [otp_app: prom_ex_module.__otp_app__()]
{:ok, pid} =
DashboardUploader.start_link(
name: uploader_process_name,
prom_ex_module: prom_ex_module,
default_dashboard_opts: default_dashboard_opts
)
receive do
{:EXIT, ^pid, :normal} ->
IO.info("\nPromEx dashboard upload complete! Review the above statuses for each dashboard.")
{:EXIT, ^pid, error_reason} ->
IO.error(
"PromEx was unable to upload your dashboards to Grafana because:\n#{Code.format_string!(inspect(error_reason))}"
)
after
timeout ->
raise "PromEx timed out trying to upload your dashboards to Grafana"
end
end
end