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,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"