Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Commit

Permalink
POD13-65 polyn.gen.release task (#30)
Browse files Browse the repository at this point in the history
* gen release task and test

* add polyn.gen.release task and don't assume which otp_app

* inject polyn migrate functions into existing release file

* update changelog

* gross fix for flaky test
  • Loading branch information
brandynbennett committed May 25, 2023
1 parent 91b03dd commit 2116bc2
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 55 deletions.
3 changes: 1 addition & 2 deletions polyn_elixir_client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

## 0.6.1

* Adds Polyn.Release module for working with `mix release`.
* Requires otp_app config
* Adds `mix polyn.gen.release` task for working with `mix release`.

## 0.6.0

Expand Down
35 changes: 24 additions & 11 deletions polyn_elixir_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ The [Cloud Event Spec](https://github.com/cloudevents/spec/blob/v1.0.2/cloudeven
config :polyn, :source_root, "orders.payments"
```

### OTP App

Polyn needs to know the name of the otp app that's using it

```elixir
config :polyn, :otp_app, :my_app
```

### NATS Connection

You will need to provide the connection settings for your NATS connection. This will differ in-between environments. More settings options can be seen [here](https://hexdocs.pm/gnat/Gnat.ConnectionSupervisor.html#content)
Expand Down Expand Up @@ -122,14 +114,35 @@ Polyn uses a shared Key-Value bucket in NATS to avoid re-running migrations. It

When using `mix release` to deploy, `mix` and Mix Tasks are not available, so you can't use `mix polyn.migrate` to do your migrations.

Instead you can use a built-in `Polyn.Release` module to execute migrations in the compiled application
Instead you'll need to run `mix polyn.gen.release` which will add a `lib/my_app/release.ex` file to your app (if you already have this file it will append to it). The file will look something like this:

```elixir
defmodule MyApp.Release do
@app :my_app

def polyn_migrate do
load_app()
{:ok, _apps} = Application.ensure_all_started(:polyn)

You can use it from the release like this:
dir = Path.join([:code.priv_dir(@app), "polyn", "migrations"])
Polyn.Migration.Migrator.run(migrations_dir: dir)
end

defp load_app do
Application.load(@app)
end
end
```

You can use the `polyn_migrate` function from this module to execute migrations in the compiled release like this:

```
_build/prod/rel/my_app/bin/my_app eval "Polyn.Release.migrate"
_build/prod/rel/my_app/bin/my_app eval "MyApp.Release.polyn_migrate"
```

Sometimes multiple OTP apps are part of a single application, so Polyn doesn't assume which app to use for accessing and running migration files. This is why you need to generate the `release.ex` file yourself and pass in the OTP app you want.


## Usage

### Publishing Messages
Expand Down
1 change: 0 additions & 1 deletion polyn_elixir_client/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Config

config :polyn, :domain, "com.acme"
config :polyn, :source_root, "user.backend"
config :polyn, :otp_app, :polyn

config :polyn, :nats, %{
name: :gnat,
Expand Down
1 change: 0 additions & 1 deletion polyn_elixir_client/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Config

config :polyn, :domain, "com.test"
config :polyn, :source_root, "user.backend"
config :polyn, :otp_app, :polyn

config :polyn, :nats, %{
name: :gnat,
Expand Down
98 changes: 98 additions & 0 deletions polyn_elixir_client/lib/mix/tasks/polyn.gen.release.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule Mix.Tasks.Polyn.Gen.Release do
@moduledoc """
Use `mix polyn.gen.release` to generate a new polyn release module for your application
"""
@shortdoc "Generates a new polyn release file"

use Mix.Task
require Mix.Generator

def run(args) do
{options, []} = OptionParser.parse!(args, strict: [dir: :string])
path = Path.join([dir(options), Atom.to_string(app_name()), "release.ex"])
assigns = [mod: module_name(), app: app_name()]

if File.exists?(path) do
check_existing(path)
else
Mix.Generator.create_file(path, release_file_template(assigns))
end

inject_into_existing(path)
end

defp dir(options) do
Keyword.get(options, :dir, Path.join(File.cwd!(), "lib"))
end

defp app_name do
Mix.Project.config() |> Keyword.fetch!(:app)
end

defp module_name do
prefix = app_name() |> Atom.to_string() |> Macro.camelize()
Module.concat([prefix, Release])
end

defp check_existing(path) do
unless prompt_allow_injection(path) do
System.halt()
end
end

defp prompt_allow_injection(path) do
Mix.shell().yes?(
"#{path} already exists. Would you like to inject Polyn release functions into it?"
)
end

defp inject_into_existing(path) do
file = File.read!(path)

lines =
String.trim_trailing(file)
|> String.trim_trailing("end")
|> String.split(["\n", "\r\n"])

lines = List.insert_at(lines, first_private_func_index(lines), polyn_migrate_text())

injected = Enum.join(lines, "\n") <> "end\n"
File.write!(path, injected)
end

defp first_private_func_index(lines) do
result =
Enum.find_index(lines, fn line ->
String.contains?(line, "defp ")
end)

# Use the very end of the file if there are no private functions
case result do
nil -> -1
index -> index
end
end

Mix.Generator.embed_template(:release_file, """
defmodule <%= inspect @mod %> do
@app <%= inspect @app %>
defp load_app do
Application.load(@app)
end
end
""")

Mix.Generator.embed_text(
:polyn_migrate,
"""
def polyn_migrate do
load_app()
{:ok, _apps} = Application.ensure_all_started(:polyn)
dir = Path.join([:code.priv_dir(@app), "polyn", "migrations"])
Polyn.Migration.Migrator.run(migrations_dir: dir)
end
"""
)
end
6 changes: 1 addition & 5 deletions polyn_elixir_client/lib/polyn/migration/migrator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ defmodule Polyn.Migration.Migrator do
Path of migration files
"""
def migrations_dir do
Path.join([:code.priv_dir(otp_app()), "polyn", "migrations"])
Path.join([File.cwd!(), "priv", "polyn", "migrations"])
end

@doc """
Expand Down Expand Up @@ -174,8 +174,4 @@ defmodule Polyn.Migration.Migrator do

state
end

defp otp_app do
Application.fetch_env!(:polyn, :otp_app)
end
end
35 changes: 0 additions & 35 deletions polyn_elixir_client/lib/polyn/release.ex

This file was deleted.

76 changes: 76 additions & 0 deletions polyn_elixir_client/test/mix/tasks/polyn.gen.release_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
defmodule Mix.Tasks.Polyn.Gen.ReleaseTest do
use ExUnit.Case, async: true

alias Mix.Tasks.Polyn.Gen

@moduletag :tmp_dir

# send output to test process rather than stdio
Mix.shell(Mix.Shell.Process)

test "makes release file", %{tmp_dir: tmp_dir} do
Gen.Release.run(["--dir", tmp_dir])

path = Path.join([tmp_dir, "polyn", "release.ex"])

file = File.read!(path)

assert [{Polyn.Release, _binary}] = Code.compile_string(file)

assert file ==
"""
defmodule Polyn.Release do
@app :polyn
def polyn_migrate do
load_app()
{:ok, _apps} = Application.ensure_all_started(:polyn)
dir = Path.join([:code.priv_dir(@app), "polyn", "migrations"])
Polyn.Migration.Migrator.run(migrations_dir: dir)
end
defp load_app do
Application.load(@app)
end
end
"""
end

test "injects polyn_migrate function if release_file exists already", %{tmp_dir: tmp_dir} do
File.mkdir!(Path.join(tmp_dir, "polyn"))

path = Path.join([tmp_dir, "polyn", "release.ex"])

File.write!(path, """
defmodule Polyn.Release do
def do_other_stuff do
end
end
""")

send(self(), {:mix_shell_input, :yes?, true})
Gen.Release.run(["--dir", tmp_dir])

prompt = "#{path} already exists. Would you like to inject Polyn release functions into it?"
assert_received {:mix_shell, :yes?, [^prompt]}

file = File.read!(path)

assert file ==
"""
defmodule Polyn.Release do
def do_other_stuff do
end
def polyn_migrate do
load_app()
{:ok, _apps} = Application.ensure_all_started(:polyn)
dir = Path.join([:code.priv_dir(@app), "polyn", "migrations"])
Polyn.Migration.Migrator.run(migrations_dir: dir)
end
end
"""
end
end
4 changes: 4 additions & 0 deletions polyn_elixir_client/test/polyn/migration/bucket_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ defmodule Polyn.Migration.BucketTest do
Application.get_env(:polyn, :source_root)
)

# wait for purge/delete to complete. The delete is async right now.
# Can remove this once it is fixed in Jetstream lib
:timer.sleep(500)

assert [] = Migration.Bucket.already_run_migrations(@bucket_name)
end

Expand Down

0 comments on commit 2116bc2

Please sign in to comment.