greenhat 0.1.4 → 0.3.1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -7
  3. data/lib/greenhat/accessors/disk.rb +58 -2
  4. data/lib/greenhat/accessors/gitlab.rb +75 -0
  5. data/lib/greenhat/accessors/memory.rb +10 -10
  6. data/lib/greenhat/accessors/process.rb +10 -1
  7. data/lib/greenhat/cli.rb +128 -57
  8. data/lib/greenhat/color.rb +27 -0
  9. data/lib/greenhat/logbot.rb +9 -9
  10. data/lib/greenhat/settings.rb +51 -3
  11. data/lib/greenhat/shell/args.rb +146 -0
  12. data/lib/greenhat/shell/cat.rb +25 -73
  13. data/lib/greenhat/shell/color_string.rb +43 -0
  14. data/lib/greenhat/shell/disk.rb +30 -42
  15. data/lib/greenhat/shell/faststats.rb +80 -61
  16. data/lib/greenhat/shell/filter_help.rb +143 -0
  17. data/lib/greenhat/shell/gitlab.rb +61 -2
  18. data/lib/greenhat/shell/help.rb +98 -15
  19. data/lib/greenhat/shell/list.rb +46 -0
  20. data/lib/greenhat/shell/log.rb +78 -203
  21. data/lib/greenhat/shell/page.rb +39 -0
  22. data/lib/greenhat/shell/process.rb +57 -2
  23. data/lib/greenhat/shell/report.rb +70 -60
  24. data/lib/greenhat/shell/shell_helper.rb +601 -0
  25. data/lib/greenhat/shell.rb +27 -13
  26. data/lib/greenhat/thing/file_types.rb +76 -8
  27. data/lib/greenhat/thing/formatters/json_shellwords.rb +0 -3
  28. data/lib/greenhat/thing/formatters/nginx.rb +44 -0
  29. data/lib/greenhat/thing/formatters/syslog.rb +39 -0
  30. data/lib/greenhat/thing/helpers.rb +4 -4
  31. data/lib/greenhat/thing/kind.rb +9 -2
  32. data/lib/greenhat/thing/spinner.rb +3 -3
  33. data/lib/greenhat/thing.rb +3 -3
  34. data/lib/greenhat/tty/columns.rb +44 -0
  35. data/lib/greenhat/version.rb +1 -1
  36. data/lib/greenhat.rb +15 -14
  37. metadata +30 -20
  38. data/lib/greenhat/shell/helper.rb +0 -514
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbb51c051ab366c7bb7a172c4a917d7646bd5bdbc00c894217403655130bb2f3
4
- data.tar.gz: b4e997388954091b257df23a1f67093490b8c1996635c860b3e462fa3f44213c
3
+ metadata.gz: cf89eef7afeef6861dde013e8cc7992a92d1c9f4dca79ca0a438913f6d9a7a89
4
+ data.tar.gz: eed033653f4723f19962f5643ac02c71d9f950f97aa4a68375cc3174152e5513
5
5
  SHA512:
6
- metadata.gz: 965baad78deb383ed1c1cb569a219439daa4c73adf7fd8b3f2852ee1b9703e758e91b0784ac1c1ac999045dd8a0d871927680d2718829ebcc3740dcc4c334a00
7
- data.tar.gz: 17111df14082319b62bca88224ccf4b0b433ac9bcda5d1fe9c922e08477632a6c0831f61dc8615f633f0d8c4b70c58d54fe765c63172c4f4573b6812fc61aee1
6
+ metadata.gz: 90c1a58ef0334bd864480ad422957758850ecacd39bc85db1fd47233192e09bda768e15331dc179d8860f4842aed58fafb439f4f9499ff22d1c25e3ebf8bff74
7
+ data.tar.gz: aea2b938e6b1a5956d7811084b1ab725cf41f2d0766affffed696755ac497cc41a67ebfc562ce516ce448ebb983e6e504a493e772348fd536f1e5c711954f612
data/README.md CHANGED
@@ -14,20 +14,15 @@ Experimental SOS and Log Parser for GitLab
14
14
 
15
15
  ## Installation
16
16
 
17
- While potentially so in the future, this is currently not a published gem. To install:
18
-
19
17
  ```
20
- git clone https://gitlab.com/gitlab-com/support/toolbox/greenhat
21
- cd greenhat
22
- bundle install
23
- bundle exec rake install
18
+ gem install greenhat
24
19
  ```
25
20
 
26
21
  ## Usage
27
22
 
28
23
  ```
29
24
  greenhat sos-archive.tar.gz
30
- greenhat production_log.json
25
+ greenhat production_json.log
31
26
  # launches a console
32
27
  >> help # the program is self-documented through the builtin help command
33
28
  ```
@@ -20,8 +20,64 @@ module GreenHat
20
20
  end
21
21
  end
22
22
 
23
- def self.df
24
- Thing.where(name: 'df_h')
23
+ def self.df(args = {})
24
+ things = Thing.where(name: 'df_h')
25
+
26
+ # Host / Archive
27
+ things.select! { |x| x.archive? args.archive } if args.archive
28
+
29
+ things
30
+ end
31
+
32
+ # Unified Output Handler
33
+ # rubocop:disable Metrics/MethodLength
34
+ def self.format_output(file, name = false, limit = nil, filter = %w[tmpfs loop])
35
+ output = []
36
+
37
+ output << file.friendly_name if name
38
+
39
+ # Reject TMPFS
40
+ disks = file.data.sort_by { |x| x.use.to_i }.reverse
41
+
42
+ # Filter
43
+ disks.reject! { |x| filter.any? { |y| x.filesystem.include? y } }
44
+
45
+ disks = disks[0..limit - 1] if limit
46
+
47
+ pad_mount, pad_size, pad_used, pad_avail = GreenHat::Disk.padding(disks)
48
+
49
+ # Headers
50
+ output << [
51
+ 'Mount'.ljust(pad_mount).pastel(:blue),
52
+ 'Size'.ljust(pad_size).pastel(:magenta),
53
+ 'Used'.ljust(pad_used).pastel(:cyan),
54
+ 'Avail'.ljust(pad_avail).pastel(:white),
55
+ '% Use'.ljust(pad_avail).pastel(:green)
56
+ ].join
57
+
58
+ # Table Summary
59
+ disks.map do |disk|
60
+ # Pretty Disk Use
61
+ use = [
62
+ disk.use.rjust(5).ljust(5).pastel(:green),
63
+ ' ['.pastel(:blue),
64
+ ('=' * (disk.use.to_i / 2)).pastel(:green),
65
+ ' ' * (50 - disk.use.to_i / 2),
66
+ ']'.pastel(:blue)
67
+ ].join
68
+
69
+ # Whole Thing
70
+ output << [
71
+ disk.mounted_on.ljust(pad_mount).pastel(:blue),
72
+ disk[:size].to_s.ljust(pad_size).pastel(:magenta),
73
+ disk.used.to_s.ljust(pad_used).pastel(:cyan),
74
+ disk.avail.to_s.ljust(pad_avail).pastel(:white),
75
+ use
76
+ ].join
77
+ end
78
+
79
+ output
25
80
  end
81
+ # rubocop:enable Metrics/MethodLength
26
82
  end
27
83
  end
@@ -0,0 +1,75 @@
1
+ module GreenHat
2
+ # GitLab App Helpers
3
+ module GitLab
4
+ def self.node_types
5
+ [
6
+ {
7
+ name: 'Web Service', pattern: %w[puma unicorn]
8
+ },
9
+ {
10
+ name: 'Sidekiq', pattern: %w[sidekiq]
11
+ },
12
+ {
13
+ name: 'Gitaly', pattern: %w[gitaly]
14
+ },
15
+ {
16
+ name: 'Redis', pattern: %w[redis]
17
+ },
18
+ {
19
+ name: 'PostgreSQL', pattern: %w[postgresql]
20
+ },
21
+ {
22
+ name: 'PGBouncer', pattern: %w[pgbouncer]
23
+ }
24
+ ]
25
+ end
26
+
27
+ def self.identify_node(archive)
28
+ gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }&.data&.keys
29
+ hostname = archive.things.find { |x| x.type == 'hostname' }.data.first
30
+
31
+ {
32
+ host: hostname,
33
+ services: gitlab_status || []
34
+ }
35
+ end
36
+
37
+ # Show GitLab Services in a grid / include versions
38
+ def self.services(archive, indent = 0)
39
+ manifest = archive.things.find { |x| x.type == 'gitlab/version-manifest.json' }
40
+ gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
41
+
42
+ return nil unless gitlab_status
43
+
44
+ list = gitlab_status.data.keys.sort.map do |service|
45
+ color = gitlab_status.data.dig(service, 0, :status) == 'run' ? :green : :red
46
+
47
+ # Collect Service version from manifest
48
+ version = manifest.data.software[service.to_sym]&.display_version
49
+
50
+ # If able to identify version use / fallback
51
+ if version
52
+ [
53
+ service.pastel(color),
54
+ "(#{version})".pastel(:bright_black)
55
+ ].join(' ')
56
+ else
57
+ service.pastel(color)
58
+ end
59
+ end
60
+
61
+ # Keep Alphabetical Sort
62
+ groups = list.each_slice((list.size / 3.to_f).round).to_a
63
+
64
+ table = TTY::Table.new do |t|
65
+ loop do
66
+ break if groups.all?(&:empty?)
67
+
68
+ t << groups.map(&:shift)
69
+ end
70
+ end
71
+
72
+ table.render(:unicode, padding: [0, 1, 0, 1], indent: indent)
73
+ end
74
+ end
75
+ end
@@ -18,24 +18,24 @@ module GreenHat
18
18
  used_name = number_to_human_size(mem.used.to_i.megabytes)
19
19
 
20
20
  bar = [
21
- '|'.colorize(:green) * percentage(mem.used, total),
22
- '|'.colorize(:blue) * percentage(mem.shared, total),
23
- '|'.colorize(:teal) * percentage(mem.buffcache, total),
21
+ '|'.pastel(:green) * percentage(mem.used, total),
22
+ '|'.pastel(:blue) * percentage(mem.shared, total),
23
+ '|'.pastel(:cyan) * percentage(mem.buffcache, total),
24
24
  ' ' * percentage(mem.free, total)
25
25
  ].join
26
26
 
27
27
  # Make Even
28
- padding = 125 - bar.uncolorize.size
28
+ padding = 125 - bar.unpastel.size
29
29
  bar += ' ' * padding if padding.positive?
30
30
 
31
31
  [
32
- mem.kind.ljust(4).colorize(:cyan),
33
- ' ['.colorize(:light_black),
32
+ mem.kind.ljust(4).pastel(:cyan),
33
+ ' ['.pastel(:bright_black),
34
34
  bar.ljust(120),
35
- used_name.colorize(:magenta),
36
- ' / '.colorize(:light_black),
37
- total_name.colorize(:blue),
38
- ']'.colorize(:light_black)
35
+ used_name.pastel(:magenta),
36
+ ' / '.pastel(:bright_black),
37
+ total_name.pastel(:blue),
38
+ ']'.pastel(:bright_black)
39
39
  ].join
40
40
  end
41
41
 
@@ -1,7 +1,16 @@
1
1
  module GreenHat
2
2
  # Sidekiq Log Helpers
3
3
  module Ps
4
- def self.ps
4
+ def self.ps(args)
5
+ things = Thing.where(name: 'ps')
6
+
7
+ # Host / Archive
8
+ things.select! { |x| x.archive? args.archive } if args.archive
9
+
10
+ things
11
+ end
12
+
13
+ def self.things
5
14
  Thing.where(name: 'ps')
6
15
  end
7
16
  end
data/lib/greenhat/cli.rb CHANGED
@@ -54,7 +54,9 @@ module GreenHat
54
54
 
55
55
  # DEBUG PRY
56
56
  reader.on(:keyctrl_p) do |event|
57
+ # rubocop:disable Lint/Debugger
57
58
  binding.pry
59
+ # rubocop:enable Lint/Debugger
58
60
  end
59
61
  reader
60
62
  end
@@ -84,7 +86,7 @@ module GreenHat
84
86
 
85
87
  # Print List of Options
86
88
  elsif matches.count > 1
87
- puts matches.join("\t").colorize(:light_green)
89
+ puts matches.join("\t").pastel(:bright_green)
88
90
 
89
91
  # No other Matches
90
92
  else
@@ -102,7 +104,7 @@ module GreenHat
102
104
  # Print List of Options
103
105
  elsif matches.count > 1
104
106
  auto_update(common_substr(matches), word)
105
- puts matches.join("\t").colorize(:light_green)
107
+ puts matches.join("\t").pastel(:bright_green)
106
108
  end
107
109
  end
108
110
 
@@ -135,15 +137,15 @@ module GreenHat
135
137
 
136
138
  if all.empty?
137
139
  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)
140
+ 'Command not found '.pastel(:red),
141
+ cmd.pastel(:bright_yellow),
142
+ ' ('.pastel(:bright_black),
143
+ 'help'.pastel(:blue),
144
+ ' to show available commands'.pastel(:bright_black),
145
+ ')'.pastel(:bright_black)
144
146
  ].join
145
147
  else
146
- puts "#{'Did you mean?'.colorize(:cyan)} #{all.join("\t").colorize(:green)}"
148
+ puts "#{'Did you mean?'.pastel(:cyan)} #{all.join("\t").pastel(:green)}"
147
149
  end
148
150
  end
149
151
 
@@ -152,7 +154,7 @@ module GreenHat
152
154
  line = reader.line
153
155
  @list = Shellwords.split line.text
154
156
  rescue StandardError => e
155
- puts "#{'Invalid Command'.colorize(:red)}: #{e.message.colorize(:green)}"
157
+ puts "#{'Invalid Command'.pastel(:red)}: #{e.message.pastel(:green)}"
156
158
  end
157
159
 
158
160
  def self.available(list)
@@ -227,39 +229,46 @@ module GreenHat
227
229
  run # Loop Back
228
230
  rescue StandardError => e
229
231
  LogBot.fatal('CLI Run', e.message)
230
- puts e.backtrace[0..4].join("\n").colorize(:red)
232
+ puts e.backtrace[0..4].join("\n").pastel(:red)
231
233
  end
232
234
 
233
235
  # Check for `default` method and files
234
236
  def self.default?
235
- files.include?(cmd) && current_methods.include?('default')
237
+ @list.any? { |x| x.include?(cmd) } && current_methods.include?('default')
236
238
  end
237
239
 
238
240
  # General Helper
239
- def self.help
241
+ def self.help(long = true)
240
242
  if current_location.methods(false).count.zero?
241
- puts 'No Commands'.colorize(:red)
243
+ puts 'No Commands'.pastel(:red)
242
244
  else
243
245
  puts 'Commands: '
244
246
  current_location.methods(false).map(&:to_s).sort.each do |item|
245
247
  next if %w[default help].any? { |x| x == item }
246
248
 
247
- puts "=> #{item.to_s.colorize(:blue)}"
249
+ puts "=> #{item.to_s.pastel(:blue)}"
248
250
  end
249
251
 
250
- current_location.send(:help) if current_methods.include? 'help'
251
252
  end
252
253
 
253
254
  puts ''
254
255
 
255
256
  if current_location.constants.count.zero?
256
- puts 'No Submodules'.colorize(:red)
257
+ puts 'No Submodules'.pastel(:red)
257
258
  else
258
259
  puts 'Submodules'
259
260
  current_location.constants.each do |item|
260
- puts "-> #{item.to_s.demodulize.downcase.colorize(:yellow)}"
261
+ puts "-> #{item.to_s.demodulize.downcase.pastel(:yellow)}"
261
262
  end
262
263
  end
264
+
265
+ # Execute local help last if exists
266
+ if current_methods.include?('help') && long
267
+ puts
268
+ current_location.send(:help)
269
+ end
270
+
271
+ puts ''
263
272
  end
264
273
 
265
274
  def self.location
@@ -306,11 +315,11 @@ module GreenHat
306
315
  end
307
316
 
308
317
  def self.location_reader
309
- location.map(&:to_s).map(&:downcase).map(&:demodulize).join('/').gsub('shell', '~').colorize(:blue)
318
+ location.map(&:to_s).map(&:downcase).map(&:demodulize).join('/').gsub('shell', '~').pastel(:blue)
310
319
  end
311
320
 
312
321
  def self.readline_notch
313
- "#{'greenhat'.colorize(:light_black)} #{location_reader} » "
322
+ "#{'greenhat'.pastel(:bright_black)} #{location_reader} » "
314
323
  end
315
324
 
316
325
  def self.clear_screen
@@ -321,51 +330,99 @@ module GreenHat
321
330
  @quiet
322
331
  end
323
332
 
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
+ # Toggle Quiet Settings
334
+ def self.quiet!
335
+ @quiet = !@quiet
336
+ end
333
337
 
334
- binding.pry if files.include? '--help'
338
+ def self.cli_help
339
+ Shell.version
340
+ puts
341
+ puts 'Usage'.pastel(:yellow)
342
+ puts ' greenhat <sos-archive.tgz> <sos-archive2.tgz> '
343
+ puts
335
344
 
336
- files.reject! { |x| startup_arg_list.include? x }
337
- end
345
+ puts 'Options'.pastel(:yellow)
346
+ puts ' --report, -r'.pastel(:green)
347
+ puts ' Run `report` against archives and exit'
348
+ puts
349
+
350
+ puts ' --quiet, -r'.pastel(:green)
351
+ puts ' Surpress GreenHat logging output'
352
+ puts
353
+
354
+ puts ' --load, -l'.pastel(:green)
355
+ puts ' Automatically attempt to read/parse/preload all included files'
356
+ puts
357
+
358
+ puts ' --command, -c'.pastel(:green)
359
+ puts ' Run and then exit a GreenHat Shell command'
360
+ puts
338
361
 
339
- def self.startup_arg_list
340
- ['--load', '--l', '--quiet', '-q', '--report', '-r']
362
+ puts ' --version, -v'.pastel(:green)
363
+ puts ' Print version and exit'
364
+ puts
341
365
  end
342
366
 
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
367
+ # Arguments before general processing
368
+ def self.pre_args(flags, files)
369
+ # Help
370
+ if flags?(%i[help h], flags)
371
+ cli_help
372
+ exit 0
373
+ end
347
374
 
348
- Settings.start
375
+ # Version
376
+ if flags?(%i[version v], flags)
377
+ Shell.version
378
+ exit 0
379
+ end
349
380
 
350
- startup_args files
351
- clear_screen
381
+ # Quiet Flag
382
+ @quiet = true if flags?(%i[quiet q], flags)
352
383
 
384
+ # rubocop:disable Style/GuardClause
385
+ # MIA Files
353
386
  if files.empty? || files.count { |x| File.exist? x }.zero?
354
- puts "No arguments or files don't exist".colorize(:red)
387
+ puts "No arguments or files don't exist".pastel(:red)
355
388
  puts 'Usage: greenhat <sos-archive.tgz> <sos-archive2.tgz>'
356
- Shell.about
389
+ cli_help
390
+ Shell.version
357
391
  end
392
+ # rubocop:enable Style/GuardClause
393
+ end
358
394
 
359
- load_files files
360
-
361
- # Handle Args
362
- if @report
395
+ # Arguments to be handled after general processing
396
+ def self.post_args(flags)
397
+ # Run report and exit / Don't Clear for reports
398
+ if flags?(%i[report r], flags)
399
+ @quiet = true
363
400
  # Don't Use Pagination
364
401
  GreenHat::Shell.report(['--raw'])
365
- return true
402
+ exit 0
403
+ else
404
+ clear_screen
366
405
  end
367
406
 
368
- Thing.all.each(&:process) if @load
407
+ # CTL Tails need to be parsed for new 'things'
408
+ Thing.where(kind: :gitlab_tail)&.map(&:process)
409
+
410
+ Thing.all.each(&:process) if flags?(%i[load l], flags)
411
+ end
412
+
413
+ # Helper to Simplify checking flags
414
+ def self.flags?(list = [], flags = {})
415
+ list.any? { |x| flags.key? x }
416
+ end
417
+
418
+ # If no arguments Supplied Print and quit - rather than nasty exception
419
+ def self.start(raw)
420
+ Settings.start
421
+ files, flags, args = Args.parse(raw)
422
+ pre_args(flags, files)
423
+ load_files files
424
+ run_command(args) if args.any? { |arg| run_command?(arg) }
425
+ post_args(flags)
369
426
 
370
427
  value ||= '' # Empty Start
371
428
 
@@ -379,11 +436,7 @@ module GreenHat
379
436
  next
380
437
  end
381
438
 
382
- if line =~ /^exit/i
383
- Settings.cmd_write
384
-
385
- break
386
- end
439
+ break if line =~ /^exit/i
387
440
 
388
441
  Settings.cmd_add(line) unless line.blank?
389
442
 
@@ -391,18 +444,36 @@ module GreenHat
391
444
  run
392
445
  end
393
446
  end
394
- # rubocop:enable Metrics/MethodLength
447
+
448
+ def self.run_command?(arg)
449
+ %i[command c].any? do |x|
450
+ arg[:field] == x
451
+ end
452
+ end
453
+
454
+ # Run and Exit Command Helper
455
+ def self.run_command(args)
456
+ args.each do |arg|
457
+ # Quick Validation
458
+ next unless run_command?(arg) && !arg.value.empty?
459
+
460
+ reader.line = ''
461
+ @list = Shellwords.split arg.value
462
+ run
463
+ end
464
+ exit 0
465
+ end
395
466
 
396
467
  def self.load_files(files)
397
468
  # TODO: Web Helpers?
398
469
  # suppress_output { GreenHat::Web.start }
399
470
 
400
471
  # Don't double up on archives / Only Existing files
401
- puts 'Loading Archives'.colorize(:blue)
472
+ puts 'Loading Archives'.pastel(:blue) unless Cli.quiet
402
473
  files.uniq.each do |file|
403
474
  next unless File.exist?(file)
404
475
 
405
- puts "- #{file}".colorize(:magenta)
476
+ puts "- #{file}".pastel(:magenta) unless Cli.quiet
406
477
  ArchiveLoader.load file
407
478
  end
408
479
  end