Vapor

Loads dynamic configuration at runtime.

Why Vapor?

Dynamically configuring elixir apps can be hard. There are major differences between configuring applications with mix and configuring applications in a release. Vapor wants to make all of that easy by providing an alternative to mix config for runtime configs. Specifically Vapor can:

Installing

Add vapor to your mix dependencies:

def deps do
[
{:vapor, "~> 0.4"},
]
end

Example

defmodule VaporExample.Config do
alias Vapor.Provider
alias Vapor.Provider.{File, Env}
def load_config do
end
end
defmodule VaporExample.Application do
use Application
def start(_type, _args) do
providers = [
%Env{bindings: [db_name: "DB_NAME", port: "PORT"]},
%File{path: "config.toml", bindings: [kafka_brokers: "kafka.brokers"]},
]
# If values could not be found we raise an exception and halt the boot
# process
config = Vapor.load!(providers)
children = [
{VaporExampleWeb.Endpoint, port: config.port}
{VaporExample.Repo, db_name: config.db_name},
{VaporExample.Kafka, brokers: config.kafka_brokers},
]
opts = [strategy: :one_for_one, name: VaporExample.Supervisor]
Supervisor.start_link(children, opts)
end
end

Precedence

Vapor merges the configuration based on the order that the providers are specified.

providers = [
%Dotenv{},
%File{path: "$HOME/.vapor/config.json", bindings: []},
%Env{bindings: []},
]

Env will have the highest precedence, followed by File, and finally Dotenv.

Translating config values

Its often useful to translate configuration values into something meaningful for your application. Vapor provides translation options to allow you to do any type conversions or data manipulation.

providers = [
%Env{bindings: [db_name: "DB_NAME", port: "PORT"]},
%File{path: "config.toml", bindings: [kafka_brokers: "kafka.brokers"]},
]
translations = [
port: fn s -> String.to_integer(s) end,
kafka_brokers: fn str ->
str
|> String.split(",")
|> Enum.map(fn broker ->
[host, port] = String.split(broker, ":")
{host, String.to_integer(port)}
end)
end
]
config = Vapor.load(providers, translations)

Reading config files

Config files can be read from a number of different file types including JSON, TOML, and YAML. Vapor determines which file format to use based on the file extension.

Overriding application environment

In some cases you may want to overwrite the keys in the application environment for convenience. While this is generally discouraged it can be a quick way to adopt Vapor. Before you start your supervision tree you can use something like this:

config = Vapor.load!(providers)
Application.put_env(:my_app, MyApp.Repo, [
database: config[:database],
username: config[:database_user],
password: config[:database_pasword],
hostname: config[:database_host],
port: config[:database_port],
])

Providers

There are several built in providers

If you need to create a new provider you can do so with the included Vapor.Provider protocol.

defmodule MyApp.DatabaseProvider do
defstruct [id: nil]
defimpl Vapor.Provider do
def load(db_provider) do
end
end
end

Why does this exist?

Vapor is intended to be used for the configuration of other runtime dependencies such as setting the port for Phoenix.Endpoint or setting the database url for Ecto.Repo. Vapor is not intended for configuration of kernel modules such as :ssh. Mix releases provide a release configuration. But configuring an app in this way still has problems:

Vapor is specifically designed to target these use cases.