Packages

A durable, resumable workflow engine for Elixir

Current section

Files

Jump to
durable lib mix tasks durable.migrations.ex
Raw

lib/mix/tasks/durable.migrations.ex

defmodule Mix.Tasks.Durable.Migrations do
@shortdoc "Displays Durable internal migration status"
@moduledoc """
Displays Durable's internal migration status for an Ecto repo.
Use `--check` in CI or deploy gates to fail when the database is behind the
Durable library version.
## Usage
mix durable.migrations
mix durable.migrations -r MyApp.Repo --check
mix durable.migrations --prefix private
mix durable.migrations --json
## Options
* `-r`, `--repo` - The Ecto repo to inspect
* `--prefix` - Durable PostgreSQL schema prefix (default: `"durable"`)
* `--json` - Emit JSON instead of text
* `--check` - Raise if any Durable migrations are pending
"""
use Mix.Task
import Mix.Ecto
alias Durable.Migration
@aliases [r: :repo]
@switches [
repo: [:string, :keep],
prefix: :string,
json: :boolean,
check: :boolean,
no_compile: :boolean,
no_deps_check: :boolean
]
@impl Mix.Task
def run(args) do
repos = parse_repos!(args)
{opts, _argv} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
reports = Enum.map(repos, &build_report(&1, args, opts))
emit_reports(reports, opts)
maybe_raise_for_pending(reports, opts)
:ok
end
defp parse_repos!(args) do
case parse_repo(args) do
[] -> Mix.raise("Could not find an Ecto repo. Pass one with -r MyApp.Repo.")
repos -> repos
end
end
defp build_report(repo, args, opts) do
ensure_repo(repo, args)
prefix = Keyword.get(opts, :prefix, "durable")
case Ecto.Migrator.with_repo(repo, &migration_status(&1, prefix), mode: :temporary) do
{:ok, report, _started} ->
report
{:error, error} ->
Mix.raise("Could not start repo #{inspect(repo)}, error: #{inspect(error)}")
end
end
defp migration_status(repo, prefix) do
current_version = Migration.current_version()
migrated_version = Migration.migrated_version(repo, prefix: prefix)
pending_versions = Migration.pending_versions(repo, prefix: prefix)
%{
repo: inspect(repo),
prefix: prefix,
current_version: current_version,
migrated_version: migrated_version,
pending_versions: pending_versions,
status: status(pending_versions)
}
end
defp status([]), do: "up"
defp status(_pending_versions), do: "pending"
defp emit_reports(reports, opts) do
if Keyword.get(opts, :json, false) do
emit_json(reports)
else
Enum.each(reports, &emit_text/1)
end
end
defp emit_json([report]), do: Mix.shell().info(Jason.encode!(report, pretty: true))
defp emit_json(reports), do: Mix.shell().info(Jason.encode!(reports, pretty: true))
defp emit_text(report) do
Mix.shell().info("""
Repo: #{report.repo}
Prefix: #{report.prefix}
Current Durable version: #{report.current_version}
Migrated database version: #{report.migrated_version}
Pending versions: #{format_pending(report.pending_versions)}
Status: #{report.status}
""")
end
defp format_pending([]), do: "none"
defp format_pending(versions), do: Enum.join(versions, ", ")
defp maybe_raise_for_pending(reports, opts) do
if Keyword.get(opts, :check, false) and Enum.any?(reports, &pending?/1) do
Mix.raise(check_error(reports))
end
end
defp pending?(report), do: report.pending_versions != []
defp check_error(reports) do
pending =
reports
|> Enum.filter(&pending?/1)
|> Enum.map_join("\n", fn report ->
" #{report.repo} prefix=#{inspect(report.prefix)} pending=#{format_pending(report.pending_versions)}"
end)
"""
Durable migrations are pending:
#{pending}
Generate an upgrade migration with `mix durable.gen.upgrade -r YourApp.Repo`,
then run `mix ecto.migrate`.
"""
end
end