greenhat 0.3.3 → 0.4.0

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/bin/greenhat +1 -4
  3. data/lib/greenhat/accessors/disk.rb +42 -3
  4. data/lib/greenhat/accessors/gitlab.rb +30 -1
  5. data/lib/greenhat/accessors/memory.rb +1 -1
  6. data/lib/greenhat/archive.rb +19 -8
  7. data/lib/greenhat/cli.rb +24 -126
  8. data/lib/greenhat/entrypoint.rb +170 -0
  9. data/lib/greenhat/host.rb +25 -37
  10. data/lib/greenhat/settings.rb +6 -0
  11. data/lib/greenhat/shell/args.rb +22 -9
  12. data/lib/greenhat/shell/faststats.rb +23 -3
  13. data/lib/greenhat/shell/field_helper.rb +1 -1
  14. data/lib/greenhat/shell/filter_help.rb +232 -9
  15. data/lib/greenhat/shell/log.rb +153 -9
  16. data/lib/greenhat/shell/markdown.rb +352 -0
  17. data/lib/greenhat/shell/old_search_helper.rb +54 -0
  18. data/lib/greenhat/shell/page.rb +1 -1
  19. data/lib/greenhat/shell/pipe.rb +31 -0
  20. data/lib/greenhat/shell/platform.rb +28 -0
  21. data/lib/greenhat/shell/report.rb +106 -25
  22. data/lib/greenhat/shell/shell_helper.rb +115 -106
  23. data/lib/greenhat/shell.rb +10 -3
  24. data/lib/greenhat/thing/file_types.rb +136 -8
  25. data/lib/greenhat/thing/formatters/json.rb +4 -0
  26. data/lib/greenhat/thing/formatters/kube_json.rb +36 -0
  27. data/lib/greenhat/thing/formatters/kube_nginx.rb +48 -0
  28. data/lib/greenhat/thing/formatters/kube_webservice.rb +51 -0
  29. data/lib/greenhat/thing/formatters/nginx.rb +6 -2
  30. data/lib/greenhat/thing/formatters/registry.rb +47 -0
  31. data/lib/greenhat/thing/formatters/time_space.rb +0 -16
  32. data/lib/greenhat/thing/helpers.rb +12 -0
  33. data/lib/greenhat/thing/kind.rb +5 -0
  34. data/lib/greenhat/thing.rb +11 -0
  35. data/lib/greenhat/version.rb +1 -1
  36. data/lib/greenhat/views/api.slim +55 -0
  37. data/lib/greenhat/views/chart.slim +42 -0
  38. data/lib/greenhat/views/chart_template.slim +31 -0
  39. data/lib/greenhat/views/chartkick.js +21 -0
  40. data/lib/greenhat/views/css.slim +47 -0
  41. data/lib/greenhat/views/gitaly.slim +53 -0
  42. data/lib/greenhat/views/headers.slim +16 -0
  43. data/lib/greenhat/views/index-old.slim +51 -0
  44. data/lib/greenhat/views/index.slim +14 -14
  45. data/lib/greenhat/views/info.slim +17 -18
  46. data/lib/greenhat/views/production.slim +55 -0
  47. data/lib/greenhat/views/sidekiq.slim +55 -0
  48. data/lib/greenhat/views/time.slim +63 -0
  49. data/lib/greenhat/views/workhorse.slim +16 -0
  50. data/lib/greenhat/web/api.rb +94 -0
  51. data/lib/greenhat/web/chartkick_shim.rb +14 -0
  52. data/lib/greenhat/web/faststats.rb +44 -0
  53. data/lib/greenhat/web/gitaly.rb +65 -0
  54. data/lib/greenhat/web/helpers.rb +198 -0
  55. data/lib/greenhat/web/production.rb +104 -0
  56. data/lib/greenhat/web/sidekiq.rb +73 -0
  57. data/lib/greenhat/web/stats_helpers.rb +74 -0
  58. data/lib/greenhat/web/time.rb +36 -0
  59. data/lib/greenhat/web/workhorse.rb +43 -0
  60. data/lib/greenhat/web.rb +63 -19
  61. data/lib/greenhat.rb +2 -0
  62. metadata +78 -5
@@ -2,6 +2,7 @@ module GreenHat
2
2
  # CLI Helper
3
3
  module Shell
4
4
  # Logs
5
+ # rubocop:disable Metrics/ModuleLength
5
6
  module Log
6
7
  def self.auto_complete(list, word)
7
8
  # Argument Parsing
@@ -47,6 +48,7 @@ module GreenHat
47
48
  puts ' filter'.pastel(:green)
48
49
  puts " Primary way for log searching within greenhat. See #{'filter_help'.pastel(:blue)}"
49
50
  puts ' Time, round, slice/except, and/or, stats, uniq, sort'
51
+ puts " #{'filter_help'.pastel(:blue)} supports filtering Ex: #{'filter_help stats'.pastel(:blue)}"
50
52
  puts
51
53
 
52
54
  puts ' show'.pastel(:green)
@@ -57,13 +59,33 @@ module GreenHat
57
59
  puts " General full text by file searching. See #{'search_help'.pastel(:blue)}"
58
60
  puts
59
61
 
62
+ puts ' save'.pastel(:green)
63
+ puts ' Save the last query result into a new searchable object'
64
+ puts
65
+
66
+ puts ' write'.pastel(:green)
67
+ puts ' Write the last query result into a local file'
68
+ puts
69
+
70
+ puts ' visualize'.pastel(:green)
71
+ puts ' Load web services and formulate last query for the UI'
72
+ puts
73
+
60
74
  puts ShellHelper::List.help
61
75
 
62
76
  puts "See #{'examples'.pastel(:bright_blue)} for query examples"
63
77
  end
64
78
 
65
- def self.filter_help
66
- ShellHelper::Filter.help
79
+ def self.filter_help(args = {})
80
+ if args.empty?
81
+ ShellHelper::Filter.help
82
+ else
83
+ list = ShellHelper::Filter.help_index.select do |k, _v|
84
+ k.to_s.include? args.first
85
+ end
86
+
87
+ puts list.values.map { |x| x.join("\n") }.join("\n\n")
88
+ end
67
89
  end
68
90
 
69
91
  def self.ls(args = [])
@@ -80,6 +102,95 @@ module GreenHat
80
102
  ShellHelper.show files.map(&:data).flatten
81
103
  end
82
104
 
105
+ def self.save(raw = [])
106
+ if ShellHelper::Log.last.nil?
107
+ puts 'No previous query found'.pastel(:red)
108
+ puts 'Run a query first then save to store as a new log'
109
+ puts
110
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
111
+ true
112
+ end
113
+
114
+ name = if raw.empty?
115
+ Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
116
+ else
117
+ raw.first
118
+ end
119
+
120
+ if name.blank?
121
+ puts 'Name required'.pastel(:red)
122
+ return true
123
+ end
124
+
125
+ results = ShellHelper.filter_internal ShellHelper::Log.last
126
+
127
+ # Don't save empty results
128
+ if results.empty?
129
+ puts 'No results'.pastel(:red)
130
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
131
+ return false
132
+ end
133
+
134
+ Thing.new.query_save(results, name)
135
+ puts "#{name.pastel(:green)} Saved!"
136
+ end
137
+
138
+ def self.write(raw = [])
139
+ if ShellHelper::Log.last.nil?
140
+ puts 'No previous query found'.pastel(:red)
141
+ puts 'Run a query first then write'
142
+ puts
143
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
144
+ true
145
+ end
146
+
147
+ name = if raw.empty?
148
+ Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
149
+ else
150
+ raw.first
151
+ end
152
+
153
+ if name.blank?
154
+ puts 'Name required'.pastel(:red)
155
+ return true
156
+ end
157
+
158
+ results = ShellHelper.filter_internal ShellHelper::Log.last
159
+
160
+ # Don't save empty results
161
+ if results.empty?
162
+ puts 'No results'.pastel(:red)
163
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
164
+ return false
165
+ end
166
+
167
+ all = results.map { |row| Oj.dump(row) }
168
+ File.write(name, all.join("\n"))
169
+ puts "#{name.pastel(:green)} File Written!"
170
+ end
171
+
172
+ def self.visualize
173
+ if ShellHelper::Log.last.nil?
174
+ puts 'No previous query found'.pastel(:red)
175
+ puts 'Run a query first then visualize to load it in the chart web page'
176
+ puts
177
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'visualize'.pastel(:green)}"
178
+ return true
179
+ end
180
+
181
+ # Load Required Files
182
+ require 'greenhat/web'
183
+
184
+ unless GreenHat::Web.alive?
185
+ GreenHat::Web.start
186
+ sleep 0.2
187
+ end
188
+
189
+ url = "http://localhost:4567/chart/time?query=#{CGI.escape(ShellHelper::Log.last)}"
190
+
191
+ GreenHat::Platform.open url
192
+ end
193
+
83
194
  # ========================================================================
84
195
  # Filter (See Filter Help)
85
196
  # ========================================================================
@@ -94,6 +205,8 @@ module GreenHat
94
205
  return true
95
206
  end
96
207
 
208
+ ShellHelper::Log.last = raw
209
+
97
210
  # Argument Parsing
98
211
  files, flags, args = Args.parse(raw)
99
212
 
@@ -104,7 +217,7 @@ module GreenHat
104
217
 
105
218
  # Skip and Print Total if set
106
219
  if flags[:total]
107
- ShellHelper.total_count(results)
220
+ ShellHelper.total_count(results, flags)
108
221
  return true
109
222
  end
110
223
 
@@ -117,13 +230,13 @@ module GreenHat
117
230
  # Check Search Results
118
231
  if results.instance_of?(Hash) && results.values.flatten.empty?
119
232
  puts 'No results'.pastel(:red)
233
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
234
+ elsif flags[:pipe]
235
+ Pipe.show(results, flags[:pipe])
120
236
  else
121
237
  # This causes the key 'colorized' output to also be included
122
238
  ShellHelper.show(results.to_a.compact.flatten, flags)
123
239
  end
124
-
125
- # log filter --path='cloud/gitlab-automation' --path='/pull' --all
126
- # log filter --project=thingy --other_filter=asdf *
127
240
  rescue StandardError => e
128
241
  LogBot.fatal('Filter', message: e.message)
129
242
  ap e.backtrace
@@ -152,9 +265,25 @@ module GreenHat
152
265
  puts 'Count/% occurences for both user and remote ip fields'.pastel(:bright_green)
153
266
  puts 'gitlab-rails/api_json.log --stats=meta.user,meta.remote_ip --exists=meta.user'
154
267
  puts
155
- end
156
268
 
269
+ puts 'Sidekiq jobs that took over 5 seconds excluding LdapSyncWorker jobs'.pastel(:bright_green)
270
+ puts 'sidekiq/current --duration_s>=5 --class!=LdapSyncWorker'
271
+ puts
272
+
273
+ puts 'Search access logs for runner requests, exclude specific runner version'.pastel(:bright_green)
274
+ puts 'nginx/gitlab_access.log --http_user_agent=gitlab-runner --http_user_agent!=13.12.0'
275
+ puts
276
+
277
+ puts 'Get a list of unique Gitaly error messages for a specific project'.pastel(:bright_green)
278
+ puts 'filter --level=error --grpc.request.glProjectPath=path/to/project gitaly/current --slice=error --uniq=error'
279
+ puts
280
+
281
+ puts 'Show workhorse duration/URI. Filter by duration bounds'.pastel(:bright_green)
282
+ puts 'gitlab-workhorse/current --duration_ms>=30000 --duration_ms<=45000 --slice=duration_ms,uri'
283
+ puts
284
+ end
157
285
  # rubocop:enable Layout/LineLength
286
+
158
287
  # ========================================================================
159
288
  # Search (Full Text / String Search)
160
289
  # ========================================================================
@@ -169,13 +298,15 @@ module GreenHat
169
298
 
170
299
  # Skip and Print Total if set
171
300
  if flags[:total]
172
- ShellHelper.total_count(results)
301
+ ShellHelper.total_count(results, flags)
173
302
  return true
174
303
  end
175
304
 
176
305
  # Check Search Results
177
306
  if results.values.flatten.empty?
178
307
  puts 'No results'.pastel(:red)
308
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
309
+
179
310
  else
180
311
  # This causes the key 'colorized' output to also be included
181
312
  ShellHelper.show(results.to_a.compact.flatten, flags)
@@ -240,7 +371,7 @@ module GreenHat
240
371
  puts 'log search --text=BuildHooksWorker --text!=start --slice=enqueued_at sidekiq/current'
241
372
  puts
242
373
  end
243
- # rubocop:enable Metrics/MethodLength
374
+ # rubocop:enable Metrics/MethodLength,Metrics/ModuleLength
244
375
 
245
376
  # ------------------------------------------------------------------------
246
377
  end
@@ -251,9 +382,22 @@ module GreenHat
251
382
  module ShellHelper
252
383
  # Log Helpers
253
384
  module Log
385
+ def self.last=(value)
386
+ @last = value.join(' ')
387
+ end
388
+
389
+ def self.last
390
+ @last
391
+ end
392
+
254
393
  def self.list
255
394
  Thing.all.select(&:log)
256
395
  end
396
+
397
+ def self.no_files_warning(files)
398
+ puts "No matching files found for pattern #{files.to_s.pastel(:yellow)}"
399
+ puts "See #{'ls'.pastel(:blue)} for available files"
400
+ end
257
401
  end
258
402
  # --------
259
403
  end
@@ -0,0 +1,352 @@
1
+ module GreenHat
2
+ # Root Level Shell / Report Helper
3
+ module Shell
4
+ def self.markdown_report(raw)
5
+ _files, flags, _args = Args.parse(raw)
6
+
7
+ archives = if flags.archive
8
+ Archive.all.select do |archive|
9
+ flags.archive.any? { |x| archive.name.include? x.to_s }
10
+ end
11
+ else
12
+ Archive.all
13
+ end
14
+
15
+ ShellHelper.show(archives.map(&:report_markdown).map(&:show).flatten, flags)
16
+ end
17
+ end
18
+ end
19
+
20
+ module GreenHat
21
+ # Report Generator Helper
22
+ # rubocop:disable Metrics/ClassLength
23
+ class ReportMarkdown
24
+ include ActionView::Helpers::NumberHelper
25
+
26
+ attr_accessor :archive, :host, :os_release, :selinux_status, :cpu, :uname,
27
+ :timedatectl, :uptime, :meminfo, :gitlab_manifest, :gitlab_status,
28
+ :production_log, :api_log, :application_log, :sidekiq_log,
29
+ :exceptions_log, :gitaly_log, :free_m, :disk_free
30
+
31
+ # Find Needed Files for Report
32
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
33
+ def initialize(archive)
34
+ self.archive = archive
35
+ self.host = archive.things.find { |x| x.name == 'hostname' }
36
+ self.os_release = archive.things.find { |x| x.name == 'etc/os-release' }
37
+ self.selinux_status = archive.things.find { |x| x.name == 'sestatus' }
38
+ self.cpu = archive.things.find { |x| x.name == 'lscpu' }
39
+ self.uname = archive.things.find { |x| x.name == 'uname' }
40
+ self.timedatectl = archive.things.find { |x| x.name == 'timedatectl' }
41
+ self.uptime = archive.things.find { |x| x.name == 'uptime' }
42
+ self.meminfo = archive.things.find { |x| x.name == 'meminfo' }
43
+ self.free_m = archive.things.find { |x| x.name == 'free_m' }
44
+ self.gitlab_manifest = archive.things.find { |x| x.name == 'gitlab/version-manifest.json' }
45
+ self.gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
46
+ self.production_log = archive.things.find { |x| x.name == 'gitlab-rails/production_json.log' }
47
+ self.api_log = archive.things.find { |x| x.name == 'gitlab-rails/api_json.log' }
48
+ self.application_log = archive.things.find { |x| x.name == 'gitlab-rails/application_json.log' }
49
+ self.exceptions_log = archive.things.find { |x| x.name == 'gitlab-rails/exceptions_json.log' }
50
+ self.gitaly_log = archive.things.find { |x| x.name == 'gitaly/current' }
51
+ self.sidekiq_log = archive.things.find { |x| x.name == 'sidekiq/current' }
52
+ self.disk_free = archive.things.find { |x| x.name == 'df_h' }
53
+ end
54
+
55
+ def show
56
+ output = [
57
+ archive.friendly_name,
58
+ ''
59
+ ]
60
+
61
+ # GitLab Version
62
+ output << "**GitLab #{gitlab_version}**\n" if gitlab_manifest || gitlab_status
63
+
64
+ # OS
65
+ output << "**OS**\n"
66
+
67
+ output << collect_host
68
+ output << ''
69
+
70
+ # Memory
71
+ if meminfo || free_m
72
+ output << "**Memory**\n"
73
+ # output << memory_perc if meminfo
74
+ output << memory_free if free_m
75
+ output << ''
76
+ end
77
+
78
+ # Disk
79
+ if disk_free
80
+ output << disks
81
+ output << ''
82
+ end
83
+
84
+ # Gitlab
85
+ output << gitlab_services if gitlab_status
86
+
87
+ output << ''
88
+
89
+ output << "**Errors**\n" if production_log || api_log || application_log || sidekiq_log
90
+ output << collect_errors
91
+
92
+ # Final Space / Return
93
+ output << ''
94
+ output
95
+ end
96
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
97
+
98
+ def collect_host
99
+ output = []
100
+ output << hostname if host
101
+ output << distro if os_release
102
+ output << selinux if selinux_status
103
+ # output << arch if cpu
104
+ output << kernel if uname
105
+ output << sys_time if timedatectl
106
+ output << sys_uptime if uptime
107
+ output << load_average if uptime && cpu
108
+
109
+ groups = output.each_slice((output.size / 2.to_f).round).to_a
110
+
111
+ table = TTY::Table.new do |t|
112
+ loop do
113
+ break if groups.all?(&:empty?)
114
+
115
+ t << groups.map(&:shift)
116
+ end
117
+ end
118
+
119
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
120
+ end
121
+
122
+ def collect_errors
123
+ output = []
124
+ output << production_errors if production_log
125
+ output << application_errors if application_log
126
+ output << sidekiq_errors if sidekiq_log
127
+ output << api_errors if api_log
128
+ output << exception_errors if exceptions_log
129
+ output << gitaly_errors if gitaly_log
130
+
131
+ # Keep Alphabetical Sort / Allow for only one
132
+ slice_size = (output.size / 3.to_f).round
133
+ slice_size = 1 unless slice_size.positive?
134
+
135
+ groups = output.each_slice(slice_size).to_a
136
+
137
+ table = TTY::Table.new do |t|
138
+ loop do
139
+ break if groups.all?(&:empty?)
140
+
141
+ t << groups.map(&:shift)
142
+ end
143
+ end
144
+
145
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
146
+ end
147
+
148
+ def exception_errors
149
+ count = exceptions_log.data.count
150
+
151
+ "Exception: #{count}"
152
+ end
153
+
154
+ def gitaly_errors
155
+ count = gitaly_log.data.count { |x| x.level == 'error' }
156
+
157
+ "Gitaly: #{count}"
158
+ end
159
+
160
+ def production_errors
161
+ count = production_log.data.count { |x| x.status == 500 }
162
+
163
+ "Production: #{count}"
164
+ end
165
+
166
+ def api_errors
167
+ count = api_log.data.count { |x| x.status == 500 }
168
+
169
+ "API: #{count}"
170
+ end
171
+
172
+ def application_errors
173
+ results = ShellHelper.filter_internal([
174
+ 'gitlab-rails/application_json.log',
175
+ '--message!="Cannot obtain an exclusive lease"',
176
+ '--severity=error',
177
+ "--archive=#{archive.name}"
178
+ ].join(' '))
179
+
180
+ "Application: #{results.count}"
181
+ end
182
+
183
+ def sidekiq_errors
184
+ count = sidekiq_log.data.count { |x| x&.severity == 'ERROR' }
185
+
186
+ "Sidekiq: #{count}"
187
+ end
188
+
189
+ def gitlab_services
190
+ [
191
+ "**Services**\n",
192
+ "\n",
193
+ GreenHat::GitLab.services_markdown(archive)
194
+ ].join
195
+ rescue StandardError => e
196
+ LogBot.fatal('GitLab Services', message: e.message, backtrace: e.backtrace.first)
197
+ end
198
+
199
+ def gitlab_version
200
+ txt = gitlab_manifest.data.dig(:software, :'gitlab-rails', :display_version) || gitlab_manifest.data.build_version
201
+
202
+ if txt.include? '-ce'
203
+ txt += ' - [😱 CE](https://about.gitlab.com/support/statement-of-support.html#free-and-community-edition-users)!'
204
+ end
205
+
206
+ "Version: #{txt}"
207
+ end
208
+
209
+ def hostname
210
+ "Hostname: #{host.data.first}"
211
+ end
212
+
213
+ def distro
214
+ [
215
+ "Distro: [#{os_release.data.ID}] ",
216
+ os_release.data.PRETTY_NAME
217
+ ].join
218
+ end
219
+
220
+ def selinux
221
+ status = selinux_status.data['SELinux status']
222
+
223
+ [
224
+ 'SeLinux: ',
225
+ status,
226
+ ' (',
227
+ selinux_status.data['Current mode'],
228
+ ')'
229
+ ].join
230
+ end
231
+
232
+ def arch
233
+ [
234
+ 'Arch: ',
235
+ cpu.data.Architecture
236
+ ].join
237
+ end
238
+
239
+ def kernel
240
+ # TODO: Better way to consistently get uname info?
241
+ value, build = uname.data.first.split[2].split('-')
242
+ [
243
+ 'Kernel: ',
244
+ value,
245
+ " (#{build})"
246
+ ].join
247
+ end
248
+
249
+ # Helper for finding if NTP is enabled
250
+ def ntp_keys
251
+ [
252
+ 'Network time on', 'NTP enabled', 'NTP service', 'System clock synchronized'
253
+ ]
254
+ end
255
+
256
+ def sys_time
257
+ # Ignore if Empty
258
+ return false if timedatectl.data.nil?
259
+
260
+ ntp_statuses = timedatectl.data.slice(*ntp_keys).values.compact
261
+
262
+ ntp_status = ntp_statuses.first
263
+
264
+ # Fall Back
265
+ ntp_status ||= 'unknown'
266
+
267
+ [
268
+ 'Sys Time: ',
269
+ timedatectl.data['Local time'],
270
+ "(ntp: #{ntp_status})"
271
+ ].join
272
+ end
273
+
274
+ # Strip/Simplify Uptime
275
+ def sys_uptime
276
+ init = uptime.data.first.split(', load average').first.strip
277
+
278
+ "Uptime: #{init.split('up ', 2).last}"
279
+ end
280
+
281
+ def load_average
282
+ cpu_count = cpu.data['CPU(s)'].to_i
283
+ intervals = uptime.data.first.split('load average: ', 2).last.split(', ').map(&:to_f)
284
+
285
+ # Generate Colorized Text for Output
286
+ intervals_text = intervals.map do |interval|
287
+ value = percent(interval, cpu_count)
288
+
289
+ "#{interval} (#{value}%)"
290
+ end
291
+
292
+ [
293
+ 'LoadAvg: ',
294
+ "[CPU #{cpu_count}] ",
295
+ intervals_text.join(', ')
296
+ ].join
297
+ end
298
+
299
+ def memory_free
300
+ free = free_m.data.find { |x| x.kind == 'Mem' }
301
+
302
+ return unless free
303
+
304
+ pad = 6
305
+ list = [
306
+ "#{title('Total', pad)} #{number_to_human_size(free.total.to_i * (1024**2))}",
307
+ "#{title('Used', pad)} #{number_to_human_size(free.used.to_i * (1024**2))}",
308
+ "#{title('Free', pad)} #{number_to_human_size(free.free.to_i * (1024**2))}",
309
+ "#{title('Avail', pad)} #{number_to_human_size(free.available.to_i * (1024**2))}"
310
+ ]
311
+
312
+ # Keep Alphabetical Sort
313
+ groups = list.each_slice((list.size / 2.to_f).round).to_a
314
+
315
+ table = TTY::Table.new do |t|
316
+ loop do
317
+ break if groups.all?(&:empty?)
318
+
319
+ t << groups.map(&:shift)
320
+ end
321
+ end
322
+
323
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
324
+ end
325
+
326
+ def disks
327
+ # GreenHat::Disk.df({archive: []})
328
+ file = GreenHat::Disk.df({ archive: [archive.name] })
329
+
330
+ disk_list = GreenHat::Disk.markdown_format(file.first, false, 3)
331
+
332
+ # Preapre / Indent List
333
+ [
334
+ '**Disks**',
335
+ "\n\n```\n#{disk_list.join("\n")}\n```"
336
+ ].join
337
+ end
338
+
339
+ # ----------------------------
340
+ # Helpers
341
+ # ----------------------------
342
+ def percent(value, total)
343
+ ((value / total.to_f) * 100).round
344
+ end
345
+
346
+ # Helper to Make Cyan Titles
347
+ def title(name, ljust = 16)
348
+ "#{name}:".ljust(ljust)
349
+ end
350
+ end
351
+ # rubocop:enable Metrics/ClassLength
352
+ end
@@ -0,0 +1,54 @@
1
+ module GreenHat
2
+ # Deprecating search stuff
3
+ module ShellHelper
4
+ # Main Entry Point for Searching
5
+ # def self.search_start(log_list, filter_type, args, opts)
6
+ def self.search_start(files, flags, args)
7
+ # Convert to Things
8
+ logs = ShellHelper.find_things(files, flags)
9
+
10
+ logs.each_with_object({}) do |log, obj|
11
+ # Ignore Empty Results / No Thing
12
+ next if log&.data.blank?
13
+
14
+ obj[log.friendly_name] = ShellHelper.search(log.data, flags, args)
15
+
16
+ obj
17
+ end
18
+ end
19
+
20
+ # Generic Search Helper / String/Regex
21
+ def self.search(data, flags = {}, args = {})
22
+ results = data.clone.flatten.compact
23
+ results.select! do |row|
24
+ args.send(flags.logic) do |arg|
25
+ search_row(row, arg, flags)
26
+ end
27
+ end
28
+
29
+ # Strip Results if Slice is defined
30
+ results.map! { |row| row.slice(*flags[:slice]) } if flags[:slice]
31
+
32
+ # Strip Results if Except is defined
33
+ results.map! { |row| row.except(*flags[:except]) } if flags[:except]
34
+
35
+ # Remove Blank from either slice or except
36
+ results.reject!(&:empty?)
37
+
38
+ results
39
+ end
40
+
41
+ # Break out filter row logic into separate method
42
+ def self.search_row(row, arg, flags)
43
+ # Sensitivity Check / Check for Match
44
+ included = filter_row_entry(row.to_s, arg, flags)
45
+
46
+ # Pivot of off include vs exclude
47
+ if arg.bang
48
+ !included
49
+ else
50
+ included
51
+ end
52
+ end
53
+ end
54
+ end
@@ -8,7 +8,7 @@ module GreenHat
8
8
  # Pass if Explicitly Set / Inverse for skip
9
9
  return !flags[:page] if flags.key? :page
10
10
 
11
- LogBot.debug('Page', count_rows(data, flags)) if ENV['DEBUG']
11
+ LogBot.debug('Page Skip', count_rows(data, flags)) if ENV['DEBUG']
12
12
 
13
13
  count_rows(data, flags)
14
14
  end
@@ -0,0 +1,31 @@
1
+ module GreenHat
2
+ # Helper for piping arguments
3
+ module Pipe
4
+ def self.show(results, arg)
5
+ uuid = SecureRandom.uuid # File Placeholder
6
+ write(results, uuid)
7
+ command(uuid, arg)
8
+ delete(uuid)
9
+ end
10
+
11
+ # Final execution into pipe
12
+ def self.command(uuid, arg)
13
+ puts `cat "#{file(uuid)}" | #{arg}`
14
+ end
15
+
16
+ # File path helper
17
+ def self.file(uuid)
18
+ "#{$TMP}/#{uuid}.txt"
19
+ end
20
+
21
+ # Helper to write all into split lines for pipe
22
+ def self.write(results, uuid)
23
+ File.write(file(uuid), results.map(&:flatten).flatten.join("\n"))
24
+ end
25
+
26
+ # Clean up created file
27
+ def self.delete(uuid)
28
+ File.delete file(uuid)
29
+ end
30
+ end
31
+ end