Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Igniter Setup #379

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ A security audit was performed by [SAFE-Erlang-Elixir](https://github.com/SAFE-E

**Please note that the minimum supported Erlang OTP version is OTP26.**

<!-- tabs-open -->

### Erlang

**directly**
Expand Down Expand Up @@ -200,8 +202,20 @@ Supervisor.init(
)
```

**using [`igniter`](https://hex.pm/packages/igniter)**
maennchen marked this conversation as resolved.
Show resolved Hide resolved

```bash
mix oidcc.gen.provider_configuration_worker \
--name MyApp.OidccConfigProvider \
--issuer https://accounts.google.com
```

<!-- tabs-close -->

## Usage

<!-- tabs-open -->

### Companion libraries

`oidcc` offers integrations for various libraries:
Expand All @@ -213,8 +227,6 @@ Supervisor.init(
- [`oidcc_plug`](https://hex.pm/packages/oidcc_plug) - Integrations for
[`plug`](https://hex.pm/packages/plug) and
[`phoenix`](https://hex.pm/packages/phoenix)
- [`phx_gen_oidcc`](https://hex.pm/packages/phx_gen_oidcc) - Setup Generator for
[`phoenix`](https://hex.pm/packages/phoenix)
- [`ueberauth_oidcc`](https://hex.pm/packages/ueberauth_oidcc) - Integration for
[`ueberauth`](https://hex.pm/packages/ueberauth)

Expand Down Expand Up @@ -332,3 +344,5 @@ for more details, see https://hexdocs.pm/oidcc/oidcc.html
```

for more details, see https://hexdocs.pm/oidcc/Oidcc.html

<!-- tabs-close -->
145 changes: 145 additions & 0 deletions lib/mix/tasks/oidcc.gen.provider_configuration_worker.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
defmodule Mix.Tasks.Oidcc.Gen.ProviderConfigurationWorker do
@example """
mix oidcc.gen.provider_configuration_worker \\
--name MyApp.OpenIDProvider \\
--issuer https://accounts.google.com \
"""

@shortdoc "Generate an OpenID Connect provider configuration worker"
if !Code.ensure_loaded?(Igniter) do
@shortdoc "#{@shortdoc} | Install `igniter` to use"
end

@moduledoc """
#{@shortdoc}

Adds an `Oidcc.ProviderConfiguration.Worker` to your application and
configures it via the `runtime.exs` configuration file.

## Example

```bash
#{@example}
```

## Options

* `--name` or `-n` - The name of the provider configuration worker
* `--issuer` or `-i` - The issuer of the provider
"""

if Code.ensure_loaded?(Igniter) do
use Igniter.Mix.Task

alias Igniter.Code.Module
alias Igniter.Project.Application
alias Igniter.Project.Config

@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
# dependencies to add
adds_deps: [],
# dependencies to add and call their associated installers, if they exist
installs: [],
# An example invocation
example: @example,
# Accept additional arguments that are not in your schema
# Does not guarantee that, when composed, the only options you get are the ones you define
extra_args?: false,
# A list of environments that this should be installed in, only relevant if this is an installer.
only: nil,
# a list of positional arguments, i.e `[:file]`
positional: [],
# Other tasks your task composes using `Igniter.compose_task`, passing in the CLI argv
# This ensures your option schema includes options from nested tasks
composes: [],
# `OptionParser` schema
schema: [name: :string, issuer: :string],
# CLI aliases
aliases: [n: :name, i: :issuer]
}
end

@impl Igniter.Mix.Task
def igniter(igniter, argv) do
# extract positional arguments according to `positional` above
{_arguments, argv} = positional_args!(argv)
# extract options according to `schema` and `aliases` above
options = setup_options(argv, igniter)

igniter
|> configure_issuer(options)
|> add_application_worker(options)
end

defp setup_options(argv, igniter) do
argv
|> options!()
|> Keyword.update(
:name,
Module.module_name(igniter, "OpenIDProvider"),
&Module.parse/1
)
|> Keyword.put(:app_name, Igniter.Project.Application.app_name(igniter))
end

defp configure_issuer(igniter, options) do
env_prefix =
options[:name] |> Macro.underscore() |> String.upcase() |> String.replace("/", "_")

config =
case Keyword.fetch(options, :issuer) do
{:ok, issuer} ->
quote do
[issuer: System.get_env(unquote("#{env_prefix}_ISSUER"), unquote(issuer))]
end

:error ->
quote do
[issuer: System.fetch_env!(unquote("#{env_prefix}_ISSUER"))]
end
end

Config.configure_new(
igniter,
"runtime.exs",
options[:app_name],
[options[:name]],
{:code, config}
)
end

defp add_application_worker(igniter, options) do
Application.add_new_child(
igniter,
{Oidcc.ProviderConfiguration.Worker,
{:code,
quote do
%{
name: unquote(options[:name]),
issuer:
Application.fetch_env!(unquote(options[:app_name]), unquote(options[:name]))[
:issuer
]
}
end}}
)
end
else
use Mix.Task

@impl Mix.Task
def run(_argv) do
Mix.shell().error("""
The task 'oidcc.gen.provider_configuration_worker' requires igniter to be run.

Please install igniter and try again.

For more information, see: https://hexdocs.pm/igniter
""")

exit({:shutdown, 1})
end
end
end
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ defmodule Oidcc.Mixfile do
{:mock, "~> 0.3.8", only: :test},
{:ex_doc, "~> 0.29", only: :dev, runtime: false},
{:credo, "~> 1.7", only: :dev, runtime: false},
{:dialyxir, "~> 1.4", only: :dev, runtime: false}
{:dialyxir, "~> 1.4", only: :dev, runtime: false},
{:igniter, "~> 0.3.34", optional: true}
]
end

Expand Down Expand Up @@ -74,7 +75,7 @@ defmodule Oidcc.Mixfile do
source_ref: ref,
main: "readme",
extras: ["README.md"],
groups_for_modules: [Erlang: [~r/oidcc/], "Elixir": [~r/Oidcc/]],
groups_for_modules: [Erlang: [~r/oidcc/], "Elixir": [~r/^Oidcc/]],
logo: "assets/logo.svg",
assets: %{"assets" => "assets"}
]
Expand Down
94 changes: 94 additions & 0 deletions test/mix/tasks/oidcc.gen.provider_configuration_worker_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
defmodule Mix.Tasks.Oidcc.Gen.ProviderConfigurationWorkerTest do
use ExUnit.Case, async: true
import Igniter.Test

test "adds configuration if the file doesn't exist yet" do
test_project()
|> Igniter.compose_task("oidcc.gen.provider_configuration_worker", [
"--name",
"Test.Provider",
"--issuer",
"https://accounts.google.com"
])
|> assert_creates("config/runtime.exs", """
import Config

config :test, Test.Provider,
issuer: System.get_env("TEST_PROVIDER_ISSUER", "https://accounts.google.com")
""")
end

test "patches configuration if the file exists" do
test_project(
files: %{
"config/runtime.exs" => """
import Config

config :logger, level: :info
"""
}
)
|> Igniter.compose_task("oidcc.gen.provider_configuration_worker", [])
|> assert_has_patch("config/runtime.exs", """
1 1 |import Config
2 2 |
3 + |config :test, Test.OpenIDProvider, issuer: System.fetch_env!("TEST_OPEN_ID_PROVIDER_ISSUER")
3 4 |config :logger, level: :info
4 5 |
""")
end

test "adds worker to application supervision tree" do
test_project()
|> Igniter.compose_task("oidcc.gen.provider_configuration_worker", ["--name", "Test.Provider"])
|> assert_creates("lib/test/application.ex", """
defmodule Test.Application do
@moduledoc false

use Application

@impl true
def start(_type, _args) do
children = [
{Oidcc.ProviderConfiguration.Worker,
%{name: Test.Provider, issuer: Application.fetch_env!(:test, Test.Provider)[:issuer]}}
]

opts = [strategy: :one_for_one, name: Test.Supervisor]
Supervisor.start_link(children, opts)
end
end
""")
end

test "keeps existing worker in application supervision tree" do
test_project(
files: %{
"config/runtime.exs" => """
import Config
config :test, Test.Provider, issuer: System.fetch_env!("TEST_PROVIDER_ISSUER")
""",
"lib/test/application.ex" => """
defmodule Test.Application do
@moduledoc false

use Application

@impl true
def start(_type, _args) do
children = [
{Oidcc.ProviderConfiguration.Worker,
%{name: Test.Provider, issuer: Application.fetch_env!(:test, Test.Provider)[:issuer]}}
]

opts = [strategy: :one_for_one, name: Test.Supervisor]
Supervisor.start_link(children, opts)
end
end
"""
}
)
|> Igniter.compose_task("oidcc.gen.provider_configuration_worker", ["--name", "Test.Provider"])
|> assert_unchanged(["config/runtime.exs", "lib/test/application.ex"])
end
end
Loading