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

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:

  • files (Array) (defaults to: CONFIGURATION_FILES)

    filenames to load

  • dir (String) (defaults to: nil)

    base directory path for provided files may be nil

Returns:

  • (OpenStruct)

    resolved profile values



436
437
438
439
440
441
442
443
# File 'lib/jac/configuration.rb', line 436

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:

  • profile (Array)

    list of profile names to merge

  • streams (Array)

    list of YAML documents and their

Returns:

  • (OpenStruct)

    instance which contains all resolved profile fields



426
427
428
429
# File 'lib/jac/configuration.rb', line 426

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