Packages

An adapter for Confex to add support for fetching config values from the AWS Parameter Store.

Current section

Files

Jump to
confex_parameter_store lib confex adapters parameter_store.ex
Raw

lib/confex/adapters/parameter_store.ex

defmodule Confex.Adapters.ParameterStore do
@moduledoc """
A [Confex](https://github.com/Nebo15/confex) adapter for [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html).
This adapter allows us to retrieve parameters at runtime via their parameter store
paths specified in system environment variables.
Also check out the [Confex Documentation](https://hexdocs.pm/confex/readme.html)
for more info on Confex itself.
# Usage
To have Confex fetch a value from the Parameter Store define your config value
this adapter by defining it as a tuple `{:via, Confex.Adapters.ParameterStore}`.
```elixir
alias Confex.Adapters.ParameterStore
config :app, Example.Repo,
adapter: Ecto.Adapters.Postgres,
url: {{:via, ParameterStore}, "DB_URL"}
config :app, Example.ApiClient,
api_key: {{:via, ParameterStore}, "API_KEY"}
```
The environment variables in turn contain the path of the parameter store value prefixed by `parameter:`.
```bash
# will be fetched from parameter store
DB_URL="parameter:/app/prod/db/url"
API_KEY="parameter:/app/prod/api_key"
```
```elixir
iex> Confex.fetch_env(:app, Example.Repo)
{:ok, [url: "ecto://postgres:postgres@localhost:5432/exampledb?ssl=false&pool_size=2"]}
iex> Confex.fetch_env!(:app, Example.ApiClient)
[api_key: "dTmIczHsTcyrbL0lBbJY"]
```
Any environment variables no prefixed by `parameter:` will be fetched as values the same
way as if we defined them as `{:system, "ENV_NAME"}`, thus allowing us to for example set up dev
enviromentes that don't use parameter store while still using environment variables.
In other words if we exchage above parameter store paths with their actual values it would work
just fine.
```bash
DB_URL="ecto://postgres:postgres@localhost:5432/exampledb?ssl=false&pool_size=2"
API_KEY="dTmIczHsTcyrbL0lBbJY"
```
```elixir
iex> Confex.fetch_env(:app, Example.Repo)
{:ok, [url: "ecto://postgres:postgres@localhost:5432/exampledb?ssl=false&pool_size=2"]}
iex> Confex.fetch_env!(:app, Example.ApiClient)
[api_key: "dTmIczHsTcyrbL0lBbJY"]
```
# Hierarchies
Following the [best practices layed out by AWS](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-walk-hierarchies.html),
and defining parameters hierarchically allows us to easily allow access to a whole parameter tree
or restrict access to certain parts of a tree.
As an example we can imagine an application deployed to different EC2 instances acting
as separate deploy environments. We can create instance roles which allow access to
certain trees like `/app/prod/*`, `/app/staging/*`, `/app/demo/*` depending on environment.
# Caching
Be sure to cache the returned value in some way if you retrieve it often and don't want
the overhead of a http request as every time it is read a request will be made to AWS.
"""
@provider Application.get_env(
:confex_parameter_store,
:provider,
Confex.ParameterStore.ProviderImpl
)
@doc """
Fetch value from the Parameter Store which path is specified in an environment variable.
"""
@spec fetch_value(binary) :: {:ok, any()} | :error
def fetch_value(key) do
System.get_env(key)
|> fetch_parameter
end
@spec fetch_parameter(binary) :: {:ok, binary} | :error
defp fetch_parameter("parameter:" <> path), do: @provider.get_parameter(path)
defp fetch_parameter("parameters_by_path:" <> path), do: @provider.get_parameters_by_path(path)
defp fetch_parameter(value) when is_binary(value), do: {:ok, value}
defp fetch_parameter(_), do: :error
end