greenhat 0.1.4

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 (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,21 @@
1
+ # Top
2
+ module GreenHat
3
+ # Log
4
+ module Formatters
5
+ # ==========================================================================
6
+ # Time Space JSON
7
+ # 2021-05-04_18:29:28.66542 {"timestamp":"2021-05-04T18:29:28.665Z","pid":3435539,"message":"Use Ctrl-C to stop"}
8
+ # ==========================================================================
9
+ def format_time_json
10
+ self.result = raw.map do |row|
11
+ time, msg = row.split(' ', 2)
12
+
13
+ result = Oj.load msg
14
+ result.time = time
15
+
16
+ result
17
+ end
18
+ end
19
+ # ==========================================================================
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ # Top
2
+ module GreenHat
3
+ # Log
4
+ module Formatters
5
+ # ==========================================================================
6
+ # Formatters Not Handled
7
+ # ==========================================================================
8
+ def format_time_shellwords
9
+ self.result = raw.map do |row|
10
+ time, msg = row.split(' ', 2)
11
+
12
+ result = Shellwords.split(msg).each_with_object({}) do |x, h|
13
+ key, value = x.split('=')
14
+ next if value.nil?
15
+
16
+ h[key] = value.numeric? ? value.to_f : value
17
+ end
18
+
19
+ # Timestamp Parsing
20
+ result.ts = Time.parse result.ts if result.key? 'ts'
21
+ result.time = Time.parse time
22
+
23
+ result
24
+ end
25
+ end
26
+ # ==========================================================================
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # Top
2
+ module GreenHat
3
+ # Log
4
+ module Formatters
5
+ # ==========================================================================
6
+ # Formatters Not Handled
7
+ # ==========================================================================
8
+ def format_time_space
9
+ self.result = raw.map do |row|
10
+ time, msg = row.split(' ', 2)
11
+
12
+ {
13
+ time: Time.parse(time),
14
+ msg: msg
15
+ }
16
+ end
17
+ end
18
+ # ==========================================================================
19
+ end
20
+ end
21
+
22
+ # def time_space
23
+ # IO.foreach(file) do |row|
24
+ # time, msg = row.split(' ', 2)
25
+ # result = {
26
+ # time: parse_datetime(time), msg: msg, host: thing.host,
27
+ # source: path_format
28
+ # }
29
+
30
+ # result[:time] ||= datetime
31
+ # post result
32
+ # rescue StandardError => e
33
+ # puts "Unable to Parse, #{name}:#{e.message}"
34
+ # post(time: datetime, msg: row, source: path_format, host: thing.host)
35
+ # end
36
+ # end
@@ -0,0 +1,71 @@
1
+ # Global Namespace
2
+ module GreenHat
3
+ # Accessors / Readers
4
+ module ThingHelpers
5
+ # Console Helper
6
+ def inspect
7
+ [
8
+ 'Thing'.colorize(:light_black),
9
+ kind&.to_s&.colorize(:blue),
10
+ type&.colorize(:light_yellow),
11
+ name&.colorize(:cyan)
12
+ ].compact.join(' ')
13
+ end
14
+
15
+ # Format the name of this thing
16
+ def build_path(divider = '_')
17
+ tmp_path = file.gsub("#{archive.path}/", '')
18
+
19
+ case tmp_path.count('/')
20
+ when 0
21
+ tmp_path
22
+ when 1
23
+ tmp_path.split('/').last
24
+ else
25
+ tmp_path.split('/').last(2).join(divider)
26
+ end
27
+ end
28
+
29
+ # Check what kind of file we have
30
+ def type_check
31
+ if info?
32
+ :info
33
+ elsif SuperLog.type?(path)
34
+ :log
35
+ else
36
+ :raw
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # Pretty sure this is a bad idea to patch string.
43
+ # https://mentalized.net/journal/2011/04/14/ruby-how-to-check-if-a-string-is-numeric/
44
+ class String
45
+ def numeric?
46
+ !Float(self).nil?
47
+ rescue StandardError
48
+ false
49
+ end
50
+ end
51
+
52
+ # Shims
53
+ class Float
54
+ def numeric?
55
+ true
56
+ end
57
+ end
58
+
59
+ # Shims
60
+ class TrueClass
61
+ def to_i
62
+ 1
63
+ end
64
+ end
65
+
66
+ # Shims
67
+ class FalseClass
68
+ def to_i
69
+ 0
70
+ end
71
+ end
@@ -0,0 +1,51 @@
1
+ module GreenHat
2
+ # Helper for Remembering what files were
3
+ module ThingHistory
4
+ def self.files
5
+ @files ||= read
6
+
7
+ @files
8
+ end
9
+
10
+ # Read File / Remove Old Entries
11
+ def self.read
12
+ if File.exist? file
13
+ results = Oj.load File.read(file)
14
+
15
+ results.reject { |_k, v| Time.at(v.time) < Time.now }
16
+ else
17
+ {}
18
+ end
19
+ ensure
20
+ {}
21
+ end
22
+
23
+ def self.file
24
+ Settings.history_file
25
+ end
26
+
27
+ # Initial Entry Point
28
+ def self.match?(name)
29
+ files.key? name.to_sym
30
+ end
31
+
32
+ def self.match(name)
33
+ files.dig(name.to_sym, :type)
34
+ end
35
+
36
+ def self.add(name, type)
37
+ files[name] = { type: type, time: expire }
38
+
39
+ write
40
+ end
41
+
42
+ # Date to Expire
43
+ def self.expire
44
+ 2.weeks.from_now.to_i
45
+ end
46
+
47
+ def self.write
48
+ File.write(file, Oj.dump(files))
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,193 @@
1
+ module GreenHat
2
+ # Info Formatter
3
+ module InfoFormat
4
+ # Is this something that can be formatted by info?
5
+ def info?
6
+ methods.include? "format_#{path}".to_sym
7
+ end
8
+
9
+ # Handle Info Formatting
10
+ def info_format
11
+ self.result = send("format_#{path}")
12
+ end
13
+
14
+ def format_headers_n_lines
15
+ # Headers to Readable Symbol
16
+ headers = raw.first.split(' ', 6).map(&:downcase).map do |x|
17
+ x.gsub(/\s+/, '_').gsub(/[^0-9A-Za-z_]/, '')
18
+ end.map(&:to_sym)
19
+
20
+ output = []
21
+
22
+ # Put fields into a Hash based on Location/Key
23
+ raw[1..].map(&:split).each do |row|
24
+ result = {}
25
+ row.each_with_index do |detail, i|
26
+ result[headers[i]] = detail
27
+ end
28
+ output.push result
29
+ end
30
+
31
+ self.result = output
32
+ end
33
+
34
+ # All Similar to Header and Lines
35
+ alias format_df_inodes format_headers_n_lines
36
+ alias format_df_h format_headers_n_lines
37
+
38
+ def format_mount(data)
39
+ data.select { |x| x.include? ':' }
40
+ end
41
+
42
+ def format_free_m
43
+ split_white_space raw
44
+ end
45
+
46
+ def format_meminfo
47
+ raw.map { |x| x.split(' ', 2) }
48
+ end
49
+
50
+ def format_netstat_conn_headers
51
+ [
52
+ 'Proto', 'Recv-Q', 'Send-Q', 'Local Address', 'Foreign Address', 'State', 'PID/Program name'
53
+ ]
54
+ end
55
+
56
+ def format_netstat_socket_headers
57
+ [
58
+ 'Proto', 'RefCnt', 'Flags', 'Type', 'State', 'I-Node', 'PID/Program name', 'Path'
59
+ ]
60
+ end
61
+
62
+ def format_netstat
63
+ # Data can return blank or single item array
64
+ conns, sockets = raw.split { |x| x.include? 'Active' }.reject(&:empty?)
65
+
66
+ formatted_conns = conns[1..].map do |entry|
67
+ entry.split(' ', 7).map(&:strip).each_with_index.map do |field, idx|
68
+ [format_netstat_conn_headers[idx], field]
69
+ end.to_h
70
+ end
71
+
72
+ formatted_sockets = sockets[1..].map do |entry|
73
+ entry.split(' ').map(&:strip).reject(&:blank?).each_with_index.map do |field, idx|
74
+ [format_netstat_socket_headers[idx], field]
75
+ end.to_h
76
+ end
77
+
78
+ {
79
+ connections: formatted_conns,
80
+ sockets: formatted_sockets
81
+ }
82
+ end
83
+
84
+ def format_netstat_i
85
+ headers = raw[1].split
86
+ result = raw[2..].map do |row|
87
+ row.split.each_with_index.map do |x, idx|
88
+ [headers[idx], x]
89
+ end
90
+ end
91
+
92
+ result.map(&:to_h)
93
+ end
94
+
95
+ def split_white_space(data)
96
+ data.map(&:split)
97
+ end
98
+
99
+ def ps_format
100
+ headers = info.ps.first.split(' ', 11)
101
+ list = info.ps[1..].each.map do |row|
102
+ row.split(' ', 11).each_with_index.each_with_object({}) do |(v, i), obj|
103
+ obj[headers[i]] = v
104
+ end
105
+ end
106
+ { headers: headers, list: list }
107
+ end
108
+
109
+ def manifest_json_format
110
+ Oj.load info[:gitlab_version_manifest_json].join
111
+ end
112
+
113
+ def cpuinfo_format
114
+ info.cpuinfo.join("\n").split("\n\n").map do |cpu|
115
+ all = cpu.split("\n").map do |row|
116
+ row.delete("\t").split(': ')
117
+ end
118
+ { details: all[1..], order: all[0].last }
119
+ end
120
+ end
121
+
122
+ def cpu_speed
123
+ return nil unless data? :lscpu
124
+
125
+ info.lscpu.find { |x| x.include? 'MHz' }.split(' ')
126
+ end
127
+
128
+ def total_memory
129
+ return nil unless data? :free_m
130
+
131
+ value = info.free_m.dig(1, 1).to_i
132
+ number_to_human_size(value * 1024 * 1024)
133
+ end
134
+
135
+ def ulimit
136
+ return nil unless data? :ulimit
137
+
138
+ results = info.ulimit.map do |entry|
139
+ {
140
+ value: entry.split[-1],
141
+ details: entry.split(' ').first
142
+ }
143
+ end
144
+
145
+ results.sort_by { |x| x[:details].downcase }
146
+ end
147
+
148
+ def systemctl_format
149
+ return nil unless data? :systemctl_unit_files
150
+
151
+ all = info.systemctl_unit_files[1..-2].map do |x|
152
+ unit, status = x.split
153
+ { unit: unit, status: status }
154
+ end
155
+ all.reject! { |x| x[:unit].nil? }
156
+ all.sort_by(&:unit)
157
+ end
158
+
159
+ # Helper to color the status files
160
+ def systemctl_color(entry)
161
+ case entry.status
162
+ when 'enabled' then :green
163
+ when 'static' then :orange
164
+ when 'disabled' then :red
165
+ else
166
+ :grey
167
+ end
168
+ end
169
+
170
+ def vmstat_format
171
+ return nil unless data? :vmstat
172
+
173
+ info.vmstat[2..].map(&:split)
174
+ end
175
+
176
+ def uptime
177
+ info.uptime.join.split(',', 4).map(&:lstrip) if info.key? :uptime
178
+ end
179
+
180
+ def mount
181
+ return nil unless info.key? :mount
182
+ return nil if info.mount.empty?
183
+
184
+ MountFormat.parse(info.mount)
185
+ end
186
+
187
+ def format_single_json
188
+ Oj.load raw.join("\n")
189
+ end
190
+
191
+ alias format_gitlab_version_manifest_json format_single_json
192
+ end
193
+ end
@@ -0,0 +1,97 @@
1
+ module GreenHat
2
+ # Overall Type Parsing
3
+ module Kind
4
+ # rubobop:disable *
5
+ def kind_collect
6
+ # If Direct Match
7
+ if types.key? name
8
+ self.type = name
9
+
10
+ return true
11
+ end
12
+
13
+ # Check Pattern Matches
14
+ matches = types.select do |_k, v|
15
+ v.pattern.any? { |x| x =~ name }
16
+ end
17
+
18
+ # If there is only one match
19
+ if matches.keys.count == 1
20
+ self.type = matches.keys.first
21
+
22
+ true
23
+
24
+ # TODO: Prompt for smaller selection
25
+ elsif matches.keys.count > 1
26
+ puts 'Multiple!'
27
+ # History Match
28
+ elsif ThingHistory.match? name
29
+ self.type = ThingHistory.match(name)
30
+ else
31
+ prompt_for_kind
32
+ end
33
+ end
34
+
35
+ # File Identifier
36
+ def kind_setup
37
+ self.kind = types.dig(type, :format)
38
+ self.log = types.dig(type, :log)
39
+ end
40
+
41
+ def prompt
42
+ # Quiet Exit
43
+ @prompt ||= TTY::Prompt.new(interrupt: :exit)
44
+ end
45
+
46
+ def check_oj_parse?(first_line)
47
+ Oj.load(first_line)
48
+ true
49
+ rescue StandardError
50
+ false
51
+ end
52
+
53
+ # rubocop:disable Style/SymbolProc
54
+ def prompt_for_kind
55
+ binding.pry if ENV['DEBUG']
56
+
57
+ # Default to everything
58
+ prompt_list = types.clone
59
+
60
+ first_line = File.open(file) { |f| f.readline }
61
+ json = check_oj_parse?(first_line)
62
+
63
+ if json
64
+ prompt_list.select! do |_k, v|
65
+ v.to_s.include? 'json'
66
+ end
67
+ end
68
+
69
+ puts "Unable to determine file type for #{name.colorize(:yellow)}"
70
+ puts "Use '#{'json'.colorize(:cyan)}' and '#{'raw'.colorize(:cyan)}' if you cannot find any matches"
71
+
72
+ option = prompt.select('Wat is this?', prompt_list.keys.sort_by(&:length), filter: true)
73
+
74
+ # Store for later
75
+ ThingHistory.add(name, option)
76
+
77
+ self.type = option
78
+ end
79
+ # rubocop:enable Style/SymbolProc
80
+
81
+ # Pattern Match / Look for `match_` patterns and then strip name for kind type
82
+ def kind_pattern_match(name)
83
+ pattern_match = methods.grep(/^match_/).find do |pattern|
84
+ send(pattern).any? { |x| name =~ x }
85
+ end
86
+
87
+ if pattern_match
88
+ pattern_match.to_s.split('match_', 2).last.to_sym
89
+ else
90
+ false
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # Load All Formatters
97
+ require_all "#{File.dirname(__FILE__)}/formatters"