Hello everyone!
I’m finally breaking down and reaching out to the community for help on this topic. Over the summer, before Distillery 2.0 came out, I spent a good deal of time learning about releases and compile-vs-runtime configuration and so on, not to mention general development of a Phoenix app (first real one I’ve done, ). After discovering and making a few necessary bug fixes to the ps1 files (we’re on Windows), we got everything working pretty well. Except for migrations and seeds, that is. I spent days on it, trying everything I could think of and every suggestion from every semi-related thread or blog post I could find, but to no avail. We ended up having to manually generate sql files using ecto.dump
and pg_dump
. There are a couple issues even still with this approach, but it got us through development of the app.
Fast-forward to this week, Distillery 2.x is out and with it many nice improvements (thank you!), we’re done with development, and we’re ready to hand everything over to the client. I thought I’d revisit the migration/seeding issue with a fresh mindset and a new version of Distillery. After several more days, I’m stuck again, though I did get a bit further. I ran into another minor issue in how Distillery discovers custom user commands on Windows (which you’ll see in a minute) but after getting around that, I ultimately am stumped with the following error: ** (ArgumentError) configuration for PhoenixDistillery.Repo not specified in :phoenix_distillery environment
.
It’s pretty clear the configuration for the Repo is not available when running this, but it’s also clearl, at least to me, that I still don’t truly understand how everything fits together, and I’m really hoping some kind and more enlightened soul out there can not only point out what I’m doing wrong, but help me understand why. In support of that, below I will provide mostly exact replication steps to get from ground zero to the exact error shown above. So here goes!
Elixir 1.6.6
Erlang/OTP 21 (compiled w/ 19)
Phoenix 1.3.3
Ecto 2.2.11
Distillery 2.012
PostgreSQL 10
IDE: VSCode running as Administrator
using regular cmd terminal, not powershell terminal
# Create Phoenix project
mix phx.new --no-html --no-brunch phoenix_distillery
# Add distillery and plug_cowboy to deps in mix.exs
defp deps do
[
...,
{:plug_cowboy, "~> 1.0"},
{:distillery, "~> 2.0"}
]
end
# Modify config/prod.exs - I’m hardcoding things here for simplicity
use Mix.Config
config :phoenix_distillery, PhoenixDistilleryWeb.Endpoint,
http: [port: {:system, "PORT"}],
url: [host: "localhost", port: {:system, "PORT"}],
server: true,
root: ".",
version: Application.spec(:phoenix_distillery, :vsn)
config :logger, level: :info
config :phoenix_distillery, PhoenixDistilleryWeb.Endpoint,
secret_key_base: "your_secret_key_base"
config :phoenix_distillery, PhoenixDistillery.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "your_password",
database: "phoenix_distillery_prod",
hostname: "localhost",
pool_size: 15
# Generate a simple schema and migration
mix phx.gen.schema Blog.Post blog_posts title:string views:integer
# Add the following to priv/repo/seeds.exs
alias PhoenixDistillery.{Blog, Repo}
_post = %Blog.Post{title: "My furst poast", views: 0}
|> Repo.insert!
Create lib\release_tasks.ex
defmodule PhoenixDistillery.ReleaseTasks do
@start_apps [:crypto, :ssl, :postgrex, :ecto]
@repos Application.get_env(:phoenix_distillery, :ecto_repos, [])
def migrate(_argv) do
start_services()
run_migrations()
stop_services()
end
def seed(_argv) do
start_services()
run_migrations()
run_seeds()
stop_services()
end
defp start_services do
IO.puts("Starting dependencies..")
Enum.each(@start_apps, &Application.ensure_all_started/1)
IO.puts("Starting repos..")
Enum.each(@repos, &(&1.start_link(pool_size: 1)))
end
defp stop_services do
IO.puts("Success!")
:init.stop()
end
defp run_migrations do
IO.puts("Running migrations..")
Enum.each(@repos, &run_migrations_for/1)
end
defp run_migrations_for(repo) do
app = Keyword.get(repo.config, :otp_app)
IO.puts ("Running migrations for #{app}")
migrations_path = priv_path_for(repo, "migrations")
Ecto.Migrator.run(repo, migrations_path, :up, all: true)
end
defp run_seeds do
Enum.each(@repos, &run_seeds_for/1)
end
defp run_seeds_for(repo) do
seed_script = priv_path_for(repo, "seeds.exs")
if File.exists?(seed_script) do
IO.puts("Running seed script..")
Code.eval_file(seed_script)
end
end
defp priv_path_for(repo, filename) do
app = Keyword.get(repo.config, :otp_app)
repo_underscore =
repo
|> Module.split()
|> List.last()
|> Macro.underscore()
priv_dir = "#{:code.priv_dir(app)}"
Path.join([priv_dir, repo_underscore, filename])
end
end
# Pull deps, compile, create db, init distillery
mix deps.get --only prod
set "MIX_ENV=prod" && mix compile
set "MIX_ENV=prod" && mix ecto.create
mix release.init
# Create rel\commands\win
folder for migrate and seed commands
mkdir rel\commands\win
# Create migrate.ps1
Write-Host "Running migrate command..." Release-Ctl eval --mfa "PhoenixDistillery.ReleaseTasks.migrate/1" --argv -- "$args"
# Create seed.ps1
Write-Host "Running seed command..." Release-Ctl eval --mfa "PhoenixDistillery.ReleaseTasks.seed/1" --argv -- "$args"
# Update rel/config.exs
- note the overlays are necessary b/c of bug in how Distillery tries to discover custom user commands on windows (phoenix_distillery.ps1 looks for commands inside _build\prod\rel\releases\<ver>\commands\win
but without extra overlays it copies the .ps1 files in the same manner as the .sh files to _build\prod\rel\releases\<ver>\commands
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))
use Mix.Releases.Config,
default_release: :default,
default_environment: Mix.env()
environment :dev do
set dev_mode: true
set include_erts: false
set cookie: :"your_dev_cookie"
end
environment :prod do
set include_erts: true
set include_src: false
set cookie: :"your_prod_cookie"
set vm_args: "rel/vm.args"
end
release :phoenix_distillery do
set version: current_version(:phoenix_distillery)
set applications: [
:runtime_tools
]
set commands: [
migrate: "rel/commands/win/migrate.ps1",
seed: "rel/commands/win/seed.ps1"
]
set overlays: [
{:mkdir, "releases/<%= release_version %>/commands/win"},
{:copy, "rel/commands/win/migrate.ps1", "releases/<%= release_version %>/commands/win/migrate.ps1"},
{:copy, "rel/commands/win/seed.ps1", "releases/<%= release_version %>/commands/win/seed.ps1"}
]
end
# Build release (it’s not clear to me whether --env=prod is necessary
set "MIX_ENV=prod" && mix release --env=prod
# Try to run in foreground (should work)
set "PORT=4000" && .\_build\prod\rel\phoenix_distillery\bin\phoenix_distillery.bat foreground
# Close and try to run migration
set "COOKIE=ytho" && .\_build\prod\rel\phoenix_distillery\bin\phoenix_distillery.bat migrate
# Result
Running migrate command...
Starting dependencies..
Starting repos..
Running migrations..
** (EXIT from #PID<0.88.0>) an exception was raised:
** (ArgumentError) configuration for PhoenixDistillery.Repo not specified in :phoenix_distillery environment
(ecto) lib/ecto/repo/supervisor.ex:32: Ecto.Repo.Supervisor.runtime_config/4
(ecto) lib/ecto/repo/supervisor.ex:153: Ecto.Repo.Supervisor.init/1
(stdlib) supervisor.erl:295: :supervisor.init/1
(stdlib) gen_server.erl:374: :gen_server.init_it/2
(stdlib) gen_server.erl:342: :gen_server.init_it/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Also of note: my sys.config file in _build\prod\rel\phoenix_distillery\releases\<ver>\sys.config
[{distillery,[{config_providers,[]}]},
{sasl,[{errlog_type,error},{sasl_error_logger,false}]},
{logger,
[{console,
[{format,<<"$time $metadata[$level] $message\n">>},
{metadata,[user_id]}]},
{level,info}]},
{phoenix_distillery,
[{ecto_repos,['Elixir.PhoenixDistillery.Repo']},
{'Elixir.PhoenixDistilleryWeb.Endpoint',
[{render_errors,
[{view,'Elixir.PhoenixDistilleryWeb.ErrorView'},
{accepts,[<<"json">>]}]},
{pubsub,
[{name,'Elixir.PhoenixDistillery.PubSub'},
{adapter,'Elixir.Phoenix.PubSub.PG2'}]},
{http,[{port,{system,<<"PORT">>}}]},
{url,[{host,<<"localhost">>},{port,{system,<<"PORT">>}}]},
{server,true},
{root,<<".">>},
{version,"0.0.1"},
{secret_key_base,
<<"my_secret_key_base">>}]},
{'Elixir.PhoenixDistillery.Repo',
[{adapter,'Elixir.Ecto.Adapters.Postgres'},
{username,<<"postgres">>},
{password,<<"my_password">>},
{database,<<"phoenix_distillery_prod">>},
{hostname,<<"localhost">>},
{pool_size,15}]}]}].
Non-exhaustive list of other things I’ve tried (and failed at perhaps b/c it isn’t intended to work that way, or perhaps because of user-error/misunderstanding ) :
- leaving it running in foreground and running migrate in another terminal
- leaving it running in foreground and running migrate in another terminal setting cookie to the same prod cookie
- using the Mix Config Provider and copying the prod.exs file using an overlay to etc/config.exs
- running the commands from a pre_start hook (couldn’t actually get this to work even for an echo, but didn’t spend a ton of time on it. Like commands, seems like there may be some discrepancies and needed tweaks in ps1 files as compared to sh files)
- using REPLACE_OS_VARS=true like in Distillery 1.x days (is this obsolete now? such confuse)
- reading DB config vars in Repo.init via System.get_env and ensuring they’re set as env variables both during release creation and during command execution
- probably half a dozen other variations I can’t remember
Whew!
So, if you made it this far, you are already my hero. If you think you can help troubleshoot this with me, you will not only be my hero, but you will actively be participating in helping convince a .NET shop that it’s OK, and maybe even preferable!, to do a second app with Elixir You will be having a Real Impact! Haha. But really, thank you in advance to everyone who gets this far. I love this language, I love this community (even though I’ve been in the shadows, generally speaking), and I’m like…90% certain I’m just overlooking something totally obvious to your keen eyes that I just don’t grok yet.
Thank you!