ash_postgres/lib/mix/tasks/ash_postgres.squash_snapshots.ex

117 lines
3.6 KiB
Elixir

defmodule Mix.Tasks.AshPostgres.SquashSnapshots do
use Mix.Task
@shortdoc "Cleans snapshots folder, leaving only one snapshot per resource"
@switches [
into: :string,
snapshot_path: :string,
quiet: :boolean,
dry_run: :boolean,
check: :boolean
]
@moduledoc """
Cleans snapshots folder, leaving only one snapshot per resource.
## Examples
mix ash_postgres.squash_snapshots
mix ash_postgres.squash_snapshots --check --quiet
mix ash_postgres.squash_snapshots --into zero
mix ash_postgres.squash_snapshots --dry-run
## Command line options
* `--into` -
`last`, `first` or `zero`. The default is `last`. Determines which name to use for
a remaining snapshot. `last` keeps the name of the last snapshot, `first` renames it to the previously first,
`zero` sets name with fourteen zeros.
* `--snapshot-path` - a custom path to stored snapshots. The default is "priv/resource_snapshots".
* `--quiet` - no messages will not be printed.
* `--dry-run` - no files are touched, instead prints folders that have snapshots to squash.
* `--check` - no files are touched, instead returns an exit(1) code if there are snapshots to squash.
"""
def run(args) do
{opts, []} = OptionParser.parse!(args, strict: @switches)
opts =
opts
|> Map.new()
|> Map.put_new(:into, "last")
|> Map.put_new(:snapshot_path, "priv/resource_snapshots")
|> Map.put_new(:quiet, false)
|> Map.put_new(:dry_run, false)
|> Map.put_new(:check, false)
|> Map.update!(:into, fn
"last" -> :last
"first" -> :first
"zero" -> :zero
_other -> raise "Valid values for --into flag are `last`, `first` and `zero`."
end)
squashable =
opts.snapshot_path
|> Path.join("**/*.json")
|> Path.wildcard()
|> Enum.filter(&String.match?(Path.basename(&1), ~r/^\d{14}\.json$/))
|> Enum.group_by(&Path.dirname(&1))
|> Enum.reduce([], fn {folder, snapshots}, squashable ->
last_snapshot = Enum.max(snapshots)
into_snapshot =
case opts.into do
:last -> last_snapshot
:first -> Enum.min(snapshots)
:zero -> Path.join(folder, "00000000000000.json")
end
if length(snapshots) > 1 or last_snapshot != into_snapshot do
[{folder, snapshots, last_snapshot, into_snapshot} | squashable]
else
squashable
end
end)
|> Enum.reverse()
if Enum.empty?(squashable) do
print(opts, "No snapshots to squash.")
else
if opts.dry_run do
print(opts, "Snapshots in following folders would have been squashed in non-dry run:")
print(opts, Enum.map_join(squashable, "\n", fn {folder, _, _, _} -> folder end))
if opts.check do
exit({:shutdown, 1})
end
end
if opts.check do
print(opts, """
Snapshots would have been squashed, but the --check flag was provided.
To see what snapshots would have been squashed, run with the --dry-run
flag. To squash those snapshots, run without either flag.
""")
exit({:shutdown, 1})
end
if not opts.dry_run do
for {_folder, snapshots, last_snapshot, into_snapshot} <- squashable do
for snapshot <- snapshots, snapshot != last_snapshot do
File.rm!(snapshot)
end
if last_snapshot != into_snapshot do
File.rename!(last_snapshot, into_snapshot)
end
end
end
end
end
defp print(%{quiet: true}, _message), do: nil
defp print(_opts, message), do: Mix.shell().info(message)
end