Spellbook v2.0.3 Spellbook View Source

Introduction

Spellbook is an Elixir library providing dynamic hierarchical configurations loading for your application. It is based on the ideas implemented in the Javascript node-config module.

It lets you define a set of default parameters, and extend them for different deployment environments (development, staging, production, etc.) or custom needs (client id, hostname, etc.).

Configurations are stored in default or custom folders containing configuration files and can be overridden and extended by environment variables.

Custom configuration static and dynamic filenames and file formats can be added as needed.

Quick Start

Read the configuration files from the standard <CWD>/config folder

config = Spellbook.load_config_folder()

Using Spellbook.load_config_folder/0 by default will use the following filename templates (in the listed order and if they exist) with the {SOMETHING} template variables substituted:

<CWD>/config/default.{EXT}
<CWD>/config/default-{INSTANCE}.{EXT}
<CWD>/config/{ENV}.{EXT}
<CWD>/config/{ENV}-{INSTANCE}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{INSTANCE}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{ENV}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/{FULL_HOSTNAME}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{INSTANCE}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{ENV}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/local.{EXT}
<CWD>/config/local-{INSTANCE}.{EXT}
<CWD>/config/local-{ENV}.{EXT}
<CWD>/config/local-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/custom-env-variables.{EXT}

Spellbook will use the default environment ({ENV} = dev) and the full hostname of the machine the code gets executed on ({FULL_HOSTNAME} = my-machine.spellbook.domain). As the other template variables are not defined, the filenames using them are ignored. The resulting filenames searched/merged will be:

<CWD>/config/default.json
<CWD>/config/default.yaml
<CWD>/config/dev.json
<CWD>/config/dev.yaml
<CWD>/config/my-machine.spellbook.domain.json
<CWD>/config/my-machine.spellbook.domain.yaml
<CWD>/config/my-machine.spellbook.domain-dev.json
<CWD>/config/my-machine.spellbook.domain-dev.yaml
<CWD>/config/local.json
<CWD>/config/local.yaml
<CWD>/config/local-dev.json
<CWD>/config/local-dev.yaml
<CWD>/config/custom-env-variables.json
<CWD>/config/custom-env-variables.yaml

By default Spellbook supports JSON and YAML file formats.

Read brand's configuration from a specific folder with custom settings for a specific client

config = Spellbook.default_config()
|> Spellbook.add_filename_format("clients/%{brand}.%{ext}")
|> Spellbook.load_config(
  folder: "./test/support/brand",
  config_filename: "brand-conf",
  vars: [instance: "job-processor", brand: "elixir", env: "prod", short_hostname: "worker"]
)

Here we specify a specific folder were to look for the configuration files (with the folder option), a custom configuration file name (with the config_filename option). The vars configuration field is used to define the variable values used in the filename templates.

The Spellbook.default_config/0 function (and the Spellbook.load_config/0 one as well) configures the Spellbook to search for the following file templates:

./test/support/brand/{CONFIG_FILENAME}.{EXT}
./test/support/brand/{CONFIG_FILENAME}-{INSTANCE}.{EXT}
./test/support/brand/{CONFIG_FILENAME}-{ENV}.{EXT}
./test/support/brand/{CONFIG_FILENAME}-{SHORT_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
./test/support/brand/{CONFIG_FILENAME}-{FULL_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
./test/support/brand/clients/{BRAND}.{EXT}
./test/support/brand/custom-env-variables.{EXT}

In this case the searched/merged files will be:

./test/support/brand/brand-conf.json
./test/support/brand/brand-conf.yaml
./test/support/brand/brand-conf-job-processor.json
./test/support/brand/brand-conf-job-processor.yaml
./test/support/brand/brand-conf-prod.json
./test/support/brand/brand-conf-prod.yaml
./test/support/brand/brand-conf-worker-prod-job-processor.json
./test/support/brand/brand-conf-worker-prod-job-processor.yaml
./test/support/brand/brand-conf-worker1.spellbook.domain-prod-job-processor.json
./test/support/brand/brand-conf-worker1.spellbook.domain-prod-job-processor.yaml
./test/support/brand/clients/elixir.json
./test/support/brand/clients/elixir.yaml
./test/support/brand/custom-env-variables.json
./test/support/brand/custom-env-variables.yaml

Get a value out of a Spellbook configuration

A configuration is just a Map.

iex> config = Spellbook.load_config_folder()
%{ "some" => %{ "value" => %{ "from" => %{ "config" => "a value" }}}}
iex> is_map(config) == true
true

You can access the configuration values using the standard language features

iex> value = config["some"]["value"]["from"]["config"]
"a value"

or using the Spellbook.get method that supports dot notation to access elements deep down the configuration structure:

iex> value = Spellbook.get(config, "some.value.from.config")
"a value"

Use environment variables in configuration files

Some situations rely heavily on environment variables to configure secrets and settings best left out of a codebase. Spellbook lets you use map the environment variable names into your configuration structure using a custom-env-variables.{EXT} file:

{
  "database": {
    "username": "DB_USERNAME",
    "password": "DB_PASSWORD"
  }
}

If the DB_USERNAME and DB_PASSWORD environment variable exist, they would override the values for database.username and database.password in the configuration.

Custom environment variables have precedence and override all configuration files, including local.json.

Link to this section Summary

Functions

Adds a filename format to the list of templates to be used to generate the list of files to be searched when the configuration is loaded.

Performs a deep merge of a configuration into an application environment.

Performs a deep merge of two maps.

Sets up the default configuration for reading a generic configuration set of files.

Sets up the default configuration for reading a generic configuration set of files.

Sets up the default configuration for reading application configuration from a folder.

Sets up the default configuration for reading application configuration from a folder.

Retrieves a configuration value.

Loads the configuration files from the provided Spellbook.

Creates a Spellbook with the default config folder filenames list and loads them into a configuration map

Creates a Spellbook with the default config folder filenames list and loads them into a configuration map

Creates a Spellbook with the default config filenames list and loads them into a configuration map.

Registers an config file format extension and its parser.

Sets Spellbook options. Option names are atoms.

Sets a variable to be used during filenames list generation using a 2 elements tuple.

Sets a variable to be used during filenames list generation.

Sets some variable to be used during filenames list generation.

Performs a deep substitution of variables used as map values.

Link to this section Functions

Link to this function

add_filename_format(spellbook, filename_formats)

View Source
add_filename_format(spellbook :: Spellbook, filename_formats :: [String.t()]) ::
  Spellbook
add_filename_format(spellbook :: Spellbook, filename_formats :: String.t()) ::
  Spellbook

Adds a filename format to the list of templates to be used to generate the list of files to be searched when the configuration is loaded.

Filename formats can contain template variables specified using the following interpolation format (%{VARIABLE}):

  • "special-%{env}.%{ext}"
  • "config-%{username}-%{role}.json"

Files are loaded in the order you specify the filename formats.

config = Spellbook.default_config()
|> Spellbook.add_filename_format("clients/%{brand}.%{ext}")
|> Spellbook.add_filename_format(["clients/special/%{brand}-%{version}.%{ext}", "clients/external-%{brand}.%{ext}"])
Link to this function

apply_config_to_application_env(config, config_key, app_name \\ nil, env_key \\ nil)

View Source
apply_config_to_application_env(
  config :: Map.t(),
  config_key :: String.t(),
  atom() | nil,
  atom() | nil
) :: :ok

Performs a deep merge of a configuration into an application environment.

Link to this function

deep_merge(left, right)

View Source
deep_merge(left :: Map.t(), right :: Map.t()) :: Map.t()

Performs a deep merge of two maps.

Examples

iex> Spellbook.deep_merge(%{"a" => %{"b" => "1", "c" => [1,2,3]}}, %{"a" => %{"b" => "X"}})
%{"a" => %{"b" => "X", "c" => [1, 2, 3]}}

Sets up the default configuration for reading a generic configuration set of files.

Accepts a list

Link to this function

default_config(spellbook \\ %Spellbook{}, params \\ %{})

View Source
default_config(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  params :: Map.t()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets up the default configuration for reading a generic configuration set of files.

The valid params are:

  • vars: Keyword or Keyword list of variables to be used in the filenames list generation.
  • options: map with Spellbook options

The default filename formats are:

<FOLDER>/{CONFIG_FILENAME}.{EXT}
<FOLDER>/{CONFIG_FILENAME}-{INSTANCE}.{EXT}
<FOLDER>/{CONFIG_FILENAME}-{ENV}.{EXT}
<FOLDER>/{CONFIG_FILENAME}-{SHORT_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<FOLDER>/{CONFIG_FILENAME}-{FULL_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<FOLDER>/custom-env-variables.{EXT}
Link to this function

default_config_folder(params)

View Source
default_config_folder(params :: keyword()) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}
default_config_folder(params :: Map.t()) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets up the default configuration for reading application configuration from a folder.

Link to this function

default_config_folder(spellbook \\ %Spellbook{}, params \\ %{})

View Source
default_config_folder(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  params :: Map.t()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets up the default configuration for reading application configuration from a folder.

The valid params are:

  • vars: Keyword or Keyword list of variables to be used in the filenames list generation.
  • options: map with Spellbook options

The default filename formats are:

<CWD>/config/default.{EXT}
<CWD>/config/default-{INSTANCE}.{EXT}
<CWD>/config/{ENV}.{EXT}
<CWD>/config/{ENV}-{INSTANCE}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{INSTANCE}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{ENV}.{EXT}
<CWD>/config/{SHORT_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/{FULL_HOSTNAME}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{INSTANCE}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{ENV}.{EXT}
<CWD>/config/{FULL_HOSTNAME}-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/local.{EXT}
<CWD>/config/local-{INSTANCE}.{EXT}
<CWD>/config/local-{ENV}.{EXT}
<CWD>/config/local-{ENV}-{INSTANCE}.{EXT}
<CWD>/config/custom-env-variables.{EXT}
Link to this function

generate(spellbook, params)

View Source
Link to this function

get(config, key)

View Source
get(config :: Map.t(), key :: String.t()) :: any()

Retrieves a configuration value.

This function supports dot notation, so you can retrieve values from deeply nested keys, like "database.config.password".

Examples

iex> Spellbook.get(%{"a" => %{"b" => "1", "c" => [1,2,3]}}, "a.b")
"1"
Link to this function

load_config(spellbook, params)

View Source
load_config(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  params :: maybe_improper_list()
) :: Map.t()
load_config(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  params :: Map.t()
) :: Map.t()

Loads the configuration files from the provided Spellbook.

Link to this function

load_config_folder(params \\ %{})

View Source
load_config_folder(params :: Map.t()) :: Map.t()

Creates a Spellbook with the default config folder filenames list and loads them into a configuration map

Link to this function

load_config_folder(spellbook, params)

View Source
load_config_folder(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  params :: Map.t()
) :: Map.t()

Creates a Spellbook with the default config folder filenames list and loads them into a configuration map

Link to this function

load_default_config(params)

View Source
load_default_config(params :: list()) :: Map.t()

Creates a Spellbook with the default config filenames list and loads them into a configuration map.

Link to this function

register_extensions(spellbook, extensions)

View Source
register_extensions(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  extensions :: Map.t()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Registers an config file format extension and its parser.

extensions = %{
  "csv" => Crazy.Parser.CSV
}
Spellbook.register_extensions(spellbook, extensions)
Link to this function

set_options(spellbook, options)

View Source
set_options(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  options :: nil | list() | Map.t()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets Spellbook options. Option names are atoms.

Valid options are:

  • :folder: folder where to find the configuration. Defaults to #{Path.join(File.cwd!(), "config")}.
  • :config_filename: name of the configuration file, default to "config".
  • :ignore_invalid_filename_formats: defauts to true. Set it to false if you want to raise an exception if a file in the generated filenames list is not found.
  • :config: optional configuration Map or Keyword list to be merged into the final configuration. Takes precedence on everything except the environment variables.
Link to this function

set_var(spellbook, arg)

View Source
set_var(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  {name :: String.t(), value :: any()}
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets a variable to be used during filenames list generation using a 2 elements tuple.

Link to this function

set_var(spellbook, name, value)

View Source
set_var(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  name :: String.t(),
  value :: any()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets a variable to be used during filenames list generation.

Link to this function

set_vars(spellbook, values)

View Source
set_vars(
  spellbook :: %Spellbook{
    extensions: term(),
    filename_formats: term(),
    options: term(),
    vars: term()
  },
  values :: maybe_improper_list()
) :: %Spellbook{
  extensions: term(),
  filename_formats: term(),
  options: term(),
  vars: term()
}

Sets some variable to be used during filenames list generation.

Link to this function

substitute_vars(config, vars)

View Source
substitute_vars(config :: Map.t(), vars :: Map.t()) :: Map.t()

Performs a deep substitution of variables used as map values.

Examples

iex> Spellbook.substitute_vars(%{"a" => %{"b" => "VAR", "c" => "NOT_A_VAR"}}, %{"VAR" => "spellbook"})
%{"a" => %{"b" => "spellbook", "c" => "NOT_A_VAR"}}