quixoten-puppetdb-terminus 2.3.8 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d309a703f223e7e3b3617452a21b6fd2dcd5bd38
4
- data.tar.gz: b6b55228059a0551498171277d36316678b13fb8
3
+ metadata.gz: fd5513b1969cbbc8de118e435e8882b9d3b6f9c1
4
+ data.tar.gz: 016c79651a57659cbbc308dd0d4df9fd309f63eb
5
5
  SHA512:
6
- metadata.gz: 056e58b8250c55111b0422f501e77efbeb979f9238decd035d58ac7ad41b9bd82d88e3b322ba627b34aab47abd394aba2fe09fc8ec8d88a60ae5baa39100a62b
7
- data.tar.gz: 5216061084c04bb2f9beacd6d89bea1e98a13725f64935535198f82563a4924e9fd667067563fab7900dc5104549e56bd2d6f4dd2ab82c884dad27fc8b3255bd
6
+ metadata.gz: 38044e3513401f5b9a6361329d3e0856a2dea01e34bfb9965284befeed31f37dd32200ad7d7a82a0530c9d376fbc1139524dd8a8509913a266d9749cffcd3fce
7
+ data.tar.gz: 0b9505971f2583012847afd93d92f766b2221e3218d7724192d18a937d1bc2a36ad33f39cbc071f7f5a8760d81f6b5dfa539c385e9de94c9753ab47a04b875e7
@@ -17,14 +17,11 @@ Puppet::Face.define(:node, '0.0.1') do
17
17
  opts = args.pop
18
18
  raise ArgumentError, "Please provide at least one node" if args.empty?
19
19
 
20
- server = Puppet::Util::Puppetdb.server
21
- port = Puppet::Util::Puppetdb.port
22
-
23
- http = Puppet::Network::HttpPool.http_instance(server, port)
24
-
25
20
  args.map do |node|
26
21
  begin
27
- response = http.get(Puppet::Util::Puppetdb.url_path("/v3/nodes/#{CGI.escape(node)}"), headers)
22
+ response = Puppet::Util::Puppetdb::Http.action("/pdb/query/v4/nodes/#{CGI.escape(node)}") do |http_instance, path|
23
+ http_instance.get(path, headers)
24
+ end
28
25
  if response.is_a? Net::HTTPSuccess
29
26
  result = JSON.parse(response.body)
30
27
  elsif response.is_a? Net::HTTPNotFound
@@ -51,6 +48,8 @@ Puppet::Face.define(:node, '0.0.1') do
51
48
 
52
49
  if status['deactivated']
53
50
  lines << "Deactivated at #{status['deactivated']}"
51
+ elsif status['expired']
52
+ lines << "Expired at #{status['expired']}"
54
53
  else
55
54
  lines << "Currently active"
56
55
  end
@@ -35,36 +35,36 @@ if Puppet::Util::Puppetdb.puppet3compat?
35
35
  begin
36
36
  Puppet::Rails.connect
37
37
 
38
+ timestamp = Time.now
39
+
38
40
  # Fetch all nodes, including exported resources and their params
39
41
  nodes = Puppet::Rails::Host.all(:include => {:resources => [:param_values, :puppet_tags]},
40
42
  :conditions => {:resources => {:exported => true}})
41
43
 
42
- catalogs = nodes.map { |node| node_to_catalog_hash(node) }
44
+ catalogs = nodes.map { |node| node_to_catalog_hash(node, timestamp.iso8601(5)) }
43
45
 
44
46
  catalog_dir = File.join(workdir, 'catalogs')
45
47
  FileUtils.mkdir(catalog_dir)
46
48
 
47
49
  catalogs.each do |catalog|
48
- filename = File.join(catalog_dir, "#{catalog[:data][:name]}.json")
50
+ filename = File.join(catalog_dir, "#{catalog[:certname]}.json")
49
51
 
50
52
  File.open(filename, 'w') do |file|
51
- file.puts catalog.to_pson
53
+ file.puts catalog.to_json
52
54
  end
53
55
  end
54
56
 
55
57
  node_names = nodes.map(&:name).sort
56
58
 
57
- timestamp = Time.now
58
-
59
59
  File.open(File.join(workdir, 'export-metadata.json'), 'w') do |file|
60
60
  metadata = {
61
61
  'timestamp' => timestamp,
62
- 'command-versions' => {
63
- 'replace-catalog' => 2,
62
+ 'command_versions' => {
63
+ 'replace_catalog' => 6,
64
64
  }
65
65
  }
66
66
 
67
- file.puts metadata.to_pson
67
+ file.puts metadata.to_json
68
68
  end
69
69
 
70
70
  tarfile = destination_file(timestamp)
@@ -116,20 +116,21 @@ if Puppet::Util::Puppetdb.puppet3compat?
116
116
  Puppet::Util::Execution.execute(command)
117
117
  end
118
118
 
119
- def node_to_catalog_hash(node)
119
+ def node_to_catalog_hash(node, timestamp)
120
120
  resources = node.resources.map { |resource| resource_to_hash(resource) }
121
121
  edges = node.resources.map { |resource| resource_to_edge_hash(resource) }
122
122
 
123
123
  {
124
+ :environment => "production",
124
125
  :metadata => {
125
126
  :api_version => 1,
126
127
  },
127
- :data => {
128
- :name => node.name,
129
- :version => node.last_compile || Time.now,
130
- :edges => edges,
131
- :resources => resources + [stage_main_hash],
132
- },
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,
133
134
  }
134
135
  end
135
136
 
@@ -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, 5)
13
+ submit_command(request.key, catalog, CommandReplaceCatalog, 6)
14
14
  end
15
15
  end
16
16
 
@@ -22,11 +22,16 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
22
22
  def extract_extra_request_data(request)
23
23
  {
24
24
  :transaction_uuid => request.options[:transaction_uuid],
25
- :environment => request.environment,
26
- :producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601,
25
+ :environment => request.environment.to_s,
26
+ :producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601(5),
27
27
  }
28
28
  end
29
29
 
30
+ def hashify_tags(hash)
31
+ hash["resources"] = hash["resources"].map { |resource| resource["tags"] = resource["tags"].to_data_hash; resource }
32
+ hash
33
+ end
34
+
30
35
  def munge_catalog(catalog, extra_request_data = {})
31
36
  profile("Munge catalog", [:puppetdb, :catalog, :munge]) do
32
37
  data = profile("Convert catalog to JSON data hash", [:puppetdb, :catalog, :convert_to_hash]) do
@@ -37,6 +42,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
37
42
  add_namevar_aliases(data, catalog)
38
43
  stringify_titles(data)
39
44
  stringify_version(data)
45
+ hashify_tags(data)
40
46
  sort_unordered_metaparams(data)
41
47
  munge_edges(data)
42
48
  synthesize_edges(data, catalog)
@@ -44,6 +50,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
44
50
  add_transaction_uuid(data, extra_request_data[:transaction_uuid])
45
51
  add_environment(data, extra_request_data[:environment])
46
52
  add_producer_timestamp(data, extra_request_data[:producer_timestamp])
53
+ change_name_to_certname(data)
47
54
 
48
55
  data
49
56
  end
@@ -60,6 +67,17 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
60
67
  # fundamentally unordered
61
68
  UnorderedMetaparams = [:alias, :audit, :before, :check, :notify, :require, :subscribe, :tag]
62
69
 
70
+ # Change the name field to certname
71
+ #
72
+ # @param hash [Hash] original data hash
73
+ # @return [Hash] returns original hash with 'name' changed to 'certname'
74
+ # @api private
75
+ def change_name_to_certname(hash)
76
+ hash['certname'] = hash.delete('name')
77
+
78
+ hash
79
+ end
80
+
63
81
  # Include environment in hash, returning the complete hash.
64
82
  #
65
83
  # @param hash [Hash] original data hash
@@ -79,7 +97,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
79
97
  # @return [Hash] returns original hash augmented with producer_timestamp
80
98
  # @api private
81
99
  def add_producer_timestamp(hash, producer_timestamp)
82
- hash['producer-timestamp'] = producer_timestamp
100
+ hash['producer_timestamp'] = producer_timestamp
83
101
 
84
102
  hash
85
103
  end
@@ -91,7 +109,7 @@ class Puppet::Resource::Catalog::Puppetdb < Puppet::Indirector::REST
91
109
  # @return [Hash] returns original hash augmented with transaction_uuid
92
110
  # @api private
93
111
  def add_transaction_uuid(hash, transaction_uuid)
94
- hash['transaction-uuid'] = transaction_uuid
112
+ hash['transaction_uuid'] = transaction_uuid
95
113
 
96
114
  hash
97
115
  end
@@ -16,6 +16,14 @@ 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
+
19
27
  def save(request)
20
28
  profile("facts#save", [:puppetdb, :facts, :save, request.key]) do
21
29
  payload = profile("Encode facts command submission payload",
@@ -27,27 +35,28 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
27
35
  facts.values[:trusted] = get_trusted_info(request.node)
28
36
  end
29
37
  {
30
- "name" => facts.name,
38
+ "certname" => facts.name,
31
39
  "values" => facts.values,
32
40
  # PDB-453: we call to_s to avoid a 'stack level too deep' error
33
41
  # when we attempt to use ActiveSupport 2.3.16 on RHEL 5 with
34
42
  # legacy storeconfigs.
35
43
  "environment" => request.options[:environment] || request.environment.to_s,
36
- "producer-timestamp" => request.options[:producer_timestamp] || Time.now.iso8601,
44
+ "producer_timestamp" => request.options[:producer_timestamp] || Time.now.iso8601(5),
37
45
  }
38
46
  end
39
47
 
40
- submit_command(request.key, payload, CommandReplaceFacts, 3)
48
+ submit_command(request.key, payload, CommandReplaceFacts, 4)
41
49
  end
42
50
  end
43
51
 
44
52
  def find(request)
45
53
  profile("facts#find", [:puppetdb, :facts, :find, request.key]) do
46
54
  begin
47
- url = Puppet::Util::Puppetdb.url_path("/v3/nodes/#{CGI.escape(request.key)}/facts")
48
- response = profile("Query for nodes facts: #{url}",
49
- [:puppetdb, :facts, :find, :query_nodes, request.key]) do
50
- http_get(request, url, headers)
55
+ response = Http.action("/pdb/query/v4/nodes/#{CGI.escape(request.key)}/facts") do |http_instance, path|
56
+ profile("Query for nodes facts: #{URI.unescape(path)}",
57
+ [:puppetdb, :facts, :find, :query_nodes, request.key]) do
58
+ http_instance.get(path, headers)
59
+ end
51
60
  end
52
61
  log_x_deprecation_header(response)
53
62
 
@@ -55,24 +64,20 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
55
64
  profile("Parse fact query response (size: #{response.body.size})",
56
65
  [:puppetdb, :facts, :find, :parse_response, request.key]) do
57
66
  result = JSON.parse(response.body)
58
- # Note: the Inventory Service API appears to expect us to return nil here
59
- # if the node isn't found. However, PuppetDB returns an empty array in
60
- # this case; for now we will just look for that condition and assume that
61
- # it means that the node wasn't found, so we will return nil. In the
62
- # future we may want to improve the logic such that we can distinguish
63
- # between the "node not found" and the "no facts for this node" cases.
64
- if result.empty?
65
- return nil
66
- end
67
+
67
68
  facts = result.inject({}) do |a,h|
68
69
  a.merge(h['name'] => h['value'])
69
70
  end
71
+
70
72
  Puppet::Node::Facts.new(request.key, facts)
71
73
  end
72
74
  else
73
75
  # Newline characters cause an HTTP error, so strip them
74
76
  raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
75
77
  end
78
+ rescue NotFoundError => e
79
+ # This is what the inventory service expects when there is no data
80
+ return nil
76
81
  rescue => e
77
82
  raise Puppet::Error, "Failed to find facts from PuppetDB at #{self.class.server}:#{self.class.port}: #{e}"
78
83
  end
@@ -115,10 +120,11 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
115
120
  query_param = CGI.escape(query.to_json)
116
121
 
117
122
  begin
118
- url = Puppet::Util::Puppetdb.url_path("/v3/nodes?query=#{query_param}")
119
- response = profile("Fact query request: #{URI.unescape(url)}",
120
- [:puppetdb, :facts, :search, :query_request, request.key]) do
121
- http_get(request, url, headers)
123
+ response = Http.action("/pdb/query/v4/nodes?query=#{query_param}") do |http_instance, path|
124
+ profile("Fact query request: #{URI.unescape(path)}",
125
+ [:puppetdb, :facts, :search, :query_request, request.key]) do
126
+ http_instance.get(path, headers)
127
+ end
122
128
  end
123
129
  log_x_deprecation_header(response)
124
130
 
@@ -132,7 +138,7 @@ class Puppet::Node::Facts::Puppetdb < Puppet::Indirector::REST
132
138
  raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
133
139
  end
134
140
  rescue => e
135
- raise Puppet::Error, "Could not perform inventory search from PuppetDB at #{self.class.server}:#{self.class.port}: #{e}"
141
+ raise Puppet::Util::Puppetdb::InventorySearchError, e.message
136
142
  end
137
143
  end
138
144
  end
@@ -12,6 +12,8 @@ class Puppet::Node::Puppetdb < Puppet::Indirector::REST
12
12
  end
13
13
 
14
14
  def destroy(request)
15
- submit_command(request.key, request.key, CommandDeactivateNode, 2)
15
+ payload = { :certname => request.key,
16
+ :producer_timestamp => request.options[:producer_timestamp] || Time.now.iso8601(5) }
17
+ submit_command(request.key, payload, CommandDeactivateNode, 3)
16
18
  end
17
19
  end
@@ -27,11 +27,13 @@ class Puppet::Resource::Puppetdb < Puppet::Indirector::REST
27
27
  query_param = CGI.escape(expr.to_json)
28
28
 
29
29
  begin
30
- url = Puppet::Util::Puppetdb.url_path("/v3/resources?query=#{query_param}")
31
- response = profile("Resources query: #{URI.unescape(url)}",
32
- [:puppetdb, :resource, :search, :query, request.key]) do
33
- http_get(request, url, headers)
30
+ response = Http.action("/pdb/query/v4/resources?query=#{query_param}") do |http_instance, path|
31
+ profile("Resources query: #{URI.unescape(path)}",
32
+ [:puppetdb, :resource, :search, :query, request.key]) do
33
+ http_instance.get(path, headers)
34
+ end
34
35
  end
36
+
35
37
  log_x_deprecation_header(response)
36
38
 
37
39
  unless response.is_a? Net::HTTPSuccess
@@ -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, 4)
22
+ submit_command(self.host, report_to_hash, CommandStoreReport, 5)
23
23
  end
24
24
 
25
25
  nil
@@ -37,17 +37,24 @@ Puppet::Reports.register_report(:puppetdb) do
37
37
  raise Puppet::Error, "Environment is nil, unable to submit report. This may be due a bug with Puppet. Ensure you are running the latest revision, see PUP-2508 for more details."
38
38
  end
39
39
 
40
+ resource_events = build_events_list
41
+ is_noop = resource_events.any? {|rs| rs["status"] == 'noop'} && resource_events.none? {|rs| rs["status"] == 'failed'}
42
+
40
43
  {
41
44
  "certname" => host,
42
- "puppet-version" => puppet_version,
43
- "report-format" => report_format,
44
- "configuration-version" => configuration_version.to_s,
45
- "start-time" => Puppet::Util::Puppetdb.to_wire_time(time),
46
- "end-time" => Puppet::Util::Puppetdb.to_wire_time(time + run_duration),
47
- "resource-events" => build_events_list,
45
+ "puppet_version" => puppet_version,
46
+ "report_format" => report_format,
47
+ "configuration_version" => configuration_version.to_s,
48
+ "producer_timestamp" => Puppet::Util::Puppetdb.to_wire_time(Time.now),
49
+ "start_time" => Puppet::Util::Puppetdb.to_wire_time(time),
50
+ "end_time" => Puppet::Util::Puppetdb.to_wire_time(time + run_duration),
51
+ "resource_events" => resource_events,
48
52
  "environment" => environment,
49
- "transaction-uuid" => transaction_uuid,
53
+ "transaction_uuid" => transaction_uuid,
50
54
  "status" => status,
55
+ "noop" => is_noop,
56
+ "logs" => build_logs_list,
57
+ "metrics" => build_metrics_list,
51
58
  }
52
59
  end
53
60
  end
@@ -69,6 +76,40 @@ Puppet::Reports.register_report(:puppetdb) do
69
76
  end
70
77
  end
71
78
 
79
+ # @return Array[Hash]
80
+ # @api private
81
+ def build_logs_list
82
+ profile("Build logs list (count: #{logs.count})",
83
+ [:puppetdb, :logs_list, :build]) do
84
+ logs.map do |log|
85
+ {
86
+ 'file' => log.file,
87
+ 'line' => log.line,
88
+ 'level' => log.level,
89
+ 'message' => log.message,
90
+ 'source' => log.source,
91
+ 'tags' => [*log.tags],
92
+ 'time' => Puppet::Util::Puppetdb.to_wire_time(log.time),
93
+ }
94
+ end
95
+ end
96
+ end
97
+
98
+ # @return Array[Hash}
99
+ # @api private
100
+ def build_metrics_list
101
+ profile("Build metrics list (count: #{metrics.count})",
102
+ [:puppetdb, :metrics_list, :build]) do
103
+ metrics_list = []
104
+ metrics.each do |name, data|
105
+ metric_hashes = data.values.map {|x| {"category" => data.name, "name" => x.first, "value" => x.last}}
106
+ metrics_list.concat(metric_hashes)
107
+ end
108
+ metrics_list
109
+ end
110
+ end
111
+
112
+
72
113
  # @return Number
73
114
  # @api private
74
115
  def run_duration
@@ -97,15 +138,15 @@ Puppet::Reports.register_report(:puppetdb) do
97
138
  {
98
139
  "status" => event.status,
99
140
  "timestamp" => Puppet::Util::Puppetdb.to_wire_time(event.time),
100
- "resource-type" => resource_status.resource_type,
101
- "resource-title" => resource_status.title.to_s,
141
+ "resource_type" => resource_status.resource_type,
142
+ "resource_title" => resource_status.title.to_s,
102
143
  "property" => event.property,
103
- "new-value" => event.desired_value,
104
- "old-value" => event.previous_value,
144
+ "new_value" => event.desired_value,
145
+ "old_value" => event.previous_value,
105
146
  "message" => event.message,
106
147
  "file" => resource_status.file,
107
148
  "line" => resource_status.line,
108
- "containment-path" => resource_status.containment_path,
149
+ "containment_path" => resource_status.containment_path,
109
150
  }
110
151
  end
111
152
 
@@ -118,15 +159,15 @@ Puppet::Reports.register_report(:puppetdb) do
118
159
  {
119
160
  "status" => event_status,
120
161
  "timestamp" => Puppet::Util::Puppetdb.to_wire_time(resource_status.time),
121
- "resource-type" => resource_status.resource_type,
122
- "resource-title" => resource_status.title.to_s,
162
+ "resource_type" => resource_status.resource_type,
163
+ "resource_title" => resource_status.title.to_s,
123
164
  "property" => nil,
124
- "new-value" => nil,
125
- "old-value" => nil,
165
+ "new_value" => nil,
166
+ "old_value" => nil,
126
167
  "message" => nil,
127
168
  "file" => resource_status.file,
128
169
  "line" => resource_status.line,
129
- "containment-path" => resource_status.containment_path,
170
+ "containment_path" => resource_status.containment_path,
130
171
  }
131
172
  end
132
173
 
@@ -10,21 +10,19 @@ require 'fileutils'
10
10
 
11
11
  module Puppet::Util::Puppetdb
12
12
 
13
- def self.server
14
- config.server
13
+ class CommandSubmissionError < Puppet::Error
14
+ def initialize(msg, context)
15
+ super(msg)
16
+ @context = context
17
+ end
15
18
  end
16
19
 
17
- def self.port
18
- config.port
20
+ class InventorySearchError < Puppet::Error
19
21
  end
20
-
21
- def self.url_path(path)
22
- unless path.start_with?("/")
23
- path = "/" + path
24
- end
25
- config.url_prefix + path
22
+ class SoftWriteFailError < Puppet::Error
26
23
  end
27
24
 
25
+
28
26
  def self.config
29
27
  @config ||= Puppet::Util::Puppetdb::Config.load
30
28
  @config
@@ -34,23 +32,6 @@ module Puppet::Util::Puppetdb
34
32
  defined?(Puppet::Parser::AST::HashOrArrayAccess)
35
33
  end
36
34
 
37
- # This magical stuff is needed so that the indirector termini will make requests to
38
- # the correct host/port, because this module gets mixed in to our indirector
39
- # termini.
40
- module ClassMethods
41
- def server
42
- Puppet::Util::Puppetdb.server
43
- end
44
-
45
- def port
46
- Puppet::Util::Puppetdb.port
47
- end
48
- end
49
-
50
- def self.included(child)
51
- child.extend ClassMethods
52
- end
53
-
54
35
  # Given an instance of ruby's Time class, this method converts it to a String
55
36
  # that conforms to PuppetDB's wire format for representing a date/time.
56
37
  def self.to_wire_time(time)
@@ -74,7 +55,7 @@ module Puppet::Util::Puppetdb
74
55
 
75
56
  # Submit a command to PuppetDB.
76
57
  #
77
- # @param certname [String] hostname name of puppetdb instance
58
+ # @param certname [String] The certname this command operates on
78
59
  # @param payload [String] payload
79
60
  # @param command_name [String] name of command
80
61
  # @param version [Number] version number of command
@@ -26,8 +26,8 @@ module Puppet::Util::Puppetdb
26
26
  end
27
27
 
28
28
  def is_event_blacklisted?(event)
29
- @events.fetch(event["resource-type"], {}).
30
- fetch(event["resource-title"], {}).
29
+ @events.fetch(event["resource_type"], {}).
30
+ fetch(event["resource_title"], {}).
31
31
  fetch(event["status"], {}).
32
32
  fetch(event["property"], false)
33
33
  end
@@ -33,7 +33,7 @@ module CharEncoding
33
33
 
34
34
 
35
35
  def self.utf8_string(str)
36
- if RUBY_VERSION =~ /1.8/
36
+ if RUBY_VERSION =~ /^1.8/
37
37
  # Ruby 1.8 doesn't have String#encode and related methods, and there
38
38
  # appears to be a bug in iconv that will interpret some byte sequences
39
39
  # as 6-byte characters. Thus, we are forced to resort to some unfortunate
@@ -1,6 +1,6 @@
1
1
  require 'puppet/error'
2
- require 'puppet/network/http_pool'
3
2
  require 'puppet/util/puppetdb'
3
+ require 'puppet/util/puppetdb/http'
4
4
  require 'puppet/util/puppetdb/command_names'
5
5
  require 'puppet/util/puppetdb/char_encoding'
6
6
  require 'json'
@@ -9,7 +9,7 @@ class Puppet::Util::Puppetdb::Command
9
9
  include Puppet::Util::Puppetdb
10
10
  include Puppet::Util::Puppetdb::CommandNames
11
11
 
12
- CommandsUrl = "/v3/commands"
12
+ CommandsUrl = "/pdb/cmd/v1"
13
13
 
14
14
  # Public instance methods
15
15
 
@@ -28,7 +28,21 @@ class Puppet::Util::Puppetdb::Command
28
28
  @version = version
29
29
  @certname = certname
30
30
  profile("Format payload", [:puppetdb, :payload, :format]) do
31
- @payload = self.class.format_payload(command, version, payload)
31
+ @payload = Puppet::Util::Puppetdb::CharEncoding.utf8_string({
32
+ :command => command,
33
+ :version => version,
34
+ :payload => payload,
35
+ # We use to_pson still here, to work around the support for shifting
36
+ # binary data from a catalog to PuppetDB. Attempting to use to_json
37
+ # we get to_json conversion errors:
38
+ #
39
+ # Puppet source sequence is illegal/malformed utf-8
40
+ # json/ext/GeneratorMethods.java:71:in `to_json'
41
+ # puppet/util/puppetdb/command.rb:31:in `initialize'
42
+ #
43
+ # This is roughly inline with how Puppet serializes for catalogs as of
44
+ # Puppet 4.1.0. We need a better answer to non-utf8 data end-to-end.
45
+ }.to_pson)
32
46
  end
33
47
  end
34
48
 
@@ -44,9 +58,9 @@ class Puppet::Util::Puppetdb::Command
44
58
 
45
59
  begin
46
60
  response = profile("Submit command HTTP post", [:puppetdb, :command, :submit]) do
47
- http = Puppet::Network::HttpPool.http_instance(config.server, config.port)
48
- http.post(Puppet::Util::Puppetdb.url_path(CommandsUrl + "?checksum=#{checksum}"),
49
- payload, headers)
61
+ Http.action("#{CommandsUrl}?checksum=#{checksum}") do |http_instance, path|
62
+ http_instance.post(path, payload, headers)
63
+ end
50
64
  end
51
65
 
52
66
  Puppet::Util::Puppetdb.log_x_deprecation_header(response)
@@ -65,42 +79,30 @@ class Puppet::Util::Puppetdb::Command
65
79
  end
66
80
  end
67
81
  rescue => e
68
- error = "Failed to submit '#{command}' command#{for_whom} to PuppetDB at #{config.server}:#{config.port}: #{e}"
69
82
  if config.soft_write_failure
70
- Puppet.err error
83
+ Puppet.err e.message
71
84
  else
72
85
  # TODO: Use new exception handling methods from Puppet 3.0 here as soon as
73
86
  # we are able to do so (can't call them yet w/o breaking backwards
74
87
  # compatibility.) We should either be using a nested exception or calling
75
88
  # Puppet::Util::Logging#log_exception or #log_and_raise here; w/o them
76
89
  # we lose context as to where the original exception occurred.
77
- puts e, e.backtrace if Puppet[:trace]
78
- raise Puppet::Error, error
90
+ if Puppet[:trace]
91
+ Puppet.err(e)
92
+ Puppet.err(e.backtrace)
93
+ end
94
+ raise Puppet::Util::Puppetdb::CommandSubmissionError.new(e.message, {:command => command, :for_whom => for_whom})
79
95
  end
80
96
  end
81
97
  end
82
98
 
83
-
84
- # @!group Private class methods
85
-
86
- # @api private
87
- def self.format_payload(command, version, payload)
88
- message = {
89
- :command => command,
90
- :version => version,
91
- :payload => payload,
92
- }.to_pson
93
-
94
- Puppet::Util::Puppetdb::CharEncoding.utf8_string(message)
95
- end
96
-
97
99
  # @!group Private instance methods
98
100
 
99
101
  # @api private
100
102
  def headers
101
103
  {
102
104
  "Accept" => "application/json",
103
- "Content-Type" => "application/json",
105
+ "Content-Type" => "application/json; charset=utf-8",
104
106
  }
105
107
  end
106
108
 
@@ -1,41 +1,43 @@
1
1
  require 'puppet/util/puppetdb/command_names'
2
2
  require 'puppet/util/puppetdb/blacklist'
3
+ require 'uri'
3
4
 
4
5
  module Puppet::Util::Puppetdb
5
- class Config
6
- include Puppet::Util::Puppetdb::CommandNames
7
-
8
- # Public class methods
9
-
10
- def self.load(config_file = nil)
11
- defaults = {
12
- :server => "puppetdb",
13
- :port => 8081,
14
- :url_prefix => "",
15
- :soft_write_failure => false,
16
- :ignore_blacklisted_events => true,
17
- }
18
-
19
- config_file ||= File.join(Puppet[:confdir], "puppetdb.conf")
20
-
21
- if File.exists?(config_file)
22
- Puppet.debug("Configuring PuppetDB terminuses with config file #{config_file}")
23
- content = File.read(config_file)
24
- else
25
- Puppet.debug("No #{config_file} file found; falling back to default server and port #{defaults[:server]}:#{defaults[:port]}")
26
- content = ''
27
- end
6
+ class Config
7
+ include Puppet::Util::Puppetdb::CommandNames
8
+
9
+ # Public class methods
10
+
11
+ def self.load(config_file = nil)
12
+ defaults = {
13
+ :server => "puppetdb",
14
+ :port => 8081,
15
+ :soft_write_failure => false,
16
+ :ignore_blacklisted_events => true,
17
+ :server_url_timeout => 30
18
+ }
19
+
20
+ config_file ||= File.join(Puppet[:confdir], "puppetdb.conf")
21
+
22
+ if File.exists?(config_file)
23
+ Puppet.debug("Configuring PuppetDB terminuses with config file #{config_file}")
24
+ content = File.read(config_file)
25
+ else
26
+ Puppet.debug("No #{config_file} file found; falling back to default server and port #{defaults[:server]}:#{defaults[:port]}")
27
+ content = ''
28
+ end
28
29
 
29
- result = {}
30
- section = nil
31
- content.lines.each_with_index do |line,number|
32
- # Gotta track the line numbers properly
33
- number += 1
34
- case line
30
+ result = {}
31
+ section = nil
32
+ content.lines.each_with_index do |line,number|
33
+ # Gotta track the line numbers properly
34
+ number += 1
35
+ case line
35
36
  when /^\[(\w+)\s*\]$/
36
37
  section = $1
37
38
  result[section] ||= {}
38
- when /^\s*(\w+)\s*=\s*(\S+)\s*$/
39
+
40
+ when /^\s*(\w+)\s*=\s*(\S+|[\S+\s*\,\s*\S]+)\s*$/
39
41
  raise "Setting '#{line}' is illegal outside of section in PuppetDB config #{config_file}:#{number}" unless section
40
42
  result[section][$1] = $2
41
43
  when /^\s*[#;]/
@@ -44,86 +46,115 @@ class Config
44
46
  # Skip blank lines
45
47
  else
46
48
  raise "Unparseable line '#{line}' in PuppetDB config #{config_file}:#{number}"
49
+ end
47
50
  end
48
- end
49
51
 
52
+ main_section = result['main'] || {}
53
+ # symbolize the keys
54
+ main_section = main_section.inject({}) {|h, (k,v)| h[k.to_sym] = v ; h}
55
+ # merge with defaults but filter out anything except the legal settings
56
+ config_hash = defaults.merge(main_section).reject do |k, v|
57
+ !([:server,
58
+ :port,
59
+ :ignore_blacklisted_events,
60
+ :soft_write_failure,
61
+ :server_urls,
62
+ :server_url_timeout].include?(k))
63
+ end
50
64
 
51
- main_section = result['main'] || {}
52
- # symbolize the keys
53
- main_section = main_section.inject({}) {|h, (k,v)| h[k.to_sym] = v ; h}
54
- # merge with defaults but filter out anything except the legal settings
55
- config_hash = defaults.merge(main_section).reject do |k, v|
56
- !([:server, :port, :url_prefix, :ignore_blacklisted_events, :soft_write_failure].include?(k))
65
+ if config_hash[:server_urls]
66
+ uses_server_urls = true
67
+ config_hash[:server_urls] = config_hash[:server_urls].split(",").map {|s| s.strip}
68
+ else
69
+ uses_server_urls = false
70
+ config_hash[:server_urls] = ["https://#{config_hash[:server].strip}:#{config_hash[:port].to_s}"]
71
+ end
72
+ config_hash[:server_urls] = convert_and_validate_urls(config_hash[:server_urls])
73
+
74
+ config_hash[:server_url_timeout] = config_hash[:server_url_timeout].to_i
75
+ config_hash[:ignore_blacklisted_events] = Puppet::Util::Puppetdb.to_bool(config_hash[:ignore_blacklisted_events])
76
+ config_hash[:soft_write_failure] = Puppet::Util::Puppetdb.to_bool(config_hash[:soft_write_failure])
77
+
78
+ self.new(config_hash, uses_server_urls)
79
+ rescue => detail
80
+ Puppet.warning "Could not configure PuppetDB terminuses: #{detail}"
81
+ Puppet.warning detail.backtrace if Puppet[:trace]
82
+ raise
57
83
  end
58
84
 
59
- config_hash[:server] = config_hash[:server].strip
60
- config_hash[:port] = config_hash[:port].to_i
61
- config_hash[:url_prefix] = normalize_url_prefix(config_hash[:url_prefix].strip)
62
- config_hash[:ignore_blacklisted_events] =
63
- Puppet::Util::Puppetdb.to_bool(config_hash[:ignore_blacklisted_events])
64
- config_hash[:soft_write_failure] =
65
- Puppet::Util::Puppetdb.to_bool(config_hash[:soft_write_failure])
66
-
67
- self.new(config_hash)
68
- rescue => detail
69
- puts detail.backtrace if Puppet[:trace]
70
- Puppet.warning "Could not configure PuppetDB terminuses: #{detail}"
71
- raise
72
- end
73
-
74
- # @!group Public instance methods
85
+ # @!group Public instance methods
75
86
 
76
- def initialize(config_hash = {})
77
- @config = config_hash
78
- initialize_blacklisted_events()
79
- end
80
-
81
- def server
82
- config[:server]
83
- end
87
+ def initialize(config_hash = {}, uses_server_urls=nil)
88
+ @config = config_hash
89
+ initialize_blacklisted_events()
90
+ if !uses_server_urls
91
+ Puppet.warning("Specification of server and port in puppetdb.conf is deprecated. Use the setting server_urls.")
92
+ end
93
+ # To provide accurate error messages to users about HTTP failures, we
94
+ # need to know whether they initially defined their config via the old
95
+ # server/port combo or the new server_urls. This boolean keeps track
96
+ # of how the user defined that config so that we can give them a
97
+ # better error message
98
+ @server_url_config = uses_server_urls
99
+ end
84
100
 
85
- def port
86
- config[:port]
87
- end
101
+ def server_url_config?
102
+ @server_url_config
103
+ end
88
104
 
89
- def url_prefix
90
- config[:url_prefix]
91
- end
105
+ def server_urls
106
+ config[:server_urls]
107
+ end
92
108
 
93
- def ignore_blacklisted_events?
94
- config[:ignore_blacklisted_events]
95
- end
109
+ def server_url_timeout
110
+ config[:server_url_timeout]
111
+ end
96
112
 
97
- def is_event_blacklisted?(event)
98
- @blacklist.is_event_blacklisted? event
99
- end
113
+ def ignore_blacklisted_events?
114
+ config[:ignore_blacklisted_events]
115
+ end
100
116
 
101
- def soft_write_failure
102
- config[:soft_write_failure]
103
- end
117
+ def is_event_blacklisted?(event)
118
+ @blacklist.is_event_blacklisted? event
119
+ end
104
120
 
105
- # @!group Private class methods
106
- def self.normalize_url_prefix(prefix)
107
- if prefix == ""
108
- prefix
109
- elsif prefix.start_with?("/")
110
- prefix
111
- else
112
- "/" + prefix
121
+ def soft_write_failure
122
+ config[:soft_write_failure]
113
123
  end
114
- end
115
124
 
116
- # @!group Private instance methods
125
+ # @!group Private instance methods
117
126
 
118
- # @!attribute [r] count
119
- # @api private
120
- attr_reader :config
127
+ # @!attribute [r] count
128
+ # @api private
129
+ attr_reader :config
121
130
 
122
- Blacklist = Puppet::Util::Puppetdb::Blacklist
131
+ Blacklist = Puppet::Util::Puppetdb::Blacklist
132
+
133
+ # @api private
134
+ def initialize_blacklisted_events(events = Blacklist::BlacklistedEvents)
135
+ @blacklist = Blacklist.new(events)
136
+ end
123
137
 
124
- # @api private
125
- def initialize_blacklisted_events(events = Blacklist::BlacklistedEvents)
126
- @blacklist = Blacklist.new(events)
138
+ def self.convert_and_validate_urls(uri_strings)
139
+ uri_strings.map do |uri_string|
140
+
141
+ begin
142
+ uri = URI(uri_string.strip)
143
+ rescue URI::InvalidURIError => e
144
+ raise URI::InvalidURIError.new, "Error parsing URL '#{uri_string}' in PuppetDB 'server_urls', error message was '#{e.message}'"
145
+ end
146
+
147
+ if uri.scheme != 'https'
148
+ raise "PuppetDB 'server_urls' must be https, found '#{uri_string}'"
149
+ end
150
+
151
+ if uri.path != '' && uri.path != '/'
152
+ raise "PuppetDB 'server_urls' cannot contain URL paths, found '#{uri_string}'"
153
+ end
154
+ uri.path = ''
155
+
156
+ uri
157
+ end
158
+ end
127
159
  end
128
160
  end
129
- end
@@ -0,0 +1,121 @@
1
+ require 'uri'
2
+ require 'puppet/network/http_pool'
3
+ require 'net/http'
4
+ require 'timeout'
5
+ require 'pp'
6
+
7
+ module Puppet::Util::Puppetdb
8
+ class Http
9
+
10
+ SERVER_URL_FAIL_MSG = "Failing over to the next PuppetDB url in the 'server_urls' list"
11
+
12
+ # Concat two url snippets, taking into account a trailing/leading slash to
13
+ # ensure a correct url is constructed
14
+ #
15
+ # @param snippet1 [String] first URL snippet
16
+ # @param snippet2 [String] second URL snippet
17
+ # @return [String] returns http response
18
+ # @api private
19
+ def self.concat_url_snippets(snippet1, snippet2)
20
+ if snippet1.end_with?('/') and snippet2.start_with?('/')
21
+ snippet1 + snippet2[1..-1]
22
+ elsif !snippet1.end_with?('/') and !snippet2.start_with?('/')
23
+ snippet1 + '/' + snippet2
24
+ else
25
+ snippet1 + snippet2
26
+ end
27
+ end
28
+
29
+ # Setup an http connection, provide a block that will do something with that http
30
+ # connection. The block should be a two argument block, accepting the connection (which
31
+ # you can call get or post on for example) and the properly constructed path, which
32
+ # will be the concatenated version of any url_prefix and the path passed in.
33
+ #
34
+ # @param path_suffix [String] path for the get/post of the http action
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)
38
+
39
+ response = nil
40
+ config = Puppet::Util::Puppetdb.config
41
+ server_url_config = config.server_url_config?
42
+
43
+ for url in Puppet::Util::Puppetdb.config.server_urls
44
+ begin
45
+ route = concat_url_snippets(url.request_uri, path_suffix)
46
+ http = Puppet::Network::HttpPool.http_instance(url.host, url.port)
47
+ request_timeout = config.server_url_timeout
48
+
49
+ response = timeout(request_timeout) do
50
+ http_callback.call(http, route)
51
+ end
52
+
53
+ if response.is_a? Net::HTTPServerError
54
+ 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}")
55
+ response = nil
56
+ elsif response.is_a? Net::HTTPNotFound
57
+ if response.body && response.body.chars.first == "{"
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
65
+ end
66
+ else
67
+ break
68
+ end
69
+ rescue Timeout::Error => e
70
+ Puppet.warning("Request to #{url.host} on #{url.port} at route #{route} timed out after #{request_timeout} seconds. #{SERVER_URL_FAIL_MSG if server_url_config}")
71
+
72
+ rescue SocketError, OpenSSL::SSL::SSLError, SystemCallError, Net::ProtocolError, IOError, Net::HTTPNotFound => e
73
+ Puppet.warning("Error connecting to #{url.host} on #{url.port} at route #{route}, error message received was '#{e.message}'. #{SERVER_URL_FAIL_MSG if server_url_config}")
74
+
75
+ rescue Puppet::Util::Puppetdb::InventorySearchError => e
76
+ Puppet.warning("Could not perform inventory search from PuppetDB at #{url.host}:#{url.port}: '#{e.message}' #{SERVER_URL_FAIL_MSG if server_url_config}")
77
+
78
+ rescue Puppet::Util::Puppetdb::CommandSubmissionError => e
79
+ error = "Failed to submit '#{e.context[:command]}' command for '#{e.context[:for_whom]}' to PuppetDB at #{url.host}:#{url.port}: '#{e.message}'."
80
+ if config.soft_write_failure
81
+ Puppet.err error
82
+ else
83
+ Puppet.warning(error + " #{SERVER_URL_FAIL_MSG if server_url_config}")
84
+ end
85
+ rescue Puppet::Util::Puppetdb::SoftWriteFailError => e
86
+ Puppet.warning("Failed to submit '#{e.context[:command]}' command for '#{e.context[:for_whom]}' to PuppetDB at #{url.host}:#{url.port}: '#{e.message}' #{SERVER_URL_FAIL_MSG if server_url_config}")
87
+ rescue Puppet::Error => e
88
+ if e.message =~ /did not match server certificate; expected one of/
89
+ Puppet.warning("Error connecting to #{url.host} on #{url.port} at route #{route}, error message received was '#{e.message}'. #{SERVER_URL_FAIL_MSG if server_url_config}")
90
+ else
91
+ raise
92
+ end
93
+ end
94
+ end
95
+
96
+ if response.nil? or response == :notfound
97
+ if server_url_config
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
112
+ end
113
+
114
+ response
115
+
116
+ end
117
+ end
118
+
119
+ class NotFoundError < Puppet::Error
120
+ end
121
+ end
@@ -1,6 +1,6 @@
1
1
  module PuppetDB
2
2
  module Terminus
3
- VERSION = "2.3.8"
3
+ VERSION = "3.0.0"
4
4
  UPSTREAM_VERSION = VERSION.split(".")[0..2].join(".")
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quixoten-puppetdb-terminus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.8
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Devin Christensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-01 00:00:00.000000000 Z
11
+ date: 2016-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,7 @@ files:
66
66
  - lib/puppet/util/puppetdb/command.rb
67
67
  - lib/puppet/util/puppetdb/command_names.rb
68
68
  - lib/puppet/util/puppetdb/config.rb
69
+ - lib/puppet/util/puppetdb/http.rb
69
70
  - lib/puppetdb-terminus.rb
70
71
  - lib/puppetdb/terminus.rb
71
72
  - lib/puppetdb/terminus/version.rb
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
92
  version: '0'
92
93
  requirements: []
93
94
  rubyforge_project:
94
- rubygems_version: 2.5.1
95
+ rubygems_version: 2.5.2
95
96
  signing_key:
96
97
  specification_version: 4
97
98
  summary: PuppetDB Terminus