Running migration in Distillery

Hi everyone!

In this post, I would like to share the migration script (migrate, rollback by step and seeding data) when deploying 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# 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 migrations_path(repo), do: priv_path_for(repo, "migrations")

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

Step 2:

Create shell script files to call functions:

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

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks migrate
  • Seed script at rel/commands/seed.sh
1
2
3
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks seed
  • Rollback script at rel/commands/rollback.sh
1
2
3
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks rollback

Step 3:

Setup commands from rel/config.exs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

Step 4:

Once you have deployed the application, you can run:

  • Migrate
1
bin/myapp migrate
  • Seed
1
bin/myapp seed
  • Rollback. You can specify how many step you wanna rollback
1
bin/myapp rollback

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