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,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,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
|