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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +202 -0
  3. data/NOTICE.txt +17 -0
  4. data/README.md +22 -0
  5. data/puppet/lib/puppet/application/storeconfigs.rb +4 -0
  6. data/puppet/lib/puppet/face/node/deactivate.rb +37 -0
  7. data/puppet/lib/puppet/face/node/status.rb +80 -0
  8. data/puppet/lib/puppet/face/storeconfigs.rb +193 -0
  9. data/puppet/lib/puppet/indirector/catalog/puppetdb.rb +400 -0
  10. data/puppet/lib/puppet/indirector/facts/puppetdb.rb +152 -0
  11. data/puppet/lib/puppet/indirector/facts/puppetdb_apply.rb +25 -0
  12. data/puppet/lib/puppet/indirector/node/puppetdb.rb +19 -0
  13. data/puppet/lib/puppet/indirector/resource/puppetdb.rb +108 -0
  14. data/puppet/lib/puppet/reports/puppetdb.rb +188 -0
  15. data/puppet/lib/puppet/util/puppetdb.rb +108 -0
  16. data/puppet/lib/puppet/util/puppetdb/char_encoding.rb +316 -0
  17. data/puppet/lib/puppet/util/puppetdb/command.rb +116 -0
  18. data/puppet/lib/puppet/util/puppetdb/command_names.rb +8 -0
  19. data/puppet/lib/puppet/util/puppetdb/config.rb +148 -0
  20. data/puppet/lib/puppet/util/puppetdb/http.rb +121 -0
  21. data/puppet/spec/README.markdown +8 -0
  22. data/puppet/spec/spec.opts +6 -0
  23. data/puppet/spec/spec_helper.rb +38 -0
  24. data/puppet/spec/unit/face/node/deactivate_spec.rb +28 -0
  25. data/puppet/spec/unit/face/node/status_spec.rb +43 -0
  26. data/puppet/spec/unit/face/storeconfigs_spec.rb +199 -0
  27. data/puppet/spec/unit/indirector/catalog/puppetdb_spec.rb +703 -0
  28. data/puppet/spec/unit/indirector/facts/puppetdb_apply_spec.rb +27 -0
  29. data/puppet/spec/unit/indirector/facts/puppetdb_spec.rb +347 -0
  30. data/puppet/spec/unit/indirector/node/puppetdb_spec.rb +61 -0
  31. data/puppet/spec/unit/indirector/resource/puppetdb_spec.rb +199 -0
  32. data/puppet/spec/unit/reports/puppetdb_spec.rb +249 -0
  33. data/puppet/spec/unit/util/puppetdb/char_encoding_spec.rb +212 -0
  34. data/puppet/spec/unit/util/puppetdb/command_spec.rb +98 -0
  35. data/puppet/spec/unit/util/puppetdb/config_spec.rb +227 -0
  36. data/puppet/spec/unit/util/puppetdb/http_spec.rb +138 -0
  37. data/puppet/spec/unit/util/puppetdb_spec.rb +33 -0
  38. metadata +115 -0
@@ -0,0 +1,400 @@
1
+ require 'puppet/resource/catalog'
2
+ require 'puppet/indirector/rest'
3
+ require 'puppet/util/puppetdb'
4
+ require 'time'
5
+
6
+ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
7
+ include Puppet::Util::Puppetdb
8
+ include Puppet::Util::Puppetdb::CommandNames
9
+
10
+ def save(request)
11
+ profile("catalog#save", [:puppetdb, :catalog, :save, request.key]) do
12
+ catalog = munge_catalog(request.instance, extract_extra_request_data(request))
13
+ submit_command(request.key, catalog, CommandReplaceCatalog, 7)
14
+ end
15
+ end
16
+
17
+ def find(request)
18
+ nil
19
+ end
20
+
21
+ # @api private
22
+ def extract_extra_request_data(request)
23
+ {
24
+ :transaction_uuid => request.options[:transaction_uuid],
25
+ :environment => request.environment.to_s,
26
+ :producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601(5),
27
+ :code_id => request.options[:code_id],
28
+ }
29
+ end
30
+
31
+ def hashify_tags(hash)
32
+ hash["resources"] = hash["resources"].map { |resource| resource["tags"] = resource["tags"].to_data_hash; resource }
33
+ hash
34
+ end
35
+
36
+ def munge_catalog(catalog, extra_request_data = {})
37
+ profile("Munge catalog", [:puppetdb, :catalog, :munge]) do
38
+ data = profile("Convert catalog to JSON data hash", [:puppetdb, :catalog, :convert_to_hash]) do
39
+ catalog.to_data_hash
40
+ end
41
+
42
+ add_parameters_if_missing(data)
43
+ add_namevar_aliases(data, catalog)
44
+ stringify_titles(data)
45
+ stringify_version(data)
46
+ hashify_tags(data)
47
+ sort_unordered_metaparams(data)
48
+ munge_edges(data)
49
+ synthesize_edges(data, catalog)
50
+ filter_keys(data)
51
+ add_transaction_uuid(data, extra_request_data[:transaction_uuid])
52
+ add_environment(data, extra_request_data[:environment])
53
+ add_producer_timestamp(data, extra_request_data[:producer_timestamp])
54
+ change_name_to_certname(data)
55
+ add_code_id(data, extra_request_data[:code_id])
56
+
57
+ data
58
+ end
59
+ end
60
+
61
+ Relationships = {
62
+ :before => {:direction => :forward, :relationship => 'before'},
63
+ :require => {:direction => :reverse, :relationship => 'required-by'},
64
+ :notify => {:direction => :forward, :relationship => 'notifies'},
65
+ :subscribe => {:direction => :reverse, :relationship => 'subscription-of'},
66
+ }
67
+
68
+ # Metaparams that may contain arrays, but whose semantics are
69
+ # fundamentally unordered
70
+ UnorderedMetaparams = [:alias, :audit, :before, :check, :notify, :require, :subscribe, :tag]
71
+
72
+ # Change the name field to certname
73
+ #
74
+ # @param hash [Hash] original data hash
75
+ # @return [Hash] returns original hash with 'name' changed to 'certname'
76
+ # @api private
77
+ def change_name_to_certname(hash)
78
+ hash['certname'] = hash.delete('name')
79
+
80
+ hash
81
+ end
82
+
83
+ # Include environment in hash, returning the complete hash.
84
+ #
85
+ # @param hash [Hash] original data hash
86
+ # @param environment [String] environment
87
+ # @return [Hash] returns original hash augmented with environment
88
+ # @api private
89
+ def add_environment(hash, environment)
90
+ hash['environment'] = environment
91
+
92
+ hash
93
+ end
94
+
95
+ # Include producer_timestamp in hash, returning the complete hash.
96
+ #
97
+ # @param hash [Hash] original data hash
98
+ # @param producer_timestamp [String or nil] producer_tiemstamp
99
+ # @return [Hash] returns original hash augmented with producer_timestamp
100
+ # @api private
101
+ def add_producer_timestamp(hash, producer_timestamp)
102
+ hash['producer_timestamp'] = producer_timestamp
103
+
104
+ hash
105
+ end
106
+
107
+ # Include transaction_uuid in hash, returning the complete hash.
108
+ #
109
+ # @param hash [Hash] original data hash
110
+ # @param transaction_uuid [String] transaction_uuid
111
+ # @return [Hash] returns original hash augmented with transaction_uuid
112
+ # @api private
113
+ def add_transaction_uuid(hash, transaction_uuid)
114
+ hash['transaction_uuid'] = transaction_uuid
115
+
116
+ hash
117
+ end
118
+
119
+ # Include code_id in hash, returning the complete hash.
120
+ #
121
+ # @param hash [Hash] original data hash
122
+ # @param code_id [String] code_id
123
+ # @return [Hash] returns original hash augmented with transaction_uuid
124
+ # @api private
125
+ def add_code_id(hash, code_id)
126
+ hash['code_id'] = code_id
127
+
128
+ hash
129
+ end
130
+
131
+ # Version is an integer (time since epoch in millis). The wire
132
+ # format specifies version should be a string
133
+ #
134
+ # @param hash [Hash] original data hash
135
+ # @return [Hash] returns a modified original hash
136
+ def stringify_version(hash)
137
+ hash['version'] = hash['version'].to_s
138
+
139
+ hash
140
+ end
141
+
142
+ def stringify_titles(hash)
143
+ resources = hash['resources']
144
+ profile("Stringify titles (resource count: #{resources.count})",
145
+ [:puppetdb, :titles, :stringify]) do
146
+ resources.each do |resource|
147
+ resource['title'] = resource['title'].to_s
148
+ end
149
+ end
150
+
151
+ hash
152
+ end
153
+
154
+ def add_parameters_if_missing(hash)
155
+ resources = hash['resources']
156
+ profile("Add parameters if missing (resource count: #{resources.count})",
157
+ [:puppetdb, :parameters, :add_missing]) do
158
+ resources.each do |resource|
159
+ resource['parameters'] ||= {}
160
+ end
161
+ end
162
+
163
+ hash
164
+ end
165
+
166
+ def add_namevar_aliases(hash, catalog)
167
+ resources = hash['resources']
168
+ profile("Add namevar aliases (resource count: #{resources.count})",
169
+ [:puppetdb, :namevar_aliases, :add]) do
170
+ resources.each do |resource|
171
+ real_resource = catalog.resource(resource['type'], resource['title'])
172
+
173
+ # Resources with composite namevars can't be referred to by
174
+ # anything other than their title when declaring
175
+ # relationships. Trying to snag the :alias for these resources
176
+ # will only return _part_ of the name (a problem with Puppet
177
+ # proper), so skipping the adding of aliases for these resources
178
+ # is both an optimization and a safeguard.
179
+ next if real_resource.key_attributes.count > 1
180
+
181
+ aliases = [real_resource[:alias]].flatten.compact
182
+
183
+ # Non-isomorphic resources aren't unique based on namevar, so we can't
184
+ # use it as an alias
185
+ type = real_resource.resource_type
186
+ if !type.respond_to?(:isomorphic?) or type.isomorphic?
187
+ # This makes me a little sad. It turns out that the "to_hash" method
188
+ # of Puppet::Resource can have side effects. In particular, if the
189
+ # resource type specifies a title_pattern, calling "to_hash" will trigger
190
+ # the title_pattern processing, which can have the side effect of
191
+ # populating the namevar (potentially with a munged value). Thus,
192
+ # it is important that we search for namevar aliases in that hash
193
+ # rather than in the resource itself.
194
+ real_resource_hash = real_resource.to_hash
195
+
196
+ name = real_resource_hash[real_resource.send(:namevar)]
197
+ unless name.nil? or real_resource.title == name or aliases.include?(name)
198
+ aliases << name
199
+ end
200
+ end
201
+
202
+ resource['parameters'][:alias] = aliases unless aliases.empty?
203
+ end
204
+ end
205
+
206
+ hash
207
+ end
208
+
209
+ def sort_unordered_metaparams(hash)
210
+ resources = hash['resources']
211
+ profile("Sort unordered metaparams (resource count: #{resources.count})",
212
+ [:puppetdb, :metaparams, :sort]) do
213
+ resources.each do |resource|
214
+ params = resource['parameters']
215
+ UnorderedMetaparams.each do |metaparam|
216
+ if params[metaparam].kind_of? Array then
217
+ values = params[metaparam].sort_by {|x| x.to_s}
218
+ params[metaparam] = values unless values.empty?
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ hash
225
+ end
226
+
227
+ def munge_edges(hash)
228
+ edges = hash['edges']
229
+ profile("Munge edges (edge count: #{edges.count})",
230
+ [:puppetdb, :edges, :munge]) do
231
+ edges.each do |edge|
232
+ %w[source target].each do |vertex|
233
+ edge[vertex] = resource_ref_to_hash(edge[vertex]) if edge[vertex].is_a?(String)
234
+ end
235
+ edge['relationship'] ||= 'contains'
236
+ end
237
+
238
+ hash
239
+ end
240
+ end
241
+
242
+ def map_aliases_to_title(hash)
243
+ resources = hash['resources']
244
+ aliases = {}
245
+
246
+ profile("Map aliases to title (resource count: #{resources.count})",
247
+ [:puppetdb, :aliases, :map_to_title]) do
248
+ resources.each do |resource|
249
+ names = Array(resource['parameters'][:alias]) || []
250
+ resource_hash = {'type' => resource['type'], 'title' => resource['title']}
251
+ names.each do |name|
252
+ alias_array = [resource['type'], name]
253
+ aliases[alias_array] = resource_hash
254
+ end
255
+ end
256
+ end
257
+
258
+ aliases
259
+ end
260
+
261
+ def synthesize_edges(hash, catalog)
262
+ profile("Synthesize edges",
263
+ [:puppetdb, :edges, :synthesize]) do
264
+ aliases = map_aliases_to_title(hash)
265
+
266
+ resource_table = {}
267
+ profile("Build up resource_table",
268
+ [:puppetdb, :edges, :synthesize, :resource_table, :build]) do
269
+ hash['resources'].each do |resource|
270
+ resource_table[ [resource['type'], resource['title']] ] = resource
271
+ end
272
+ end
273
+
274
+ profile("Primary synthesis",
275
+ [:puppetdb, :edges, :synthesize, :primary_synthesis])do
276
+ hash['resources'].each do |resource|
277
+ # Standard virtual resources don't appear in the catalog. However,
278
+ # exported resources which haven't been also collected will appears as
279
+ # exported and virtual (collected ones will only be exported). They will
280
+ # eventually be removed from the catalog, so we can't add edges involving
281
+ # them. Puppet::Resource#to_data_hash omits 'virtual', so we have to
282
+ # look it up in the catalog to find that information. This isn't done in
283
+ # a separate step because we don't actually want to send the field (it
284
+ # will always be false). See ticket #16472.
285
+ #
286
+ # The outer conditional is here because Class[main] can't properly be
287
+ # looked up using catalog.resource and will return nil. See ticket
288
+ # #16473. Yay.
289
+ if real_resource = catalog.resource(resource['type'], resource['title'])
290
+ next if real_resource.virtual?
291
+ end
292
+
293
+ Relationships.each do |param,relation|
294
+ if value = resource['parameters'][param]
295
+ [value].flatten.each do |other_ref|
296
+ edge = {'relationship' => relation[:relationship]}
297
+
298
+ resource_hash = {'type' => resource['type'], 'title' => resource['title']}
299
+ other_hash = resource_ref_to_hash(other_ref)
300
+
301
+ # Puppet doesn't always seem to check this correctly. If we don't
302
+ # users will later get an invalid relationship error instead.
303
+ #
304
+ # Primarily we are trying to catch the non-capitalized resourceref
305
+ # case problem here: http://projects.puppetlabs.com/issues/19474
306
+ # Once that problem is solved and older versions of Puppet that have
307
+ # the bug are no longer supported we can probably remove this code.
308
+ unless other_ref =~ /^[A-Z][a-z0-9_-]*(::[A-Z][a-z0-9_-]*)*\[.*\]/m
309
+ rel = edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)
310
+ raise Puppet::Error, "Invalid relationship: #{rel}, because " +
311
+ "#{other_ref} doesn't seem to be in the correct format. " +
312
+ "Resource references should be formatted as: " +
313
+ "Classname['title'] or Modulename::Classname['title'] (take " +
314
+ "careful note of the capitalization)."
315
+ end
316
+
317
+ # This is an unfortunate hack. Puppet does some weird things w/rt
318
+ # munging off trailing slashes from file resources, and users may
319
+ # legally specify relationships using a different number of trailing
320
+ # slashes than the resource was originally declared with.
321
+ # We do know that for any file resource in the catalog, there should
322
+ # be a canonical entry for it that contains no trailing slashes. So,
323
+ # here, in case someone has specified a relationship to a file resource
324
+ # and has used one or more trailing slashes when specifying the
325
+ # relationship, we will munge off the trailing slashes before
326
+ # we look up the resource in the catalog to create the edge.
327
+ if other_hash['type'] == 'File' and other_hash['title'] =~ /\/$/
328
+ other_hash['title'] = other_hash['title'].sub(/\/+$/, '')
329
+ end
330
+
331
+ other_array = [other_hash['type'], other_hash['title']]
332
+
333
+ # Try to find the resource by type/title or look it up as an alias
334
+ # and try that
335
+ other_resource = resource_table[other_array]
336
+ if other_resource.nil? and alias_hash = aliases[other_array]
337
+ other_resource = resource_table[ alias_hash.values_at('type', 'title') ]
338
+ end
339
+
340
+ raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} doesn't seem to be in the catalog" unless other_resource
341
+
342
+ # As above, virtual exported resources will eventually be removed,
343
+ # so if a real resource refers to one, it's wrong. Non-virtual
344
+ # exported resources are exported resources that were also
345
+ # collected in this catalog, so they're okay. Virtual non-exported
346
+ # resources can't appear in the catalog in the first place, so it
347
+ # suffices to check for virtual.
348
+ if other_real_resource = catalog.resource(other_resource['type'], other_resource['title'])
349
+ if other_real_resource.virtual?
350
+ raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} is exported but not collected"
351
+ end
352
+ end
353
+
354
+ # If the ref was an alias, it will have a different title, so use
355
+ # that
356
+ other_hash['title'] = other_resource['title']
357
+
358
+ if relation[:direction] == :forward
359
+ edge.merge!('source' => resource_hash, 'target' => other_hash)
360
+ else
361
+ edge.merge!('source' => other_hash, 'target' => resource_hash)
362
+ end
363
+ hash['edges'] << edge
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
369
+
370
+ profile("Make edges unique",
371
+ [:puppetdb, :edges, :synthesize, :make_unique]) do
372
+ hash['edges'].uniq!
373
+ end
374
+
375
+ hash
376
+ end
377
+ end
378
+
379
+ def filter_keys(hash)
380
+ profile("Filter extraneous keys from the catalog",
381
+ [:puppetdb, :keys, :filter_extraneous]) do
382
+ hash.delete_if do |k,v|
383
+ ! ['name', 'version', 'edges', 'resources'].include?(k)
384
+ end
385
+ end
386
+ end
387
+
388
+ def resource_ref_to_hash(ref)
389
+ ref =~ /^([^\[\]]+)\[(.+)\]$/m
390
+ {'type' => $1, 'title' => $2}
391
+ end
392
+
393
+ def resource_hash_to_ref(hash)
394
+ "#{hash['type']}[#{hash['title']}]"
395
+ end
396
+
397
+ def edge_to_s(specifier_resource, referred_resource, param)
398
+ "#{specifier_resource} { #{param} => #{referred_resource} }"
399
+ end
400
+ end
@@ -0,0 +1,152 @@
1
+ require 'uri'
2
+ require 'puppet/node/facts'
3
+ require 'puppet/indirector/rest'
4
+ require 'puppet/util/puppetdb'
5
+ require 'json'
6
+ require 'time'
7
+
8
+ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
9
+ include Puppet::Util::Puppetdb
10
+ include Puppet::Util::Puppetdb::CommandNames
11
+
12
+ def get_trusted_info(node)
13
+ trusted = Puppet.lookup(:trusted_information) do
14
+ Puppet::Context::TrustedInformation.local(node)
15
+ end
16
+ trusted.to_h
17
+ end
18
+
19
+ def maybe_strip_internal(facts)
20
+ if Puppet::Node::Facts.method_defined? :strip_internal
21
+ facts.strip_internal
22
+ else
23
+ facts.values
24
+ end
25
+ end
26
+
27
+ def save(request)
28
+ profile("facts#save", [:puppetdb, :facts, :save, request.key]) do
29
+ payload = profile("Encode facts command submission payload",
30
+ [:puppetdb, :facts, :encode]) do
31
+ facts = request.instance.dup
32
+ facts.values = facts.strip_internal.dup
33
+
34
+ if ! Puppet::Util::Puppetdb.puppet3compat? || Puppet[:trusted_node_data]
35
+ facts.values[:trusted] = get_trusted_info(request.node)
36
+ end
37
+ {
38
+ "certname" => facts.name,
39
+ "values" => facts.values,
40
+ # PDB-453: we call to_s to avoid a 'stack level too deep' error
41
+ # when we attempt to use ActiveSupport 2.3.16 on RHEL 5 with
42
+ # legacy storeconfigs.
43
+ "environment" => request.options[:environment] || request.environment.to_s,
44
+ "producer_timestamp" => request.options[:producer_timestamp] || Time.now.iso8601(5),
45
+ }
46
+ end
47
+
48
+ submit_command(request.key, payload, CommandReplaceFacts, 4)
49
+ end
50
+ end
51
+
52
+ def find(request)
53
+ profile("facts#find", [:puppetdb, :facts, :find, request.key]) do
54
+ begin
55
+ response = Http.action("/pdb/query/v4/nodes/#{CGI.escape(request.key)}/facts") do |http_instance, path|
56
+ profile("Query for nodes facts: #{URI.unescape(path)}",
57
+ [:puppetdb, :facts, :find, :query_nodes, request.key]) do
58
+ http_instance.get(path, headers)
59
+ end
60
+ end
61
+ log_x_deprecation_header(response)
62
+
63
+ if response.is_a? Net::HTTPSuccess
64
+ profile("Parse fact query response (size: #{response.body.size})",
65
+ [:puppetdb, :facts, :find, :parse_response, request.key]) do
66
+ result = JSON.parse(response.body)
67
+
68
+ facts = result.inject({}) do |a,h|
69
+ a.merge(h['name'] => h['value'])
70
+ end
71
+
72
+ Puppet::Node::Facts.new(request.key, facts)
73
+ end
74
+ else
75
+ # Newline characters cause an HTTP error, so strip them
76
+ raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
77
+ end
78
+ rescue NotFoundError => e
79
+ # This is what the inventory service expects when there is no data
80
+ return nil
81
+ rescue => e
82
+ raise Puppet::Error, "Failed to find facts from PuppetDB at #{self.class.server}:#{self.class.port}: #{e}"
83
+ end
84
+ end
85
+ end
86
+
87
+ # Search for nodes matching a set of fact constraints. The constraints are
88
+ # specified as a hash of the form:
89
+ #
90
+ # `{type.name.operator => value`
91
+ #
92
+ # The only accepted `type` is 'facts'.
93
+ #
94
+ # `name` must be the fact name to query against.
95
+ #
96
+ # `operator` may be one of {eq, ne, lt, gt, le, ge}, and will default to 'eq'
97
+ # if unspecified.
98
+ def search(request)
99
+ profile("facts#search", [:puppetdb, :facts, :search, request.key]) do
100
+ return [] unless request.options
101
+ operator_map = {
102
+ 'eq' => '=',
103
+ 'gt' => '>',
104
+ 'lt' => '<',
105
+ 'ge' => '>=',
106
+ 'le' => '<=',
107
+ }
108
+ filters = request.options.sort.map do |key,value|
109
+ type, name, operator = key.to_s.split('.')
110
+ operator ||= 'eq'
111
+ raise Puppet::Error, "Fact search against keys of type '#{type}' is unsupported" unless type == 'facts'
112
+ if operator == 'ne'
113
+ ['not', ['=', ['fact', name], value]]
114
+ else
115
+ [operator_map[operator], ['fact', name], value]
116
+ end
117
+ end
118
+
119
+ query = ["and"] + filters
120
+ query_param = CGI.escape(query.to_json)
121
+
122
+ begin
123
+ response = Http.action("/pdb/query/v4/nodes?query=#{query_param}") do |http_instance, path|
124
+ profile("Fact query request: #{URI.unescape(path)}",
125
+ [:puppetdb, :facts, :search, :query_request, request.key]) do
126
+ http_instance.get(path, headers)
127
+ end
128
+ end
129
+ log_x_deprecation_header(response)
130
+
131
+ if response.is_a? Net::HTTPSuccess
132
+ profile("Parse fact query response (size: #{response.body.size})",
133
+ [:puppetdb, :facts, :search, :parse_query_response, request.key,]) do
134
+ JSON.parse(response.body).collect {|s| s["name"]}
135
+ end
136
+ else
137
+ # Newline characters cause an HTTP error, so strip them
138
+ raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
139
+ end
140
+ rescue => e
141
+ raise Puppet::Util::Puppetdb::InventorySearchError, e.message
142
+ end
143
+ end
144
+ end
145
+
146
+ def headers
147
+ {
148
+ "Accept" => "application/json",
149
+ "Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8",
150
+ }
151
+ end
152
+ end