DEV Community

Vincent Nguyen
Vincent Nguyen

Posted on

Migration scripts (migrate/rollback/seeding) database when releasing a Phoenix/OTP app by Distillery

Hi everyone!

In this post, I would like to share the migration script (migrate, rollback by step and seeding data) when releasing a Phoenix/OTP application. This script is extended from original Running Migration documentation of Distillery’s creator, I and my colleagues - we wrote more functions to support database rollback and seeding data.

Step 1:

  • Init a new module
# apps/myapp/lib/migration.ex

defmodule MyApp.ReleaseTasks do
  @start_apps [
    :postgrex,
    :ecto
  ]
  @app :myapp

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

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

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

  defp 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 migrate do
    prepare()
    Enum.each(repos(), &run_migrations_for/1)
    IO.puts("Migrations successful!")
  end

  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 rollback do
    prepare()

    get_step =
      IO.gets("Enter the number of steps: ")
      |> String.trim()
      |> Integer.parse()

    case get_step do
      {int, _trailing} ->
        Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end)
        IO.puts("Rollback successful!")

      :error ->
        IO.puts("Invalid integer")
    end
  end

  defp run_rollbacks_for(repo, step) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts("Running rollbacks for #{app} (STEP=#{step})")
    Ecto.Migrator.run(repo, migrations_path(repo), :down, all: false, step: step)
  end

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

    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))
  end

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

  defp priv_path_for(repo, filename) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts("App: #{app}")
    repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore()
    Path.join([priv_dir(app), repo_underscore, filename])
  end

  defp priv_dir(app), do: "#{:code.priv_dir(app)}"
end
Enter fullscreen mode Exit fullscreen mode

Step 2:

Create shell script files to call functions:

  • Migrate script at rel/commands/migrate.sh
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks migrate
Enter fullscreen mode Exit fullscreen mode
  • Seed script at rel/commands/seed.sh
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks seed
Enter fullscreen mode Exit fullscreen mode
  • Rollback script at rel/commands/rollback.sh
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks rollback
Enter fullscreen mode Exit fullscreen mode

Step 3:

Setup commands from rel/config.exs

release :myapp do
  set version: current_version(:myapp)
  set applications: [
    :runtime_tools,
    :misc_random,
    admin: :permanent, graphql: :permanent,
    redis: :permanent,
    session: :permanent,
    myapp: :permanent
  ]

  set commands: [
    "seed": "rel/commands/seed.sh",
    "migrate": "rel/commands/migrate.sh",
    "rollback": "rel/commands/rollback.sh",
  ]
end
Enter fullscreen mode Exit fullscreen mode

Step 4:

Once you have deployed the application, you can run:

  • Migrate
bin/myapp migrate
Enter fullscreen mode Exit fullscreen mode
  • Seed
bin/myapp seed
Enter fullscreen mode Exit fullscreen mode
  • Rollback. You can specify how many steps you wanna rollback
bin/myapp rollback
Enter fullscreen mode Exit fullscreen mode

You guys can download the code from my gist. Thanks!

Top comments (0)