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 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(file, type=:yaml)
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(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(file))
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 #{file} should be a Hash" unless scope.is_a?(Hash)
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
@@ -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 = nil
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
- unless answer
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
- unless File.exist?(datafile)
26
- Hiera.warn("Cannot find datafile #{datafile}, skipping")
27
- next
28
- end
18
+ yamlfile = Backend.datafile(:yaml, scope, source, "yaml") || next
29
19
 
30
- data = YAML.load_file(datafile)
20
+ data = YAML.load_file(yamlfile)
31
21
 
32
- next if data.empty?
33
- next unless data.include?(key)
22
+ next if data.empty?
23
+ next unless data.include?(key)
34
24
 
35
- answer = Backend.parse_string(data[key], scope)
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
@@ -0,0 +1,8 @@
1
+ class Hiera
2
+ module Puppet_logger
3
+ class << self
4
+ def warn(msg); Puppet.notice("hiera(): #{msg}"); end
5
+ def debug(msg); Puppet.debug("hiera(): #{msg}"); end
6
+ end
7
+ end
8
+ end
@@ -1,4 +1,4 @@
1
- $:.insert(0, File.join([File.dirname(__FILE__), "..", "..", "lib"]))
1
+ $:.insert(0, File.join([File.dirname(__FILE__), "..", "lib"]))
2
2
 
3
3
  require 'rubygems'
4
4
  require 'rspec'
@@ -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
- before do
17
- Hiera.stubs(:debug)
18
- Hiera.stubs(:warn)
19
- @backend = Yaml_backend.new
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
- expect {
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 look for data in all sources" do
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
- File.expects(:exist?).with("/nonexisting/one.yaml").returns(false)
35
- File.expects(:exist?).with("/nonexisting/two.yaml").returns(false)
36
- @backend.lookup("key", {}, nil, nil)
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 pick data earliest source that has it" do
40
- Backend.expects(:datadir).with(:yaml, {}).returns("/nonexisting")
41
- File.expects(:directory?).with("/nonexisting").returns(true)
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
- File.expects(:exist?).with("/nonexisting/two.yaml").never
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, nil).should == "answer"
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
- File.expects(:exist?).with("/nonexisting/one.yaml").returns(true)
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
- @backend.lookup("key", {"rspec" => "test"}, nil, nil).should == "test_test"
56
+
57
+ @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
59
58
  end
60
59
  end
61
60
  end
@@ -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
- - 1
8
- - 1
9
- version: 0.1.1
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-05 00:00:00 +01:00
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