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() :: {:ok, list()}
defmacro __using__(_) do
quote do
require Logger
@behaviour Weave.Loader
@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