quixoten-puppetdb-terminus 3.2.4 → 4.0.0
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 +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
|