defmodule Phoenix.Ecto.CheckRepoStatus do @moduledoc """ A plug that does some checks on your application repos. Checks if the storage is up (database is created) or if there are any pending migrations. Both checks can raise an error if the conditions are not met. ## Plug options * `:otp_app` - name of the application which the repos are fetched from * `:migration_paths` - a function that accepts a repo and returns a migration directory, or a list of migration directories, that is used to check for pending migrations * `:migration_lock` - the locking strategy used by the Ecto Adapter when checking for pending migrations. Set to `false` to disable migration locks. * `:prefix` - the prefix used to check for pending migrations. """ @behaviour Plug alias Plug.Conn @migration_opts [:migration_lock, :prefix] @compile {:no_warn_undefined, Ecto.Migrator} def init(opts) do Keyword.fetch!(opts, :otp_app) opts end def call(%Conn{} = conn, opts) do repos = Application.get_env(opts[:otp_app], :ecto_repos, []) for repo <- repos, Process.whereis(repo) do check_pending_migrations!(repo, opts) || check_storage_up!(repo) end conn end defp check_storage_up!(repo) do try do adapter = repo.__adapter__() if Code.ensure_loaded?(adapter) && function_exported?(adapter, :storage_status, 1) do adapter.storage_status(repo.config()) end rescue _ -> true else :down -> raise Phoenix.Ecto.StorageNotCreatedError, repo: repo _ -> true end end defp check_pending_migrations!(repo, opts) do dirs = migration_directories(repo, opts) migrations_fun = Keyword.get_lazy(opts, :mock_migrations_fn, fn -> if Code.ensure_loaded?(Ecto.Migrator), do: &Ecto.Migrator.migrations/3, else: fn _repo, _paths, _opts -> raise "to be rescued" end end) true = is_function(migrations_fun, 3) migration_opts = Keyword.take(opts, @migration_opts) try do repo |> migrations_fun.(dirs, migration_opts) |> Enum.any?(fn {status, _version, _migration} -> status == :down end) rescue _ -> false else true -> raise Phoenix.Ecto.PendingMigrationError, repo: repo, directories: dirs, migration_opts: migration_opts false -> true end end defp migration_directories(repo, opts) do case Keyword.fetch(opts, :migration_paths) do {:ok, migration_directories_fn} -> List.wrap(migration_directories_fn.(repo)) :error -> try do [Ecto.Migrator.migrations_path(repo)] rescue _ -> [] end end end end