greenhat 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +64 -0
  3. data/bin/greenhat +12 -0
  4. data/lib/greenhat.rb +80 -0
  5. data/lib/greenhat/accessors/disk.rb +27 -0
  6. data/lib/greenhat/accessors/logs/production.rb +41 -0
  7. data/lib/greenhat/accessors/logs/sidekiq.rb +41 -0
  8. data/lib/greenhat/accessors/memory.rb +46 -0
  9. data/lib/greenhat/accessors/network.rb +8 -0
  10. data/lib/greenhat/accessors/process.rb +8 -0
  11. data/lib/greenhat/archive.rb +108 -0
  12. data/lib/greenhat/cli.rb +448 -0
  13. data/lib/greenhat/host.rb +182 -0
  14. data/lib/greenhat/logbot.rb +86 -0
  15. data/lib/greenhat/pry_helpers.rb +51 -0
  16. data/lib/greenhat/settings.rb +51 -0
  17. data/lib/greenhat/shell.rb +92 -0
  18. data/lib/greenhat/shell/cat.rb +125 -0
  19. data/lib/greenhat/shell/disk.rb +68 -0
  20. data/lib/greenhat/shell/faststats.rb +195 -0
  21. data/lib/greenhat/shell/gitlab.rb +45 -0
  22. data/lib/greenhat/shell/help.rb +15 -0
  23. data/lib/greenhat/shell/helper.rb +514 -0
  24. data/lib/greenhat/shell/log.rb +344 -0
  25. data/lib/greenhat/shell/memory.rb +31 -0
  26. data/lib/greenhat/shell/network.rb +12 -0
  27. data/lib/greenhat/shell/process.rb +12 -0
  28. data/lib/greenhat/shell/report.rb +319 -0
  29. data/lib/greenhat/thing.rb +121 -0
  30. data/lib/greenhat/thing/file_types.rb +705 -0
  31. data/lib/greenhat/thing/formatters/api_json.rb +34 -0
  32. data/lib/greenhat/thing/formatters/bracket_log.rb +44 -0
  33. data/lib/greenhat/thing/formatters/clean_raw.rb +23 -0
  34. data/lib/greenhat/thing/formatters/colon_split_strip.rb +12 -0
  35. data/lib/greenhat/thing/formatters/dotenv.rb +10 -0
  36. data/lib/greenhat/thing/formatters/format.rb +12 -0
  37. data/lib/greenhat/thing/formatters/free_m.rb +29 -0
  38. data/lib/greenhat/thing/formatters/gitlab_ctl_tail.rb +51 -0
  39. data/lib/greenhat/thing/formatters/gitlab_status.rb +26 -0
  40. data/lib/greenhat/thing/formatters/json.rb +63 -0
  41. data/lib/greenhat/thing/formatters/json_shellwords.rb +44 -0
  42. data/lib/greenhat/thing/formatters/multiline_json.rb +10 -0
  43. data/lib/greenhat/thing/formatters/raw.rb +18 -0
  44. data/lib/greenhat/thing/formatters/shellwords.rb +23 -0
  45. data/lib/greenhat/thing/formatters/table.rb +26 -0
  46. data/lib/greenhat/thing/formatters/time_json.rb +21 -0
  47. data/lib/greenhat/thing/formatters/time_shellwords.rb +28 -0
  48. data/lib/greenhat/thing/formatters/time_space.rb +36 -0
  49. data/lib/greenhat/thing/helpers.rb +71 -0
  50. data/lib/greenhat/thing/history.rb +51 -0
  51. data/lib/greenhat/thing/info_format.rb +193 -0
  52. data/lib/greenhat/thing/kind.rb +97 -0
  53. data/lib/greenhat/thing/spinner.rb +52 -0
  54. data/lib/greenhat/thing/super_log.rb +102 -0
  55. data/lib/greenhat/tty/custom_line.rb +29 -0
  56. data/lib/greenhat/tty/line.rb +326 -0
  57. data/lib/greenhat/tty/reader.rb +110 -0
  58. data/lib/greenhat/version.rb +3 -0
  59. data/lib/greenhat/views/css.slim +126 -0
  60. data/lib/greenhat/views/disk_free.slim +18 -0
  61. data/lib/greenhat/views/index.slim +51 -0
  62. data/lib/greenhat/views/info.slim +39 -0
  63. data/lib/greenhat/views/manifest.slim +22 -0
  64. data/lib/greenhat/views/memory.slim +18 -0
  65. data/lib/greenhat/views/netstat.slim +20 -0
  66. data/lib/greenhat/views/process.slim +21 -0
  67. data/lib/greenhat/views/systemctl.slim +40 -0
  68. data/lib/greenhat/views/ulimit.slim +15 -0
  69. data/lib/greenhat/web.rb +46 -0
  70. metadata +476 -0
@@ -0,0 +1,448 @@
1
+ # Top level namespace
2
+ module GreenHat
3
+ # CLI Methods
4
+ # rubocop:disable Metrics/ModuleLength
5
+ module Cli
6
+ def self.cursor
7
+ @cursor = TTY::Cursor
8
+ end
9
+
10
+ # Input Loop Listener
11
+ def self.reader
12
+ @reader ||= reader_setup
13
+ end
14
+
15
+ def self.files
16
+ @files ||= Thing.all.map(&:name).uniq
17
+ end
18
+
19
+ def self.reader_setup
20
+ reader = TTY::Reader.new(history_duplicates: false, interrupt: -> { back })
21
+
22
+ Settings.cmd_history_clean.each do |line|
23
+ reader.add_to_history(line)
24
+ end
25
+
26
+ # Blank?
27
+ reader.add_to_history ''
28
+
29
+ # Remove Line
30
+ reader.on(:keyctrl_u) do |_event|
31
+ reader.line.remove reader.line.text.size
32
+ end
33
+
34
+ # Navigate Word Left
35
+ reader.on(:keyctrl_left) { reader.line.move_word_left }
36
+
37
+ # Navigate Word Right
38
+ reader.on(:keyctrl_right) { reader.line.move_word_right }
39
+
40
+ # Navigate Beginning
41
+ reader.on(:keyctrl_a) { reader.line.move_to_start }
42
+
43
+ # Navigate End
44
+ reader.on(:keyctrl_e) { reader.line.move_to_end }
45
+
46
+ reader.on(:keyback_tab) { back }
47
+
48
+ reader.on(:keytab) do
49
+ process
50
+ auto
51
+ end
52
+
53
+ reader.instance_variable_get(:@history)
54
+
55
+ # DEBUG PRY
56
+ reader.on(:keyctrl_p) do |event|
57
+ binding.pry
58
+ end
59
+ reader
60
+ end
61
+
62
+ # =========================================================
63
+ # Auto Complete
64
+ # =========================================================
65
+ def self.auto
66
+ word = reader.line.word
67
+
68
+ if word.blank?
69
+ help
70
+ else
71
+ auto_match(current_methods + current_submodules + cmd_list, word)
72
+ end
73
+ end
74
+
75
+ def self.auto_match(matches, word)
76
+ matches.select! { |x| x[/^#{Regexp.escape(word)}/] }
77
+
78
+ if submodule?
79
+ submodule!
80
+ reader.breaker = true
81
+ # Only one Match!
82
+ elsif matches.count == 1
83
+ auto_update(matches.first, word)
84
+
85
+ # Print List of Options
86
+ elsif matches.count > 1
87
+ puts matches.join("\t").colorize(:light_green)
88
+
89
+ # No other Matches
90
+ else
91
+ file_matches = files.select { |x| x[/^#{Regexp.escape(word)}/] }
92
+ auto_files(file_matches, word) unless file_matches.empty?
93
+
94
+ end
95
+ end
96
+
97
+ # Auto Complete File Names
98
+ def self.auto_files(matches, word)
99
+ if matches.count == 1
100
+ auto_update(matches.first, word)
101
+
102
+ # Print List of Options
103
+ elsif matches.count > 1
104
+ auto_update(common_substr(matches), word)
105
+ puts matches.join("\t").colorize(:light_green)
106
+ end
107
+ end
108
+
109
+ # Handle Updates to Reader
110
+ def self.auto_update(match, word)
111
+ add = match.split(word, 2).last
112
+ reader.line.insert add unless add.nil?
113
+ end
114
+
115
+ # Complete as much as possible comparison
116
+ # https://stackoverflow.com/a/2158481/1678507
117
+ def self.common_substr(strings)
118
+ shortest = strings.min_by(&:length)
119
+ maxlen = shortest.length
120
+ maxlen.downto(0) do |len|
121
+ 0.upto(maxlen - len) do |start|
122
+ substr = shortest[start, len]
123
+ return substr if strings.all? { |str| str.include? substr }
124
+ end
125
+ end
126
+ end
127
+ # =========================================================
128
+
129
+ def self.did_you_mean
130
+ dictionary = current_methods + current_submodules + cmd_list
131
+
132
+ # all.select! { |x| x.include? cmd }
133
+
134
+ all = DidYouMean::SpellChecker.new(dictionary: dictionary).correct(cmd)
135
+
136
+ if all.empty?
137
+ puts [
138
+ 'Command not found '.colorize(:red),
139
+ cmd.colorize(:light_yellow),
140
+ ' ('.colorize(:light_black),
141
+ 'help'.colorize(:blue),
142
+ ' to show available commands'.colorize(:light_black),
143
+ ')'.colorize(:light_black)
144
+ ].join
145
+ else
146
+ puts "#{'Did you mean?'.colorize(:cyan)} #{all.join("\t").colorize(:green)}"
147
+ end
148
+ end
149
+
150
+ # Auto/Run Process - Populate and parse: @list
151
+ def self.process
152
+ line = reader.line
153
+ @list = Shellwords.split line.text
154
+ rescue StandardError => e
155
+ puts "#{'Invalid Command'.colorize(:red)}: #{e.message.colorize(:green)}"
156
+ end
157
+
158
+ def self.available(list)
159
+ list.map(&:to_s).map(&:downcase)
160
+ end
161
+
162
+ # Reader helper
163
+ def self.cmd
164
+ @list.first
165
+ end
166
+
167
+ def self.run?
168
+ current_methods.include? cmd
169
+ end
170
+
171
+ # Special Command Overrides
172
+ def self.cmd_list
173
+ ['help', '~', '..', 'back', 'clear']
174
+ end
175
+
176
+ def self.cmd?
177
+ cmd_list.any? cmd
178
+ end
179
+
180
+ def self.submodule?
181
+ current_submodules.include? @list.first
182
+ end
183
+
184
+ def self.cmd_run
185
+ case cmd
186
+ when 'help' then help
187
+ when '~' then home
188
+ when '..', 'back' then back
189
+ when 'clear' then clear_screen
190
+ end
191
+
192
+ @list.shift
193
+ end
194
+
195
+ # Final Command Execution
196
+ def self.run!
197
+ # Shift to prevent duplicate Runs / Arity for identifying if Method has params
198
+ if current_location.method(@list.first).arity.zero?
199
+ current_location.send(@list.shift.clone)
200
+ else
201
+ current_location.send(@list.shift.clone, @list.clone)
202
+ end
203
+ end
204
+
205
+ def self.run
206
+ return true if @list.blank?
207
+
208
+ if cmd?
209
+ cmd_run
210
+ elsif run?
211
+ run!
212
+
213
+ return true # End Early
214
+
215
+ elsif current_submodule?
216
+ @list.shift
217
+ elsif submodule?
218
+ submodule!
219
+ # Prepend Default if exists
220
+ elsif default?
221
+ @list.unshift 'default'
222
+ else
223
+ did_you_mean
224
+ @list.shift
225
+ end
226
+
227
+ run # Loop Back
228
+ rescue StandardError => e
229
+ LogBot.fatal('CLI Run', e.message)
230
+ puts e.backtrace[0..4].join("\n").colorize(:red)
231
+ end
232
+
233
+ # Check for `default` method and files
234
+ def self.default?
235
+ files.include?(cmd) && current_methods.include?('default')
236
+ end
237
+
238
+ # General Helper
239
+ def self.help
240
+ if current_location.methods(false).count.zero?
241
+ puts 'No Commands'.colorize(:red)
242
+ else
243
+ puts 'Commands: '
244
+ current_location.methods(false).map(&:to_s).sort.each do |item|
245
+ next if %w[default help].any? { |x| x == item }
246
+
247
+ puts "=> #{item.to_s.colorize(:blue)}"
248
+ end
249
+
250
+ current_location.send(:help) if current_methods.include? 'help'
251
+ end
252
+
253
+ puts ''
254
+
255
+ if current_location.constants.count.zero?
256
+ puts 'No Submodules'.colorize(:red)
257
+ else
258
+ puts 'Submodules'
259
+ current_location.constants.each do |item|
260
+ puts "-> #{item.to_s.demodulize.downcase.colorize(:yellow)}"
261
+ end
262
+ end
263
+ end
264
+
265
+ def self.location
266
+ @location ||= [Shell]
267
+ end
268
+
269
+ # Replace Location
270
+ def self.move(spot)
271
+ @location = [spot]
272
+ end
273
+
274
+ # Append to Location
275
+ def self.submodule!
276
+ spot = @list.shift
277
+ reader.line.replace @list.join(' ')
278
+ @location << current_location.const_get(spot.capitalize.to_sym)
279
+ end
280
+
281
+ def self.home
282
+ move Shell
283
+ end
284
+
285
+ def self.back
286
+ move location[-2] if location.count > 1
287
+ end
288
+
289
+ # Reader to Get Last Location's Available Methods / Keep Helper Methods
290
+ def self.current_methods
291
+ current_location.methods(false).map(&:to_s)
292
+ # .reject { |x| x.include? '_help' }
293
+ end
294
+
295
+ def self.current_submodules
296
+ current_location.constants.map(&:to_s).map(&:downcase)
297
+ end
298
+
299
+ # Full Commands at the Submodule `disk summary` from `disk`
300
+ def self.current_submodule?
301
+ current_location.to_s.demodulize.downcase == @list.first
302
+ end
303
+
304
+ def self.current_location
305
+ location.last
306
+ end
307
+
308
+ def self.location_reader
309
+ location.map(&:to_s).map(&:downcase).map(&:demodulize).join('/').gsub('shell', '~').colorize(:blue)
310
+ end
311
+
312
+ def self.readline_notch
313
+ "#{'greenhat'.colorize(:light_black)} #{location_reader} » "
314
+ end
315
+
316
+ def self.clear_screen
317
+ print cursor.clear_screen + cursor.move_to(0, 0)
318
+ end
319
+
320
+ def self.quiet
321
+ @quiet
322
+ end
323
+
324
+ # Process any Args
325
+ # --report
326
+ # --quiet
327
+ # --load -- Auto Load Things
328
+ # TODO: Add Help (--help)
329
+ def self.startup_args(files)
330
+ @report = true if files.any? { |x| ['--report', '-r'].include? x }
331
+ @quiet = true if files.any? { |x| ['--quiet', '-q'].include? x }
332
+ @load = true if files.any? { |x| ['--load', '-l'].include? x }
333
+
334
+ binding.pry if files.include? '--help'
335
+
336
+ files.reject! { |x| startup_arg_list.include? x }
337
+ end
338
+
339
+ def self.startup_arg_list
340
+ ['--load', '--l', '--quiet', '-q', '--report', '-r']
341
+ end
342
+
343
+ # rubocop:disable Metrics/MethodLength
344
+ def self.start(files)
345
+ # If no arguments Supplied Print and quit - rather than nasty exception
346
+ # TODO: Add Usage
347
+
348
+ Settings.start
349
+
350
+ startup_args files
351
+ clear_screen
352
+
353
+ if files.empty? || files.count { |x| File.exist? x }.zero?
354
+ puts "No arguments or files don't exist".colorize(:red)
355
+ puts 'Usage: greenhat <sos-archive.tgz> <sos-archive2.tgz>'
356
+ Shell.about
357
+ end
358
+
359
+ load_files files
360
+
361
+ # Handle Args
362
+ if @report
363
+ # Don't Use Pagination
364
+ GreenHat::Shell.report(['--raw'])
365
+ return true
366
+ end
367
+
368
+ Thing.all.each(&:process) if @load
369
+
370
+ value ||= '' # Empty Start
371
+
372
+ loop do
373
+ line = reader.read_line(readline_notch, value: value)
374
+ value = '' # Remove Afterwards
375
+
376
+ if reader.breaker
377
+ value = line
378
+ puts ''
379
+ next
380
+ end
381
+
382
+ if line =~ /^exit/i
383
+ Settings.cmd_write
384
+
385
+ break
386
+ end
387
+
388
+ Settings.cmd_add(line) unless line.blank?
389
+
390
+ process
391
+ run
392
+ end
393
+ end
394
+ # rubocop:enable Metrics/MethodLength
395
+
396
+ def self.load_files(files)
397
+ # TODO: Web Helpers?
398
+ # suppress_output { GreenHat::Web.start }
399
+
400
+ # Don't double up on archives / Only Existing files
401
+ puts 'Loading Archives'.colorize(:blue)
402
+ files.uniq.each do |file|
403
+ next unless File.exist?(file)
404
+
405
+ puts "- #{file}".colorize(:magenta)
406
+ ArchiveLoader.load file
407
+ end
408
+ end
409
+
410
+ def self.suppress_output
411
+ original_stderr = $stderr.clone
412
+ original_stdout = $stdout.clone
413
+ $stderr.reopen(File.new('/dev/null', 'w'))
414
+ $stdout.reopen(File.new('/dev/null', 'w'))
415
+ yield
416
+ ensure
417
+ $stdout.reopen(original_stdout)
418
+ $stderr.reopen(original_stderr)
419
+ end
420
+
421
+ def self.template
422
+ "#{__dir__}/views/index.slim"
423
+ end
424
+
425
+ def self.output
426
+ 'greenhat.html'
427
+ end
428
+
429
+ def self.prompt
430
+ TTY::Prompt.new(active_color: :cyan)
431
+ end
432
+
433
+ # TODO
434
+ def self.menu
435
+ prompt.select('Wat do?') do |menu|
436
+ menu.choice name: 'exit'
437
+ menu.choice name: 'Export Kibana Dashboard', value: 'export'
438
+ menu.choice name: 'console (pry)', value: 'console'
439
+ end
440
+ end
441
+
442
+ def self.bad_file(archive)
443
+ puts color("Cannot find archive: #{archive}", :red)
444
+ exit 1
445
+ end
446
+ end
447
+ # rubocop:enable Metrics/ModuleLength
448
+ end
@@ -0,0 +1,182 @@
1
+ # Individual Host Helper
2
+ class Host < Teron
3
+ include ActionView::Helpers::DateHelper
4
+ include ActionView::Helpers::NumberHelper
5
+
6
+ field :name
7
+
8
+ belongs_to :archive
9
+
10
+ # def load(file)
11
+ # @file = file
12
+ # self.files = Archive.load(file)
13
+ # save!
14
+ # end
15
+
16
+ def archive_name
17
+ File.basename archive
18
+ end
19
+
20
+ def find_file(file_name)
21
+ files.find { |x| x.name == file_name }
22
+ end
23
+
24
+ def icon
25
+ release_file = find_file files.map(&:name).grep(/_release/).first
26
+
27
+ release = release_file.raw.join
28
+
29
+ if release.include? 'ubuntu'
30
+ 'fa-ubuntu'
31
+ elsif release.include? 'suse'
32
+ 'fa-suse'
33
+ elsif release.include? 'redhat'
34
+ 'fa-redhat'
35
+ elsif release.include? 'centos'
36
+ 'fa-centos'
37
+ else
38
+ 'fa-linux'
39
+ end
40
+ end
41
+
42
+ # ----------------------------
43
+ # Helpers
44
+ # ----------------------------
45
+ def percent(value, total)
46
+ (value.to_i / total.to_f).round(2) * 100
47
+ end
48
+
49
+ def systemctl_color(entry)
50
+ case entry.status
51
+ when 'enabled' then :green
52
+ when 'static' then :orange
53
+ when 'disabled' then :red
54
+ else
55
+ :grey
56
+ end
57
+ end
58
+ # ---------------------------
59
+
60
+ def manifest
61
+ file = find_file 'gitlab_version_manifest_json'
62
+ return nil unless file
63
+
64
+ Oj.load file.raw.join
65
+ end
66
+
67
+ def uptime
68
+ file = find_file 'uptime'
69
+ return nil unless file
70
+
71
+ file.raw
72
+ end
73
+
74
+ def uname
75
+ file = find_file 'uname'
76
+ return nil unless file
77
+
78
+ file.raw.join
79
+ end
80
+
81
+ def cpuinfo
82
+ file = find_file 'cpuinfo'
83
+ return nil unless file
84
+
85
+ file.raw.join("\n").split("\n\n").map do |cpu|
86
+ all = cpu.split("\n").map do |row|
87
+ row.delete("\t").split(': ')
88
+ end
89
+ { details: all[1..], order: all[0].last }
90
+ end
91
+ end
92
+
93
+ def cpu_speed
94
+ file = find_file 'lscpu'
95
+ return nil unless file
96
+
97
+ details = file.raw.find { |x| x.include? 'MHz' }
98
+ details.reverse.split(' ', 2).map(&:reverse).reverse
99
+ end
100
+
101
+ def total_memory
102
+ file = find_file 'free_m'
103
+ return nil unless file
104
+
105
+ value = file.raw.dig(1, 1).to_i
106
+ number_to_human_size(value * 1024 * 1024)
107
+ end
108
+
109
+ def free_m
110
+ file = find_file 'free_m'
111
+ return nil unless file
112
+
113
+ file.raw
114
+ end
115
+
116
+ def df_h
117
+ file = find_file 'df_h'
118
+ return nil unless file
119
+
120
+ file.raw
121
+ end
122
+
123
+ def netstat
124
+ file = find_file 'netstat'
125
+ return nil unless file
126
+
127
+ file.raw
128
+ end
129
+
130
+ def ulimit
131
+ file = find_file 'ulimit'
132
+ return nil unless file
133
+
134
+ results = file.raw.map do |entry|
135
+ {
136
+ value: entry.split[-1],
137
+ details: entry.split(' ').first
138
+ }
139
+ end
140
+
141
+ results.sort_by { |x| x[:details].downcase }
142
+ end
143
+
144
+ def processes
145
+ file = find_file 'ps'
146
+ return nil unless file
147
+
148
+ headers = file.raw.first.split(' ', 11)
149
+ list = file.raw[1..].each.map do |row|
150
+ row.split(' ', 11).each_with_index.each_with_object({}) do |(v, i), obj|
151
+ obj[headers[i]] = v
152
+ end
153
+ end
154
+ { headers: headers, list: list }
155
+ end
156
+
157
+ def systemctl_unit_files
158
+ file = find_file 'systemctl_unit_files'
159
+ return nil unless file
160
+
161
+ all = file.raw[1..-2].map do |x|
162
+ unit, status = x.split
163
+ { unit: unit, status: status }
164
+ end
165
+
166
+ all.reject! { |x| x[:unit].nil? }
167
+ all.sort_by(&:unit)
168
+ end
169
+ end
170
+
171
+ # # Slim Wrapper
172
+ # class List
173
+ # attr_accessor :hosts
174
+
175
+ # def initialize
176
+ # self.hosts = []
177
+ # end
178
+
179
+ # def slim(file, host = nil)
180
+ # Slim::Template.new("#{__dir__}/views/#{file}.slim").render(host)
181
+ # end
182
+ # end