marty 1.0.44 → 1.0.46

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: 817a8f32d8a912597e8941cb60889a6f9e3723bc
4
- data.tar.gz: cccaf421c0216156db7c106b69577f0e363c4c58
3
+ metadata.gz: 0437c9ffb81afc74c8bd834226b6855064e0d8bf
4
+ data.tar.gz: 27f841b78da83b42493bb74e5a6d3ee5944ee3b0
5
5
  SHA512:
6
- metadata.gz: 030c85527c4809aeb5a18634425c5dc5924534b7f8b3f44fc83189adccfc7724b87b97e06cdeac5c5004b1301c4f9745918bcb47c8004ac0b07492d6e2fcc2b3
7
- data.tar.gz: dcbabb68d76e981e927eda2e9c8e9b3a31a4896ef66561f7bcaa1d553fc31f4b8e5d056b084b7199982e242f67938964ec59366b645588c7e266a97d9ea724aa
6
+ metadata.gz: 1ab061544b74d83b417815f07a85e3e8ee106a9ee03da67b0e54974b749dfdcb3a57387bc1e60f55a62fefc994ec15e5c59228d4ebe58e11999da2a7d9e816fb
7
+ data.tar.gz: a5466cb70f5908c1c2a93cf96b938fa10815f8b2683d0502a1293a04f75b943796f9bc0518ca180d2fdfe70eb3efcfdb2fe70f9353bf2e913a1e8ac14c6cdd17
data/Gemfile CHANGED
@@ -11,6 +11,9 @@ gem 'mime-types', '< 3.0', platforms: :ruby_19
11
11
  gem 'rails', '~> 4.2.1'
12
12
  gem 'pg', '~> 0.18.4'
13
13
  gem 'sqlite3'
14
+ # for signing of aws ec2 requests
15
+ gem 'aws-sigv4', '~> 1.0', '>= 1.0.2'
16
+
14
17
 
15
18
  group :development, :test do
16
19
  gem 'pry-rails'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marty (1.0.44)
4
+ marty (1.0.46)
5
5
  axlsx (= 2.1.0pre)
6
6
  coderay
7
7
  delorean_lang (~> 0.1)
@@ -54,6 +54,7 @@ GEM
54
54
  addressable (2.5.2)
55
55
  public_suffix (>= 2.0.2, < 4.0)
56
56
  arel (6.0.4)
57
+ aws-sigv4 (1.0.2)
57
58
  axlsx (2.1.0.pre)
58
59
  htmlentities (~> 4.3.1)
59
60
  nokogiri (>= 1.4.1)
@@ -81,7 +82,7 @@ GEM
81
82
  delayed_job_active_record (4.1.2)
82
83
  activerecord (>= 3.0, < 5.2)
83
84
  delayed_job (>= 3.0, < 5)
84
- delorean_lang (0.3.30)
85
+ delorean_lang (0.3.32)
85
86
  activerecord (>= 3.2)
86
87
  treetop (~> 1.5)
87
88
  diff-lcs (1.3)
@@ -205,6 +206,7 @@ PLATFORMS
205
206
  ruby
206
207
 
207
208
  DEPENDENCIES
209
+ aws-sigv4 (~> 1.0, >= 1.0.2)
208
210
  capybara
209
211
  daemons (~> 1.1.9)
210
212
  database_cleaner
@@ -1,101 +1,353 @@
1
+ require 'erb'
1
2
  module Marty
2
3
  class DiagnosticController < ActionController::Base
3
- layout 'marty/diagnostic'
4
-
5
- def index
6
- if action_methods.include?(params[:testop].to_s)
7
- send(params[:testop])
8
- elsif params[:testop] == 'env'
9
- send('environment')
4
+ layout false
5
+ def op
6
+ begin
7
+ # inject request into Base class of all diagnostics
8
+ Base.request = request
9
+ params[:scope] = 'nodal' unless params[:scope]
10
+ diag = self.class.get_sub_class(params[:op])
11
+ @result = params[:scope] == 'local' ? diag.generate : diag.aggregate
12
+ rescue NameError
13
+ render file: 'public/400', formats: [:html], status: 400, layout: false
10
14
  else
11
- render file: 'public/404', status: 404, layout: false
15
+ respond_to do |format|
16
+ format.html {@result = diag.display(@result, params[:scope])}
17
+ format.json {render json: @result}
18
+ end
12
19
  end
13
20
  end
14
21
 
15
- def version
16
- diag_response git_details +
17
- [Diagnostic.new('Marty Version', true, VERSION)]
22
+ def self.get_sub_class klass
23
+ const_get(klass.downcase.camelize)
18
24
  end
19
25
 
20
- def environment
21
- rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
22
-
23
- details = [
24
- Diagnostic.new('Environment', true, Rails.env),
25
- Diagnostic.new('Rails Version', true, Rails.version),
26
- Diagnostic.new('Netzke Core Version', true, Netzke::Core::VERSION),
27
- Diagnostic.new('Netzke Basepack Version', true,
28
- Netzke::Basepack::VERSION),
29
- Diagnostic.new('Ruby Version', true, rbv),
30
- Diagnostic.new('RubyGems Version', true, Gem::VERSION),
31
- Diagnostic.new('Database Adapter', true,
32
- ActiveRecord::Base.connection.adapter_name)
33
- ]
34
- begin
35
- status = true
36
- result = ActiveRecord::Base.connection.execute('SELECT VERSION();')
37
- message = result[0]['version'] if result
38
- rescue => e
39
- status = false
40
- message = e.message
26
+ private
27
+ ############################################################################
28
+ #
29
+ # Diagnostics
30
+ #
31
+ ############################################################################
32
+ class Base
33
+ @@request = nil
34
+ @@read_only = Marty::Util.db_in_recovery?
35
+
36
+ def self.request= req
37
+ @@request = req
41
38
  end
42
- details << Diagnostic.new('Database Version', status, message)
43
39
 
44
- begin
45
- status, message = true, ActiveRecord::Migrator.current_version
46
- rescue => e
47
- status = false
48
- message = e.message
40
+ def self.request
41
+ @@request
42
+ end
43
+
44
+ def self.aggregate op_name=name.demodulize
45
+ get_nodal_diags(op_name)
46
+ end
47
+
48
+ def self.get_nodal_diags op_name, scope='local'
49
+ self.get_nodes.map do |n|
50
+ ssl = ENV['HTTPS'] == 'on'
51
+ uri = Addressable::URI.new(host: n, port: ssl ? 443 : @@request.port)
52
+ uri.query_values = {op: op_name.underscore,
53
+ scope: scope}
54
+ uri.scheme = ssl ? 'https' : 'http'
55
+ uri.path = '/marty/diag.json'
56
+ opts = {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}
57
+ {n => JSON.parse(open(uri, opts).readlines[0])}
58
+ end.sum
59
+ end
60
+
61
+ def self.errors data
62
+ data.keys.count{|n| is_failure?(data[n])}
63
+ end
64
+
65
+ def self.diff data
66
+ data.keys.map{|n| data[n]}.uniq.length != 1
67
+ end
68
+
69
+ def self.package message
70
+ {name.demodulize => message}
71
+ end
72
+
73
+ def self.is_failure? message
74
+ message.to_s.include?('Failure')
75
+ end
76
+
77
+ def self.error message
78
+ "Failure: (#{message})"
79
+ end
80
+
81
+ def self.display data, type='nodal'
82
+ data = {'local' => data} if type == 'local'
83
+ display = <<-ERB
84
+ <% inconsistent = diff(data) %>
85
+ <h3><%=name.demodulize%></h3>
86
+ <%='<h3 class="error">&#x26a0; Issues Detected</h3>' if
87
+ inconsistent%>
88
+ <div class="wrapper">
89
+ <% data.each do |node, result| %>
90
+ <table>
91
+ <% issues = ('error' if inconsistent) %>
92
+ <th class="<%=issues%>"><%=inconsistent ? node :
93
+ '<small>consistent</small>'%></th>
94
+ <th class="<%=issues%>"></th>
95
+ <% result.each do |name, value| %>
96
+ <tr class="<%=is_failure?(value) ? 'failed' :
97
+ 'passed' %>">
98
+ <td><%=name%></td>
99
+ <td class="overflow"><%=value%></td>
100
+ </tr>
101
+ <% end %>
102
+ </table>
103
+ <% break unless inconsistent %>
104
+ <% end %>
105
+ </div>
106
+ ERB
107
+ ERB.new(display.html_safe).result(binding)
108
+ end
109
+
110
+ def self.get_pg_connections
111
+ info = ActiveRecord::Base.connection.execute("SELECT datname,"\
112
+ "application_name,"\
113
+ "state,"\
114
+ "pid,"\
115
+ "client_addr "\
116
+ "FROM pg_stat_activity")
117
+ info.each_with_object({}) do |x, h|
118
+ h[x["datname"]] ||= []
119
+ h[x["datname"]] << {"name" => x["application_name"],
120
+ "address"=> x["client_addr"],
121
+ "state" => x["state"],
122
+ "pid" => x["pid"]}
123
+ end
124
+ end
125
+
126
+ def self.resolve_target_nodes target
127
+ db = ActiveRecord::Base.connection_config[:database]
128
+ db_conns = get_pg_connections
129
+ target_conns = db_conns[db].select{|x|
130
+ x['name'].include? target}
131
+ target_conns.map{|x| x['address']}.uniq.compact
132
+ end
133
+
134
+ def self.get_nodes
135
+ nodes = resolve_target_nodes("Passenger")
136
+ nodes.empty? ? ['127.0.0.1'] : nodes
137
+ end
138
+ end
139
+ ############################################################################
140
+ #
141
+ # Diagnostic Definitions
142
+ # Default: pulls from all nodes; force local with '&scope=local'
143
+ #
144
+ ############################################################################
145
+ class Version < Base
146
+ def self.generate
147
+ begin
148
+ message = `cd #{Rails.root.to_s}; git describe;`.strip
149
+ rescue
150
+ message = error("Failed accessing git")
151
+ end
152
+ {
153
+ 'Git' => message,
154
+ 'Marty' => Marty::VERSION,
155
+ 'Delorean' => Delorean::VERSION,
156
+ 'Mcfly' => Mcfly::VERSION
157
+ }
49
158
  end
50
- details << Diagnostic.new('Database Schema Version', status, message)
51
- diag_response details
52
159
  end
53
160
 
54
- private
55
- def diag_response details
56
- if @aggregate_diags
57
- @aggregated_details += details
58
- else
59
- @details = details
60
- respond_to do |format|
61
- format.html { render 'diagnostic' }
62
- format.json { render json: [{ error_count: error_count(details),
63
- diag_count: details.count }] + details }
161
+ class Database < Base
162
+ def self.db_server_name
163
+ ActiveRecord::Base.connection_config[:host] || 'undefined'
164
+ end
165
+
166
+ def self.db_adapter_name
167
+ ActiveRecord::Base.connection.adapter_name
168
+ end
169
+
170
+ def self.db_time
171
+ ActiveRecord::Base.connection.execute('SELECT NOW();')
172
+ end
173
+
174
+ def self.db_version
175
+ begin
176
+ message = ActiveRecord::Base.connection.
177
+ execute('SELECT VERSION();')[0]['version']
178
+ rescue => e
179
+ return error(message)
180
+ end
181
+ message
182
+ end
183
+
184
+ def self.db_schema
185
+ begin
186
+ message = ActiveRecord::Migrator.current_version
187
+ rescue => e
188
+ return error(e.message)
64
189
  end
190
+ message
65
191
  end
66
192
  end
67
193
 
68
- def aggregate_diags
69
- begin
70
- @aggregate_diags = true
71
- @aggregated_details = []
72
- yield
73
- ensure
74
- @aggregate_diags = false
75
- diag_response @aggregated_details
194
+ class Environment < Database
195
+ def self.generate
196
+ rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
197
+ infos = {'Environment' => Rails.env,
198
+ 'Rails' => Rails.version,
199
+ 'Netzke Core' => Netzke::Core::VERSION,
200
+ 'Netzke Basepack' => Netzke::Basepack::VERSION,
201
+ 'Ruby' => rbv,
202
+ 'RubyGems' => Gem::VERSION,
203
+ 'Database Adapter' => db_adapter_name,
204
+ 'Database Server' => db_server_name,
205
+ 'Database Version' => db_version,
206
+ 'Database Schema Version' => db_schema}
76
207
  end
77
208
  end
78
209
 
79
- def error_count details
80
- details.count { |detail| !detail.status }
210
+ class Nodes < Base
211
+ def self.generate
212
+ a_nodes = AwsInstanceInfo.is_aws? ? AwsInstanceInfo.new.nodes.sort : []
213
+ pg_nodes = get_nodes.sort
214
+ message = pg_nodes == a_nodes ? pg_nodes.join(', ') :
215
+ error("Postgres: [#{pg_nodes.join(', ')}]"\
216
+ " - AWS: [#{a_nodes.join(', ')}]")
217
+ {"PG/AWS" => message}
218
+ end
219
+
220
+ def self.aggregate
221
+ {'local' => generate}
222
+ end
81
223
  end
82
224
 
83
- def git_details app_name = Rails.application.class.parent.to_s
84
- [
85
- Diagnostic.new("#{app_name} Git Version", true,
86
- `git describe 2>&1`.strip),
87
- Diagnostic.new("#{app_name} Git Details", true,
88
- `git show --pretty=format:"sha: %h, %D" --no-patch 2>&1`.strip)
89
- ]
225
+ ############################################################################
226
+ #
227
+ # Reports
228
+ #
229
+ ############################################################################
230
+ class Report < Base
231
+ class << self
232
+ attr_accessor :diags
233
+ end
234
+
235
+ def diags
236
+ self.class.diags
237
+ end
238
+
239
+ self.diags = ['nodes', 'version', 'environment']
240
+
241
+ def self.get_diag_klass diag
242
+ controller = name.split(name.demodulize)[0].constantize
243
+ controller.const_get(diag.capitalize)
244
+ end
245
+
246
+ def self.generate
247
+ diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).generate}
248
+ end
249
+
250
+ def self.aggregate
251
+ diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).aggregate}
252
+ end
253
+
254
+ def self.display data, type
255
+ report = '<h3>' +
256
+ name.demodulize +
257
+ " #{'(' + type + ')' if type == 'local'}" +
258
+ '</h3>'
259
+ displays = diags.map{|d| get_diag_klass(d).display(data[d], type)}
260
+ ([report] + displays).sum
261
+ end
90
262
  end
91
263
 
92
- class Diagnostic < Struct.new(:name, :status, :description)
93
- def status_css
94
- status ? 'passed' : 'failed'
264
+ ############################################################################
265
+ #
266
+ # AWS Helper Class
267
+ #
268
+ ############################################################################
269
+ class AwsInstanceInfo
270
+ attr_accessor :id, :doc, :role, :creds, :version, :host, :tag, :nodes
271
+
272
+ # aws reserved host used to get instance meta-data
273
+ META_DATA_HOST = '169.254.169.254'
274
+
275
+ def self.is_aws?
276
+ uri = URI.parse("http://#{META_DATA_HOST}")
277
+ !(Net::HTTP.get(uri) rescue nil).nil?
278
+ end
279
+
280
+ def initialize
281
+ @id = get_instance_id
282
+ @doc = get_document
283
+ @role = get_role
284
+ @creds = get_credentials
285
+ @host = "ec2.#{@doc['region']}.amazonaws.com"
286
+ @version = '2016-11-15'
287
+ @tag = get_tag
288
+ @nodes = get_private_ips
289
+ end
290
+
291
+ private
292
+ def query_meta_data query
293
+ uri = URI.parse("http://#{META_DATA_HOST}/latest/meta-data/#{query}/")
294
+ Net::HTTP.get(uri)
295
+ end
296
+
297
+ def query_dynamic query
298
+ uri = URI.parse("http://#{META_DATA_HOST}/latest/dynamic/#{query}/")
299
+ Net::HTTP.get(uri)
300
+ end
301
+
302
+ def get_instance_id
303
+ query_meta_data('instance-id').to_s
304
+ end
305
+
306
+ def get_role
307
+ query_meta_data('iam/security-credentials').to_s
308
+ end
309
+
310
+ def get_credentials
311
+ JSON.parse(query_meta_data("iam/security-credentials/#{@role}"))
312
+ end
313
+
314
+ def get_document
315
+ JSON.parse(query_dynamic('instance-identity/document'))
316
+ end
317
+
318
+ def ec2_req action, params = {}
319
+ url = "https://#{@host}/?Action=#{action}&Version=#{@version}"
320
+ params.each{|a, v| url += "&#{a}=#{v}"}
321
+
322
+ sig = Aws::Sigv4::Signer.new(service: 'ec2',
323
+ region: @doc['region'],
324
+ access_key_id: @creds['AccessKeyId'],
325
+ secret_access_key: @creds['SecretAccessKey'],
326
+ session_token: @creds['Token'])
327
+ signed_url = sig.presign_url(http_method:'GET', url: url)
328
+
329
+ http = Net::HTTP.new(@host, 443)
330
+ http.use_ssl = true
331
+ Hash.from_xml(Net::HTTP.get(signed_url))["#{action}Response"]
332
+ end
333
+
334
+ def get_tag
335
+ params = {'Filter.1.Name' => 'resource-id',
336
+ 'Filter.1.Value.1' => get_instance_id,
337
+ 'Filter.2.Name' => 'key',
338
+ 'Filter.2.Value.1' => 'Name'}
339
+ ec2_req('DescribeTags', params)['tagSet']['item']['value']
340
+ end
341
+
342
+ def get_instances
343
+ params = {'Filter.1.Name' => 'tag-value',
344
+ 'Filter.1.Value.1' => @tag}
345
+ ec2_req('DescribeInstances', params)
95
346
  end
96
347
 
97
- def status_text
98
- status ? 'Passed' : 'Failed'
348
+ def get_private_ips
349
+ get_instances['reservationSet']['item'].
350
+ map{|i| i['instancesSet']['item']['privateIpAddress']}
99
351
  end
100
352
  end
101
353
  end