hiera 2.0.0-x64-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +202 -0
  3. data/LICENSE +18 -0
  4. data/README.md +276 -0
  5. data/bin/hiera +248 -0
  6. data/lib/hiera/backend/json_backend.rb +58 -0
  7. data/lib/hiera/backend/yaml_backend.rb +63 -0
  8. data/lib/hiera/backend.rb +325 -0
  9. data/lib/hiera/config.rb +90 -0
  10. data/lib/hiera/console_logger.rb +13 -0
  11. data/lib/hiera/error.rb +4 -0
  12. data/lib/hiera/fallback_logger.rb +41 -0
  13. data/lib/hiera/filecache.rb +86 -0
  14. data/lib/hiera/interpolate.rb +98 -0
  15. data/lib/hiera/noop_logger.rb +8 -0
  16. data/lib/hiera/puppet_logger.rb +17 -0
  17. data/lib/hiera/recursive_guard.rb +20 -0
  18. data/lib/hiera/util.rb +47 -0
  19. data/lib/hiera/version.rb +89 -0
  20. data/lib/hiera.rb +115 -0
  21. data/spec/spec_helper.rb +78 -0
  22. data/spec/unit/backend/json_backend_spec.rb +85 -0
  23. data/spec/unit/backend/yaml_backend_spec.rb +138 -0
  24. data/spec/unit/backend_spec.rb +743 -0
  25. data/spec/unit/config_spec.rb +118 -0
  26. data/spec/unit/console_logger_spec.rb +19 -0
  27. data/spec/unit/fallback_logger_spec.rb +80 -0
  28. data/spec/unit/filecache_spec.rb +142 -0
  29. data/spec/unit/fixtures/interpolate/config/hiera.yaml +6 -0
  30. data/spec/unit/fixtures/interpolate/data/niltest.yaml +2 -0
  31. data/spec/unit/fixtures/interpolate/data/recursive.yaml +3 -0
  32. data/spec/unit/fixtures/override/config/hiera.yaml +5 -0
  33. data/spec/unit/fixtures/override/data/alternate.yaml +1 -0
  34. data/spec/unit/fixtures/override/data/common.yaml +2 -0
  35. data/spec/unit/hiera_spec.rb +81 -0
  36. data/spec/unit/interpolate_spec.rb +36 -0
  37. data/spec/unit/puppet_logger_spec.rb +31 -0
  38. data/spec/unit/util_spec.rb +49 -0
  39. data/spec/unit/version_spec.rb +44 -0
  40. metadata +128 -0
@@ -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
@@ -0,0 +1,13 @@
1
+ class Hiera
2
+ module Console_logger
3
+ class << self
4
+ def warn(msg)
5
+ STDERR.puts("WARN: %s: %s" % [Time.now.to_s, msg])
6
+ end
7
+
8
+ def debug(msg)
9
+ STDERR.puts("DEBUG: %s: %s" % [Time.now.to_s, msg])
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ class Hiera
2
+ class Error < StandardError; end
3
+ class InvalidConfigurationError < Error; end
4
+ end
@@ -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,8 @@
1
+ class Hiera
2
+ module Noop_logger
3
+ class << self
4
+ def warn(msg);end
5
+ def debug(msg);end
6
+ end
7
+ end
8
+ 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