hiera 2.0.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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