hiera 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of hiera might be problematic. Click here for more details.
- data/bin/hiera +42 -10
- data/lib/hiera/backend.rb +63 -1
- data/lib/hiera/backend/yaml_backend.rb +16 -18
- data/lib/hiera/puppet_logger.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/backend/yaml_backend_spec.rb +27 -28
- data/spec/unit/backend_spec.rb +71 -4
- metadata +5 -4
data/bin/hiera
CHANGED
@@ -15,8 +15,7 @@ require 'rubygems'
|
|
15
15
|
require 'hiera'
|
16
16
|
require 'optparse'
|
17
17
|
|
18
|
-
|
19
|
-
options = {:default => nil, :config => "/etc/hiera.yaml", :scope => {}, :key => nil, :verbose => false}
|
18
|
+
options = {:default => nil, :config => "/etc/hiera.yaml", :scope => {}, :key => nil, :verbose => false, :resolution_type => :priority}
|
20
19
|
|
21
20
|
class Hiera::Noop_logger
|
22
21
|
class << self
|
@@ -26,11 +25,29 @@ class Hiera::Noop_logger
|
|
26
25
|
end
|
27
26
|
|
28
27
|
# Loads the scope from YAML or JSON files
|
29
|
-
def load_scope(
|
30
|
-
raise "Cannot find scope #{type} file #{file}" unless File.exist?(file)
|
31
|
-
|
28
|
+
def load_scope(source, type=:yaml)
|
32
29
|
case type
|
30
|
+
when :mcollective
|
31
|
+
begin
|
32
|
+
require 'mcollective'
|
33
|
+
|
34
|
+
include MCollective::RPC
|
35
|
+
|
36
|
+
util = rpcclient("rpcutil")
|
37
|
+
util.progress = false
|
38
|
+
nodestats = util.custom_request("inventory", {}, source, {"identity" => source}).first
|
39
|
+
|
40
|
+
raise "Failed to retrieve facts for node #{source}: #{nodestats[:statusmsg]}" unless nodestats[:statuscode] == 0
|
41
|
+
|
42
|
+
scope = nodestats[:data][:facts]
|
43
|
+
rescue Exception => e
|
44
|
+
STDERR.puts "MCollective lookup failed: #{e.class}: #{e}"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
33
48
|
when :yaml
|
49
|
+
raise "Cannot find scope #{type} file #{source}" unless File.exist?(source)
|
50
|
+
|
34
51
|
require 'yaml'
|
35
52
|
|
36
53
|
# Attempt to load puppet in case we're going to be fed
|
@@ -40,19 +57,21 @@ def load_scope(file, type=:yaml)
|
|
40
57
|
rescue
|
41
58
|
end
|
42
59
|
|
43
|
-
scope = YAML.load_file(
|
60
|
+
scope = YAML.load_file(source)
|
44
61
|
|
45
62
|
# Puppet makes dumb yaml files that do not promote data reuse.
|
46
63
|
scope = scope.values if scope.is_a?(Puppet::Node::Facts)
|
47
64
|
when :json
|
65
|
+
raise "Cannot find scope #{type} file #{source}" unless File.exist?(source)
|
66
|
+
|
48
67
|
require 'json'
|
49
68
|
|
50
|
-
scope = JSON.load(File.read(
|
69
|
+
scope = JSON.load(File.read(source))
|
51
70
|
else
|
52
71
|
raise "Don't know how to load data type #{type}"
|
53
72
|
end
|
54
73
|
|
55
|
-
raise "Scope from #{type} file #{
|
74
|
+
raise "Scope from #{type} file #{source} should be a Hash" unless scope.is_a?(Hash)
|
56
75
|
|
57
76
|
scope
|
58
77
|
end
|
@@ -67,6 +86,10 @@ OptionParser.new do |opts|
|
|
67
86
|
options[:verbose] = true
|
68
87
|
end
|
69
88
|
|
89
|
+
opts.on("--array", "-a", "Array search") do
|
90
|
+
options[:resolution_type] = :array
|
91
|
+
end
|
92
|
+
|
70
93
|
opts.on("--config CONFIG", "-c", "Configuration file") do |v|
|
71
94
|
if File.exist?(v)
|
72
95
|
options[:config] = v
|
@@ -87,12 +110,21 @@ OptionParser.new do |opts|
|
|
87
110
|
|
88
111
|
opts.on("--yaml SCOPE", "-y", "YAML format file to load scope from") do |v|
|
89
112
|
begin
|
90
|
-
options[:scope] = load_scope(v)
|
113
|
+
options[:scope] = load_scope(v, :json)
|
91
114
|
rescue Exception => e
|
92
115
|
STDERR.puts "Could not load YAML scope: #{e.class}: #{e}"
|
93
116
|
exit 1
|
94
117
|
end
|
95
118
|
end
|
119
|
+
|
120
|
+
opts.on("--mcollective IDENTITY", "-m", "Retrieve facts from a node via mcollective as scope") do |v|
|
121
|
+
begin
|
122
|
+
options[:scope] = load_scope(v, :mcollective)
|
123
|
+
rescue Exception => e
|
124
|
+
STDERR.puts "Could not load MCollective scope: #{e.class}: #{e}"
|
125
|
+
exit 1
|
126
|
+
end
|
127
|
+
end
|
96
128
|
end.parse!
|
97
129
|
|
98
130
|
# arguments can be:
|
@@ -134,7 +166,7 @@ unless options[:verbose]
|
|
134
166
|
Hiera.logger = "noop"
|
135
167
|
end
|
136
168
|
|
137
|
-
ans = hiera.lookup(options[:key], options[:default], options[:scope])
|
169
|
+
ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, options[:resolution_type])
|
138
170
|
|
139
171
|
if ans.is_a?(String)
|
140
172
|
puts ans
|
data/lib/hiera/backend.rb
CHANGED
@@ -15,6 +15,32 @@ class Hiera
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
# Finds the path to a datafile based on the Backend#datadir
|
19
|
+
# and extension
|
20
|
+
#
|
21
|
+
# If the file is not found nil is returned
|
22
|
+
def datafile(backend, scope, source, extension)
|
23
|
+
file = File.join([datadir(backend, scope), "#{source}.#{extension}"])
|
24
|
+
|
25
|
+
unless File.exist?(file)
|
26
|
+
Hiera.debug("Cannot find datafile #{file}, skipping")
|
27
|
+
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
return file
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an appropriate empty answer dependant on resolution type
|
35
|
+
def empty_answer(resolution_type)
|
36
|
+
case resolution_type
|
37
|
+
when :array
|
38
|
+
return []
|
39
|
+
else
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
18
44
|
# Constructs a list of data sources to search
|
19
45
|
#
|
20
46
|
# If you give it a specific hierarchy it will just use that
|
@@ -62,6 +88,9 @@ class Hiera
|
|
62
88
|
var = $1
|
63
89
|
val = scope[var] || extra_data[var] || ""
|
64
90
|
|
91
|
+
# Puppet can return this for unknown scope vars
|
92
|
+
val = "" if val == :undefined
|
93
|
+
|
65
94
|
tdata.gsub!(/%\{#{var}\}/, val)
|
66
95
|
end
|
67
96
|
end
|
@@ -69,6 +98,39 @@ class Hiera
|
|
69
98
|
return tdata
|
70
99
|
end
|
71
100
|
|
101
|
+
# Parses a answer received from data files
|
102
|
+
#
|
103
|
+
# Ultimately it just pass the data through parse_string but
|
104
|
+
# it makes some effort to handle arrays of strings as well
|
105
|
+
def parse_answer(data, scope, extra_data={})
|
106
|
+
if data.is_a?(String)
|
107
|
+
return parse_string(data, scope, extra_data)
|
108
|
+
elsif data.is_a?(Hash)
|
109
|
+
answer = {}
|
110
|
+
data.each_pair do |key, val|
|
111
|
+
answer[key] = parse_answer(val, scope, extra_data)
|
112
|
+
end
|
113
|
+
|
114
|
+
return answer
|
115
|
+
elsif data.is_a?(Array)
|
116
|
+
answer = []
|
117
|
+
data.each do |item|
|
118
|
+
answer << parse_answer(item, scope, extra_data)
|
119
|
+
end
|
120
|
+
|
121
|
+
return answer
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def resolve_answer(answer, resolution_type)
|
126
|
+
case resolution_type
|
127
|
+
when :array
|
128
|
+
[answer].flatten.uniq.sort
|
129
|
+
else
|
130
|
+
answer
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
72
134
|
# Calls out to all configured backends in the order they
|
73
135
|
# were specified. The first one to answer will win.
|
74
136
|
#
|
@@ -95,7 +157,7 @@ class Hiera
|
|
95
157
|
end
|
96
158
|
end
|
97
159
|
|
98
|
-
answer || parse_string(default, scope)
|
160
|
+
resolve_answer(answer, resolution_type) || parse_string(default, scope)
|
99
161
|
end
|
100
162
|
end
|
101
163
|
end
|
@@ -8,37 +8,35 @@ class Hiera
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def lookup(key, scope, order_override, resolution_type)
|
11
|
-
answer =
|
11
|
+
answer = Backend.empty_answer(resolution_type)
|
12
12
|
|
13
13
|
Hiera.debug("Looking up #{key} in YAML backend")
|
14
14
|
|
15
|
-
datadir = Backend.datadir(:yaml, scope)
|
16
|
-
|
17
|
-
raise "Cannot find data directory #{datadir}" unless File.directory?(datadir)
|
18
|
-
|
19
15
|
Backend.datasources(scope, order_override) do |source|
|
20
|
-
|
21
|
-
Hiera.debug("Looking for data source #{source}")
|
22
|
-
|
23
|
-
datafile = File.join([datadir, "#{source}.yaml"])
|
16
|
+
Hiera.debug("Looking for data source #{source}")
|
24
17
|
|
25
|
-
|
26
|
-
Hiera.warn("Cannot find datafile #{datafile}, skipping")
|
27
|
-
next
|
28
|
-
end
|
18
|
+
yamlfile = Backend.datafile(:yaml, scope, source, "yaml") || next
|
29
19
|
|
30
|
-
|
20
|
+
data = YAML.load_file(yamlfile)
|
31
21
|
|
32
|
-
|
33
|
-
|
22
|
+
next if data.empty?
|
23
|
+
next unless data.include?(key)
|
34
24
|
|
35
|
-
|
25
|
+
# for array resolution we just append to the array whatever
|
26
|
+
# we find, we then goes onto the next file and keep adding to
|
27
|
+
# the array
|
28
|
+
#
|
29
|
+
# for priority searches we break after the first found data item
|
30
|
+
case resolution_type
|
31
|
+
when :array
|
32
|
+
answer << Backend.parse_answer(data[key], scope)
|
36
33
|
else
|
34
|
+
answer = Backend.parse_answer(data[key], scope)
|
37
35
|
break
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
answer
|
39
|
+
return answer
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,6 +5,12 @@ require 'hiera/backend/yaml_backend'
|
|
5
5
|
class Hiera
|
6
6
|
module Backend
|
7
7
|
describe Yaml_backend do
|
8
|
+
before do
|
9
|
+
Hiera.stubs(:debug)
|
10
|
+
Hiera.stubs(:warn)
|
11
|
+
@backend = Yaml_backend.new
|
12
|
+
end
|
13
|
+
|
8
14
|
describe "#initialize" do
|
9
15
|
it "should announce its creation" do # because other specs checks this
|
10
16
|
Hiera.expects(:debug).with("Hiera YAML backend starting")
|
@@ -13,49 +19,42 @@ class Hiera
|
|
13
19
|
end
|
14
20
|
|
15
21
|
describe "#lookup" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should fail for missing datadir" do
|
23
|
-
Backend.expects(:datadir).with(:yaml, {}).returns("/nonexisting")
|
22
|
+
it "should look for data in all sources" do
|
23
|
+
Backend.expects(:datasources).multiple_yields(["one"], ["two"])
|
24
|
+
Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns(nil)
|
25
|
+
Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns(nil)
|
24
26
|
|
25
|
-
|
26
|
-
@backend.lookup("key", {}, nil, nil)
|
27
|
-
}.to raise_error("Cannot find data directory /nonexisting")
|
27
|
+
@backend.lookup("key", {}, nil, :priority)
|
28
28
|
end
|
29
29
|
|
30
|
-
it "should
|
31
|
-
Backend.expects(:datadir).with(:yaml, {}).returns("/nonexisting")
|
32
|
-
File.expects(:directory?).with("/nonexisting").returns(true)
|
30
|
+
it "should pick data earliest source that has it for priority searches" do
|
33
31
|
Backend.expects(:datasources).multiple_yields(["one"], ["two"])
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
|
33
|
+
Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns(nil).never
|
34
|
+
YAML.expects(:load_file).with("/nonexisting/one.yaml").returns({"key" => "answer"})
|
35
|
+
|
36
|
+
@backend.lookup("key", {}, nil, :priority).should == "answer"
|
37
37
|
end
|
38
38
|
|
39
|
-
it "should
|
40
|
-
Backend.expects(:
|
41
|
-
|
39
|
+
it "should build an array of all data sources for array searches" do
|
40
|
+
Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
|
41
|
+
Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
|
42
|
+
|
42
43
|
Backend.expects(:datasources).multiple_yields(["one"], ["two"])
|
43
|
-
File.expects(:exist?).with("/nonexisting/one.yaml").returns(true)
|
44
|
-
YAML.expects(:load_file).with("/nonexisting/one.yaml").returns({"key" => "answer"})
|
45
44
|
|
46
|
-
|
45
|
+
YAML.expects(:load_file).with("/nonexisting/one.yaml").returns({"key" => "answer"})
|
46
|
+
YAML.expects(:load_file).with("/nonexisting/two.yaml").returns({"key" => "answer"})
|
47
47
|
|
48
|
-
@backend.lookup("key", {}, nil,
|
48
|
+
@backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
|
49
49
|
end
|
50
50
|
|
51
51
|
it "should parse the answer for scope variables" do
|
52
|
-
Backend.expects(:datadir).with(:yaml, {"rspec" => "test"}).returns("/nonexisting")
|
53
|
-
File.expects(:directory?).with("/nonexisting").returns(true)
|
54
52
|
Backend.expects(:datasources).yields("one")
|
55
|
-
|
53
|
+
Backend.expects(:datafile).with(:yaml, {"rspec" => "test"}, "one", "yaml").returns("/nonexisting/one.yaml")
|
56
54
|
YAML.expects(:load_file).with("/nonexisting/one.yaml").returns({"key" => "test_%{rspec}"})
|
57
55
|
|
58
|
-
|
56
|
+
|
57
|
+
@backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
|
59
58
|
end
|
60
59
|
end
|
61
60
|
end
|
data/spec/unit/backend_spec.rb
CHANGED
@@ -16,6 +16,30 @@ class Hiera
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
describe "#empty_anwer" do
|
20
|
+
it "should return [] for array searches" do
|
21
|
+
Backend.empty_answer(:array).should == []
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return nil otherwise" do
|
25
|
+
Backend.empty_answer(:meh).should == nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#datafile" do
|
30
|
+
it "should check if the file exist and return nil if not" do
|
31
|
+
Hiera.expects(:debug).with("Cannot find datafile /nonexisting/test.yaml, skipping")
|
32
|
+
Backend.expects(:datadir).returns("/nonexisting")
|
33
|
+
Backend.datafile(:yaml, {}, "test", "yaml").should == nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return the correct file name" do
|
37
|
+
Backend.expects(:datadir).returns("/nonexisting")
|
38
|
+
File.expects(:exist?).with("/nonexisting/test.yaml").returns(true)
|
39
|
+
Backend.datafile(:yaml, {}, "test", "yaml").should == "/nonexisting/test.yaml"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
19
43
|
describe "#datasources" do
|
20
44
|
it "should use the supplied hierarchy" do
|
21
45
|
expected = ["one", "two"]
|
@@ -101,6 +125,43 @@ class Hiera
|
|
101
125
|
input = "test_%{rspec}_test"
|
102
126
|
Backend.parse_string(input, {"rspec" => "test"}, {"rspec" => "fail"}).should == "test_test_test"
|
103
127
|
end
|
128
|
+
|
129
|
+
it "should treat :undefined in scope as empty" do
|
130
|
+
input = "test_%{rspec}_test"
|
131
|
+
Backend.parse_string(input, {"rspec" => :undefined}).should == "test__test"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#parse_answer" do
|
136
|
+
it "should parse strings correctly" do
|
137
|
+
input = "test_%{rspec}_test"
|
138
|
+
Backend.parse_answer(input, {"rspec" => "test"}).should == "test_test_test"
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should parse each string in an array" do
|
142
|
+
input = ["test_%{rspec}_test", "test_%{rspec}_test", ["test_%{rspec}_test"]]
|
143
|
+
Backend.parse_answer(input, {"rspec" => "test"}).should == ["test_test_test", "test_test_test", ["test_test_test"]]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should parse each string in a hash" do
|
147
|
+
input = {"foo" => "test_%{rspec}_test", "bar" => "test_%{rspec}_test"}
|
148
|
+
Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should parse mixed arrays and hashes" do
|
152
|
+
input = {"foo" => "test_%{rspec}_test", "bar" => ["test_%{rspec}_test", "test_%{rspec}_test"]}
|
153
|
+
Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#resolve_answer" do
|
158
|
+
it "should correctly parse array data" do
|
159
|
+
Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :array).should == ["bar", "foo"]
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should just return the answer for non array data" do
|
163
|
+
Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :priority).should == ["foo", ["foo", "foo"], "bar"]
|
164
|
+
end
|
104
165
|
end
|
105
166
|
|
106
167
|
describe "#lookup" do
|
@@ -110,7 +171,6 @@ class Hiera
|
|
110
171
|
end
|
111
172
|
|
112
173
|
it "should cache backends" do
|
113
|
-
Hiera.stubs(:debug)
|
114
174
|
Hiera.expects(:debug).with(regexp_matches(/Hiera YAML backend starting/)).once
|
115
175
|
|
116
176
|
Config.load({:yaml => {:datadir => "/tmp"}})
|
@@ -121,7 +181,6 @@ class Hiera
|
|
121
181
|
end
|
122
182
|
|
123
183
|
it "should return the answer from the backend" do
|
124
|
-
Hiera.stubs(:debug)
|
125
184
|
Config.load({:yaml => {:datadir => "/tmp"}})
|
126
185
|
Config.load_backends
|
127
186
|
|
@@ -133,7 +192,6 @@ class Hiera
|
|
133
192
|
it "should call to all backends till an answer is found" do
|
134
193
|
backend = mock
|
135
194
|
backend.expects(:lookup).returns("answer")
|
136
|
-
Hiera.stubs(:debug)
|
137
195
|
Config.load({})
|
138
196
|
Config.instance_variable_set("@config", {:backends => ["yaml", "rspec"]})
|
139
197
|
Backend.instance_variable_set("@backends", {"rspec" => backend})
|
@@ -144,8 +202,17 @@ class Hiera
|
|
144
202
|
|
145
203
|
end
|
146
204
|
|
205
|
+
it "should parse the answers based on resolution_type" do
|
206
|
+
Config.load({:yaml => {:datadir => "/tmp"}})
|
207
|
+
Config.load_backends
|
208
|
+
|
209
|
+
Backend.expects(:resolve_answer).with("test_test", :priority).returns("parsed")
|
210
|
+
Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, :priority).returns("test_test")
|
211
|
+
|
212
|
+
Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, :priority).should == "parsed"
|
213
|
+
end
|
214
|
+
|
147
215
|
it "should return the default with variables parsed if nothing is found" do
|
148
|
-
Hiera.stubs(:debug)
|
149
216
|
Config.load({:yaml => {:datadir => "/tmp"}})
|
150
217
|
Config.load_backends
|
151
218
|
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- R.I.Pienaar
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-06-
|
17
|
+
date: 2011-06-12 00:00:00 +01:00
|
18
18
|
default_executable: hiera
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -29,6 +29,7 @@ extra_rdoc_files: []
|
|
29
29
|
files:
|
30
30
|
- bin/hiera
|
31
31
|
- lib/hiera.rb
|
32
|
+
- lib/hiera/puppet_logger.rb
|
32
33
|
- lib/hiera/backend.rb
|
33
34
|
- lib/hiera/config.rb
|
34
35
|
- lib/hiera/console_logger.rb
|