Module: Jac::Configuration

Defined in:
lib/jac/configuration.rb,
lib/jac/merger.rb

Overview

Configuration is loaded from well formed YAML streams. Each document expected to be key-value mapping where keys a profile names and values is a profile content. Profile itself is key-value mapping too. Except reserved key names (i.e. extends) each key in profile is a configuration field. For example following yaml document

“‘yml

foo:
  bar: 42
qoo:
  bar: 32

“‘

represents description of two profiles, foo and qoo, where field bar is set to 42 and 32 respectively.

Profile can be constructed using combination of other profiles for example having debug and release profiles for testing and production. And having remote and local profiles for building on local or remote machine. We cant get ‘debug,local`, `debug,remote`, `release,local` and `release,remote` profiles. Each of such profiles is a result of merging values of listed profiles. When merging profile with another configuration resolver overwrites existing fields. For example if debug and local for some reason have same field. In profile `debug,local` value from debug profile will be overwritten with value from local profile.

## Extending profiles

One profile can extend another. Or any amount of other profiles. To specify this one should use extends field like that

“‘yml

base:

application_name: my-awesome-app
port: 80

version:

version_name: 0.0.0
version_code: 42

debug:

extends: [base, version]
port: 9292

“‘

In this example debug will receive following fields:

“‘yml application_name: my-awesome-app # from base profile port: 9292 # from debug profile version_name: 0.0.0 # from version profile version_code: 42 # from version profile “`

## Merging multiple configuration files

Configuration can be loaded from multiple YAML documents. Before resolve requested profile all described profiles are merged down together. Having sequence of files like .application.yml, .application.user.yml with following content

“‘yml # .application.yml base:

user: deployer

debug:

extends: base
# ... other values

“‘

“‘yml # .application.user.yml base:

user: developer

“‘

We’ll get user field overwritten with value from .application.user.yml. And only after that construction of resulting profile will begin (for example debug)

## String evaluation

Configuration resolver comes with powerful yet dangerous feature: it allows evaluate strings as ruby expressions like this:

“‘yml foo:

build_time: "#{Time.now}" # Will be evaluated at configuration resolving step

“‘

Configuration values are available to and can be referenced with c:

“‘yml base:

application_name: my-awesome-app

debug:

extends: base
server_name: "#{c.application_name}-debug"   # yields to my-awesome-app-debug

release:

extends: base
server_name: "#{c.application_name}-release" # yields to my-awesome-app-release

“‘

All strings evaluated after profile is constructed thus you don’t need to have declared values in current profile but be ready to get nil.

## Merging values

By default if one value have multiple defenitions it will be overwritten by topmost value. Except several cases where Jac handles value resolution specialy

### Merging hashes

Hashes inside profile are recurseively merged automaticly. This rule works for profile extensions and value redefenitions too.

Example:

“‘yml base:

servers:
  release: http://release.com

debug:

extends: base
  debug: http://debug.com

“‘

### Merging sets

Sets allowed to be merged with ‘nil`s and any instance of Enumerable. Merge result is always new Set instance. “`yml release:

extends:
  - no_rtti
  - no_debug
flags: !set {} # empty set

no_rtti:

flags:
  - -fno-rtti

no_debug:

flags:
  - -DNDEBUG

“‘

Resulting profile will have ‘-fno-rtti, -DNDEBUG` in `release profile` ## Generic profiles

Same result as shown above can be achieved with generic profiles. Generic profile is a profile which name is regex (i.e. contained in /.../):

“‘ base:

application_name: my-awesome-app

/(release|debug)/: # Profile name is a regex, with single capture (1)

extends: base
server_name: "#{c.application_name}-#{c.captures[1]}"  # yields  my-awesome-app-release or  my-awesome-app-debug

“‘

If profile name matches multiple generic profiles it not defined which profile will be used.

Defined Under Namespace

Classes: ConfigurationEvaluator, ConfigurationReader, EvaluationContext, Merger, ProfileResolver

Constant Summary collapse

TOP_PROFILE_NAME =

Name of top level profile. ^top profile is implicit it automaticly merged on top of resulting configuration

'^top'.freeze
BASE_PROFILE_NAME =

Name of base level profile. ^base profile is implicit resulting configuration automaticly merged on top of it.

'^base'.freeze
DEFAULT_PROFILE_NAME =

Any configuration set always contains default profile which is loaded when no profile requested.

'default'.freeze
BASIC_PROFILES =

List of default profiles. Having this list we can easily create default configuration.

[
  TOP_PROFILE_NAME,
  BASE_PROFILE_NAME,
  DEFAULT_PROFILE_NAME
].freeze
EXTENDS_KEY =

Key where all inherited profiles listed

'extends'.freeze
CONFIGURATION_FILES =

List of files where configuration can be placed

  • jac.yml - expected to be main configuration file.

Usually it placed under version control.

  • jac.user.yml - user defined overrides for main

configuration, sensitive data which can’t be placed under version control.

  • jac.override.yml - final overrides.

%w[jac.yml jac.user.yml jac.override.yml].freeze

Class Method Summary collapse

Class Method Details

.load(profile, files: CONFIGURATION_FILES, dir: nil) ⇒ OpenStruct

Read configuration from configuration files.

Parameters:

  • (defaults to: CONFIGURATION_FILES)

    filenames to load

  • (defaults to: nil)

    base directory path for provided files may be nil

Returns:

  • resolved profile values



492
493
494
495
496
497
498
499
# File 'lib/jac/configuration.rb', line 492

def load(profile, files: CONFIGURATION_FILES, dir: nil)
  # Read all known files
  streams = files
            .map { |f| [dir ? File.join(dir, f) : f, f] }
            .select { |path, _name| File.exist?(path) }
            .map { |path, name| [IO.read(path), name] }
  read(profile, *streams)
end

.read(profile, *streams) ⇒ OpenStruct

Generates configuration object for given profile and list of streams with YAML document names to read

Parameters:

  • list of profile names to merge

  • list of YAML documents and their

Returns:

  • instance which contains all resolved profile fields



482
483
484
485
# File 'lib/jac/configuration.rb', line 482

def read(profile, *streams)
  profile = [Configuration::DEFAULT_PROFILE_NAME] if profile.empty?
  ConfigurationReader.new(streams).read(*profile)
end