Packages

Weave allows you to configure your application just-in-time (JIT), embracing the 12-Factor manifesto. Weave supports loading configuration from environment variables and files, with prefix and filename filtering. Works great with Kubernetes and Docker Swarm.

Current section

Files

Jump to
weave lib loader.ex
Raw

lib/loader.ex

defmodule Weave.Loader do
@moduledoc """
This behaviour should be implemented by all loaders. This will be handled for you with:
```elixir
use Weave.Loader
```
"""
@callback load_configuration(options: list, only: list, handler: atom) :: list
defmacro __using__(_) do
quote do
require Logger
@behaviour Weave.Loader
@spec load_configuration(options: list, only: list, handler: atom) :: list
@spec apply_configuration(binary(), binary(), atom()) :: :ok
@spec configure({atom(), atom(), any()}) :: :ok
@spec configure([{atom(), atom(), any()}]) :: :ok | []
@spec merge(any(), any()) :: list() | map() | binary()
@spec handle_configuration(binary(), binary()) :: :ok
@spec sanitize(binary()) :: binary()
defp apply_configuration(name, contents, handler) do
name
|> sanitize()
|> handler.handle_configuration(contents)
|> configure()
:ok
rescue
error in [UndefinedFunctionError, FunctionClauseError] ->
Logger.info(fn -> inspect(error) end)
handle_configuration(name, contents)
end
# Allow handlers to return :ok
defp configure(:ok) do
:ok
end
# We're transforming this to a List, as I believe we'll
# eventually enforce all handle_configuration/1's return List
defp configure({app, key, value}) do
configure([{app, key, value}])
end
defp configure([{app, key, value} | tail]) do
Application.put_env(
app,
key,
merge(Application.get_env(app, key), value)
)
Logger.debug(fn ->
"Configuration for #{app}:#{key} loaded: " <>
"#{inspect(Application.get_env(app, key))}"
end)
configure(tail)
end
defp configure([]) do
:ok
end
defp merge(nil, new) when is_list(new) do
merge([], new)
end
defp merge(old, new) when is_list(new) do
if Keyword.keyword?(new) do
Logger.debug(fn -> "Merging keywords: #{inspect(old)} and #{inspect(new)}" end)
Keyword.merge(old, new)
else
Logger.debug(fn -> "Merging lists #{inspect(old)} and #{inspect(new)}" end)
new ++ old
end
end
defp merge(nil, new) when is_map(new) do
merge(%{}, new)
end
defp merge(old, new) when is_map(new) do
Map.merge(old, new)
end
defp merge(old, new) when is_binary(new) do
new
end
defp merge(old, new) when is_atom(new) do
new
end
defp handle_configuration(parameter_name, "{:auto," <> configuration) do
{{app, key, value}, []} = Code.eval_string(~s/{#{configuration}/)
Application.put_env(
app,
key,
merge(Application.get_env(app, key), value)
)
:ok
end
defp handle_configuration(file_name, configuration) do
Logger.warn(fn -> "External configuration (#{file_name}) provided, but not loaded" end)
Logger.debug(fn ->
"configuration value for ignored '#{file_name}' is '#{configuration}'"
end)
:ok
end
defp sanitize(name) do
String.downcase(name)
end
end
end
end