Current section

Files

Jump to
cloak lib mix tasks cloak.migrate.ex
Raw

lib/mix/tasks/cloak.migrate.ex

defmodule Mix.Tasks.Cloak.Migrate do
@moduledoc """
Migrate all configured models to your new encryption configuration.
While Cloak will automatically decrypt rows which use an old decryption cipher
or key, this isn't usually enough. Usually, you want to retire the old key, so
it won't do to leave it configured indefinitely.
This task allows you to proactively upgrade all rows in your database to the
new encryption configuration, so that you can remove the old key.
## Before You Run This Task...
1. Ensure that you have configured your new encryption cipher.
2. Set the new cipher and/or key as the `:default`. Otherwise, running this
task will have no effect.
## Configuration
In order for the Mix task to update rows in the correct database, it must have
access to the correct repo, and a list of models to migrate.
config :cloak, :migration,
repo: MyApp.Repo,
models: [MyApp.Model1, MyApp.Model2]
## Usage
mix cloak.migrate
The task allows you to customize the repo and models which will be migrated at
runtime.
mix cloak.migrate --model MyApp.Model --repo MyApp.Repo
mix cloak.migrate -m MyApp.Model -r MyApp.Repo
"""
use Mix.Task
import Ecto.Query, only: [from: 2]
import Logger, only: [info: 1]
import String, only: [to_existing_atom: 1]
@config Application.get_env(:cloak, :migration)
@repo @config[:repo]
@models @config[:models] || []
@version Cloak.version
@doc false
def run(args) do
info "=== Starting Migration ==="
{repo, models} = parse_args(args)
Mix.Task.run "app.start", args
Enum.each(models, &migrate(&1, repo))
info "=== Migration Complete ==="
end
defp parse_args(args) do
{opts, _, _} = OptionParser.parse(args, aliases: [m: :model, r: :repo])
repo = case opts[:repo] do
nil -> @repo
other -> to_existing_atom("Elixir." <> other)
end
models = case opts[:model] do
nil -> @models
other -> [to_existing_atom("Elixir." <> other)]
end
{repo, models}
end
defp migrate(model, repo) do
info "--- Migrating #{model_name(model)} Model ---"
ids = ids_for(model, repo)
info "#{length(ids)} records found needing migration"
for id <- ids do
repo.get(model, id) |> migrate_row(repo)
end
end
defp model_name(model) do
model
|> Atom.to_string
|> String.replace(~r/^Elixir\./, "")
end
defp ids_for(model, repo) do
query = from m in model,
where: field(m, ^model.__encryption_version_field__) != ^@version,
select: m.id
repo.all(query)
end
defp migrate_row(row, repo) do
version = Map.get(row, row.__struct__.__encryption_version_field__)
if version != @version do
repo.update!(row)
end
end
end