Skogsrå
The Skogsrå was a mythical creature of the forest that appears in the form of a small, beautiful woman with a seemingly friendly temperament. However, those who are enticed into following her into the forest are never seen again.
This library attempts to improve the use of OS environment variables for application configuration:
- Variable defaults.
- Automatic type casting of values.
- Automatic documentation generation for variables.
- Runtime reloading.
- Setting variable's values at runtime.
- Fast cached values access by using
:persistent_termas temporal storage. - YAML configuration provider for Elixir releases.
Small example
You would create a config module e.g:
defmodule MyApp.Config do
use Skogsra
@envdoc "My hostname"
app_env :my_hostname, :myapp, :hostname,
default: "localhost"
end
Calling MyApp.Config.my_hostname() will retrieve the value for the
hostname in the following order:
- From the OS environment variable
$MYAPP_HOSTNAME(can be overriden by the optionos_env). From the configuration file e.g:
config :myapp, hostname: "my.custom.host"- From the default value if it exists (In this case, it would return
"localhost").
Available options
There are several options for configuring an environment variables:
| Option | Type | Default | Description |
|---|---|---|---|
default | any | nil | Sets the Default value for the variable. |
type | Skogsra.Env.type() | :binary | Sets the explicit type for the variable. |
os_env | binary | autogenerated | Overrides automatically generated OS environment variable name. |
skip_system | boolean | false | Whether it should skip looking for a value in the OS or not. |
skip_config | boolean | false | Whether it should skip looking for a value in the application config or not. |
required | boolean | false | Whether the variable is required or not. |
cached | boolean | true | Whether the variable should be cached or not. |
Additional topics:
- Automatic type casting.
- Explicit type casting.
- Explicit OS environment variable names.
- Required variables.
- Caching variables.
- Handling different environments.
- Setting and reloading variables.
- YAML Config Provider.
- Using with Hab.
- Installation.
Automatic type casting
If the default value is set (and no explicit type is found), the variable
value will be casted as the same type of the default value. For this to work,
the default value should be of the following types:
:binary:integer:float.:boolean:atom
e.g. in the following example, the value will be casted to :atom
automatically:
defmodule MyApp.Config do
use Skogsra
@envdoc "My environment"
app_env :my_environmen, :myapp, :environment,
default: :prod
end
If either of the system OS or the application environment variables are defined,
Skogsra will try to cast their values to the default value's type which it
atom e.g:
iex(1)> System.get_env("MYAPP_ENVIRONMENT")
"staging"
iex(2)> MyApp.Config.my_environment()
{:ok, :staging}
or
iex(1)> Application.get_env(:myapp, :environment)
"staging"
iex(2)> MyApp.Config.my_environment()
{:ok, :staging}
Note: If the variable is already of the desired type, it won't be casted.
Explicit type casting
A type can be explicitly set. The available types are:
:binary(default).:integer.:float.:boolean.:atom.:module(modules loaded in the system).:unsafe_module(modules that might or might not be loaded in the system)- A module name with an implementation for the behaviour
Skogsra.Type.
e.g. a possible implementation for casting "1, 2, 3, 4" to [integer()]
would be:
defmodule MyList do
use Skogsra.Type
def cast(value) when is_binary(value) do
list =
value
|> String.split(~r/,/)
|> Stream.map(&String.trim/1)
|> Enum.map(String.to_integer/1)
{:ok, list}
end
def cast(value) when is_list(value) do
if Enum.all?(value, &is_integer/1), do: {:ok, value}, else: :error
end
def cast(_) do
:error
end
end
If then we define the following enviroment variable with Skogsra:
defmodule MyApp.Config do
use Skogsra
app_env :my_integers, :myapp, :integers,
type: MyList
end
If either of the system OS or the application environment variables are defined,
Skogsra will try to cast their values using our implementation e.g:
iex(1)> System.get_env("MYAPP_INTEGERS")
"1, 2, 3"
iex(2)> MyApp.Config.my_integers()
{:ok, [1, 2, 3]}
or
iex(1)> Application.get_env(:myapp, :integers)
[1, 2, 3]
iex(2)> MyApp.Config.my_integers()
{:ok, [1, 2, 3]}
Important: The
defaultvalue is not cast according totype.
Explicit OS environment variable names
Though Skogsra automatically generates the names for the OS environment
variables, they can be overriden by using the option os_env e.g:
defmodule MyApp.Config do
use Skogsra
app_env :db_hostname, :myapp, [:postgres, :hostname],
os_env: "PGHOST"
end
This will override the value $MYAPP_POSTGRES_HOSTNAME with $PGHOST e.g:
iex(1)> System.get_env("MYAPP_POSTGRES_HOSTNAME")
"unreachable"
iex(2)> System.get_env("PGHOST")
"reachable"
iex(3)> MyApp.Config.db_hostname()
{:ok, "reachable"}
Required variables
It is possible to set a environment variable as required with the required
option e.g:
defmodule MyApp.Config do
use Skogsra
@envdoc "My port"
app_env :my_port, :myapp, :port,
required: true
end
If none of the system OS or the application environment variables are defined,
Skogsra will return an error e.g:
iex(1)> System.get_env("MYAPP_PORT")
nil
iex(2)> Application.get_env(:myapp, :port)
nil
iex(2)> MyApp.Config.my_port()
{:error, "Variable port in app myapp is undefined"}
Handling different environments
If it's necessary to keep several environments, it's possible to use a
namespace e.g. given the following variable:
defmodule MyApp.Config do
use Skogsra
@envdoc "My port"
app_env :my_port, :myapp, :port,
default: 4000
end
Calling MyApp.Config.my_port(Test) will retrieve the value for the hostname
in the following order:
- From the OS environment variable
$TEST_MYAPP_PORT. From the configuration file e.g:
config :myapp, Test, port: 4001- From the OS environment variable
$MYAPP_PORT. From the configuraton file e.g:
config :myapp, port: 80- From the default value if it exists. In our example,
4000.
The ability of loading different environments allows us to do the following with our configuration file:
# file: config/config.exs
use Mix.Config
config :myapp, Prod
port: 80
config :myapp, Test,
port: 4001
config :myapp,
port: 4000
While our Skogsra module would look like:
defmodule MyApp.Config do
use Skogsra
@envdoc "Application environment"
app_env :env, :myapp, :environment,
type: :unsafe_module
@envdoc "Application port"
app_env :port, :myapp, :port,
default: 4000
end
The we can retrieve the values depending on the value of the OS environment
variable $MYAPP_ENVIRONMENT:
...
with {:ok, env} <- MyApp.Config.env(),
{:ok, port} <- Myapp.Config.port(env) do
... do something with the port ...
end
...
Caching variables
By default, Skogsra caches the values of the variables using
:persistent_term Erlang module. This makes reads very fast, but writes are
very slow.
So avoid setting or reloading values to avoid performance issues (see Setting and reloading variables).
If you don't want to cache the values, you can set it to false:
defmodule MyApp.Config do
use Skogsra
app_env :value, :myapp, :value,
cached: false
end
Setting and reloading variables
Every variable definition will generate two additional functions for setting and reloading the values e.g:
defmodule MyApp.Config do
use Skogsra
@envdoc "My port"
app_env :my_port, :myapp, :port,
default: 4000
end
will have the functions:
MyApp.Config.put_my_port/1for setting the value of the variable at runtime.MyApp.Config.reload_my_port/for reloading the value of the variable at runtime.
YAML Config Provider
Skogsra includes a simple YAML configuration provider compatible with
mix release for Elixir ≥ 1.9.
The following is the supported configuration format:
# file: /etc/my_app/config.yml
- app: "my_app" # Name of the application.
module: "MyApp.Repo" # Optional module/namespace.
config: # Actual configuration.
- database: "my_app_db"
username: "postgres"
password: "postgres"
hostname: "localhost"
port: 5432
The previous configuration file would translate to:
config :my_app, MyApp.Repo,
database: "my_App_db",
username: "postgres"
password: "postgres"
hostname: "localhost"
port: 5432
For using this config provider, just add the following to your release configuration:
config_providers: [{Skogsra.Provider.Yaml, ["/path/to/config/file.yml"]}]
Note: If the
moduleyou're using in you're config does not exist, then change it tonamespacee.g:namespace: "MyApp.Repo". Otherwise, it will fail loading it.
Using with Hab
Hab is an Oh My ZSH plugin for loading OS environment variables automatically.
By default, Hab will try to load .envrc file, but it's possible to have
several of those files for different purposes e.g:
.envrc.prodfor production OS variables..envrc.testfor testing OS variables..envrcfor development variables.
Hab will load the development variables by default, but it can load the
other files using the command hab_load <extension> e.g. loading
.envrc.prod would be as follows:
~/my_project $ hab_load prod
[SUCCESS] Loaded hab [/home/user/my_project/.envrc.prod]
Installation
The package can be installed by adding skogsra to your list of dependencies
in mix.exs.
- For Elixir ≥ 1.8 and Erlang ≥ 22
def deps do
[{:skogsra, "~> 2.0"}]
end
- For Elixir ≥ 1.9, Erlang ≥ 22 and YAML config provider support:
def deps do
[
{:skogsra, "~> 2.0"},
{:yamerl, "~> 0.7"}
]
end
Author
Alexander de Sousa.
License
Skogsrå is released under the MIT License. See the LICENSE file for further details.