quixoten-puppetdb-terminus 2.3.8 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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