haizop
How to run Triplex tenant migrations when deploying via mix release?
Scenario
We are using Triplex to manage our multi-schema Postges DB. We deploy using Mix releases, thus Mix is not available and we cannot migrate using the Triplex Mix tasks. We run our public schema migrations via a similar Release module to the one recommended in the Phoenix documentation. To that basic structure, we added an additional migrate_tenants/0 function:
defmodule MyApp.Release do
@app :my_app
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def migrate_tenants do
load_app()
for repo <- repos() do
for tenant <- Triplex.all(repo) do
Triplex.migrate(tenant, repo)
end
end
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end
Problem
The migrate_tenants function succeeds when run via a remote IEx session or rpc, but fails when run via eval, ie bin/my_app eval "MyApp.Release.migrate_tenants()". This is in contrast to the migrate function. The error observed is the following:
** (RuntimeError) could not lookup Ecto repo MyApp.Repo because it was not started or it does not exist
lib/ecto/repo/registry.ex:19: Ecto.Repo.Registry.lookup/1
lib/ecto/adapter.ex:127: Ecto.Adapter.lookup_meta/1
lib/ecto/adapters/sql.ex:404: Ecto.Adapters.SQL.query/4
lib/ecto/adapters/sql.ex:362: Ecto.Adapters.SQL.query!/4
lib/triplex.ex:289: Triplex.all/1
(myapp 0.1.0) lib/myapp/release.ex:21: anonymous fn/2 in MyApp.Release.migrate_tenants/0
(elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
(myapp 0.1.0) lib/myapp/release.ex:20: MyApp.Release.migrate_tenants/0
Questions
- Is it possible to migrate tenants using Triplex via eval on a release?
- Is there a known root cause for this issue?
- Is there a recommended approach for using Triplex with Mix releases in general?
Note: I posted this as an issue on the Triplex repo several weeks ago, but there have been no responses thus far.
Most Liked
riebeekn
This is what we’re doing for our release module:
def migrate do
{:ok, _} = Application.ensure_all_started(:my_app)
migrate_public_schema()
migrate_tenant_schemas()
end
defp migrate_public_schema do
path = Application.app_dir(:my_app, "priv/repo/migrations")
Migrator.run(MyApp.Repo, path, :up, all: true)
end
defp migrate_tenant_schemas do
path = Application.app_dir(:my_app, "priv/repo/tenant_migrations")
Accounts.list_tenants()
|> Enum.each(&Migrator.run(MyApp.Repo, path, :up, all: true, prefix: &1.prefix))
end
Wasn’t aware of the Triplex.all function, I could probably replace the call to our Accounts module with that, but anyway using the Migrator.run function instead of Triplex.migrate has worked for us.
haizop
@riebeekn - This is what I have working now. Thanks again for steering me in the right direction.
defmodule MyApp.Release do
@moduledoc """
Used for executing DB release tasks when run in production without Mix
installed.
"""
alias Ecto.Migrator
alias MyApp.Repo
alias MyApp.Tenants
@app :my_app
def migrate_public_schema do
load_app()
{:ok, _, _} = Migrator.with_repo(Repo, &Migrator.run(&1, :up, all: true))
end
def migrate_tenant_schemas do
load_app()
Migrator.with_repo(Repo, fn repo ->
Tenants.list_tenants()
|> Enum.each(
&Migrator.run(repo, tenant_migrations_path(), :up, all: true, prefix: &1.schema_name)
)
end)
end
def rollback_public_schema(version) do
load_app()
{:ok, _, _} = Migrator.with_repo(Repo, &Migrator.run(&1, :down, to: version))
end
def rollback_tenant_schemas(version) do
load_app()
Migrator.with_repo(Repo, fn repo ->
Tenants.list_tenants()
|> Enum.each(
&Migrator.run(repo, tenant_migrations_path(), :down, to: version, prefix: &1.schema_name)
)
end)
end
defp tenant_migrations_path() do
Triplex.migrations_path(Repo)
end
defp load_app do
Application.load(@app)
end
end
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









