forked from puppetlabs/hiera
/
backend.rb
181 lines (158 loc) · 5.78 KB
/
backend.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
class Hiera
module Backend
class << self
# Data lives in /var/lib/hiera by default. If a backend
# supplies a datadir in the config it will be used and
# subject to variable expansion based on scope
def datadir(backend, scope)
backend = backend.to_sym
default = "/var/lib/hiera"
if Config.include?(backend)
parse_string(Config[backend][:datadir] || default, scope)
else
parse_string(default, scope)
end
end
# Finds the path to a datafile based on the Backend#datadir
# and extension
#
# If the file is not found nil is returned
def datafile(backend, scope, source, extension)
file = File.join([datadir(backend, scope), "#{source}.#{extension}"])
unless File.exist?(file)
Hiera.debug("Cannot find datafile #{file}, skipping")
return nil
end
return file
end
# Returns an appropriate empty answer dependant on resolution type
def empty_answer(resolution_type)
case resolution_type
when :array
return []
when :hash
return {}
else
return nil
end
end
# Constructs a list of data sources to search
#
# If you give it a specific hierarchy it will just use that
# else it will use the global configured one, failing that
# it will just look in the 'common' data source.
#
# An override can be supplied that will be pre-pended to the
# hierarchy.
#
# The source names will be subject to variable expansion based
# on scope
def datasources(scope, override=nil, hierarchy=nil)
if hierarchy
hierarchy = [hierarchy]
elsif Config.include?(:hierarchy)
hierarchy = [Config[:hierarchy]].flatten
else
hierarchy = ["common"]
end
hierarchy.insert(0, override) if override
hierarchy.flatten.map do |source|
source = parse_string(source, scope)
yield(source) unless source == "" or source =~ /(^\/|\/\/|\/$)/
end
end
# Parse a string like '%{foo}' against a supplied
# scope and additional scope. If either scope or
# extra_scope includes the varaible 'foo' it will
# be replaced else an empty string will be placed.
#
# If both scope and extra_data has "foo" scope
# will win. See hiera-puppet for an example of
# this to make hiera aware of additional non scope
# variables
def parse_string(data, scope, extra_data={})
return nil unless data
tdata = data.clone
if tdata.is_a?(String)
while tdata =~ /%\{(.+?)\}/
var = $1
val = scope[var] || extra_data[var] || ""
# Puppet can return this for unknown scope vars
val = "" if val == :undefined
tdata.gsub!(/%\{#{var}\}/, val)
end
end
return tdata
end
# Parses a answer received from data files
#
# Ultimately it just pass the data through parse_string but
# it makes some effort to handle arrays of strings as well
def parse_answer(data, scope, extra_data={})
if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
return data
elsif data.is_a?(String)
return parse_string(data, scope, extra_data)
elsif data.is_a?(Hash)
answer = {}
data.each_pair do |key, val|
answer[key] = parse_answer(val, scope, extra_data)
end
return answer
elsif data.is_a?(Array)
answer = []
data.each do |item|
answer << parse_answer(item, scope, extra_data)
end
return answer
end
end
def resolve_answer(answer, resolution_type)
case resolution_type
when :array
[answer].flatten.uniq.compact
when :hash
answer # Hash structure should be preserved
else
answer
end
end
# Calls out to all configured backends in the order they
# were specified. The first one to answer will win.
#
# This lets you declare multiple backends, a possible
# use case might be in Puppet where a Puppet module declares
# default data using in-module data while users can override
# using JSON/YAML etc. By layering the backends and putting
# the Puppet one last you can override module author data
# easily.
#
# Backend instances are cached so if you need to connect to any
# databases then do so in your constructor, future calls to your
# backend will not create new instances
def lookup(key, default, scope, order_override, resolution_type)
@backends ||= {}
answer = empty_answer(resolution_type)
Config[:backends].each do |backend|
if constants.include?("#{backend.capitalize}_backend") || constants.include?("#{backend.capitalize}_backend".to_sym)
@backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
new_answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
case resolution_type
when :priority
answer = new_answer unless !new_answer.nil?
break if answer != empty_answer(:priority)
when :array
answer << new_answer
when :hash
answer = new_answer.merge answer
end
end
end
answer = resolve_answer(answer, resolution_type)
answer = parse_string(default, scope) if answer.nil?
return default if answer == empty_answer(resolution_type) unless default.nil?
return answer
end
end
end
end