hiera 2.0.0-x86-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.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
|