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,316 @@
1
+ require 'puppet'
2
+
3
+ module Puppet
4
+ module Util
5
+ module Puppetdb
6
+ module CharEncoding
7
+
8
+
9
+ # Some of this code is modeled after:
10
+ # https://github.com/brianmario/utf8/blob/ef10c033/ext/utf8/utf8proc.c
11
+ # https://github.com/brianmario/utf8/blob/ef10c033/ext/utf8/string_utf8.c
12
+
13
+ Utf8CharLens = [
14
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
15
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
16
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
17
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
18
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
19
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
20
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
21
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
22
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
23
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
26
+ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
27
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
28
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
29
+ 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
30
+ ]
31
+
32
+ Utf8ReplacementChar = [ 0xEF, 0xBF, 0xBD ].pack("c*")
33
+
34
+ DEFAULT_INVALID_CHAR = "\ufffd"
35
+
36
+ # @api private
37
+ def self.all_indexes_of_char(str, char)
38
+ (0..str.length).find_all{ |i| str[i] == char}
39
+ end
40
+
41
+ # @api private
42
+ #
43
+ # Takes an array and returns a sub-array without the last element
44
+ #
45
+ # @return [Object]
46
+ def self.drop_last(array)
47
+ array[0..-2]
48
+ end
49
+
50
+ # @api private
51
+ #
52
+ # Takes an array of increasing integers and collapses the sequential
53
+ # integers into ranges
54
+ #
55
+ # @param index_array an array of sorted integers
56
+ # @return [Range]
57
+ def self.collapse_ranges(index_array)
58
+ ranges = index_array.each.inject([]) do |spans, n|
59
+ if spans.empty? || spans.last.end != n - 1
60
+ spans << Range.new(n, n)
61
+ else
62
+ drop_last(spans) << Range.new(spans.last.begin,n)
63
+ end
64
+ end
65
+ end
66
+
67
+ # @api private
68
+ #
69
+ # Scans the string s with bad characters found at bad_char_indexes
70
+ # and returns an array of messages that give some context around the
71
+ # bad characters. This will give up to 100 characters prior to the
72
+ # bad character and 100 after. It will return fewer if it's at the
73
+ # beginning of a string or if another bad character appears before
74
+ # reaching the 100 characters
75
+ #
76
+ # @param str string coming from to_pson, likely a command to be submitted to PDB
77
+ # @param bad_char_indexes an array of indexes into the string where invalid characters were found
78
+ # @return [String]
79
+ def self.error_char_context(str, bad_char_indexes)
80
+ bad_char_ranges = collapse_ranges(bad_char_indexes)
81
+ bad_char_ranges.each_with_index.inject([]) do |state, (r, index)|
82
+ gap = r.to_a.length
83
+
84
+ prev_bad_char_end = bad_char_ranges[index-1].end + 1 if index > 0
85
+ next_bad_char_begin = bad_char_ranges[index+1].begin - 1 if index < bad_char_ranges.length - 1
86
+
87
+ start_char = [prev_bad_char_end || 0, r.begin-100].max
88
+ end_char = [next_bad_char_begin || str.length - 1, r.end+100].min
89
+ x = [next_bad_char_begin || str.length, r.end+100, str.length]
90
+ prefix = str[start_char..r.begin-1]
91
+ suffix = str[r.end+1..end_char]
92
+
93
+ state << "'#{prefix}' followed by #{gap} invalid/undefined bytes then '#{suffix}'"
94
+ end
95
+ end
96
+
97
+ # @api private
98
+ #
99
+ # Warns the user if an invalid character was found. If debugging is
100
+ # enabled will also log contextual information about where the bad
101
+ # character(s) were found
102
+ #
103
+ # @param str A string coming from to_pson, likely a command to be submitted to PDB
104
+ # @param error_context_str information about where this string came from for use in error messages
105
+ # @return String
106
+ def self.warn_if_invalid_chars(str, error_context_str)
107
+ bad_char_indexes = all_indexes_of_char(str, DEFAULT_INVALID_CHAR)
108
+ if bad_char_indexes.empty?
109
+ str
110
+ else
111
+ Puppet.warning "#{error_context_str} ignoring invalid UTF-8 byte sequences in data to be sent to PuppetDB, see debug logging for more info"
112
+ if Puppet.settings[:log_level] == "debug"
113
+ Puppet.debug error_context_str + "\n" + error_char_context(str, bad_char_indexes).join("\n")
114
+ end
115
+
116
+ str
117
+ end
118
+ end
119
+
120
+ # @api private
121
+ #
122
+ # Attempts to coerce str to UTF-8, if that fails will output context
123
+ # information using error_context_str
124
+ #
125
+ # @param str A string coming from to_pson, likely a command to be submitted to PDB
126
+ # @param error_context_str information about where this string came from for use in error messages
127
+ # @return Str
128
+ def self.coerce_to_utf8(str, error_context_str)
129
+ str_copy = str.dup
130
+ # This code is passed in a string that was created by
131
+ # to_pson. to_pson calls force_encoding('ASCII-8BIT') on the
132
+ # string before it returns it. This leaves the actual UTF-8 bytes
133
+ # alone. Below we check to see if this is the case (this should be
134
+ # most common). In this case, the bytes are still UTF-8 and we can
135
+ # just encode! and we're good to go. If They are not valid UTF-8
136
+ # bytes, that means there is probably some binary data mixed in
137
+ # the middle of the UTF-8 string. In this case we need to output a
138
+ # warning and give the user more information
139
+ str_copy.force_encoding("UTF-8")
140
+ if str_copy.valid_encoding?
141
+ str_copy.encode!("UTF-8")
142
+ else
143
+ # This is force_encoded as US-ASCII to avoid any overlapping
144
+ # byte related issues that could arise from mis-interpreting a
145
+ # random extra byte as part of a multi-byte UTF-8 character
146
+ str_copy.force_encoding("US-ASCII")
147
+ warn_if_invalid_chars(str_copy.encode!("UTF-8",
148
+ :invalid => :replace,
149
+ :undef => :replace,
150
+ :replace => DEFAULT_INVALID_CHAR),
151
+ error_context_str)
152
+ end
153
+ end
154
+
155
+ def self.utf8_string(str, error_context_str)
156
+ if RUBY_VERSION =~ /^1.8/
157
+ # Ruby 1.8 doesn't have String#encode and related methods, and there
158
+ # appears to be a bug in iconv that will interpret some byte sequences
159
+ # as 6-byte characters. Thus, we are forced to resort to some unfortunate
160
+ # manual chicanery.
161
+ warn_if_changed(str, ruby18_clean_utf8(str))
162
+ else
163
+ begin
164
+ coerce_to_utf8(str, error_context_str)
165
+ rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
166
+ # If we got an exception, the string is either invalid or not
167
+ # convertible to UTF-8, so drop those bytes.
168
+
169
+ warn_if_changed(str, str.encode('UTF-8', :invalid => :replace, :undef => :replace))
170
+ end
171
+ end
172
+ end
173
+
174
+ # @api private
175
+ def self.warn_if_changed(str, converted_str)
176
+ if converted_str != str
177
+ Puppet.warning "Ignoring invalid UTF-8 byte sequences in data to be sent to PuppetDB"
178
+ end
179
+ converted_str
180
+ end
181
+
182
+ # @api private
183
+ def self.ruby18_clean_utf8(str)
184
+ #iconv_to_utf8(str)
185
+ #ruby18_manually_clean_utf8(str)
186
+
187
+ # So, we've tried doing this UTF8 cleaning for ruby 1.8 a few different
188
+ # ways. Doing it via IConv, we don't do a good job of handling characters
189
+ # whose codepoints would exceed the legal maximum for UTF-8. Doing it via
190
+ # our manual scrubbing process is slower and doesn't catch overlong
191
+ # encodings. Since this code really shouldn't even exist in the first place
192
+ # we've decided to simply compose the two scrubbing methods for now, rather
193
+ # than trying to add detection of overlong encodings. It'd be a non-trivial
194
+ # chunk of code, and it'd have to do a lot of bitwise arithmetic (which Ruby
195
+ # is not blazingly fast at).
196
+ ruby18_manually_clean_utf8(iconv_to_utf8(str))
197
+ end
198
+
199
+
200
+ # @todo we're not using this anymore, but I wanted to leave it around
201
+ # for a little while just to make sure that the new code pans out.
202
+ # @api private
203
+ def self.iconv_to_utf8(str)
204
+ iconv = Iconv.new('UTF-8//IGNORE', 'UTF-8')
205
+
206
+ # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/
207
+ iconv.iconv(str + " ")[0..-2]
208
+ end
209
+
210
+ # @api private
211
+ def self.get_char_len(byte)
212
+ Utf8CharLens[byte]
213
+ end
214
+
215
+ # Manually cleans a string by stripping any byte sequences that are
216
+ # not valid UTF-8 characters. If you'd prefer for the invalid bytes to be
217
+ # replaced with the unicode replacement character rather than being stripped,
218
+ # you may pass `false` for the optional second parameter (`strip`, which
219
+ # defaults to `true`).
220
+ #
221
+ # @api private
222
+ def self.ruby18_manually_clean_utf8(str, strip = true)
223
+
224
+ # This is a hack to allow this code to work with either ruby 1.8 or 1.9,
225
+ # which is useful for debugging and benchmarking. For more info see the
226
+ # comments in the #get_byte method below.
227
+ @has_get_byte = str.respond_to?(:getbyte)
228
+
229
+
230
+ i = 0
231
+ len = str.length
232
+ result = ""
233
+
234
+ while i < len
235
+ byte = get_byte(str, i)
236
+
237
+ i += 1
238
+
239
+ char_len = get_char_len(byte)
240
+ case char_len
241
+ when 0
242
+ result.concat(Utf8ReplacementChar) unless strip
243
+ when 1
244
+ result << byte
245
+ when 2..4
246
+ ruby18_handle_multibyte_char(result, byte, str, i, char_len, strip)
247
+ i += char_len - 1
248
+ else
249
+ raise Puppet::DevError, "Unhandled UTF8 char length: '#{char_len}'"
250
+ end
251
+
252
+ end
253
+
254
+ result
255
+ end
256
+
257
+ # @api private
258
+ def self.ruby18_handle_multibyte_char(result_str, byte, str, i, char_len, strip = true)
259
+ # keeping an array of bytes for now because we need to do some
260
+ # bitwise math on them.
261
+ char_additional_bytes = []
262
+
263
+ # If we don't have enough bytes left to read the full character, we
264
+ # put on a replacement character and bail.
265
+ if i + (char_len - 1) > str.length
266
+ result_str.concat(Utf8ReplacementChar) unless strip
267
+ return
268
+ end
269
+
270
+ # we've already read the first byte, so we need to set up a range
271
+ # from 0 to (n-2); e.g. if it's a 2-byte char, we will have a range
272
+ # from 0 to 0 which will result in reading 1 more byte
273
+ (0..char_len - 2).each do |x|
274
+ char_additional_bytes << get_byte(str, i + x)
275
+ end
276
+
277
+ if (is_valid_multibyte_suffix(byte, char_additional_bytes))
278
+ result_str << byte
279
+ result_str.concat(char_additional_bytes.pack("c*"))
280
+ else
281
+ result_str.concat(Utf8ReplacementChar) unless strip
282
+ end
283
+ end
284
+
285
+ # @api private
286
+ def self.is_valid_multibyte_suffix(byte, additional_bytes)
287
+ # This is heinous, but the UTF-8 spec says that codepoints greater than
288
+ # 0x10FFFF are illegal. The first character that is over that limit is
289
+ # 0xF490bfbf, so if the first byte is F4 then we have to check for
290
+ # that condition.
291
+ if byte == 0xF4
292
+ val = additional_bytes.inject(0) { |result, b | (result << 8) + b}
293
+ if val >= 0x90bfbf
294
+ return false
295
+ end
296
+ end
297
+ additional_bytes.all? { |b| ((b & 0xC0) == 0x80) }
298
+ end
299
+
300
+ # @api private
301
+ def self.get_byte(str, index)
302
+ # This method is a hack to allow this code to work with either ruby 1.8
303
+ # or 1.9. In production this code path should never be exercised by
304
+ # 1.9 because it has a much more sane way to accomplish our goal, but
305
+ # for testing, it is useful to be able to run the 1.8 codepath in 1.9.
306
+ if @has_get_byte
307
+ str.getbyte(index)
308
+ else
309
+ str[index]
310
+ end
311
+ end
312
+
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,116 @@
1
+ require 'puppet/error'
2
+ require 'puppet/util/puppetdb'
3
+ require 'puppet/util/puppetdb/http'
4
+ require 'puppet/util/puppetdb/command_names'
5
+ require 'puppet/util/puppetdb/char_encoding'
6
+ require 'json'
7
+
8
+ class Puppet::Util::Puppetdb::Command
9
+ include Puppet::Util::Puppetdb
10
+ include Puppet::Util::Puppetdb::CommandNames
11
+
12
+ CommandsUrl = "/pdb/cmd/v1"
13
+
14
+ # Public instance methods
15
+
16
+ # Initialize a Command object, for later submission.
17
+ #
18
+ # @param command String the name of the command; should be one of the
19
+ # constants defined in `Puppet::Util::Puppetdb::CommandNames`
20
+ # @param version Integer the command version number
21
+ # @param certname The certname that this command operates on (is not
22
+ # included in the actual submission)
23
+ # @param payload Object the payload of the command. This object should be a
24
+ # primitive (numeric type, string, array, or hash) that is natively supported
25
+ # by JSON serialization / deserialization libraries.
26
+ def initialize(command, version, certname, payload)
27
+ @command = command
28
+ @version = version
29
+ @certname = certname
30
+ profile("Format payload", [:puppetdb, :payload, :format]) do
31
+ @payload = Puppet::Util::Puppetdb::CharEncoding.utf8_string({
32
+ :command => command,
33
+ :version => version,
34
+ :payload => payload,
35
+ # We use to_pson still here, to work around the support for shifting
36
+ # binary data from a catalog to PuppetDB. Attempting to use to_json
37
+ # we get to_json conversion errors:
38
+ #
39
+ # Puppet source sequence is illegal/malformed utf-8
40
+ # json/ext/GeneratorMethods.java:71:in `to_json'
41
+ # puppet/util/puppetdb/command.rb:31:in `initialize'
42
+ #
43
+ # This is roughly inline with how Puppet serializes for catalogs as of
44
+ # Puppet 4.1.0. We need a better answer to non-utf8 data end-to-end.
45
+ }.to_pson, "Error encoding a '#{command}' command for host '#{certname}'")
46
+ end
47
+ end
48
+
49
+ attr_reader :command, :version, :certname, :payload
50
+
51
+ # Submit the command, returning the result hash.
52
+ #
53
+ # @return [Hash <String, String>]
54
+ def submit
55
+ checksum = Digest::SHA1.hexdigest(payload)
56
+
57
+ for_whom = " for #{certname}" if certname
58
+
59
+ begin
60
+ response = profile("Submit command HTTP post", [:puppetdb, :command, :submit]) do
61
+ Http.action("#{CommandsUrl}?checksum=#{checksum}") do |http_instance, path|
62
+ http_instance.post(path, payload, headers)
63
+ end
64
+ end
65
+
66
+ Puppet::Util::Puppetdb.log_x_deprecation_header(response)
67
+
68
+ if response.is_a? Net::HTTPSuccess
69
+ result = JSON.parse(response.body)
70
+ Puppet.info "'#{command}' command#{for_whom} submitted to PuppetDB with UUID #{result['uuid']}"
71
+ result
72
+ else
73
+ # Newline characters cause an HTTP error, so strip them
74
+ error = "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
75
+ if config.soft_write_failure
76
+ Puppet.err "'#{command}'command#{for_whom} failed during submission to PuppetDB: #{error}"
77
+ else
78
+ raise Puppet::Error, error
79
+ end
80
+ end
81
+ rescue => e
82
+ if config.soft_write_failure
83
+ Puppet.err e.message
84
+ else
85
+ # TODO: Use new exception handling methods from Puppet 3.0 here as soon as
86
+ # we are able to do so (can't call them yet w/o breaking backwards
87
+ # compatibility.) We should either be using a nested exception or calling
88
+ # Puppet::Util::Logging#log_exception or #log_and_raise here; w/o them
89
+ # we lose context as to where the original exception occurred.
90
+ if Puppet[:trace]
91
+ Puppet.err(e)
92
+ Puppet.err(e.backtrace)
93
+ end
94
+ raise Puppet::Util::Puppetdb::CommandSubmissionError.new(e.message, {:command => command, :for_whom => for_whom})
95
+ end
96
+ end
97
+ end
98
+
99
+ # @!group Private instance methods
100
+
101
+ # @api private
102
+ def headers
103
+ {
104
+ "Accept" => "application/json",
105
+ "Content-Type" => "application/json; charset=utf-8",
106
+ }
107
+ end
108
+
109
+ # @api private
110
+ def config
111
+ # Would prefer to pass this to the constructor or acquire it some other
112
+ # way besides this pseudo-global reference.
113
+ Puppet::Util::Puppetdb.config
114
+ end
115
+
116
+ end
@@ -0,0 +1,8 @@
1
+ module Puppet::Util::Puppetdb
2
+ module CommandNames
3
+ CommandReplaceCatalog = "replace catalog"
4
+ CommandReplaceFacts = "replace facts"
5
+ CommandDeactivateNode = "deactivate node"
6
+ CommandStoreReport = "store report"
7
+ end
8
+ end
@@ -0,0 +1,148 @@
1
+ require 'puppet/util/puppetdb/command_names'
2
+ require 'uri'
3
+
4
+ module Puppet::Util::Puppetdb
5
+ class Config
6
+ include Puppet::Util::Puppetdb::CommandNames
7
+
8
+ # Public class methods
9
+
10
+ def self.load(config_file = nil)
11
+ defaults = {
12
+ :server => "puppetdb",
13
+ :port => 8081,
14
+ :soft_write_failure => false,
15
+ :server_url_timeout => 30,
16
+ :include_unchanged_resources => false,
17
+ }
18
+
19
+ config_file ||= File.join(Puppet[:confdir], "puppetdb.conf")
20
+
21
+ if File.exists?(config_file)
22
+ Puppet.debug("Configuring PuppetDB terminuses with config file #{config_file}")
23
+ content = File.read(config_file)
24
+ else
25
+ Puppet.debug("No #{config_file} file found; falling back to default server and port #{defaults[:server]}:#{defaults[:port]}")
26
+ content = ''
27
+ end
28
+
29
+ result = {}
30
+ section = nil
31
+ content.lines.each_with_index do |line,number|
32
+ # Gotta track the line numbers properly
33
+ number += 1
34
+ case line
35
+ when /^\[(\w+)\s*\]$/
36
+ section = $1
37
+ result[section] ||= {}
38
+
39
+ when /^\s*(\w+)\s*=\s*(\S+|[\S+\s*\,\s*\S]+)\s*$/
40
+ raise "Setting '#{line}' is illegal outside of section in PuppetDB config #{config_file}:#{number}" unless section
41
+ result[section][$1] = $2
42
+ when /^\s*[#;]/
43
+ # Skip comments
44
+ when /^\s*$/
45
+ # Skip blank lines
46
+ else
47
+ raise "Unparseable line '#{line}' in PuppetDB config #{config_file}:#{number}"
48
+ end
49
+ end
50
+
51
+ main_section = result['main'] || {}
52
+ # symbolize the keys
53
+ main_section = main_section.inject({}) {|h, (k,v)| h[k.to_sym] = v ; h}
54
+ # merge with defaults but filter out anything except the legal settings
55
+ config_hash = defaults.merge(main_section).reject do |k, v|
56
+ !([:server,
57
+ :port,
58
+ :ignore_blacklisted_events,
59
+ :include_unchanged_resources,
60
+ :soft_write_failure,
61
+ :server_urls,
62
+ :server_url_timeout].include?(k))
63
+ end
64
+
65
+ if config_hash[:server_urls]
66
+ uses_server_urls = true
67
+ config_hash[:server_urls] = config_hash[:server_urls].split(",").map {|s| s.strip}
68
+ else
69
+ uses_server_urls = false
70
+ config_hash[:server_urls] = ["https://#{config_hash[:server].strip}:#{config_hash[:port].to_s}"]
71
+ end
72
+ config_hash[:server_urls] = convert_and_validate_urls(config_hash[:server_urls])
73
+
74
+ config_hash[:server_url_timeout] = config_hash[:server_url_timeout].to_i
75
+ config_hash[:include_unchanged_resources] = Puppet::Util::Puppetdb.to_bool(config_hash[:include_unchanged_resources])
76
+ config_hash[:soft_write_failure] = Puppet::Util::Puppetdb.to_bool(config_hash[:soft_write_failure])
77
+
78
+ self.new(config_hash, uses_server_urls)
79
+ rescue => detail
80
+ Puppet.warning "Could not configure PuppetDB terminuses: #{detail}"
81
+ Puppet.warning detail.backtrace if Puppet[:trace]
82
+ raise
83
+ end
84
+
85
+ # @!group Public instance methods
86
+
87
+ def initialize(config_hash = {}, uses_server_urls=nil)
88
+ @config = config_hash
89
+ if !uses_server_urls
90
+ Puppet.warning("Specification of server and port in puppetdb.conf is deprecated. Use the setting server_urls.")
91
+ end
92
+ # To provide accurate error messages to users about HTTP failures, we
93
+ # need to know whether they initially defined their config via the old
94
+ # server/port combo or the new server_urls. This boolean keeps track
95
+ # of how the user defined that config so that we can give them a
96
+ # better error message
97
+ @server_url_config = uses_server_urls
98
+ end
99
+
100
+ def server_url_config?
101
+ @server_url_config
102
+ end
103
+
104
+ def server_urls
105
+ config[:server_urls]
106
+ end
107
+
108
+ def server_url_timeout
109
+ config[:server_url_timeout]
110
+ end
111
+
112
+ def include_unchanged_resources?
113
+ config[:include_unchanged_resources]
114
+ end
115
+
116
+ def soft_write_failure
117
+ config[:soft_write_failure]
118
+ end
119
+
120
+ # @!group Private instance methods
121
+
122
+ # @!attribute [r] count
123
+ # @api private
124
+ attr_reader :config
125
+
126
+ # @api private
127
+ def self.convert_and_validate_urls(uri_strings)
128
+ uri_strings.map do |uri_string|
129
+
130
+ begin
131
+ uri = URI(uri_string.strip)
132
+ rescue URI::InvalidURIError => e
133
+ raise URI::InvalidURIError.new, "Error parsing URL '#{uri_string}' in PuppetDB 'server_urls', error message was '#{e.message}'"
134
+ end
135
+
136
+ if uri.scheme != 'https'
137
+ raise "PuppetDB 'server_urls' must be https, found '#{uri_string}'"
138
+ end
139
+
140
+ if uri.path != '' && uri.path != '/'
141
+ raise "PuppetDB 'server_urls' cannot contain URL paths, found '#{uri_string}'"
142
+ end
143
+ uri.path = ''
144
+ uri
145
+ end
146
+ end
147
+ end
148
+ end