hiera 2.0.0-x86-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.
- 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.rb +115 -0
- data/lib/hiera/backend.rb +325 -0
- data/lib/hiera/backend/json_backend.rb +58 -0
- data/lib/hiera/backend/yaml_backend.rb +63 -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/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 +142 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Backend
|
3
|
+
class Json_backend
|
4
|
+
def initialize(cache=nil)
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
Hiera.debug("Hiera JSON backend starting")
|
8
|
+
|
9
|
+
@cache = cache || Filecache.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def lookup(key, scope, order_override, resolution_type, context)
|
13
|
+
answer = nil
|
14
|
+
found = false
|
15
|
+
|
16
|
+
Hiera.debug("Looking up #{key} in JSON backend")
|
17
|
+
|
18
|
+
Backend.datasources(scope, order_override) do |source|
|
19
|
+
Hiera.debug("Looking for data source #{source}")
|
20
|
+
|
21
|
+
jsonfile = Backend.datafile(:json, scope, source, "json") || next
|
22
|
+
|
23
|
+
next unless File.exist?(jsonfile)
|
24
|
+
|
25
|
+
data = @cache.read_file(jsonfile, Hash) do |data|
|
26
|
+
JSON.parse(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
next if data.empty?
|
30
|
+
next unless data.include?(key)
|
31
|
+
found = true
|
32
|
+
|
33
|
+
# for array resolution we just append to the array whatever
|
34
|
+
# we find, we then goes onto the next file and keep adding to
|
35
|
+
# the array
|
36
|
+
#
|
37
|
+
# for priority searches we break after the first found data item
|
38
|
+
new_answer = Backend.parse_answer(data[key], scope, {}, context)
|
39
|
+
case resolution_type.is_a?(Hash) ? :hash : resolution_type
|
40
|
+
when :array
|
41
|
+
raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
|
42
|
+
answer ||= []
|
43
|
+
answer << new_answer
|
44
|
+
when :hash
|
45
|
+
raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
|
46
|
+
answer ||= {}
|
47
|
+
answer = Backend.merge_answer(new_answer, answer, resolution_type)
|
48
|
+
else
|
49
|
+
answer = new_answer
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
53
|
+
throw :no_such_key unless found
|
54
|
+
return answer
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Backend
|
3
|
+
class Yaml_backend
|
4
|
+
def initialize(cache=nil)
|
5
|
+
require 'yaml'
|
6
|
+
Hiera.debug("Hiera YAML backend starting")
|
7
|
+
|
8
|
+
@cache = cache || Filecache.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def lookup(key, scope, order_override, resolution_type, context)
|
12
|
+
answer = nil
|
13
|
+
found = false
|
14
|
+
|
15
|
+
Hiera.debug("Looking up #{key} in YAML backend")
|
16
|
+
|
17
|
+
Backend.datasourcefiles(:yaml, scope, "yaml", order_override) do |source, yamlfile|
|
18
|
+
data = @cache.read_file(yamlfile, Hash) do |data|
|
19
|
+
YAML.load(data) || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
next if data.empty?
|
23
|
+
next unless data.include?(key)
|
24
|
+
found = true
|
25
|
+
|
26
|
+
# Extra logging that we found the key. This can be outputted
|
27
|
+
# multiple times if the resolution type is array or hash but that
|
28
|
+
# should be expected as the logging will then tell the user ALL the
|
29
|
+
# places where the key is found.
|
30
|
+
Hiera.debug("Found #{key} in #{source}")
|
31
|
+
|
32
|
+
# for array resolution we just append to the array whatever
|
33
|
+
# we find, we then goes onto the next file and keep adding to
|
34
|
+
# the array
|
35
|
+
#
|
36
|
+
# for priority searches we break after the first found data item
|
37
|
+
new_answer = Backend.parse_answer(data[key], scope, {}, context)
|
38
|
+
case resolution_type.is_a?(Hash) ? :hash : resolution_type
|
39
|
+
when :array
|
40
|
+
raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
|
41
|
+
answer ||= []
|
42
|
+
answer << new_answer
|
43
|
+
when :hash
|
44
|
+
raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
|
45
|
+
answer ||= {}
|
46
|
+
answer = Backend.merge_answer(new_answer, answer, resolution_type)
|
47
|
+
else
|
48
|
+
answer = new_answer
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
throw :no_such_key unless found
|
53
|
+
return answer
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def file_exists?(path)
|
59
|
+
File.exist? path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
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
|