ml-puppetdb-terminus 3.2.1
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +202 -0
- data/NOTICE.txt +17 -0
- data/README.md +22 -0
- data/puppet/lib/puppet/application/storeconfigs.rb +4 -0
- data/puppet/lib/puppet/face/node/deactivate.rb +37 -0
- data/puppet/lib/puppet/face/node/status.rb +80 -0
- data/puppet/lib/puppet/face/storeconfigs.rb +193 -0
- data/puppet/lib/puppet/indirector/catalog/puppetdb.rb +400 -0
- data/puppet/lib/puppet/indirector/facts/puppetdb.rb +152 -0
- data/puppet/lib/puppet/indirector/facts/puppetdb_apply.rb +25 -0
- data/puppet/lib/puppet/indirector/node/puppetdb.rb +19 -0
- data/puppet/lib/puppet/indirector/resource/puppetdb.rb +108 -0
- data/puppet/lib/puppet/reports/puppetdb.rb +188 -0
- data/puppet/lib/puppet/util/puppetdb.rb +108 -0
- data/puppet/lib/puppet/util/puppetdb/char_encoding.rb +316 -0
- data/puppet/lib/puppet/util/puppetdb/command.rb +116 -0
- data/puppet/lib/puppet/util/puppetdb/command_names.rb +8 -0
- data/puppet/lib/puppet/util/puppetdb/config.rb +148 -0
- data/puppet/lib/puppet/util/puppetdb/http.rb +121 -0
- data/puppet/spec/README.markdown +8 -0
- data/puppet/spec/spec.opts +6 -0
- data/puppet/spec/spec_helper.rb +38 -0
- data/puppet/spec/unit/face/node/deactivate_spec.rb +28 -0
- data/puppet/spec/unit/face/node/status_spec.rb +43 -0
- data/puppet/spec/unit/face/storeconfigs_spec.rb +199 -0
- data/puppet/spec/unit/indirector/catalog/puppetdb_spec.rb +703 -0
- data/puppet/spec/unit/indirector/facts/puppetdb_apply_spec.rb +27 -0
- data/puppet/spec/unit/indirector/facts/puppetdb_spec.rb +347 -0
- data/puppet/spec/unit/indirector/node/puppetdb_spec.rb +61 -0
- data/puppet/spec/unit/indirector/resource/puppetdb_spec.rb +199 -0
- data/puppet/spec/unit/reports/puppetdb_spec.rb +249 -0
- data/puppet/spec/unit/util/puppetdb/char_encoding_spec.rb +212 -0
- data/puppet/spec/unit/util/puppetdb/command_spec.rb +98 -0
- data/puppet/spec/unit/util/puppetdb/config_spec.rb +227 -0
- data/puppet/spec/unit/util/puppetdb/http_spec.rb +138 -0
- data/puppet/spec/unit/util/puppetdb_spec.rb +33 -0
- metadata +115 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'puppet/indirector/facts/puppetdb_apply'
|
3
|
+
|
4
|
+
describe Puppet::Node::Facts::PuppetdbApply do
|
5
|
+
describe "#save" do
|
6
|
+
let(:facts) { Puppet::Node::Facts.new('foo') }
|
7
|
+
|
8
|
+
def save
|
9
|
+
subject.save(Puppet::Node::Facts.indirection.request(:save, facts.name, facts))
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should only submit commands once" do
|
13
|
+
subject.expects(:submit_command).once
|
14
|
+
save
|
15
|
+
save
|
16
|
+
save
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#find" do
|
22
|
+
it "always returns nil to force the cache to be skipped" do
|
23
|
+
subject.find('foo').should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'puppet/util/feature'
|
5
|
+
require 'puppet/indirector/facts/puppetdb'
|
6
|
+
require 'puppet/util/puppetdb'
|
7
|
+
require 'puppet/util/puppetdb/command_names'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
|
11
|
+
describe Puppet::Node::Facts::Puppetdb do
|
12
|
+
|
13
|
+
CommandReplaceFacts = Puppet::Util::Puppetdb::CommandNames::CommandReplaceFacts
|
14
|
+
|
15
|
+
let(:http) {stub 'http'}
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
Puppet::Util::Puppetdb.config.stubs(:server_urls).returns [URI("https://localhost:8282")]
|
19
|
+
Puppet::Node::Facts.indirection.stubs(:terminus).returns(subject)
|
20
|
+
Puppet::Network::HttpPool.stubs(:http_instance).returns(http)
|
21
|
+
create_environmentdir("my_environment")
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#save" do
|
25
|
+
let(:response) { Net::HTTPOK.new('1.1', 200, 'OK') }
|
26
|
+
let(:facts) { Puppet::Node::Facts.new('foo') }
|
27
|
+
|
28
|
+
let(:options) {{
|
29
|
+
:producer_timestamp => 'a test',
|
30
|
+
:environment => "my_environment",
|
31
|
+
}}
|
32
|
+
|
33
|
+
before :each do
|
34
|
+
response.stubs(:body).returns '{"uuid": "a UUID"}'
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
subject.save(Puppet::Node::Facts.indirection.request(:save, facts.name, facts, options))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should POST the facts as a JSON string" do
|
42
|
+
Puppet::Util::Puppetdb.stubs(:puppet3compat?).returns(true)
|
43
|
+
f = {
|
44
|
+
"certname" => facts.name,
|
45
|
+
"values" => subject.maybe_strip_internal(facts),
|
46
|
+
"environment" => "my_environment",
|
47
|
+
"producer_timestamp" => "a test",
|
48
|
+
}
|
49
|
+
|
50
|
+
payload = {
|
51
|
+
:command => CommandReplaceFacts,
|
52
|
+
:version => 4,
|
53
|
+
:payload => f,
|
54
|
+
}.to_json
|
55
|
+
|
56
|
+
http.expects(:post).with do |uri, body, headers|
|
57
|
+
expect(body).to eq(payload)
|
58
|
+
end.returns response
|
59
|
+
|
60
|
+
save
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should POST the trusted data we tell it to" do
|
64
|
+
|
65
|
+
if Puppet::Util::Puppetdb.puppet3compat?
|
66
|
+
Puppet[:trusted_node_data] = true
|
67
|
+
end
|
68
|
+
|
69
|
+
trusted_data = {"foo" => "foobar", "certname" => "testing_posting"}
|
70
|
+
subject.stubs(:get_trusted_info).returns trusted_data
|
71
|
+
|
72
|
+
f = {
|
73
|
+
"certname" => facts.name,
|
74
|
+
"values" => subject.maybe_strip_internal(facts).merge({"trusted" => trusted_data}),
|
75
|
+
"environment" => "my_environment",
|
76
|
+
"producer_timestamp" => "a test",
|
77
|
+
}
|
78
|
+
|
79
|
+
payload = {
|
80
|
+
:command => CommandReplaceFacts,
|
81
|
+
:version => 4,
|
82
|
+
:payload => f,
|
83
|
+
}.to_pson
|
84
|
+
|
85
|
+
http.expects(:post).with do |uri, body, headers|
|
86
|
+
expect(body).to eq(payload)
|
87
|
+
end.returns response
|
88
|
+
|
89
|
+
save
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
it "should retain integer type when submitting" do
|
94
|
+
facts.values['something'] = 100
|
95
|
+
|
96
|
+
sent_payload = nil
|
97
|
+
http.expects(:post).with do |uri, body, headers|
|
98
|
+
sent_payload = body
|
99
|
+
end.returns response
|
100
|
+
|
101
|
+
save
|
102
|
+
|
103
|
+
message = JSON.parse(sent_payload)
|
104
|
+
sent_facts = message['payload']
|
105
|
+
|
106
|
+
# We shouldn't modify the original instance
|
107
|
+
facts.values['something'].should == 100
|
108
|
+
sent_facts['values']['something'].should == 100
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#get_trusted_info" do
|
113
|
+
it 'should return trusted data' do
|
114
|
+
node = Puppet::Node.new('my_certname')
|
115
|
+
trusted = subject.get_trusted_info(node)
|
116
|
+
# Extra keys domain & hostname introduced by PUP-5097, Puppet 4.3.0
|
117
|
+
if trusted.has_key?("domain")
|
118
|
+
expect(trusted).to eq({'authenticated'=>'local', 'certname'=>'testing',
|
119
|
+
'extensions'=>{}, 'hostname'=>'testing', 'domain'=>nil})
|
120
|
+
else
|
121
|
+
# Puppet 4.2.x and older
|
122
|
+
expect(trusted).to eq({'authenticated'=>'local', 'certname'=>'testing', 'extensions'=>{}})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should return trusted data when falling back to the node' do
|
127
|
+
# This removes :trusted_information from the global context, triggering our fallback code.
|
128
|
+
if Puppet.methods.include? :rollback_context
|
129
|
+
Puppet.rollback_context('initial testing state')
|
130
|
+
else
|
131
|
+
Puppet.pop_context # puppet 3.5.1
|
132
|
+
end
|
133
|
+
|
134
|
+
node = Puppet::Node.new('my_certname', :parameters => {'clientcert' => 'trusted_certname'})
|
135
|
+
trusted = subject.get_trusted_info(node)
|
136
|
+
|
137
|
+
# Extra keys domainname & hostname introduced by PUP-5097, Puppet 4.3.0
|
138
|
+
if trusted.has_key?("domain")
|
139
|
+
expect(trusted).to eq({'authenticated'=>'local', 'certname'=>'trusted_certname',
|
140
|
+
'extensions'=>{}, 'hostname'=>'trusted_certname', 'domain'=>nil})
|
141
|
+
else
|
142
|
+
# Puppet 4.2.x and older
|
143
|
+
expect(trusted).to eq({'authenticated'=>'local', 'certname'=>'trusted_certname', 'extensions'=>{}})
|
144
|
+
end
|
145
|
+
|
146
|
+
# Put the context back the way the test harness expects
|
147
|
+
Puppet.push_context({}, 'context to make the tests happy')
|
148
|
+
if Puppet.methods.include? :mark_context
|
149
|
+
Puppet.mark_context('initial testing state')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#find" do
|
155
|
+
def find_facts()
|
156
|
+
Puppet::Node::Facts.indirection.find('some_node')
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should return the facts if they're found" do
|
160
|
+
body = [{"certname" => "some_node", "environment" => "production", "name" => "a", "value" => "1"},
|
161
|
+
{"certname" => "some_node", "environment" => "production", "name" => "b", "value" => "2"}].to_json
|
162
|
+
|
163
|
+
response = Net::HTTPOK.new('1.1', 200, 'OK')
|
164
|
+
response.stubs(:body).returns body
|
165
|
+
|
166
|
+
http.stubs(:get).with("/pdb/query/v4/nodes/some_node/facts", subject.headers).returns response
|
167
|
+
|
168
|
+
result = find_facts
|
169
|
+
result.should be_a(Puppet::Node::Facts)
|
170
|
+
result.name.should == 'some_node'
|
171
|
+
result.values.should include('a' => '1', 'b' => '2')
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should return nil if no facts are found" do
|
175
|
+
body = {"error" => "No information known about factset some_node"}.to_json
|
176
|
+
|
177
|
+
response = Net::HTTPNotFound.new('1.1', 404, 'NotFound')
|
178
|
+
response.stubs(:body).returns body
|
179
|
+
|
180
|
+
http.stubs(:get).with("/pdb/query/v4/nodes/some_node/facts", subject.headers).returns response
|
181
|
+
|
182
|
+
find_facts.should be_nil
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should fail if an HTTP error code is returned" do
|
186
|
+
response = Net::HTTPForbidden.new('1.1', 403, "Forbidden")
|
187
|
+
response.stubs(:body).returns ''
|
188
|
+
|
189
|
+
http.stubs(:get).with("/pdb/query/v4/nodes/some_node/facts", subject.headers).returns response
|
190
|
+
|
191
|
+
expect {
|
192
|
+
find_facts
|
193
|
+
}.to raise_error Puppet::Error, /\[403 Forbidden\]/
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should fail if an error occurs" do
|
197
|
+
http.stubs(:get).with("/pdb/query/v4/nodes/some_node/facts", subject.headers).raises Puppet::Error, "Everything is terrible!"
|
198
|
+
|
199
|
+
expect {
|
200
|
+
find_facts
|
201
|
+
}.to raise_error Puppet::Error, /Everything is terrible!/
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should log a deprecation warning if one is returned from PuppetDB" do
|
205
|
+
response = Net::HTTPOK.new('1.1', 200, 'OK')
|
206
|
+
response['x-deprecation'] = "This is deprecated!"
|
207
|
+
|
208
|
+
body = [].to_json
|
209
|
+
|
210
|
+
response.stubs(:body).returns body
|
211
|
+
|
212
|
+
http.stubs(:get).with("/pdb/query/v4/nodes/some_node/facts", subject.headers).returns(response)
|
213
|
+
|
214
|
+
Puppet.expects(:deprecation_warning).with do |msg|
|
215
|
+
msg =~ /This is deprecated!/
|
216
|
+
end
|
217
|
+
|
218
|
+
find_facts
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "#search" do
|
223
|
+
def search_facts(query)
|
224
|
+
Puppet::Node::Facts.indirection.search('facts', query)
|
225
|
+
end
|
226
|
+
let(:response) { Net::HTTPOK.new('1.1', 200, 'OK') }
|
227
|
+
|
228
|
+
it "should return the nodes from the response" do
|
229
|
+
args = {
|
230
|
+
'facts.kernel.eq' => 'Linux',
|
231
|
+
}
|
232
|
+
|
233
|
+
response.stubs(:body).returns '["foo", "bar", "baz"]'
|
234
|
+
response.stubs(:body).returns '[{"name": "foo", "deactivated": null, "expired": null, "catalog_timestamp": null, "facts_timestamp": null, "report_timestamp": null},
|
235
|
+
{"name": "bar", "deactivated": null, "expired": null, "catalog_timestamp": null, "facts_timestamp": null, "report_timestamp": null},
|
236
|
+
{"name": "baz", "deactivated": null, "expired": null, "catalog_timestamp": null, "facts_timestamp": null, "report_timestamp": null}]'
|
237
|
+
|
238
|
+
query = CGI.escape("[\"and\",[\"=\",[\"fact\",\"kernel\"],\"Linux\"]]")
|
239
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
240
|
+
|
241
|
+
search_facts(args).should == ['foo', 'bar', 'baz']
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should only allow searches against facts" do
|
245
|
+
args = {
|
246
|
+
'facts.kernel.eq' => 'Linux',
|
247
|
+
'wrong.kernel.eq' => 'Linux',
|
248
|
+
}
|
249
|
+
|
250
|
+
expect do
|
251
|
+
search_facts(args)
|
252
|
+
end.to raise_error(Puppet::Error, /Fact search against keys of type 'wrong' is unsupported/)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should combine multiple terms with 'and'" do
|
256
|
+
args = {
|
257
|
+
'facts.kernel.eq' => 'Linux',
|
258
|
+
'facts.uptime.eq' => '10 days',
|
259
|
+
}
|
260
|
+
|
261
|
+
query = CGI.escape(["and", ["=", ["fact", "kernel"], "Linux"],
|
262
|
+
["=", ["fact", "uptime"], "10 days"]].to_json)
|
263
|
+
|
264
|
+
response.stubs(:body).returns '[]'
|
265
|
+
|
266
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
267
|
+
|
268
|
+
search_facts(args)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should add 'not' to a != query" do
|
272
|
+
args = {
|
273
|
+
'facts.kernel.ne' => 'Linux',
|
274
|
+
}
|
275
|
+
|
276
|
+
query = CGI.escape(["and", ["not", ["=", ["fact", "kernel"], "Linux"]]].to_json)
|
277
|
+
|
278
|
+
response.stubs(:body).returns '[]'
|
279
|
+
|
280
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
281
|
+
|
282
|
+
search_facts(args)
|
283
|
+
end
|
284
|
+
|
285
|
+
it "should default the operator to = if one is not specified" do
|
286
|
+
args = {
|
287
|
+
'facts.kernel' => 'Linux',
|
288
|
+
}
|
289
|
+
|
290
|
+
query = CGI.escape(["and", ["=", ["fact", "kernel"], "Linux"]].to_json)
|
291
|
+
|
292
|
+
response.stubs(:body).returns '[]'
|
293
|
+
|
294
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
295
|
+
|
296
|
+
search_facts(args)
|
297
|
+
end
|
298
|
+
|
299
|
+
{
|
300
|
+
'gt' => '>',
|
301
|
+
'lt' => '<',
|
302
|
+
'ge' => '>=',
|
303
|
+
'le' => '<='
|
304
|
+
}.each do |name, operator|
|
305
|
+
it "should map '#{name}' to #{operator}" do
|
306
|
+
args = {
|
307
|
+
"facts.kernel.#{name}" => 'Linux',
|
308
|
+
}
|
309
|
+
|
310
|
+
query = CGI.escape(["and", [operator, ["fact", "kernel"], "Linux"]].to_json)
|
311
|
+
|
312
|
+
response.stubs(:body).returns '[]'
|
313
|
+
|
314
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
315
|
+
|
316
|
+
search_facts(args)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should raise an error if a failure occurs" do
|
321
|
+
response = Net::HTTPBadRequest.new('1.1', 400, 'Bad Request')
|
322
|
+
response.stubs(:body).returns 'Something bad happened!'
|
323
|
+
|
324
|
+
query = CGI.escape(["and"].to_json)
|
325
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
326
|
+
|
327
|
+
expect do
|
328
|
+
search_facts(nil)
|
329
|
+
end.to raise_error(Puppet::Error, /\[400 Bad Request\] Something bad happened!/)
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should log a deprecation warning if one is returned from PuppetDB" do
|
334
|
+
response['x-deprecation'] = "This is deprecated!"
|
335
|
+
response.stubs(:body).returns '[]'
|
336
|
+
|
337
|
+
query = CGI.escape(["and"].to_json)
|
338
|
+
http.stubs(:get).with("/pdb/query/v4/nodes?query=#{query}", subject.headers).returns(response)
|
339
|
+
|
340
|
+
Puppet.expects(:deprecation_warning).with do |msg|
|
341
|
+
msg =~ /This is deprecated!/
|
342
|
+
end
|
343
|
+
|
344
|
+
search_facts(nil)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'puppet/indirector/node/puppetdb'
|
6
|
+
require 'puppet/util/puppetdb/command_names'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
describe Puppet::Node::Puppetdb do
|
10
|
+
|
11
|
+
CommandDeactivateNode = Puppet::Util::Puppetdb::CommandNames::CommandDeactivateNode
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
Puppet::Node.indirection.stubs(:terminus).returns(subject)
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:node) { "something.example.com" }
|
18
|
+
let(:producer_timestamp) { Time.now.iso8601(5) }
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
Puppet::Node.indirection.destroy(node, {:producer_timestamp => producer_timestamp})
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#destroy" do
|
25
|
+
let(:response) { Net::HTTPOK.new('1.1', 200, 'OK') }
|
26
|
+
let(:http) { mock 'http' }
|
27
|
+
before :each do
|
28
|
+
Puppet::Network::HttpPool.expects(:http_instance).returns http
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should POST a '#{CommandDeactivateNode}' command" do
|
32
|
+
response.stubs(:body).returns '{"uuid": "a UUID"}'
|
33
|
+
|
34
|
+
payload = {
|
35
|
+
:command => CommandDeactivateNode,
|
36
|
+
:version => 3,
|
37
|
+
:payload => { :certname => node,
|
38
|
+
:producer_timestamp => producer_timestamp },
|
39
|
+
}.to_json
|
40
|
+
|
41
|
+
http.expects(:post).with do |uri,body,headers|
|
42
|
+
expect(body).to eq(payload)
|
43
|
+
end.returns response
|
44
|
+
|
45
|
+
destroy
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should log a deprecation warning if one is returned from PuppetDB" do
|
49
|
+
response['x-deprecation'] = 'A horrible deprecation warning!'
|
50
|
+
response.stubs(:body).returns '{"uuid": "a UUID"}'
|
51
|
+
|
52
|
+
Puppet.expects(:deprecation_warning).with do |msg|
|
53
|
+
msg =~ /A horrible deprecation warning!/
|
54
|
+
end
|
55
|
+
|
56
|
+
http.stubs(:post).returns response
|
57
|
+
|
58
|
+
destroy
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'puppet/indirector/resource/puppetdb'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
describe Puppet::Resource::Puppetdb do
|
8
|
+
before :each do
|
9
|
+
Puppet::Util::Puppetdb.stubs(:server).returns 'localhost'
|
10
|
+
Puppet::Util::Puppetdb.stubs(:port).returns 0
|
11
|
+
Puppet::Resource.indirection.stubs(:terminus).returns(subject)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#search" do
|
15
|
+
let(:host) { 'default.local' }
|
16
|
+
|
17
|
+
def search(type)
|
18
|
+
# The API for creating scope objects is different between Puppet 2.7 and
|
19
|
+
# 3.0. The scope here isn't really used for anything relevant, so it's
|
20
|
+
# easiest to make it a stub to run against both versions of Puppet.
|
21
|
+
scope = stub('scope', :source => nil)
|
22
|
+
args = { :host => host, :filter => nil, :scope => scope }
|
23
|
+
Puppet::Resource.indirection.search(type, args)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return an empty array if no resources match" do
|
27
|
+
response = Net::HTTPOK.new('1.1', 200, 'OK')
|
28
|
+
response.stubs(:body).returns '[]'
|
29
|
+
|
30
|
+
query = CGI.escape(["and", ["=", "type", "exec"], ["=", "exported", true], ["not", ["=", "certname", "default.local"]]].to_json)
|
31
|
+
http = stub 'http'
|
32
|
+
Puppet::Network::HttpPool.stubs(:http_instance).returns(http)
|
33
|
+
http.stubs(:get).with("/pdb/query/v4/resources?query=#{query}", subject.headers).returns response
|
34
|
+
|
35
|
+
search("exec").should == []
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should log a deprecation warning if one is returned from PuppetDB" do
|
39
|
+
response = Net::HTTPOK.new('1.1', 200, 'OK')
|
40
|
+
response['x-deprecation'] = "Deprecated, yo."
|
41
|
+
|
42
|
+
response.stubs(:body).returns '[]'
|
43
|
+
|
44
|
+
query = CGI.escape(["and", ["=", "type", "exec"], ["=", "exported", true], ["not", ["=", "certname", "default.local"]]].to_json)
|
45
|
+
http = stub 'http'
|
46
|
+
Puppet::Network::HttpPool.stubs(:http_instance).returns(http)
|
47
|
+
http.stubs(:get).with("/pdb/query/v4/resources?query=#{query}", subject.headers).returns response
|
48
|
+
|
49
|
+
Puppet.expects(:deprecation_warning).with do |msg|
|
50
|
+
msg =~ /Deprecated, yo\./
|
51
|
+
end
|
52
|
+
|
53
|
+
search("exec")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should fail it can't connect to the PuppetDB server" do
|
57
|
+
expect { search("user") }.to raise_error(Puppet::Error, /Could not retrieve resources/)
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "with a matching resource" do
|
61
|
+
|
62
|
+
let (:query) {
|
63
|
+
['and',
|
64
|
+
['=', 'type', 'File'],
|
65
|
+
['=', 'exported', true],
|
66
|
+
['not', ['=', 'certname', host]]]
|
67
|
+
}
|
68
|
+
|
69
|
+
def make_resource_hash(name, certname="localhost", exported=true)
|
70
|
+
metadata = { :file => '/etc/puppet/manifests/site.pp',
|
71
|
+
:line => 10,
|
72
|
+
:exported => exported,
|
73
|
+
:certname => certname,
|
74
|
+
:hash => 'foobarbaz', }
|
75
|
+
|
76
|
+
res = Puppet::Type.type(:file).new(:title => File.expand_path(name),
|
77
|
+
:ensure => :present,
|
78
|
+
:mode => "0777")
|
79
|
+
params = res.to_hash
|
80
|
+
res_hash = metadata.merge(:type => res.type, :title => res.title)
|
81
|
+
res_hash.merge(:parameters => params)
|
82
|
+
end
|
83
|
+
|
84
|
+
def stub_response(resource_hashes)
|
85
|
+
body = resource_hashes.to_json
|
86
|
+
|
87
|
+
response = Net::HTTPOK.new('1.1', 200, 'OK')
|
88
|
+
response.stubs(:body).returns body
|
89
|
+
|
90
|
+
http = stub 'http'
|
91
|
+
Puppet::Network::HttpPool.stubs(:http_instance).returns(http)
|
92
|
+
http.stubs(:get).with("/pdb/query/v4/resources?query=#{CGI.escape(query.to_json)}", subject.headers).returns response
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with resources from a single host" do
|
96
|
+
before :each do
|
97
|
+
stub_response([make_resource_hash('foo'), make_resource_hash('bar')])
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
it "should return a list of parser resources if any resources are found" do
|
102
|
+
found = search('File')
|
103
|
+
found.length.should == 2
|
104
|
+
found.each do |item|
|
105
|
+
item.should be_a(Puppet::Parser::Resource)
|
106
|
+
item.type.should == 'File'
|
107
|
+
item[:ensure].should == 'present'
|
108
|
+
item[:mode].should == '777'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
it "should not filter resources that have been found before" do
|
114
|
+
search('File').should == search('File')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "with resources from multiple hosts" do
|
119
|
+
before :each do
|
120
|
+
stub_response([make_resource_hash('foo', 'localhost'), make_resource_hash('foo', 'remotehost')])
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should supply unique collector_id vals for resources collected from different hosts" do
|
124
|
+
found = search('File')
|
125
|
+
found.length.should == 2
|
126
|
+
found[0].collector_id.should_not == found[1].collector_id
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "#build_expression" do
|
135
|
+
it "should return nil if there is no filter" do
|
136
|
+
subject.build_expression(nil).should == nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should fail if the filter uses an illegal operator" do
|
140
|
+
expect do
|
141
|
+
subject.build_expression(['foo', 'xor', 'bar'])
|
142
|
+
end.to raise_error(Puppet::Error, /not supported/)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should return an equal query if the operator is '='" do
|
146
|
+
subject.build_expression(['param','==','value']).should == ['=',['parameter','param'],'value']
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should return a not-equal query if the operator is '!='" do
|
150
|
+
subject.build_expression(['param','!=','value']).should == ['not', ['=', ['parameter','param'],'value']]
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should handle title correctly" do
|
154
|
+
subject.build_expression(['title','==','value']).should == ['=', 'title', 'value']
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should preserve the case of title queries" do
|
158
|
+
subject.build_expression(['title','==','VALUE']).should == ['=', 'title', 'VALUE']
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should handle tag correctly" do
|
162
|
+
subject.build_expression(['tag','==','value']).should == ['=', 'tag', 'value']
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should generate lowercase tag queries for case-insensitivity" do
|
166
|
+
subject.build_expression(['tag','==','VALUE']).should == ['=', 'tag', 'value']
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should conjoin 'and' queries with 'and'" do
|
170
|
+
query = [['tag', '==', 'one'], 'and', ['tag', '==', 'two']]
|
171
|
+
subject.build_expression(query).should == ['and',
|
172
|
+
['=', 'tag', 'one'],
|
173
|
+
['=', 'tag', 'two']]
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should conjoin 'or' queries with 'or'" do
|
177
|
+
query = [['tag', '==', 'one'], 'or', ['tag', '==', 'two']]
|
178
|
+
subject.build_expression(query).should == ['or',
|
179
|
+
['=', 'tag', 'one'],
|
180
|
+
['=', 'tag', 'two']]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should construct complex, nested queries" do
|
184
|
+
query = [[['tag', '==', 'one'], 'and', ['tag', '==', 'two']], 'or', ['tag', '!=', 'three']]
|
185
|
+
subject.build_expression(query).should == ['or',
|
186
|
+
['and',
|
187
|
+
['=', 'tag', 'one'],
|
188
|
+
['=', 'tag', 'two']],
|
189
|
+
['not',
|
190
|
+
['=', 'tag', 'three']]]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "#headers" do
|
195
|
+
it "should accept the correct mime type" do
|
196
|
+
subject.headers['Accept'].should == 'application/json'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|