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.
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.rb +115 -0
  7. data/lib/hiera/backend.rb +325 -0
  8. data/lib/hiera/backend/json_backend.rb +58 -0
  9. data/lib/hiera/backend/yaml_backend.rb +63 -0
  10. data/lib/hiera/config.rb +90 -0
  11. data/lib/hiera/console_logger.rb +13 -0
  12. data/lib/hiera/error.rb +4 -0
  13. data/lib/hiera/fallback_logger.rb +41 -0
  14. data/lib/hiera/filecache.rb +86 -0
  15. data/lib/hiera/interpolate.rb +98 -0
  16. data/lib/hiera/noop_logger.rb +8 -0
  17. data/lib/hiera/puppet_logger.rb +17 -0
  18. data/lib/hiera/recursive_guard.rb +20 -0
  19. data/lib/hiera/util.rb +47 -0
  20. data/lib/hiera/version.rb +89 -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 +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
@@ -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