Running ecto migration via release from distillery

I have an elixir umbrella project with three applications. I am creating its binary (release) via distillery.

Running this command creates .tar.gz file in _build/prod/rel/se/releases/0.1.0:

MIX_ENV=prod mix release --env=qa

And I am able to extract and run the application. To run ecto migration I have added this module for release tasks [by following https://hexdocs.pm/distillery/running-migrations.html ]:

defmodule Se.ReleaseTasks do

@start_apps [
    :postgrex,
    :ecto
]

def myapp, do: Application.get_application(__MODULE__)

def repos, do: Application.get_env(myapp(), :ecto_repos, [])

def seed() do
    me = myapp()

    IO.puts "Loading #{me}.."
    # Load the code for myapp, but don't start it
    :ok = Application.load(me)

    IO.puts "Starting dependencies.."
    # Start apps necessary for executing migrations
    Enum.each(@start_apps, &Application.ensure_all_started/1)

    # Start the Repo(s) for myapp
    IO.puts "Starting repos.."
    Enum.each(repos(), &(&1.start_link(pool_size: 1)))

    # Run migrations
    migrate()

    # Run seed script
    Enum.each(repos(), &run_seeds_for/1)

    # Signal shutdown
    IO.puts "Success!"
    :init.stop()
end

def migrate, do: Enum.each(repos(), &run_migrations_for/1)

def priv_dir(app), do: "#{:code.priv_dir(app)}"

defp run_migrations_for(repo) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts "Running migrations for #{app}"
    Ecto.Migrator.run(repo, migrations_path(repo), :up, all: true)
end

def run_seeds_for(repo) do
    # Run the seed script if it exists
    seed_script = seeds_path(repo)
    if File.exists?(seed_script) do
    IO.puts "Running seed script.."
    Code.eval_file(seed_script)
    end
end

def migrations_path(repo), do: priv_path_for(repo, "migrations")

def seeds_path(repo), do: priv_path_for(repo, "seeds.exs")

def priv_path_for(repo, filename) do
    app = Keyword.get(repo.config, :otp_app)
    repo_underscore = repo |> Module.split |> List.last |> Macro.underscore
    Path.join([priv_dir(app), repo_underscore, filename])
end
end

Application is run and compiled with this code located in one of the umbrella project where we require migrations. After compilation and starting server, when I try to run it via:

bin/se_cloud command Elixir.Se.ReleaseTasks seed

I get this error:

Elixir.Se.ReleaseTasks.seed is either not defined or has a non-zero arity

Did anyone else encounter this issue? Or I am mis-configuring something here?

Can you connect to the remote shell bin/se_cloud remote_console and check that this module actually exists (or loaded)?

iex> :erlang.module_loaded(Elixir.Se.ReleaseTasks)
true # or false

I did that and following is the output:

iex(se_cloud@127.0.0.1)1> :erlang.module_loaded(Elixir.Se.ReleaseTasks)
false

That might be the problem. For some reason this module does not get loaded in your release. What “otp app” do you define this module in? Does that “otp app” get added to the release?

In other words, it seems that your app is named :se (judging by Se). Is :se anywhere in your rel/config.exs?

1 Like

Yes. I have it defined in my rel/config.exs as (it was in fact generated by distillery itself when I ran mix release.init I only added the set commands option.):

release :se_cloud do
set version: "0.1.0"
set commands: [
    "migrate": "rel/commands/migrate.sh"
]
set applications: [
    :runtime_tools,
    se: :permanent,
    se_web: :permanent
]
end

Ok, and what’s the folder of the file where Se.ReleaseTasks is defined? Is it under apps/se/lib/?

No this is file is defined under apps/se/

File name is release_tasks.ex

After moving release_tasks.ex to app/se/lib and creating release again I am getting this:

iex(se_cloud@127.0.0.1)1> :erlang.module_loaded(Elixir.Se.ReleaseTasks)
true

However, now when I run the seed command, I am getting this error:

{“init terminating in do_boot”,{{badmatch,{error,{“no such file or directory”,“nil.app”}}},[{‘Elixir.Se.ReleaseTasks’,seed,0,[{file,“lib/release_tasks.ex”},{line,17}]},{init,start_em,1,},{init,do_boot,3,}]}}
init terminating in do_boot ({{badmatch,{error,{[],[]}}},[{Elixir.Se.ReleaseTasks,seed,0,[{},{}]},{init,start_em,1,},{init,do_boot,3,}]})

I also checked it from remote console:

iex(se_cloud@127.0.0.1)1> Application.get_application(:se)
nil

I am not sure whey getting this error now while I am able to run the bin/se_cloud start with seeing any error on console or logs.

You probably need to load your application before running the migrations.

Yes. In ReleaseTasks module I am trying to load application first, as you can see in my code:

def myapp, do: Application.get_application(:se)

But it is failing upon loading it. I also tried to run this via remote console:

iex(se_cloud@127.0.0.1)1> Application.get_application(:se)
nil

And getting this nil which means it is not able find application while I am able to run bin/se_cloud start without any error printed to console / logs.

I don’t think Application.get_application(:se) should return anything but nil unless you have defined :se module for some application.

What you should probably do is

def myapp do
  Application.get_application(__MODULE__) || raise("couldn't get application for #{__MODULE__}")
end

I’ve added a raise/1 here to “fail fast” in the case no application gets found. It would also provide us with a more constructive error message (couldn't get application for Se.ReleaseTasks) than what we’ve had before ({{badmatch,{error,{“no such file or directory”,“nil.app”}}}).

I was having problems with this exact line of code from the Distillery docs. I am pretty sure that Application.get_application doesn’t work at runtime in Distillery releases when you actually deploy them.

What I did is just hard-code my OTP app name like :myapp into the myapp function like so:

def myapp, do: :myapp

Besides the above, I don’t think your version is working since Application.get_application/1 takes in a module name. Something like MyApp.Foo. You can see this here: https://hexdocs.pm/elixir/Application.html#get_application/1.

It worked for me as well as I defined it as:

def myapp do
    :se
end

And now the seed is successfully run.

I am pretty sure that Application.get_application doesn’t work at runtime in Distillery releases when you actually deploy them.

I’ve just connected to a remote_console and run

iex(<app_name>@127.0.0.1)1> Application.get_application Web
:web

so it works for me at least.

The app is running in a docker container on k8s if that’s of any importance.

Did

def myapp do
  Application.get_application(__MODULE__) || raise("couldn't get application for #{__MODULE__}")
end

work for you?

@idi527 After adding the raise part to code and executed I am getting this error:

{“init terminating in do_boot”,{#{‘exception’=>true,‘struct’=>‘Elixir.RuntimeError’,message=><<“couldn’t get application for Elixir.Se.Application”>>},[{‘Elixir.Se.ReleaseTasks’,myapp,0,[{file,“lib/release_tasks.ex”},{line,9}]},{‘Elixir.Se.ReleaseTasks’,seed,0,[{file,“lib/release_tasks.ex”},{line,16}]},{init,start_em,1,},{init,do_boot,3,}]}}
init terminating in do_boot ({,[{Elixir.Se.ReleaseTasks,myapp,0,[{},{}]},{Elixir.Se.ReleaseTasks,seed,0,[{},{}]},{init,start_em,1,},{init,do_boot,3,}]})

Hm, maybe @bitwalker could know more.

Is this a release? Because this implies that this application was not included in the release.

And why is it “couldn’t get application for Elixir.Se.Application”. I would’ve expected “couldn’t get application for Elixir.Se.ReleaseTasks”, since that’s where I thought you would call this from.

Yeah it must work for some people since it’s in the Distillery docs but I’ve never been able to get it working without hardcoding my OTP app name. Whenever I test it Application.get_application/1 always returns nil.