quixoten-puppetdb-terminus 3.2.4 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/puppet/face/node/status.rb +1 -1
- data/lib/puppet/functions/puppetdb_query.rb +6 -0
- data/lib/puppet/indirector/catalog/puppetdb.rb +29 -10
- data/lib/puppet/indirector/facts/puppetdb.rb +4 -15
- data/lib/puppet/indirector/resource/puppetdb.rb +1 -1
- data/lib/puppet/reports/puppetdb.rb +22 -15
- data/lib/puppet/util/puppetdb.rb +16 -4
- data/lib/puppet/util/puppetdb/atom.rb +22 -0
- data/lib/puppet/util/puppetdb/char_encoding.rb +20 -153
- data/lib/puppet/util/puppetdb/command.rb +10 -8
- data/lib/puppet/util/puppetdb/config.rb +60 -32
- data/lib/puppet/util/puppetdb/http.rb +175 -67
- data/lib/puppetdb/terminus/version.rb +1 -1
- metadata +3 -3
- data/lib/puppet/application/storeconfigs.rb +0 -4
- data/lib/puppet/face/storeconfigs.rb +0 -193
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4470280424f9b67fc3543f3009a0f91916257ff4
|
4
|
+
data.tar.gz: 7bfa68983d4a6f301b16250e78019299e5d2d88f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11c510c0300fc56d183a5c8a80322cd16115111e45c4cf238ffdb842bb9de465aab2ae84199b356bae065a8167fef637290b65ff724820b394e8099018f7574f
|
7
|
+
data.tar.gz: 670591770ccaebb59f44f00fdcb7e7e5ec3969c878845edcb47599e6094cc89221977084585a45b7f1b7926d3c3d48514663c17de4f323857357da65baa57cd1
|
@@ -19,7 +19,7 @@ Puppet::Face.define(:node, '0.0.1') do
|
|
19
19
|
|
20
20
|
args.map do |node|
|
21
21
|
begin
|
22
|
-
response = Puppet::Util::Puppetdb::Http.action("/pdb/query/v4/nodes/#{CGI.escape(node)}") do |http_instance, path|
|
22
|
+
response = Puppet::Util::Puppetdb::Http.action("/pdb/query/v4/nodes/#{CGI.escape(node)}", :query) do |http_instance, path|
|
23
23
|
http_instance.get(path, headers)
|
24
24
|
end
|
25
25
|
if response.is_a? Net::HTTPSuccess
|
@@ -10,7 +10,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
10
10
|
def save(request)
|
11
11
|
profile("catalog#save", [:puppetdb, :catalog, :save, request.key]) do
|
12
12
|
catalog = munge_catalog(request.instance, extract_extra_request_data(request))
|
13
|
-
submit_command(request.key, catalog, CommandReplaceCatalog,
|
13
|
+
submit_command(request.key, catalog, CommandReplaceCatalog, 8)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -24,7 +24,6 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
24
24
|
:transaction_uuid => request.options[:transaction_uuid],
|
25
25
|
:environment => request.environment.to_s,
|
26
26
|
:producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601(5),
|
27
|
-
:code_id => request.options[:code_id],
|
28
27
|
}
|
29
28
|
end
|
30
29
|
|
@@ -39,6 +38,8 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
39
38
|
catalog.to_data_hash
|
40
39
|
end
|
41
40
|
|
41
|
+
add_code_id_if_missing(data)
|
42
|
+
add_catalog_uuid_if_missing(data, extra_request_data[:transaction_uuid])
|
42
43
|
add_parameters_if_missing(data)
|
43
44
|
add_namevar_aliases(data, catalog)
|
44
45
|
stringify_titles(data)
|
@@ -47,12 +48,11 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
47
48
|
sort_unordered_metaparams(data)
|
48
49
|
munge_edges(data)
|
49
50
|
synthesize_edges(data, catalog)
|
51
|
+
change_name_to_certname(data)
|
50
52
|
filter_keys(data)
|
51
53
|
add_transaction_uuid(data, extra_request_data[:transaction_uuid])
|
52
54
|
add_environment(data, extra_request_data[:environment])
|
53
55
|
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
56
|
|
57
57
|
data
|
58
58
|
end
|
@@ -119,11 +119,25 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
119
119
|
# Include code_id in hash, returning the complete hash.
|
120
120
|
#
|
121
121
|
# @param hash [Hash] original data hash
|
122
|
-
# @
|
123
|
-
# @
|
122
|
+
# @return [Hash] returns original hash with a gaurunteed code_id key
|
123
|
+
# @api private
|
124
|
+
def add_code_id_if_missing(hash)
|
125
|
+
# This weird code ensure that `hash` will always have a `code_id` key and if
|
126
|
+
# it already had a `code_id` key we use that as the value. If `hash` didn't
|
127
|
+
# have a `code_id` key the lookup will return nil and hash['code_id'] == nil
|
128
|
+
hash['code_id'] = hash['code_id']
|
129
|
+
|
130
|
+
hash
|
131
|
+
end
|
132
|
+
|
133
|
+
# Include code_id in hash, returning the complete hash.
|
134
|
+
#
|
135
|
+
# @param hash [Hash] original data hash
|
136
|
+
# @param default [String] default catalog_uuid to use if hash doesn't have one
|
137
|
+
# @return [Hash] returns original hash with a gaurunteed catalog_uuid key/value
|
124
138
|
# @api private
|
125
|
-
def
|
126
|
-
hash['
|
139
|
+
def add_catalog_uuid_if_missing(hash, default)
|
140
|
+
hash['catalog_uuid'] = hash['catalog_uuid'] || default
|
127
141
|
|
128
142
|
hash
|
129
143
|
end
|
@@ -305,7 +319,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
305
319
|
# case problem here: http://projects.puppetlabs.com/issues/19474
|
306
320
|
# Once that problem is solved and older versions of Puppet that have
|
307
321
|
# the bug are no longer supported we can probably remove this code.
|
308
|
-
unless other_ref =~ /^[A-Z][a-z0-9_
|
322
|
+
unless other_ref =~ /^[A-Z][a-z0-9_]*(::[A-Z][a-z0-9_]*)*\[.*\]/m
|
309
323
|
rel = edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)
|
310
324
|
raise Puppet::Error, "Invalid relationship: #{rel}, because " +
|
311
325
|
"#{other_ref} doesn't seem to be in the correct format. " +
|
@@ -380,7 +394,12 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
|
|
380
394
|
profile("Filter extraneous keys from the catalog",
|
381
395
|
[:puppetdb, :keys, :filter_extraneous]) do
|
382
396
|
hash.delete_if do |k,v|
|
383
|
-
! ['
|
397
|
+
! ['certname',
|
398
|
+
'version',
|
399
|
+
'edges',
|
400
|
+
'resources',
|
401
|
+
'code_id',
|
402
|
+
'catalog_uuid'].include?(k)
|
384
403
|
end
|
385
404
|
end
|
386
405
|
end
|
@@ -16,24 +16,13 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
|
|
16
16
|
trusted.to_h
|
17
17
|
end
|
18
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
19
|
def save(request)
|
28
20
|
profile("facts#save", [:puppetdb, :facts, :save, request.key]) do
|
29
21
|
payload = profile("Encode facts command submission payload",
|
30
22
|
[:puppetdb, :facts, :encode]) do
|
31
23
|
facts = request.instance.dup
|
32
|
-
facts.values = facts.
|
33
|
-
|
34
|
-
if ! Puppet::Util::Puppetdb.puppet3compat? || Puppet[:trusted_node_data]
|
35
|
-
facts.values[:trusted] = get_trusted_info(request.node)
|
36
|
-
end
|
24
|
+
facts.values = facts.values.dup
|
25
|
+
facts.values[:trusted] = get_trusted_info(request.node)
|
37
26
|
{
|
38
27
|
"certname" => facts.name,
|
39
28
|
"values" => facts.values,
|
@@ -52,7 +41,7 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
|
|
52
41
|
def find(request)
|
53
42
|
profile("facts#find", [:puppetdb, :facts, :find, request.key]) do
|
54
43
|
begin
|
55
|
-
response = Http.action("/pdb/query/v4/nodes/#{CGI.escape(request.key)}/facts") do |http_instance, path|
|
44
|
+
response = Http.action("/pdb/query/v4/nodes/#{CGI.escape(request.key)}/facts", :query) do |http_instance, path|
|
56
45
|
profile("Query for nodes facts: #{URI.unescape(path)}",
|
57
46
|
[:puppetdb, :facts, :find, :query_nodes, request.key]) do
|
58
47
|
http_instance.get(path, headers)
|
@@ -120,7 +109,7 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
|
|
120
109
|
query_param = CGI.escape(query.to_json)
|
121
110
|
|
122
111
|
begin
|
123
|
-
response = Http.action("/pdb/query/v4/nodes?query=#{query_param}") do |http_instance, path|
|
112
|
+
response = Http.action("/pdb/query/v4/nodes?query=#{query_param}", :query) do |http_instance, path|
|
124
113
|
profile("Fact query request: #{URI.unescape(path)}",
|
125
114
|
[:puppetdb, :facts, :search, :query_request, request.key]) do
|
126
115
|
http_instance.get(path, headers)
|
@@ -27,7 +27,7 @@ class Puppet::Resource::Puppetdb < Puppet::Indirector::REST
|
|
27
27
|
query_param = CGI.escape(expr.to_json)
|
28
28
|
|
29
29
|
begin
|
30
|
-
response = Http.action("/pdb/query/v4/resources?query=#{query_param}") do |http_instance, path|
|
30
|
+
response = Http.action("/pdb/query/v4/resources?query=#{query_param}", :query) do |http_instance, path|
|
31
31
|
profile("Resources query: #{URI.unescape(path)}",
|
32
32
|
[:puppetdb, :resource, :search, :query, request.key]) do
|
33
33
|
http_instance.get(path, headers)
|
@@ -19,7 +19,7 @@ Puppet::Reports.register_report(:puppetdb) do
|
|
19
19
|
# @return [void]
|
20
20
|
def process
|
21
21
|
profile("report#process", [:puppetdb, :report, :process]) do
|
22
|
-
submit_command(self.host, report_to_hash, CommandStoreReport,
|
22
|
+
submit_command(self.host, report_to_hash, CommandStoreReport, 7)
|
23
23
|
end
|
24
24
|
|
25
25
|
nil
|
@@ -40,21 +40,28 @@ Puppet::Reports.register_report(:puppetdb) do
|
|
40
40
|
resources = build_resources_list
|
41
41
|
is_noop = resources.any? { |rs| has_noop_event?(rs) } and resources.none? { |rs| has_failed_event?(rs) }
|
42
42
|
|
43
|
+
|
44
|
+
defaulted_catalog_uuid = defined?(catalog_uuid) ? catalog_uuid : transaction_uuid
|
45
|
+
defaulted_code_id = defined?(code_id) ? code_id : nil
|
46
|
+
defaulted_cached_catalog_status = defined?(cached_catalog_status) ? cached_catalog_status : nil
|
43
47
|
{
|
44
|
-
"certname"
|
45
|
-
"puppet_version"
|
46
|
-
"report_format"
|
47
|
-
"configuration_version"
|
48
|
-
"producer_timestamp"
|
49
|
-
"start_time"
|
50
|
-
"end_time"
|
51
|
-
"environment"
|
52
|
-
"transaction_uuid"
|
53
|
-
"status"
|
54
|
-
"noop"
|
55
|
-
"logs"
|
56
|
-
"metrics"
|
57
|
-
"resources"
|
48
|
+
"certname" => host,
|
49
|
+
"puppet_version" => puppet_version,
|
50
|
+
"report_format" => report_format,
|
51
|
+
"configuration_version" => configuration_version.to_s,
|
52
|
+
"producer_timestamp" => Puppet::Util::Puppetdb.to_wire_time(Time.now),
|
53
|
+
"start_time" => Puppet::Util::Puppetdb.to_wire_time(time),
|
54
|
+
"end_time" => Puppet::Util::Puppetdb.to_wire_time(time + run_duration),
|
55
|
+
"environment" => environment,
|
56
|
+
"transaction_uuid" => transaction_uuid,
|
57
|
+
"status" => status,
|
58
|
+
"noop" => is_noop,
|
59
|
+
"logs" => build_logs_list,
|
60
|
+
"metrics" => build_metrics_list,
|
61
|
+
"resources" => resources,
|
62
|
+
"catalog_uuid" => defaulted_catalog_uuid,
|
63
|
+
"code_id" => defaulted_code_id,
|
64
|
+
"cached_catalog_status" => defaulted_cached_catalog_status,
|
58
65
|
}
|
59
66
|
end
|
60
67
|
end
|
data/lib/puppet/util/puppetdb.rb
CHANGED
@@ -6,6 +6,7 @@ require 'puppet/util/puppetdb/command'
|
|
6
6
|
require 'puppet/util/puppetdb/config'
|
7
7
|
require 'digest/sha1'
|
8
8
|
require 'time'
|
9
|
+
require 'json'
|
9
10
|
require 'fileutils'
|
10
11
|
|
11
12
|
module Puppet::Util::Puppetdb
|
@@ -28,10 +29,6 @@ module Puppet::Util::Puppetdb
|
|
28
29
|
@config
|
29
30
|
end
|
30
31
|
|
31
|
-
def self.puppet3compat?
|
32
|
-
defined?(Puppet::Parser::AST::HashOrArrayAccess)
|
33
|
-
end
|
34
|
-
|
35
32
|
# Given an instance of ruby's Time class, this method converts it to a String
|
36
33
|
# that conforms to PuppetDB's wire format for representing a date/time.
|
37
34
|
def self.to_wire_time(time)
|
@@ -68,6 +65,21 @@ module Puppet::Util::Puppetdb
|
|
68
65
|
end
|
69
66
|
end
|
70
67
|
|
68
|
+
# Query PuppetDB.
|
69
|
+
#
|
70
|
+
# @param query [String, Array] The PQL or AST query for PuppetDB
|
71
|
+
# @return [Array<Hash>]
|
72
|
+
def self.query_puppetdb(query)
|
73
|
+
Puppet::Util::Profiler.profile("Submitted query '#{query}'", [:puppetdb, :query, query]) do
|
74
|
+
headers = { "Accept" => "application/json",
|
75
|
+
"Content-Type" => "application/json; charset=UTF-8" }
|
76
|
+
response = Puppet::Util::Puppetdb::Http.action("/pdb/query/v4", :query) do |http_instance, path|
|
77
|
+
http_instance.post(path, { 'query' => query }.to_json, headers)
|
78
|
+
end
|
79
|
+
JSON.parse(response.body)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
71
83
|
# Profile a block of code and log the time it took to execute.
|
72
84
|
#
|
73
85
|
# This outputs logs entries to the Puppet masters logging destination
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Puppet::Util::Puppetdb
|
4
|
+
class Atom
|
5
|
+
def initialize(value)
|
6
|
+
@value = value
|
7
|
+
@mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def deref()
|
11
|
+
@mutex.synchronize {
|
12
|
+
@value
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset(value)
|
17
|
+
@mutex.synchronize {
|
18
|
+
@value = value
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -65,7 +65,7 @@ module CharEncoding
|
|
65
65
|
# @param bad_char_range a range indicating a block of invalid characters
|
66
66
|
# @return String
|
67
67
|
def self.error_char_context(str, bad_char_range)
|
68
|
-
|
68
|
+
|
69
69
|
gap = bad_char_range.to_a.length
|
70
70
|
|
71
71
|
start_char = [0, bad_char_range.begin-100].max
|
@@ -106,9 +106,10 @@ module CharEncoding
|
|
106
106
|
# information using error_context_str
|
107
107
|
#
|
108
108
|
# @param str A string coming from to_pson, likely a command to be submitted to PDB
|
109
|
-
# @param error_context_str information about where this string came from for
|
109
|
+
# @param error_context_str information about where this string came from for
|
110
|
+
# use in error messages. Defaults to nil, in which case no error is reported.
|
110
111
|
# @return Str
|
111
|
-
def self.coerce_to_utf8(str, error_context_str)
|
112
|
+
def self.coerce_to_utf8(str, error_context_str=nil)
|
112
113
|
str_copy = str.dup
|
113
114
|
# This code is passed in a string that was created by
|
114
115
|
# to_pson. to_pson calls force_encoding('ASCII-8BIT') on the
|
@@ -127,30 +128,27 @@ module CharEncoding
|
|
127
128
|
# byte related issues that could arise from mis-interpreting a
|
128
129
|
# random extra byte as part of a multi-byte UTF-8 character
|
129
130
|
str_copy.force_encoding("US-ASCII")
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
|
132
|
+
str_lossy = str_copy.encode!("UTF-8",
|
133
|
+
:invalid => :replace,
|
134
|
+
:undef => :replace,
|
135
|
+
:replace => DEFAULT_INVALID_CHAR)
|
136
|
+
if !error_context_str.nil?
|
137
|
+
warn_if_invalid_chars(str_lossy, error_context_str)
|
138
|
+
else
|
139
|
+
str_lossy
|
140
|
+
end
|
135
141
|
end
|
136
142
|
end
|
137
143
|
|
138
144
|
def self.utf8_string(str, error_context_str)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
#
|
143
|
-
#
|
144
|
-
warn_if_changed(str, ruby18_clean_utf8(str))
|
145
|
-
else
|
146
|
-
begin
|
147
|
-
coerce_to_utf8(str, error_context_str)
|
148
|
-
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
|
149
|
-
# If we got an exception, the string is either invalid or not
|
150
|
-
# convertible to UTF-8, so drop those bytes.
|
145
|
+
begin
|
146
|
+
coerce_to_utf8(str, error_context_str)
|
147
|
+
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
|
148
|
+
# If we got an exception, the string is either invalid or not
|
149
|
+
# convertible to UTF-8, so drop those bytes.
|
151
150
|
|
152
|
-
|
153
|
-
end
|
151
|
+
warn_if_changed(str, str.encode('UTF-8', :invalid => :replace, :undef => :replace))
|
154
152
|
end
|
155
153
|
end
|
156
154
|
|
@@ -162,137 +160,6 @@ module CharEncoding
|
|
162
160
|
converted_str
|
163
161
|
end
|
164
162
|
|
165
|
-
# @api private
|
166
|
-
def self.ruby18_clean_utf8(str)
|
167
|
-
#iconv_to_utf8(str)
|
168
|
-
#ruby18_manually_clean_utf8(str)
|
169
|
-
|
170
|
-
# So, we've tried doing this UTF8 cleaning for ruby 1.8 a few different
|
171
|
-
# ways. Doing it via IConv, we don't do a good job of handling characters
|
172
|
-
# whose codepoints would exceed the legal maximum for UTF-8. Doing it via
|
173
|
-
# our manual scrubbing process is slower and doesn't catch overlong
|
174
|
-
# encodings. Since this code really shouldn't even exist in the first place
|
175
|
-
# we've decided to simply compose the two scrubbing methods for now, rather
|
176
|
-
# than trying to add detection of overlong encodings. It'd be a non-trivial
|
177
|
-
# chunk of code, and it'd have to do a lot of bitwise arithmetic (which Ruby
|
178
|
-
# is not blazingly fast at).
|
179
|
-
ruby18_manually_clean_utf8(iconv_to_utf8(str))
|
180
|
-
end
|
181
|
-
|
182
|
-
|
183
|
-
# @todo we're not using this anymore, but I wanted to leave it around
|
184
|
-
# for a little while just to make sure that the new code pans out.
|
185
|
-
# @api private
|
186
|
-
def self.iconv_to_utf8(str)
|
187
|
-
iconv = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
188
|
-
|
189
|
-
# http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/
|
190
|
-
iconv.iconv(str + " ")[0..-2]
|
191
|
-
end
|
192
|
-
|
193
|
-
# @api private
|
194
|
-
def self.get_char_len(byte)
|
195
|
-
Utf8CharLens[byte]
|
196
|
-
end
|
197
|
-
|
198
|
-
# Manually cleans a string by stripping any byte sequences that are
|
199
|
-
# not valid UTF-8 characters. If you'd prefer for the invalid bytes to be
|
200
|
-
# replaced with the unicode replacement character rather than being stripped,
|
201
|
-
# you may pass `false` for the optional second parameter (`strip`, which
|
202
|
-
# defaults to `true`).
|
203
|
-
#
|
204
|
-
# @api private
|
205
|
-
def self.ruby18_manually_clean_utf8(str, strip = true)
|
206
|
-
|
207
|
-
# This is a hack to allow this code to work with either ruby 1.8 or 1.9,
|
208
|
-
# which is useful for debugging and benchmarking. For more info see the
|
209
|
-
# comments in the #get_byte method below.
|
210
|
-
@has_get_byte = str.respond_to?(:getbyte)
|
211
|
-
|
212
|
-
|
213
|
-
i = 0
|
214
|
-
len = str.length
|
215
|
-
result = ""
|
216
|
-
|
217
|
-
while i < len
|
218
|
-
byte = get_byte(str, i)
|
219
|
-
|
220
|
-
i += 1
|
221
|
-
|
222
|
-
char_len = get_char_len(byte)
|
223
|
-
case char_len
|
224
|
-
when 0
|
225
|
-
result.concat(Utf8ReplacementChar) unless strip
|
226
|
-
when 1
|
227
|
-
result << byte
|
228
|
-
when 2..4
|
229
|
-
ruby18_handle_multibyte_char(result, byte, str, i, char_len, strip)
|
230
|
-
i += char_len - 1
|
231
|
-
else
|
232
|
-
raise Puppet::DevError, "Unhandled UTF8 char length: '#{char_len}'"
|
233
|
-
end
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
|
-
result
|
238
|
-
end
|
239
|
-
|
240
|
-
# @api private
|
241
|
-
def self.ruby18_handle_multibyte_char(result_str, byte, str, i, char_len, strip = true)
|
242
|
-
# keeping an array of bytes for now because we need to do some
|
243
|
-
# bitwise math on them.
|
244
|
-
char_additional_bytes = []
|
245
|
-
|
246
|
-
# If we don't have enough bytes left to read the full character, we
|
247
|
-
# put on a replacement character and bail.
|
248
|
-
if i + (char_len - 1) > str.length
|
249
|
-
result_str.concat(Utf8ReplacementChar) unless strip
|
250
|
-
return
|
251
|
-
end
|
252
|
-
|
253
|
-
# we've already read the first byte, so we need to set up a range
|
254
|
-
# from 0 to (n-2); e.g. if it's a 2-byte char, we will have a range
|
255
|
-
# from 0 to 0 which will result in reading 1 more byte
|
256
|
-
(0..char_len - 2).each do |x|
|
257
|
-
char_additional_bytes << get_byte(str, i + x)
|
258
|
-
end
|
259
|
-
|
260
|
-
if (is_valid_multibyte_suffix(byte, char_additional_bytes))
|
261
|
-
result_str << byte
|
262
|
-
result_str.concat(char_additional_bytes.pack("c*"))
|
263
|
-
else
|
264
|
-
result_str.concat(Utf8ReplacementChar) unless strip
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
# @api private
|
269
|
-
def self.is_valid_multibyte_suffix(byte, additional_bytes)
|
270
|
-
# This is heinous, but the UTF-8 spec says that codepoints greater than
|
271
|
-
# 0x10FFFF are illegal. The first character that is over that limit is
|
272
|
-
# 0xF490bfbf, so if the first byte is F4 then we have to check for
|
273
|
-
# that condition.
|
274
|
-
if byte == 0xF4
|
275
|
-
val = additional_bytes.inject(0) { |result, b | (result << 8) + b}
|
276
|
-
if val >= 0x90bfbf
|
277
|
-
return false
|
278
|
-
end
|
279
|
-
end
|
280
|
-
additional_bytes.all? { |b| ((b & 0xC0) == 0x80) }
|
281
|
-
end
|
282
|
-
|
283
|
-
# @api private
|
284
|
-
def self.get_byte(str, index)
|
285
|
-
# This method is a hack to allow this code to work with either ruby 1.8
|
286
|
-
# or 1.9. In production this code path should never be exercised by
|
287
|
-
# 1.9 because it has a much more sane way to accomplish our goal, but
|
288
|
-
# for testing, it is useful to be able to run the 1.8 codepath in 1.9.
|
289
|
-
if @has_get_byte
|
290
|
-
str.getbyte(index)
|
291
|
-
else
|
292
|
-
str[index]
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
163
|
end
|
297
164
|
end
|
298
165
|
end
|
@@ -24,13 +24,11 @@ class Puppet::Util::Puppetdb::Command
|
|
24
24
|
# primitive (numeric type, string, array, or hash) that is natively supported
|
25
25
|
# by JSON serialization / deserialization libraries.
|
26
26
|
def initialize(command, version, certname, payload)
|
27
|
-
@command = command
|
28
|
-
@version = version
|
29
|
-
@certname = certname
|
30
27
|
profile("Format payload", [:puppetdb, :payload, :format]) do
|
31
|
-
@
|
28
|
+
@checksum_payload = Puppet::Util::Puppetdb::CharEncoding.utf8_string({
|
32
29
|
:command => command,
|
33
30
|
:version => version,
|
31
|
+
:certname => certname,
|
34
32
|
:payload => payload,
|
35
33
|
# We use to_pson still here, to work around the support for shifting
|
36
34
|
# binary data from a catalog to PuppetDB. Attempting to use to_json
|
@@ -44,21 +42,25 @@ class Puppet::Util::Puppetdb::Command
|
|
44
42
|
# Puppet 4.1.0. We need a better answer to non-utf8 data end-to-end.
|
45
43
|
}.to_pson, "Error encoding a '#{command}' command for host '#{certname}'")
|
46
44
|
end
|
45
|
+
@command = Puppet::Util::Puppetdb::CharEncoding.coerce_to_utf8(command).gsub(" ", "_")
|
46
|
+
@version = version
|
47
|
+
@certname = Puppet::Util::Puppetdb::CharEncoding.coerce_to_utf8(certname)
|
48
|
+
@payload = Puppet::Util::Puppetdb::CharEncoding.coerce_to_utf8(payload.to_pson)
|
47
49
|
end
|
48
50
|
|
49
|
-
attr_reader :command, :version, :certname, :payload
|
51
|
+
attr_reader :command, :version, :certname, :payload, :checksum_payload
|
50
52
|
|
51
53
|
# Submit the command, returning the result hash.
|
52
54
|
#
|
53
55
|
# @return [Hash <String, String>]
|
54
56
|
def submit
|
55
|
-
checksum = Digest::SHA1.hexdigest(
|
57
|
+
checksum = Digest::SHA1.hexdigest(checksum_payload)
|
56
58
|
|
57
59
|
for_whom = " for #{certname}" if certname
|
58
|
-
|
60
|
+
params = "checksum=#{checksum}&version=#{version}&certname=#{certname}&command=#{command}"
|
59
61
|
begin
|
60
62
|
response = profile("Submit command HTTP post", [:puppetdb, :command, :submit]) do
|
61
|
-
Http.action("#{CommandsUrl}
|
63
|
+
Http.action("#{CommandsUrl}?#{params}", :command) do |http_instance, path|
|
62
64
|
http_instance.post(path, payload, headers)
|
63
65
|
end
|
64
66
|
end
|
@@ -9,11 +9,14 @@ module Puppet::Util::Puppetdb
|
|
9
9
|
|
10
10
|
def self.load(config_file = nil)
|
11
11
|
defaults = {
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:server_url_timeout => 30,
|
12
|
+
:server_urls => "https://puppetdb:8081",
|
13
|
+
:soft_write_failure => false,
|
14
|
+
:server_url_timeout => 30,
|
16
15
|
:include_unchanged_resources => false,
|
16
|
+
:min_successful_submissions => 1,
|
17
|
+
:submit_only_server_urls => "",
|
18
|
+
:command_broadcast => false,
|
19
|
+
:sticky_read_failover => false
|
17
20
|
}
|
18
21
|
|
19
22
|
config_file ||= File.join(Puppet[:confdir], "puppetdb.conf")
|
@@ -22,7 +25,7 @@ module Puppet::Util::Puppetdb
|
|
22
25
|
Puppet.debug("Configuring PuppetDB terminuses with config file #{config_file}")
|
23
26
|
content = File.read(config_file)
|
24
27
|
else
|
25
|
-
Puppet.debug("No #{config_file} file found; falling back to default
|
28
|
+
Puppet.debug("No #{config_file} file found; falling back to default server_urls #{defaults[:server_urls]}")
|
26
29
|
content = ''
|
27
30
|
end
|
28
31
|
|
@@ -51,31 +54,53 @@ module Puppet::Util::Puppetdb
|
|
51
54
|
main_section = result['main'] || {}
|
52
55
|
# symbolize the keys
|
53
56
|
main_section = main_section.inject({}) {|h, (k,v)| h[k.to_sym] = v ; h}
|
57
|
+
|
54
58
|
# merge with defaults but filter out anything except the legal settings
|
55
59
|
config_hash = defaults.merge(main_section).reject do |k, v|
|
56
|
-
!([:
|
57
|
-
:port,
|
60
|
+
!([:server_urls,
|
58
61
|
:ignore_blacklisted_events,
|
59
62
|
:include_unchanged_resources,
|
60
63
|
:soft_write_failure,
|
61
|
-
:
|
62
|
-
:
|
64
|
+
:server_url_timeout,
|
65
|
+
:min_successful_submissions,
|
66
|
+
:submit_only_server_urls,
|
67
|
+
:command_broadcast,
|
68
|
+
:sticky_read_failover].include?(k))
|
63
69
|
end
|
64
70
|
|
65
|
-
|
66
|
-
|
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])
|
71
|
+
parsed_urls = config_hash[:server_urls].split(",").map {|s| s.strip}
|
72
|
+
config_hash[:server_urls] = convert_and_validate_urls(parsed_urls)
|
73
73
|
|
74
74
|
config_hash[:server_url_timeout] = config_hash[:server_url_timeout].to_i
|
75
75
|
config_hash[:include_unchanged_resources] = Puppet::Util::Puppetdb.to_bool(config_hash[:include_unchanged_resources])
|
76
76
|
config_hash[:soft_write_failure] = Puppet::Util::Puppetdb.to_bool(config_hash[:soft_write_failure])
|
77
77
|
|
78
|
-
|
78
|
+
config_hash[:submit_only_server_urls] = convert_and_validate_urls(config_hash[:submit_only_server_urls].split(",").map {|s| s.strip})
|
79
|
+
config_hash[:min_successful_submissions] = config_hash[:min_successful_submissions].to_i
|
80
|
+
config_hash[:command_broadcast] = Puppet::Util::Puppetdb.to_bool(config_hash[:command_broadcast])
|
81
|
+
config_hash[:sticky_read_failover] = Puppet::Util::Puppetdb.to_bool(config_hash[:sticky_read_failover])
|
82
|
+
|
83
|
+
if config_hash[:soft_write_failure] and config_hash[:min_successful_submissions] > 1
|
84
|
+
raise "soft_write_failure cannot be enabled when min_successful_submissions is greater than 1"
|
85
|
+
end
|
86
|
+
|
87
|
+
overlapping_server_urls = config_hash[:server_urls] & config_hash[:submit_only_server_urls]
|
88
|
+
if overlapping_server_urls.length > 0
|
89
|
+
overlapping_server_urls_strs = overlapping_server_urls.map { |u| u.to_s }
|
90
|
+
raise "Server URLs must be in either server_urls or submit_only_server_urls, not both. "\
|
91
|
+
"(#{overlapping_server_urls_strs.to_s} are in both)"
|
92
|
+
end
|
93
|
+
|
94
|
+
if config_hash[:min_successful_submissions] > 1 and not config_hash[:command_broadcast]
|
95
|
+
raise "command_broadcast must be set to true to use min_successful_submissions"
|
96
|
+
end
|
97
|
+
|
98
|
+
if config_hash[:min_successful_submissions] > config_hash[:server_urls].length
|
99
|
+
raise "min_successful_submissions (#{config_hash[:min_successful_submissions]}) must be less than "\
|
100
|
+
"or equal to the number of server_urls (#{config_hash[:server_urls].length})"
|
101
|
+
end
|
102
|
+
|
103
|
+
self.new(config_hash)
|
79
104
|
rescue => detail
|
80
105
|
Puppet.warning "Could not configure PuppetDB terminuses: #{detail}"
|
81
106
|
Puppet.warning detail.backtrace if Puppet[:trace]
|
@@ -84,21 +109,8 @@ module Puppet::Util::Puppetdb
|
|
84
109
|
|
85
110
|
# @!group Public instance methods
|
86
111
|
|
87
|
-
def initialize(config_hash = {}
|
112
|
+
def initialize(config_hash = {})
|
88
113
|
@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
114
|
end
|
103
115
|
|
104
116
|
def server_urls
|
@@ -117,6 +129,22 @@ module Puppet::Util::Puppetdb
|
|
117
129
|
config[:soft_write_failure]
|
118
130
|
end
|
119
131
|
|
132
|
+
def min_successful_submissions
|
133
|
+
config[:min_successful_submissions]
|
134
|
+
end
|
135
|
+
|
136
|
+
def submit_only_server_urls
|
137
|
+
config[:submit_only_server_urls]
|
138
|
+
end
|
139
|
+
|
140
|
+
def command_broadcast
|
141
|
+
config[:command_broadcast]
|
142
|
+
end
|
143
|
+
|
144
|
+
def sticky_read_failover
|
145
|
+
config[:sticky_read_failover]
|
146
|
+
end
|
147
|
+
|
120
148
|
# @!group Private instance methods
|
121
149
|
|
122
150
|
# @!attribute [r] count
|
@@ -3,14 +3,17 @@ require 'puppet/network/http_pool'
|
|
3
3
|
require 'net/http'
|
4
4
|
require 'timeout'
|
5
5
|
require 'pp'
|
6
|
+
require 'thread'
|
7
|
+
require 'puppet/util/puppetdb/atom'
|
6
8
|
|
7
9
|
module Puppet::Util::Puppetdb
|
8
10
|
class Http
|
11
|
+
SERVER_URL_FAIL_MSG = "Failing over to the next PuppetDB server_url in the 'server_urls' list"
|
9
12
|
|
10
|
-
|
13
|
+
@@last_good_query_server_url_index = Atom.new(0)
|
11
14
|
|
12
|
-
# Concat two
|
13
|
-
# ensure a correct
|
15
|
+
# Concat two server_url snippets, taking into account a trailing/leading slash to
|
16
|
+
# ensure a correct server_url is constructed
|
14
17
|
#
|
15
18
|
# @param snippet1 [String] first URL snippet
|
16
19
|
# @param snippet2 [String] second URL snippet
|
@@ -26,94 +29,199 @@ module Puppet::Util::Puppetdb
|
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
# @param http_callback [Proc] proc containing the code calling the action on the http connection
|
36
|
-
# @return [Response] returns http response
|
37
|
-
def self.action(path_suffix, &http_callback)
|
32
|
+
# Run the given block (cb) in a begin/rescue, catching common network
|
33
|
+
# exceptions and logging useful information about them. If an expected
|
34
|
+
# exception was caught, it's returned. An unexpected exception will be
|
35
|
+
# re-thrown. Returns nil on success.
|
36
|
+
def self.with_http_error_logging(server_url, route, &cb)
|
37
|
+
config = Puppet::Util::Puppetdb.config
|
38
38
|
|
39
|
+
begin
|
40
|
+
cb.call()
|
41
|
+
rescue Timeout::Error => e
|
42
|
+
Puppet.warning("Request to #{server_url.host} on #{server_url.port} at route #{route} timed out " \
|
43
|
+
"after #{config.server_url_timeout} seconds. #{SERVER_URL_FAIL_MSG}")
|
44
|
+
return e
|
45
|
+
|
46
|
+
rescue SocketError, OpenSSL::SSL::SSLError, SystemCallError, Net::ProtocolError, IOError, Net::HTTPNotFound => e
|
47
|
+
Puppet.warning("Error connecting to #{server_url.host} on #{server_url.port} at route #{route}, " \
|
48
|
+
"error message received was '#{e.message}'. #{SERVER_URL_FAIL_MSG}")
|
49
|
+
return e
|
50
|
+
|
51
|
+
rescue Puppet::Util::Puppetdb::InventorySearchError => e
|
52
|
+
Puppet.warning("Could not perform inventory search from PuppetDB at #{server_url.host}:#{server_url.port}: " \
|
53
|
+
"'#{e.message}' #{SERVER_URL_FAIL_MSG}")
|
54
|
+
return e
|
55
|
+
|
56
|
+
rescue Puppet::Util::Puppetdb::CommandSubmissionError => e
|
57
|
+
error = "Failed to submit '#{e.context[:command]}' command for '#{e.context[:for_whom]}' to PuppetDB " \
|
58
|
+
"at #{server_url.host}:#{server_url.port}: '#{e.message}'."
|
59
|
+
if config.soft_write_failure
|
60
|
+
Puppet.err error
|
61
|
+
else
|
62
|
+
Puppet.warning(error + " #{SERVER_URL_FAIL_MSG}")
|
63
|
+
end
|
64
|
+
return e
|
65
|
+
|
66
|
+
rescue Puppet::Util::Puppetdb::SoftWriteFailError => e
|
67
|
+
Puppet.warning("Failed to submit '#{e.context[:command]}' command for '#{e.context[:for_whom]}' to PuppetDB " \
|
68
|
+
"at #{server_url.host}:#{server_url.port}: '#{e.message}' #{SERVER_URL_FAIL_MSG}")
|
69
|
+
return e
|
70
|
+
|
71
|
+
rescue Puppet::Error => e
|
72
|
+
if e.message =~ /did not match server certificate; expected one of/
|
73
|
+
Puppet.warning("Error connecting to #{server_url.host} on #{server_url.port} at route #{route}, " \
|
74
|
+
"error message received was '#{e.message}'. #{SERVER_URL_FAIL_MSG}")
|
75
|
+
return e
|
76
|
+
else
|
77
|
+
raise
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check an http reponse from puppetdb; log a useful message if it looks like
|
85
|
+
# something went wrong. Return a symbol indicating the problem
|
86
|
+
# (:server_error, :notfound, or :other_404), or nil if there wasn't one.
|
87
|
+
def self.check_http_response(response, server_url, route)
|
88
|
+
if response.is_a? Net::HTTPServerError
|
89
|
+
Puppet.warning("Error connecting to #{server_url.host} on #{server_url.port} at route #{route}, " \
|
90
|
+
"error message received was '#{response.message}'. #{SERVER_URL_FAIL_MSG}")
|
91
|
+
:server_error
|
92
|
+
elsif response.is_a? Net::HTTPNotFound
|
93
|
+
if response.body && response.body.chars.first == "{"
|
94
|
+
# If it appears to be json, we've probably gotten an authentic 'not found' message.
|
95
|
+
Puppet.debug("HTTP 404 (probably normal) when connecting to #{server_url.host} on #{server_url.port} " \
|
96
|
+
"at route #{route}, error message received was '#{response.message}'. #{SERVER_URL_FAIL_MSG}")
|
97
|
+
:notfound
|
98
|
+
else
|
99
|
+
# But we can also get 404s when conneting to a puppetdb that's still starting or due to misconfiguration.
|
100
|
+
Puppet.warning("Error connecting to #{server_url.host} on #{server_url.port} at route #{route}, " \
|
101
|
+
"error message received was '#{response.message}'. #{SERVER_URL_FAIL_MSG}")
|
102
|
+
:other_404
|
103
|
+
end
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.raise_request_error(response, response_error, path_suffix)
|
110
|
+
server_url_strings = Puppet::Util::Puppetdb.config.server_urls.map {|server_url| server_url.to_s}.join(', ')
|
111
|
+
if response_error == :notfound
|
112
|
+
raise NotFoundError, "Failed to find '#{path_suffix}' on any of the following 'server_urls': #{server_url_strings}"
|
113
|
+
else
|
114
|
+
min_successful_submissions = Puppet::Util::Puppetdb.config.min_successful_submissions
|
115
|
+
raise Puppet::Error, "Failed to execute '#{path_suffix}' on at least #{min_successful_submissions} of the following 'server_urls': #{server_url_strings}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.failover_action(path_suffix, server_urls, sticky, http_callback)
|
39
120
|
response = nil
|
121
|
+
response_error = nil
|
40
122
|
config = Puppet::Util::Puppetdb.config
|
41
|
-
|
123
|
+
last_good_index = 0
|
124
|
+
|
125
|
+
if sticky
|
126
|
+
last_good_index = @@last_good_query_server_url_index.deref()
|
127
|
+
end
|
128
|
+
|
129
|
+
server_count = server_urls.length
|
130
|
+
server_try_order = (0...server_count).map { |i| (i + last_good_index) % server_count }
|
42
131
|
|
43
|
-
for
|
44
|
-
|
45
|
-
|
46
|
-
http = Puppet::Network::HttpPool.http_instance(url.host, url.port)
|
47
|
-
request_timeout = config.server_url_timeout
|
132
|
+
for server_url_index in server_try_order
|
133
|
+
server_url = server_urls[server_url_index]
|
134
|
+
route = concat_url_snippets(server_url.request_uri, path_suffix)
|
48
135
|
|
49
|
-
|
136
|
+
request_exception = with_http_error_logging(server_url, route) {
|
137
|
+
http = Puppet::Network::HttpPool.http_instance(server_url.host, server_url.port)
|
138
|
+
|
139
|
+
response = Timeout.timeout(config.server_url_timeout) do
|
50
140
|
http_callback.call(http, route)
|
51
141
|
end
|
142
|
+
}
|
52
143
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# If it appears to be json, we've probably gotten an authentic 'not found' message.
|
59
|
-
Puppet.debug("HTTP 404 (probably normal) when connecting to #{url.host} on #{url.port} at route #{route}, error message received was '#{response.message}'. #{SERVER_URL_FAIL_MSG if server_url_config}")
|
60
|
-
response = :notfound
|
61
|
-
else
|
62
|
-
# But we can also get 404s when conneting to a puppetdb that's still starting or due to misconfiguration.
|
63
|
-
Puppet.warning("Error connecting to #{url.host} on #{url.port} at route #{route}, error message received was '#{response.message}'. #{SERVER_URL_FAIL_MSG if server_url_config}")
|
64
|
-
response = nil
|
144
|
+
if request_exception.nil?
|
145
|
+
response_error = check_http_response(response, server_url, route)
|
146
|
+
if response_error.nil?
|
147
|
+
if server_url_index != server_try_order.first()
|
148
|
+
@@last_good_query_server_url_index.reset(server_url_index)
|
65
149
|
end
|
66
|
-
else
|
67
150
|
break
|
68
151
|
end
|
69
|
-
|
70
|
-
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if response.nil? or not(response_error.nil?)
|
156
|
+
raise_request_error(response, response_error, path_suffix)
|
157
|
+
end
|
158
|
+
|
159
|
+
response
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.broadcast_action(path_suffix, server_urls, http_callback)
|
163
|
+
response = nil
|
164
|
+
response_error = nil
|
165
|
+
config = Puppet::Util::Puppetdb.config
|
166
|
+
successful_submit_count = 0
|
71
167
|
|
72
|
-
|
73
|
-
|
168
|
+
for server_url in server_urls
|
169
|
+
route = concat_url_snippets(server_url.request_uri, path_suffix)
|
74
170
|
|
75
|
-
|
76
|
-
Puppet.
|
171
|
+
request_exception = with_http_error_logging(server_url, route) {
|
172
|
+
http = Puppet::Network::HttpPool.http_instance(server_url.host, server_url.port)
|
77
173
|
|
78
|
-
|
79
|
-
|
80
|
-
if config.soft_write_failure
|
81
|
-
Puppet.err error
|
82
|
-
else
|
83
|
-
Puppet.warning(error + " #{SERVER_URL_FAIL_MSG if server_url_config}")
|
174
|
+
response = Timeout.timeout(config.server_url_timeout) do
|
175
|
+
http_callback.call(http, route)
|
84
176
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
raise
|
177
|
+
}
|
178
|
+
|
179
|
+
if request_exception.nil?
|
180
|
+
response_error = check_http_response(response, server_url, route)
|
181
|
+
if response_error.nil?
|
182
|
+
successful_submit_count += 1
|
92
183
|
end
|
93
184
|
end
|
94
185
|
end
|
95
186
|
|
96
|
-
if
|
97
|
-
|
98
|
-
server_url_strings = Puppet::Util::Puppetdb.config.server_urls.map {|url| url.to_s}.join(', ')
|
99
|
-
if response == :notfound
|
100
|
-
raise NotFoundError, "Failed to find '#{path_suffix}' on any of the following 'server_urls': #{server_url_strings}"
|
101
|
-
else
|
102
|
-
raise Puppet::Error, "Failed to execute '#{path_suffix}' on any of the following 'server_urls': #{server_url_strings}"
|
103
|
-
end
|
104
|
-
else
|
105
|
-
uri = Puppet::Util::Puppetdb.config.server_urls.first
|
106
|
-
if response == :notfound
|
107
|
-
raise NotFoundError, "Failed to find '#{path_suffix}' on server: '#{uri.host}' and port: '#{uri.port}'"
|
108
|
-
else
|
109
|
-
raise Puppet::Error, "Failed to execute '#{path_suffix}' on server: '#{uri.host}' and port: '#{uri.port}'"
|
110
|
-
end
|
111
|
-
end
|
187
|
+
if successful_submit_count < config.min_successful_submissions
|
188
|
+
raise_request_error(response, response_error, path_suffix)
|
112
189
|
end
|
113
190
|
|
114
191
|
response
|
192
|
+
end
|
115
193
|
|
194
|
+
# Setup an http connection, provide a block that will do something with that http
|
195
|
+
# connection. The block should be a two argument block, accepting the connection (which
|
196
|
+
# you can call get or post on for example) and the properly constructed path, which
|
197
|
+
# will be the concatenated version of any url_prefix and the path passed in.
|
198
|
+
#
|
199
|
+
# @param path_suffix [String] path for the get/post of the http action
|
200
|
+
# @param request_type [Symbol] :query or :command
|
201
|
+
# @param http_callback [Proc] proc containing the code calling the action on the http connection
|
202
|
+
# @return [Response] returns http response
|
203
|
+
def self.action(path_suffix, request_mode, &http_callback)
|
204
|
+
config = Puppet::Util::Puppetdb.config
|
205
|
+
|
206
|
+
case request_mode
|
207
|
+
when :query
|
208
|
+
self.failover_action(path_suffix, config.server_urls, config.sticky_read_failover, http_callback)
|
209
|
+
when :command
|
210
|
+
submit_server_urls = config.server_urls + config.submit_only_server_urls
|
211
|
+
if config.command_broadcast
|
212
|
+
self.broadcast_action(path_suffix, submit_server_urls, http_callback)
|
213
|
+
else
|
214
|
+
self.failover_action(path_suffix, submit_server_urls, false, http_callback)
|
215
|
+
end
|
216
|
+
else
|
217
|
+
raise Puppet::Error, "Unknown request mode: #{request_mode}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.reset_query_failover()
|
222
|
+
@@last_good_query_server_url_index.reset(0)
|
116
223
|
end
|
224
|
+
|
117
225
|
end
|
118
226
|
|
119
227
|
class NotFoundError < Puppet::Error
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quixoten-puppetdb-terminus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Devin Christensen
|
@@ -50,10 +50,9 @@ files:
|
|
50
50
|
- LICENSE.txt
|
51
51
|
- README.md
|
52
52
|
- Rakefile
|
53
|
-
- lib/puppet/application/storeconfigs.rb
|
54
53
|
- lib/puppet/face/node/deactivate.rb
|
55
54
|
- lib/puppet/face/node/status.rb
|
56
|
-
- lib/puppet/
|
55
|
+
- lib/puppet/functions/puppetdb_query.rb
|
57
56
|
- lib/puppet/indirector/catalog/puppetdb.rb
|
58
57
|
- lib/puppet/indirector/facts/puppetdb.rb
|
59
58
|
- lib/puppet/indirector/facts/puppetdb_apply.rb
|
@@ -61,6 +60,7 @@ files:
|
|
61
60
|
- lib/puppet/indirector/resource/puppetdb.rb
|
62
61
|
- lib/puppet/reports/puppetdb.rb
|
63
62
|
- lib/puppet/util/puppetdb.rb
|
63
|
+
- lib/puppet/util/puppetdb/atom.rb
|
64
64
|
- lib/puppet/util/puppetdb/char_encoding.rb
|
65
65
|
- lib/puppet/util/puppetdb/command.rb
|
66
66
|
- lib/puppet/util/puppetdb/command_names.rb
|
@@ -1,193 +0,0 @@
|
|
1
|
-
require 'puppet/util/puppetdb'
|
2
|
-
require 'puppet/face'
|
3
|
-
|
4
|
-
if Puppet::Util::Puppetdb.puppet3compat?
|
5
|
-
require 'tmpdir'
|
6
|
-
|
7
|
-
Puppet::Face.define(:storeconfigs, '0.0.1') do
|
8
|
-
copyright "Puppet Labs", 2011
|
9
|
-
license "Apache 2 license"
|
10
|
-
|
11
|
-
summary "Interact with the storeconfigs database"
|
12
|
-
description <<-DESC
|
13
|
-
This subcommand interacts with the ActiveRecord storeconfigs database, and
|
14
|
-
can be used to export a dump of that data which is suitable for import by
|
15
|
-
PuppetDB.
|
16
|
-
DESC
|
17
|
-
|
18
|
-
action :export do
|
19
|
-
summary "Export the storeconfigs database"
|
20
|
-
description <<-DESC
|
21
|
-
Generate a dump of all catalogs from the storeconfigs database, as a
|
22
|
-
tarball which can be imported by PuppetDB. Only exported resources are
|
23
|
-
included; non-exported resources, edges, facts, or other data are
|
24
|
-
omitted. Returns the location of the output.
|
25
|
-
DESC
|
26
|
-
|
27
|
-
when_invoked do |options|
|
28
|
-
|
29
|
-
require 'puppet/rails'
|
30
|
-
|
31
|
-
tmpdir = Dir.mktmpdir
|
32
|
-
workdir = File.join(tmpdir, 'puppetdb-bak')
|
33
|
-
Dir.mkdir(workdir)
|
34
|
-
|
35
|
-
begin
|
36
|
-
Puppet::Rails.connect
|
37
|
-
|
38
|
-
timestamp = Time.now
|
39
|
-
|
40
|
-
# Fetch all nodes, including exported resources and their params
|
41
|
-
nodes = Puppet::Rails::Host.all(:include => {:resources => [:param_values, :puppet_tags]},
|
42
|
-
:conditions => {:resources => {:exported => true}})
|
43
|
-
|
44
|
-
catalogs = nodes.map { |node| node_to_catalog_hash(node, timestamp.iso8601(5)) }
|
45
|
-
|
46
|
-
catalog_dir = File.join(workdir, 'catalogs')
|
47
|
-
FileUtils.mkdir(catalog_dir)
|
48
|
-
|
49
|
-
catalogs.each do |catalog|
|
50
|
-
filename = File.join(catalog_dir, "#{catalog[:certname]}.json")
|
51
|
-
|
52
|
-
File.open(filename, 'w') do |file|
|
53
|
-
file.puts catalog.to_json
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
node_names = nodes.map(&:name).sort
|
58
|
-
|
59
|
-
File.open(File.join(workdir, 'export-metadata.json'), 'w') do |file|
|
60
|
-
metadata = {
|
61
|
-
'timestamp' => timestamp,
|
62
|
-
'command_versions' => {
|
63
|
-
'replace_catalog' => 6,
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
|
-
file.puts metadata.to_json
|
68
|
-
end
|
69
|
-
|
70
|
-
tarfile = destination_file(timestamp)
|
71
|
-
|
72
|
-
if tar = Puppet::Util.which('tar')
|
73
|
-
execute("cd #{tmpdir} && #{tar} -cf #{tarfile} puppetdb-bak")
|
74
|
-
|
75
|
-
FileUtils.rm_rf(workdir)
|
76
|
-
|
77
|
-
if gzip = Puppet::Util.which('gzip')
|
78
|
-
execute("#{gzip} #{tarfile}")
|
79
|
-
"#{tarfile}.gz"
|
80
|
-
else
|
81
|
-
Puppet.warning "Can't find the `gzip` command to compress the tarball; output will not be compressed"
|
82
|
-
tarfile
|
83
|
-
end
|
84
|
-
else
|
85
|
-
Puppet.warning "Can't find the `tar` command to produce a tarball; output will remain in the temporary working directory"
|
86
|
-
workdir
|
87
|
-
end
|
88
|
-
rescue => e
|
89
|
-
# Clean up if something goes wrong. We don't want to ensure this,
|
90
|
-
# because we want the directory to stick around in the case where they
|
91
|
-
# don't have tar.
|
92
|
-
FileUtils.rm_rf(workdir)
|
93
|
-
raise
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
when_rendering :console do |filename|
|
98
|
-
"Exported storeconfigs data to #{filename}"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Returns the location to leave the output. This is really only here for testing. :/
|
103
|
-
def destination_file(timestamp)
|
104
|
-
File.expand_path("storeconfigs-#{timestamp.strftime('%Y%m%d%H%M%S')}.tar")
|
105
|
-
end
|
106
|
-
|
107
|
-
# Execute a command using Puppet's execution static method.
|
108
|
-
#
|
109
|
-
# @param command [Array<String>, String] the command to execute. If it is
|
110
|
-
# an Array the first element should be the executable and the rest of the
|
111
|
-
# elements should be the individual arguments to that executable.
|
112
|
-
# @return [Puppet::Util::Execution::ProcessOutput] output as specified by options
|
113
|
-
# @raise [Puppet::ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is
|
114
|
-
# `true`.
|
115
|
-
def execute(command)
|
116
|
-
Puppet::Util::Execution.execute(command)
|
117
|
-
end
|
118
|
-
|
119
|
-
def node_to_catalog_hash(node, timestamp)
|
120
|
-
resources = node.resources.map { |resource| resource_to_hash(resource) }
|
121
|
-
edges = node.resources.map { |resource| resource_to_edge_hash(resource) }
|
122
|
-
|
123
|
-
{
|
124
|
-
:environment => "production",
|
125
|
-
:metadata => {
|
126
|
-
:api_version => 1,
|
127
|
-
},
|
128
|
-
:certname => node.name,
|
129
|
-
:version => node.last_compile || Time.now,
|
130
|
-
:edges => edges,
|
131
|
-
:resources => resources + [stage_main_hash],
|
132
|
-
:timestamp => timestamp,
|
133
|
-
:producer_timestamp => timestamp,
|
134
|
-
}
|
135
|
-
end
|
136
|
-
|
137
|
-
def resource_to_hash(resource)
|
138
|
-
parameters = resource.param_values.inject({}) do |params,param_value|
|
139
|
-
if params.has_key?(param_value.param_name.name)
|
140
|
-
value = [params[param_value.param_name.name],param_value.value].flatten
|
141
|
-
else
|
142
|
-
value = param_value.value
|
143
|
-
end
|
144
|
-
params.merge(param_value.param_name.name => value)
|
145
|
-
end
|
146
|
-
|
147
|
-
tags = resource.puppet_tags.map(&:name).uniq.sort
|
148
|
-
|
149
|
-
hash = {
|
150
|
-
:type => resource.restype,
|
151
|
-
:title => resource.title,
|
152
|
-
:exported => true,
|
153
|
-
:parameters => parameters,
|
154
|
-
:tags => tags,
|
155
|
-
}
|
156
|
-
|
157
|
-
hash[:file] = resource.file if resource.file
|
158
|
-
hash[:line] = resource.line if resource.line
|
159
|
-
|
160
|
-
hash
|
161
|
-
end
|
162
|
-
|
163
|
-
# The catalog *must* have edges, so everything is contained by Stage[main]!
|
164
|
-
def resource_to_edge_hash(resource)
|
165
|
-
{
|
166
|
-
'source' => {'type' => 'Stage', 'title' => 'main'},
|
167
|
-
'target' => {'type' => resource.restype, 'title' => resource.title},
|
168
|
-
'relationship' => 'contains',
|
169
|
-
}
|
170
|
-
end
|
171
|
-
|
172
|
-
def stage_main_hash
|
173
|
-
{
|
174
|
-
:type => 'Stage',
|
175
|
-
:title => 'main',
|
176
|
-
:exported => false,
|
177
|
-
:parameters => {},
|
178
|
-
:tags => ['stage', 'main'],
|
179
|
-
}
|
180
|
-
end
|
181
|
-
end
|
182
|
-
else
|
183
|
-
Puppet::Face.define(:storeconfigs, '0.0.1') do
|
184
|
-
copyright "Puppet Labs", 2011
|
185
|
-
license "Apache 2 license"
|
186
|
-
|
187
|
-
summary "storeconfigs is not supported on Puppet 4.0.0+"
|
188
|
-
description <<-DESC
|
189
|
-
Users needing this feature should migrate using Puppet 3.7.2 or a more recent
|
190
|
-
3.7 release.
|
191
|
-
DESC
|
192
|
-
end
|
193
|
-
end
|