hiera 2.0.0-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +202 -0
- data/LICENSE +18 -0
- data/README.md +276 -0
- data/bin/hiera +248 -0
- data/lib/hiera/backend/json_backend.rb +58 -0
- data/lib/hiera/backend/yaml_backend.rb +63 -0
- data/lib/hiera/backend.rb +325 -0
- data/lib/hiera/config.rb +90 -0
- data/lib/hiera/console_logger.rb +13 -0
- data/lib/hiera/error.rb +4 -0
- data/lib/hiera/fallback_logger.rb +41 -0
- data/lib/hiera/filecache.rb +86 -0
- data/lib/hiera/interpolate.rb +98 -0
- data/lib/hiera/noop_logger.rb +8 -0
- data/lib/hiera/puppet_logger.rb +17 -0
- data/lib/hiera/recursive_guard.rb +20 -0
- data/lib/hiera/util.rb +47 -0
- data/lib/hiera/version.rb +89 -0
- data/lib/hiera.rb +115 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/unit/backend/json_backend_spec.rb +85 -0
- data/spec/unit/backend/yaml_backend_spec.rb +138 -0
- data/spec/unit/backend_spec.rb +743 -0
- data/spec/unit/config_spec.rb +118 -0
- data/spec/unit/console_logger_spec.rb +19 -0
- data/spec/unit/fallback_logger_spec.rb +80 -0
- data/spec/unit/filecache_spec.rb +142 -0
- data/spec/unit/fixtures/interpolate/config/hiera.yaml +6 -0
- data/spec/unit/fixtures/interpolate/data/niltest.yaml +2 -0
- data/spec/unit/fixtures/interpolate/data/recursive.yaml +3 -0
- data/spec/unit/fixtures/override/config/hiera.yaml +5 -0
- data/spec/unit/fixtures/override/data/alternate.yaml +1 -0
- data/spec/unit/fixtures/override/data/common.yaml +2 -0
- data/spec/unit/hiera_spec.rb +81 -0
- data/spec/unit/interpolate_spec.rb +36 -0
- data/spec/unit/puppet_logger_spec.rb +31 -0
- data/spec/unit/util_spec.rb +49 -0
- data/spec/unit/version_spec.rb +44 -0
- metadata +128 -0
data/lib/hiera/config.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
class Hiera::Config
|
2
|
+
class << self
|
3
|
+
##
|
4
|
+
# load takes a string or hash as input, strings are treated as filenames
|
5
|
+
# hashes are stored as data that would have been in the config file
|
6
|
+
#
|
7
|
+
# Unless specified it will only use YAML as backend with a single
|
8
|
+
# 'common' hierarchy and console logger
|
9
|
+
#
|
10
|
+
# @return [Hash] representing the configuration. e.g.
|
11
|
+
# {:backends => "yaml", :hierarchy => "common"}
|
12
|
+
def load(source)
|
13
|
+
@config = {:backends => "yaml",
|
14
|
+
:hierarchy => "common",
|
15
|
+
:merge_behavior => :native }
|
16
|
+
|
17
|
+
if source.is_a?(String)
|
18
|
+
if File.exist?(source)
|
19
|
+
config = begin
|
20
|
+
yaml_load_file(source)
|
21
|
+
rescue TypeError => detail
|
22
|
+
case detail.message
|
23
|
+
when /no implicit conversion from nil to integer/
|
24
|
+
false
|
25
|
+
else
|
26
|
+
raise detail
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@config.merge! config if config
|
30
|
+
else
|
31
|
+
raise "Config file #{source} not found"
|
32
|
+
end
|
33
|
+
elsif source.is_a?(Hash)
|
34
|
+
@config.merge! source
|
35
|
+
end
|
36
|
+
|
37
|
+
@config[:backends] = [ @config[:backends] ].flatten
|
38
|
+
|
39
|
+
if @config.include?(:logger)
|
40
|
+
Hiera.logger = @config[:logger].to_s
|
41
|
+
else
|
42
|
+
@config[:logger] = "console"
|
43
|
+
Hiera.logger = "console"
|
44
|
+
end
|
45
|
+
|
46
|
+
self.validate!
|
47
|
+
|
48
|
+
@config
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate!
|
52
|
+
case @config[:merge_behavior]
|
53
|
+
when :deep,'deep',:deeper,'deeper'
|
54
|
+
begin
|
55
|
+
require "deep_merge"
|
56
|
+
rescue LoadError
|
57
|
+
raise Hiera::Error, "Must have 'deep_merge' gem installed for the configured merge_behavior."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# yaml_load_file directly delegates to YAML.load_file and is intended to be
|
64
|
+
# a private, internal method suitable for stubbing and mocking.
|
65
|
+
#
|
66
|
+
# @return [Object] return value of {YAML.load_file}
|
67
|
+
def yaml_load_file(source)
|
68
|
+
YAML.load_file(source)
|
69
|
+
end
|
70
|
+
private :yaml_load_file
|
71
|
+
|
72
|
+
def load_backends
|
73
|
+
@config[:backends].each do |backend|
|
74
|
+
begin
|
75
|
+
require "hiera/backend/#{backend.downcase}_backend"
|
76
|
+
rescue LoadError => e
|
77
|
+
Hiera.warn "Cannot load backend #{backend}: #{e}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def include?(key)
|
83
|
+
@config.include?(key)
|
84
|
+
end
|
85
|
+
|
86
|
+
def [](key)
|
87
|
+
@config[key]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/hiera/error.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Select from a given list of loggers the first one that
|
2
|
+
# it suitable and use that as the actual logger
|
3
|
+
#
|
4
|
+
# @api private
|
5
|
+
class Hiera::FallbackLogger
|
6
|
+
# Chooses the first suitable logger. For all of the loggers that are
|
7
|
+
# unsuitable it will issue a warning using the suitable logger stating that
|
8
|
+
# the unsuitable logger is not being used.
|
9
|
+
#
|
10
|
+
# @param implementations [Array<Hiera::Logger>] the implementations to choose from
|
11
|
+
# @raises when there are no suitable loggers
|
12
|
+
def initialize(*implementations)
|
13
|
+
warnings = []
|
14
|
+
@implementation = implementations.find do |impl|
|
15
|
+
if impl.respond_to?(:suitable?)
|
16
|
+
if impl.suitable?
|
17
|
+
true
|
18
|
+
else
|
19
|
+
warnings << "Not using #{impl.name}. It does not report itself to be suitable."
|
20
|
+
false
|
21
|
+
end
|
22
|
+
else
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if @implementation.nil?
|
28
|
+
raise "No suitable logging implementation found."
|
29
|
+
end
|
30
|
+
|
31
|
+
warnings.each { |message| warn(message) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def warn(message)
|
35
|
+
@implementation.warn(message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def debug(message)
|
39
|
+
@implementation.debug(message)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class Hiera
|
2
|
+
class Filecache
|
3
|
+
def initialize
|
4
|
+
@cache = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
# Reads a file, optionally parse it in some way check the
|
8
|
+
# output type and set a default
|
9
|
+
#
|
10
|
+
# Simply invoking it this way will return the file contents
|
11
|
+
#
|
12
|
+
# data = read("/some/file")
|
13
|
+
#
|
14
|
+
# But as most cases of file reading in hiera involves some kind
|
15
|
+
# of parsing through a serializer there's some help for those
|
16
|
+
# cases:
|
17
|
+
#
|
18
|
+
# data = read("/some/file", Hash, {}) do |data|
|
19
|
+
# JSON.parse(data)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# In this case it will read the file, parse it using JSON then
|
23
|
+
# check that the end result is a Hash, if it's not a hash or if
|
24
|
+
# reading/parsing fails it will return {} instead
|
25
|
+
#
|
26
|
+
# Prior to calling this method you should be sure the file exist
|
27
|
+
def read(path, expected_type = Object, default=nil, &block)
|
28
|
+
read_file(path, expected_type, &block)
|
29
|
+
rescue TypeError => detail
|
30
|
+
Hiera.debug("#{detail.message}, setting defaults")
|
31
|
+
@cache[path][:data] = default
|
32
|
+
rescue => detail
|
33
|
+
error = "Reading data from #{path} failed: #{detail.class}: #{detail}"
|
34
|
+
if default.nil?
|
35
|
+
raise detail
|
36
|
+
else
|
37
|
+
Hiera.debug(error)
|
38
|
+
@cache[path][:data] = default
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Read a file when it changes. If a file is re-read and has not changed since the last time
|
43
|
+
# then the last, processed, contents will be returned.
|
44
|
+
#
|
45
|
+
# The processed data can also be checked against an expected type. If the
|
46
|
+
# type does not match a TypeError is raised.
|
47
|
+
#
|
48
|
+
# No error handling is done inside this method. Any failed reads or errors
|
49
|
+
# in processing will be propagated to the caller
|
50
|
+
def read_file(path, expected_type = Object)
|
51
|
+
if stale?(path)
|
52
|
+
data = File.read(path)
|
53
|
+
@cache[path][:data] = block_given? ? yield(data) : data
|
54
|
+
|
55
|
+
if !@cache[path][:data].is_a?(expected_type)
|
56
|
+
raise TypeError, "Data retrieved from #{path} is #{data.class} not #{expected_type}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@cache[path][:data]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def stale?(path)
|
66
|
+
meta = path_metadata(path)
|
67
|
+
|
68
|
+
@cache[path] ||= {:data => nil, :meta => nil}
|
69
|
+
|
70
|
+
if @cache[path][:meta] == meta
|
71
|
+
return false
|
72
|
+
else
|
73
|
+
@cache[path][:meta] = meta
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# This is based on the old caching in the YAML backend and has a
|
79
|
+
# resolution of 1 second, changes made within the same second of
|
80
|
+
# a previous read will be ignored
|
81
|
+
def path_metadata(path)
|
82
|
+
stat = File.stat(path)
|
83
|
+
{:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'hiera/backend'
|
2
|
+
require 'hiera/recursive_guard'
|
3
|
+
|
4
|
+
|
5
|
+
class Hiera::InterpolationInvalidValue < StandardError; end
|
6
|
+
|
7
|
+
class Hiera::Interpolate
|
8
|
+
class << self
|
9
|
+
INTERPOLATION = /%\{([^\}]*)\}/
|
10
|
+
METHOD_INTERPOLATION = /%\{(scope|hiera|literal|alias)\(['"]([^"']*)["']\)\}/
|
11
|
+
|
12
|
+
def interpolate(data, scope, extra_data, context)
|
13
|
+
if data.is_a?(String)
|
14
|
+
# Wrapping do_interpolation in a gsub block ensures we process
|
15
|
+
# each interpolation site in isolation using separate recursion guards.
|
16
|
+
context ||= {}
|
17
|
+
new_context = context.clone
|
18
|
+
new_context[:recurse_guard] ||= Hiera::RecursiveGuard.new
|
19
|
+
data.gsub(INTERPOLATION) do |match|
|
20
|
+
interp_val = do_interpolation(match, scope, extra_data, new_context)
|
21
|
+
|
22
|
+
# Get interp method in case we are aliasing
|
23
|
+
if data.is_a?(String) && (match = data.match(INTERPOLATION))
|
24
|
+
interpolate_method, key = get_interpolation_method_and_key(data)
|
25
|
+
else
|
26
|
+
interpolate_method = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
if ( (interpolate_method == :alias_interpolate) and (!interp_val.is_a?(String)) )
|
30
|
+
if data.match("^#{INTERPOLATION}$")
|
31
|
+
return interp_val
|
32
|
+
else
|
33
|
+
raise Hiera::InterpolationInvalidValue, "Cannot call alias in the string context"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
interp_val
|
37
|
+
end
|
38
|
+
end
|
39
|
+
else
|
40
|
+
data
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_interpolation(data, scope, extra_data, context)
|
45
|
+
if data.is_a?(String) && (match = data.match(INTERPOLATION))
|
46
|
+
interpolation_variable = match[1]
|
47
|
+
context[:recurse_guard].check(interpolation_variable) do
|
48
|
+
interpolate_method, key = get_interpolation_method_and_key(data)
|
49
|
+
interpolated_data = send(interpolate_method, data, key, scope, extra_data, context)
|
50
|
+
|
51
|
+
# Halt recursion if we encounter a literal.
|
52
|
+
return interpolated_data if interpolate_method == :literal_interpolate
|
53
|
+
|
54
|
+
do_interpolation(interpolated_data, scope, extra_data, context)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
private :do_interpolation
|
61
|
+
|
62
|
+
def get_interpolation_method_and_key(data)
|
63
|
+
if (match = data.match(METHOD_INTERPOLATION))
|
64
|
+
case match[1]
|
65
|
+
when 'hiera' then [:hiera_interpolate, match[2]]
|
66
|
+
when 'scope' then [:scope_interpolate, match[2]]
|
67
|
+
when 'literal' then [:literal_interpolate, match[2]]
|
68
|
+
when 'alias' then [:alias_interpolate, match[2]]
|
69
|
+
end
|
70
|
+
elsif (match = data.match(INTERPOLATION))
|
71
|
+
[:scope_interpolate, match[1]]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
private :get_interpolation_method_and_key
|
75
|
+
|
76
|
+
def scope_interpolate(data, key, scope, extra_data, context)
|
77
|
+
segments = key.split('.')
|
78
|
+
catch(:no_such_key) { return Hiera::Backend.qualified_lookup(segments, scope) }
|
79
|
+
catch(:no_such_key) { Hiera::Backend.qualified_lookup(segments, extra_data) }
|
80
|
+
end
|
81
|
+
private :scope_interpolate
|
82
|
+
|
83
|
+
def hiera_interpolate(data, key, scope, extra_data, context)
|
84
|
+
Hiera::Backend.lookup(key, nil, scope, context[:order_override], :priority, context)
|
85
|
+
end
|
86
|
+
private :hiera_interpolate
|
87
|
+
|
88
|
+
def literal_interpolate(data, key, scope, extra_data, context)
|
89
|
+
key
|
90
|
+
end
|
91
|
+
private :literal_interpolate
|
92
|
+
|
93
|
+
def alias_interpolate(data, key, scope, extra_data, context)
|
94
|
+
Hiera::Backend.lookup(key, nil, scope, context[:order_override], :priority, context)
|
95
|
+
end
|
96
|
+
private :alias_interpolate
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Puppet_logger
|
3
|
+
class << self
|
4
|
+
def suitable?
|
5
|
+
defined?(::Puppet) == "constant"
|
6
|
+
end
|
7
|
+
|
8
|
+
def warn(msg)
|
9
|
+
Puppet.notice("hiera(): #{msg}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(msg)
|
13
|
+
Puppet.debug("hiera(): #{msg}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Allow for safe recursive lookup of values during variable interpolation.
|
2
|
+
#
|
3
|
+
# @api private
|
4
|
+
class Hiera::InterpolationLoop < StandardError; end
|
5
|
+
|
6
|
+
class Hiera::RecursiveGuard
|
7
|
+
def initialize
|
8
|
+
@seen = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def check(value, &block)
|
12
|
+
if @seen.include?(value)
|
13
|
+
raise Hiera::InterpolationLoop, "Detected in [#{@seen.join(', ')}]"
|
14
|
+
end
|
15
|
+
@seen.push(value)
|
16
|
+
ret = yield
|
17
|
+
@seen.pop
|
18
|
+
ret
|
19
|
+
end
|
20
|
+
end
|
data/lib/hiera/util.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Util
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def posix?
|
6
|
+
require 'etc'
|
7
|
+
Etc.getpwuid(0) != nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def microsoft_windows?
|
11
|
+
return false unless file_alt_separator
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'win32/dir'
|
15
|
+
true
|
16
|
+
rescue LoadError => err
|
17
|
+
warn "Cannot run on Microsoft Windows without the win32-dir gem: #{err}"
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def config_dir
|
23
|
+
if microsoft_windows?
|
24
|
+
File.join(common_appdata, 'PuppetLabs', 'code')
|
25
|
+
else
|
26
|
+
'/etc/puppetlabs/code'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def var_dir
|
31
|
+
if microsoft_windows?
|
32
|
+
File.join(common_appdata, 'PuppetLabs', 'code', 'hieradata')
|
33
|
+
else
|
34
|
+
'/etc/puppetlabs/code/hieradata'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def file_alt_separator
|
39
|
+
File::ALT_SEPARATOR
|
40
|
+
end
|
41
|
+
|
42
|
+
def common_appdata
|
43
|
+
Dir::COMMON_APPDATA
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# The version method and constant are isolated in hiera/version.rb so that a
|
2
|
+
# simple `require 'hiera/version'` allows a rubygems gemspec or bundler
|
3
|
+
# Gemfile to get the hiera version of the gem install.
|
4
|
+
#
|
5
|
+
# The version is programatically settable because we want to allow the
|
6
|
+
# Raketasks and such to set the version based on the output of `git describe`
|
7
|
+
|
8
|
+
|
9
|
+
class Hiera
|
10
|
+
VERSION = "2.0.0"
|
11
|
+
|
12
|
+
##
|
13
|
+
# version is a public API method intended to always provide a fast and
|
14
|
+
# lightweight way to determine the version of hiera.
|
15
|
+
#
|
16
|
+
# The intent is that software external to hiera be able to determine the
|
17
|
+
# hiera version with no side-effects. The expected use is:
|
18
|
+
#
|
19
|
+
# require 'hiera/version'
|
20
|
+
# version = Hiera.version
|
21
|
+
#
|
22
|
+
# This function has the following ordering precedence. This precedence list
|
23
|
+
# is designed to facilitate automated packaging tasks by simply writing to
|
24
|
+
# the VERSION file in the same directory as this source file.
|
25
|
+
#
|
26
|
+
# 1. If a version has been explicitly assigned using the Hiera.version=
|
27
|
+
# method, return that version.
|
28
|
+
# 2. If there is a VERSION file, read the contents, trim any
|
29
|
+
# trailing whitespace, and return that version string.
|
30
|
+
# 3. Return the value of the Hiera::VERSION constant hard-coded into
|
31
|
+
# the source code.
|
32
|
+
#
|
33
|
+
# If there is no VERSION file, the method must return the version string of
|
34
|
+
# the nearest parent version that is an officially released version. That is
|
35
|
+
# to say, if a branch named 3.1.x contains 25 patches on top of the most
|
36
|
+
# recent official release of 3.1.1, then the version method must return the
|
37
|
+
# string "3.1.1" if no "VERSION" file is present.
|
38
|
+
#
|
39
|
+
# By design the version identifier is _not_ intended to vary during the life
|
40
|
+
# a process. There is no guarantee provided that writing to the VERSION file
|
41
|
+
# while a Hiera process is running will cause the version string to be
|
42
|
+
# updated. On the contrary, the contents of the VERSION are cached to reduce
|
43
|
+
# filesystem accesses.
|
44
|
+
#
|
45
|
+
# The VERSION file is intended to be used by package maintainers who may be
|
46
|
+
# applying patches or otherwise changing the software version in a manner
|
47
|
+
# that warrants a different software version identifier. The VERSION file is
|
48
|
+
# intended to be managed and owned by the release process and packaging
|
49
|
+
# related tasks, and as such should not reside in version control. The
|
50
|
+
# VERSION constant is intended to be version controlled in history.
|
51
|
+
#
|
52
|
+
# Ideally, this behavior will allow package maintainers to precisely specify
|
53
|
+
# the version of the software they're packaging as in the following example:
|
54
|
+
#
|
55
|
+
# $ git describe --match "1.2.*" > lib/hiera/VERSION
|
56
|
+
# $ ruby -r hiera/version -e 'puts Hiera.version'
|
57
|
+
# 1.2.1-9-g9fda440
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
# @return [String] containing the hiera version, e.g. "1.2.1"
|
62
|
+
def self.version
|
63
|
+
version_file = File.join(File.dirname(__FILE__), 'VERSION')
|
64
|
+
return @hiera_version if @hiera_version
|
65
|
+
if version = read_version_file(version_file)
|
66
|
+
@hiera_version = version
|
67
|
+
end
|
68
|
+
@hiera_version ||= VERSION
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.version=(version)
|
72
|
+
@hiera_version = version
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# read_version_file reads the content of the "VERSION" file that lives in the
|
77
|
+
# same directory as this source code file.
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
#
|
81
|
+
# @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION
|
82
|
+
# file does not exist.
|
83
|
+
def self.read_version_file(path)
|
84
|
+
if File.exists?(path)
|
85
|
+
File.read(path).chomp
|
86
|
+
end
|
87
|
+
end
|
88
|
+
private_class_method :read_version_file
|
89
|
+
end
|
data/lib/hiera.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Hiera
|
4
|
+
require "hiera/error"
|
5
|
+
require "hiera/version"
|
6
|
+
require "hiera/config"
|
7
|
+
require "hiera/util"
|
8
|
+
require "hiera/backend"
|
9
|
+
require "hiera/console_logger"
|
10
|
+
require "hiera/puppet_logger"
|
11
|
+
require "hiera/noop_logger"
|
12
|
+
require "hiera/fallback_logger"
|
13
|
+
require "hiera/filecache"
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_reader :logger
|
17
|
+
|
18
|
+
# Loggers are pluggable, just provide a class called
|
19
|
+
# Hiera::Foo_logger and respond to :warn and :debug
|
20
|
+
#
|
21
|
+
# See hiera-puppet for an example that uses the Puppet
|
22
|
+
# loging system instead of our own
|
23
|
+
def logger=(logger)
|
24
|
+
require "hiera/#{logger}_logger"
|
25
|
+
|
26
|
+
@logger = Hiera::FallbackLogger.new(
|
27
|
+
Hiera.const_get("#{logger.capitalize}_logger"),
|
28
|
+
Hiera::Console_logger)
|
29
|
+
rescue Exception => e
|
30
|
+
@logger = Hiera::Console_logger
|
31
|
+
warn("Failed to load #{logger} logger: #{e.class}: #{e}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def warn(msg); @logger.warn(msg); end
|
35
|
+
def debug(msg); @logger.debug(msg); end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :options, :config
|
39
|
+
|
40
|
+
# If the config option is a string its assumed to be a filename,
|
41
|
+
# else a hash of what would have been in the YAML config file
|
42
|
+
def initialize(options={})
|
43
|
+
options[:config] ||= File.join(Util.config_dir, 'hiera.yaml')
|
44
|
+
|
45
|
+
@config = Config.load(options[:config])
|
46
|
+
|
47
|
+
Config.load_backends
|
48
|
+
end
|
49
|
+
|
50
|
+
# Calls the backends to do the actual lookup.
|
51
|
+
#
|
52
|
+
# The _scope_ can be anything that responds to `[]`, if you have input
|
53
|
+
# data like a Puppet Scope that does not you can wrap that data in a
|
54
|
+
# class that has a `[]` method that fetches the data from your source.
|
55
|
+
# See hiera-puppet for an example of this.
|
56
|
+
#
|
57
|
+
# The order-override will insert as first in the hierarchy a data source
|
58
|
+
# of your choice.
|
59
|
+
#
|
60
|
+
# Possible values for the _resolution_type_ parameter:
|
61
|
+
#
|
62
|
+
# - _:priority_ - This is the default. First found value is returned and no merge is performed
|
63
|
+
# - _:array_ - An array merge lookup assembles a value from every matching level of the hierarchy. It retrieves all
|
64
|
+
# of the (string or array) values for a given key, then flattens them into a single array of unique values.
|
65
|
+
# If _priority_ lookup can be thought of as a “default with overrides” pattern, _array_ merge lookup can be though
|
66
|
+
# of as “default with additions.”
|
67
|
+
# - _:hash_ - A hash merge lookup assembles a value from every matching level of the hierarchy. It retrieves all of
|
68
|
+
# the (hash) values for a given key, then merges the hashes into a single hash. Hash merge lookups will fail with
|
69
|
+
# an error if any of the values found in the data sources are strings or arrays. It only works when every value
|
70
|
+
# found is a hash. The actual merge behavior is determined by looking up the keys `:merge_behavior` and
|
71
|
+
# `:deep_merge_options` in the Hiera config. `:merge_behavior` can be set to `:deep`, :deeper` or `:native`
|
72
|
+
# (explained in detail below).
|
73
|
+
# - _{ deep merge options }_ - Configured values for `:merge_behavior` and `:deep_merge_options`will be completely
|
74
|
+
# ignored. Instead the _resolution_type_ will be a `:hash` merge where the `:merge_behavior` will be the value
|
75
|
+
# keyed by `:behavior` in the given hash and the `:deep_merge_options` will be the remaining top level entries of
|
76
|
+
# that same hash.
|
77
|
+
#
|
78
|
+
# Valid behaviors for the _:hash_ resolution type:
|
79
|
+
#
|
80
|
+
# - _native_ - Performs a simple hash-merge by overwriting keys of lower lookup priority.
|
81
|
+
# - _deeper_ - In a deeper hash merge, Hiera recursively merges keys and values in each source hash. For each key,
|
82
|
+
# if the value is:
|
83
|
+
# - only present in one source hash, it goes into the final hash.
|
84
|
+
# - a string/number/boolean and exists in two or more source hashes, the highest priority value goes into
|
85
|
+
# the final hash.
|
86
|
+
# - an array and exists in two or more source hashes, the values from each source are merged into a single
|
87
|
+
# array and de-duplicated (but not automatically flattened, as in an array merge lookup).
|
88
|
+
# - a hash and exists in two or more source hashes, the values from each source are recursively merged, as
|
89
|
+
# though they were source hashes.
|
90
|
+
# - mismatched between two or more source hashes, we haven’t validated the behavior. It should act as
|
91
|
+
# described in the deep_merge gem documentation.
|
92
|
+
# - _deep_ - In a deep hash merge, Hiera behaves the same as for _deeper_, except that when a string/number/boolean
|
93
|
+
# exists in two or more source hashes, the lowest priority value goes into the final hash. This is considered
|
94
|
+
# largely useless and should be avoided. Use _deeper_ instead.
|
95
|
+
#
|
96
|
+
# The _merge_ can be given as a hash with the mandatory key `:strategy` to denote the actual strategy. This
|
97
|
+
# is useful for the `:deeper` and `:deep` strategy since they can use additional options to control the behavior.
|
98
|
+
# The options can be passed as top level keys in the `merge` parameter when it is a given as a hash. Recognized
|
99
|
+
# options are:
|
100
|
+
#
|
101
|
+
# - 'knockout_prefix' Set to string value to signify prefix which deletes elements from existing element. Defaults is _undef_
|
102
|
+
# - 'sort_merged_arrays' Set to _true_ to sort all arrays that are merged together. Default is _false_
|
103
|
+
# - 'unpack_arrays' Set to string value used as a deliminator to join all array values and then split them again. Default is _undef_
|
104
|
+
# - 'merge_hash_arrays' Set to _true_ to merge hashes within arrays. Default is _false_
|
105
|
+
#
|
106
|
+
# @param key [String] The key to lookup
|
107
|
+
# @param default [Object,nil] The value to return when there is no match for _key_
|
108
|
+
# @param scope [#[],nil] The scope to use for the lookup
|
109
|
+
# @param order_override [#[]] An override that will considered the first source of lookup
|
110
|
+
# @param resolution_type [String,Hash<Symbol,String>] Symbolic resolution type or deep merge configuration
|
111
|
+
# @return [Object] The found value or the given _default_ value
|
112
|
+
def lookup(key, default, scope, order_override=nil, resolution_type=:priority)
|
113
|
+
Backend.lookup(key, default, scope, order_override, resolution_type)
|
114
|
+
end
|
115
|
+
end
|