greenhat 0.2.0 → 0.3.3
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.
- checksums.yaml +4 -4
- data/lib/greenhat/accessors/disk.rb +13 -13
- data/lib/greenhat/accessors/gitlab.rb +75 -0
- data/lib/greenhat/accessors/memory.rb +10 -10
- data/lib/greenhat/accessors/process.rb +4 -0
- data/lib/greenhat/cli.rb +147 -58
- data/lib/greenhat/color.rb +27 -0
- data/lib/greenhat/logbot.rb +9 -9
- data/lib/greenhat/settings.rb +27 -7
- data/lib/greenhat/shell/args.rb +146 -0
- data/lib/greenhat/shell/cat.rb +25 -73
- data/lib/greenhat/shell/color_string.rb +8 -8
- data/lib/greenhat/shell/disk.rb +31 -4
- data/lib/greenhat/shell/faststats.rb +103 -56
- data/lib/greenhat/shell/field_helper.rb +75 -0
- data/lib/greenhat/shell/filter_help.rb +162 -0
- data/lib/greenhat/shell/gitlab.rb +61 -2
- data/lib/greenhat/shell/help.rb +98 -15
- data/lib/greenhat/shell/list.rb +46 -0
- data/lib/greenhat/shell/log.rb +118 -104
- data/lib/greenhat/shell/page.rb +11 -5
- data/lib/greenhat/shell/process.rb +29 -17
- data/lib/greenhat/shell/report.rb +37 -47
- data/lib/greenhat/shell/shell_helper.rb +661 -0
- data/lib/greenhat/shell.rb +23 -9
- data/lib/greenhat/thing/file_types.rb +31 -5
- data/lib/greenhat/thing/formatters/json_shellwords.rb +0 -3
- data/lib/greenhat/thing/formatters/nginx.rb +44 -0
- data/lib/greenhat/thing/helpers.rb +4 -4
- data/lib/greenhat/thing/kind.rb +9 -2
- data/lib/greenhat/thing/spinner.rb +3 -3
- data/lib/greenhat/thing.rb +25 -3
- data/lib/greenhat/tty/columns.rb +4 -0
- data/lib/greenhat/version.rb +1 -1
- data/lib/greenhat.rb +15 -14
- metadata +38 -18
- data/lib/greenhat/shell/filter.rb +0 -128
- data/lib/greenhat/shell/helper.rb +0 -584
@@ -1,128 +0,0 @@
|
|
1
|
-
module GreenHat
|
2
|
-
# CLI Helper
|
3
|
-
module ShellHelper
|
4
|
-
# Unify Filter / Filter Help
|
5
|
-
module Filter
|
6
|
-
# rubocop:disable Metrics/MethodLength
|
7
|
-
def self.help
|
8
|
-
puts "\u2500".colorize(:cyan) * 20
|
9
|
-
puts 'Filter'.colorize(:yellow)
|
10
|
-
puts "\u2500".colorize(:cyan) * 20
|
11
|
-
|
12
|
-
puts 'Options'.colorize(:blue)
|
13
|
-
puts '--raw'.colorize(:green)
|
14
|
-
puts ' Do not use less/paging'
|
15
|
-
puts
|
16
|
-
|
17
|
-
puts '--page'.colorize(:green)
|
18
|
-
puts ' Specifically enable or disable paging'
|
19
|
-
puts ' E.g. --page (default to true), --page=true, --page=false'
|
20
|
-
puts
|
21
|
-
|
22
|
-
puts '--round'.colorize(:green)
|
23
|
-
puts ' Attempt to round all integers. Default: 2.'
|
24
|
-
puts ' E.g. --round, --round=3, --round=0'
|
25
|
-
puts
|
26
|
-
|
27
|
-
puts '--truncate'.colorize(:green)
|
28
|
-
puts ' Limit output values. Defaults to 4 lines. 0 to disable'
|
29
|
-
puts ' E.g. --truncate=100 or --truncate=0'
|
30
|
-
puts
|
31
|
-
|
32
|
-
puts '--json'.colorize(:green)
|
33
|
-
puts ' Print output back into JSON'
|
34
|
-
puts
|
35
|
-
|
36
|
-
puts '--or'.colorize(:green)
|
37
|
-
puts ' Filters will use OR instead of AND'
|
38
|
-
puts
|
39
|
-
|
40
|
-
puts '--total'.colorize(:green)
|
41
|
-
puts ' Print only total count of matching entries'
|
42
|
-
puts
|
43
|
-
|
44
|
-
puts '--slice'.colorize(:green)
|
45
|
-
puts ' Extract specific fields from entries (slice multiple with comma)'
|
46
|
-
puts ' Ex: --slice=path or --slice=path,params'
|
47
|
-
puts
|
48
|
-
|
49
|
-
puts '--except'.colorize(:green)
|
50
|
-
puts ' Exclude specific fields (except multiple with comma)'
|
51
|
-
puts ' Ex: --except=params --except=params,path'
|
52
|
-
puts
|
53
|
-
|
54
|
-
puts '--stats'.colorize(:green)
|
55
|
-
puts ' Order/Count occurrances by field'
|
56
|
-
puts ' Ex: --stats=params --except=params,path'
|
57
|
-
puts
|
58
|
-
|
59
|
-
puts '--uniq'.colorize(:green)
|
60
|
-
puts ' Show unique values only'
|
61
|
-
puts ' Ex: --uniq=params --uniq=params,path'
|
62
|
-
puts
|
63
|
-
|
64
|
-
puts '--pluck'.colorize(:green)
|
65
|
-
puts ' Extract values from entries'
|
66
|
-
puts ' Ex: --pluck=params --pluck=params,path'
|
67
|
-
puts
|
68
|
-
|
69
|
-
puts '--archive'.colorize(:green)
|
70
|
-
puts ' Limit to specific archvie name (inclusive). Matching SOS tar.gz name'
|
71
|
-
puts ' Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
|
72
|
-
puts
|
73
|
-
|
74
|
-
puts '--sort'.colorize(:green)
|
75
|
-
puts ' Sort by multiple fields'
|
76
|
-
puts ' Ex: --sort=duration_s,db_duration_s'
|
77
|
-
puts
|
78
|
-
|
79
|
-
puts '--reverse'.colorize(:green)
|
80
|
-
puts ' Reverse all results'
|
81
|
-
puts ' Ex: --reverse'
|
82
|
-
puts
|
83
|
-
|
84
|
-
puts '--combine'.colorize(:green)
|
85
|
-
puts ' Omit archive identifier dividers. Useful with sort or time filters'
|
86
|
-
puts ' Ex: --combine'
|
87
|
-
puts
|
88
|
-
|
89
|
-
puts '--start'.colorize(:green)
|
90
|
-
puts ' Show events after specified time. Filtered by the `time` field'
|
91
|
-
puts ' Use with `--end` for between selections'
|
92
|
-
puts ' Ex: log filter --start="2021-06-22 14:44 UTC" --end="2021-06-22 14:45 UTC"'
|
93
|
-
puts
|
94
|
-
|
95
|
-
puts '--end'.colorize(:green)
|
96
|
-
puts ' Show events before specified time. Filtered by the `time` field'
|
97
|
-
puts ' Use with `--start` for between selections'
|
98
|
-
puts ' Ex: log filter --end="2021-06-22"'
|
99
|
-
puts
|
100
|
-
|
101
|
-
puts '--truncate'.colorize(:green)
|
102
|
-
puts ' Truncate field length. On by default (4 rows). Performance issues!'
|
103
|
-
puts ' Disable with --truncate=0'.colorize(:light_red)
|
104
|
-
puts ' Ex: --truncate=200, --truncate=2048"'
|
105
|
-
puts
|
106
|
-
|
107
|
-
puts 'Field Searching'.colorize(:blue)
|
108
|
-
puts ' --[key]=[value]'
|
109
|
-
puts ' Search in key for value'
|
110
|
-
puts ' Example: --path=mirror/pull'
|
111
|
-
puts
|
112
|
-
|
113
|
-
puts 'Search specific logs'.colorize(:blue)
|
114
|
-
puts ' Any non dash parameters will be the log list to search from'
|
115
|
-
puts " Ex: log filter --path=api sidekiq/current (hint: use `#{'ls'.colorize(:yellow)}` for log names"
|
116
|
-
puts
|
117
|
-
|
118
|
-
puts 'Example Queries'.colorize(:blue)
|
119
|
-
puts " Also see #{'examples'.colorize(:light_blue)} for even more examples"
|
120
|
-
puts ' log filter --class=BuildFinishedWorker sidekiq/current --slice=time,message'
|
121
|
-
puts ' log filter gitlab-rails/api_json.log --slice=ua --uniq=ua --ua=gitlab-runner'
|
122
|
-
|
123
|
-
puts
|
124
|
-
end
|
125
|
-
# rubocop:enable Metrics/MethodLength
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
@@ -1,584 +0,0 @@
|
|
1
|
-
module GreenHat
|
2
|
-
# Common Helpers
|
3
|
-
# rubocop:disable Metrics/ModuleLength
|
4
|
-
module ShellHelper
|
5
|
-
# Generic Parameter Parsing
|
6
|
-
def self.param_parse(params, skip_args = [])
|
7
|
-
# Turn Params into Hash
|
8
|
-
opts = params.flat_map { |param| param_opt_scan(param) }
|
9
|
-
|
10
|
-
# Collect and Remove Args
|
11
|
-
args = params.each_with_object({}) { |param, obj| param_arg_scan(param, obj) }
|
12
|
-
|
13
|
-
# Move Special Arguments
|
14
|
-
opts.reject! do |opt|
|
15
|
-
if param_special_opts.include? opt.field
|
16
|
-
args[opt.field] = opt.value.split(',').map(&:to_sym)
|
17
|
-
true
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Remove Opts and Args
|
22
|
-
params.reject! do |param|
|
23
|
-
opt_field_remove?(opts, param) || arg_field_remove?(args, param)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Update other user defaults
|
27
|
-
Settings.default_log_args(args, skip_args)
|
28
|
-
|
29
|
-
[params, opts, args]
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.opt_field_remove?(opts, param)
|
33
|
-
opts.any? do |opt|
|
34
|
-
param.include? "--#{opt.field}#{opt.bang ? '!' : nil}=#{opt.value}"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.arg_field_remove?(args, param)
|
39
|
-
args.keys.any? { |field| param.include? "--#{field}" }
|
40
|
-
end
|
41
|
-
|
42
|
-
# Def Param Scan (Split -- values into keys)
|
43
|
-
def self.param_opt_scan(param)
|
44
|
-
param.scan(/--([^=]+)=(.*)/).map do |field, value|
|
45
|
-
bang = false
|
46
|
-
if field.include? '!'
|
47
|
-
field.delete!('!')
|
48
|
-
bang = true
|
49
|
-
end
|
50
|
-
|
51
|
-
{ field: field.to_sym, value: value, bang: bang }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Params that should be set as Args
|
56
|
-
# Comma Delimited
|
57
|
-
# --slice=thing,thing2
|
58
|
-
# --except=time,params
|
59
|
-
# --round=2
|
60
|
-
|
61
|
-
def self.param_special_opts
|
62
|
-
%i[
|
63
|
-
slice except stats uniq pluck round archive start end sort limit truncate page case
|
64
|
-
]
|
65
|
-
end
|
66
|
-
|
67
|
-
# Parameter Extraction
|
68
|
-
# Special Opts: --slice, --except
|
69
|
-
def self.param_arg_scan(param, obj)
|
70
|
-
# TODO: Why is capture group doing two arrays
|
71
|
-
param.scan(/--([^=]+)$/).flatten.each do |field|
|
72
|
-
obj[field.to_sym] = param_arg_defaults(field.to_sym)
|
73
|
-
end
|
74
|
-
|
75
|
-
obj
|
76
|
-
end
|
77
|
-
|
78
|
-
# Arg Defaults
|
79
|
-
def self.param_arg_defaults(field)
|
80
|
-
case field
|
81
|
-
when :round then [2]
|
82
|
-
when :limit then [TTY::Screen.height / 2]
|
83
|
-
when :truncate then [TTY::Screen.width * 4]
|
84
|
-
when :page, :case then [:true]
|
85
|
-
when *param_special_opts then []
|
86
|
-
else
|
87
|
-
true
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Use File Process for Output
|
92
|
-
def self.file_output(files)
|
93
|
-
results = file_process(files) do |file|
|
94
|
-
[
|
95
|
-
file.friendly_name,
|
96
|
-
file.output(false),
|
97
|
-
"\n"
|
98
|
-
]
|
99
|
-
end
|
100
|
-
|
101
|
-
ShellHelper.show(results.flatten)
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.file_process(files, &block)
|
105
|
-
files.map do |file|
|
106
|
-
next if file.output(false).empty?
|
107
|
-
|
108
|
-
block.call(file)
|
109
|
-
end.flatten
|
110
|
-
end
|
111
|
-
|
112
|
-
# Pagination Helper
|
113
|
-
def self.page(data)
|
114
|
-
TTY::Pager.page do |pager|
|
115
|
-
data.flatten.each do |output|
|
116
|
-
pager.write("\n#{output}") # write line to the pager
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Show Data / Auto Paginate Helper
|
122
|
-
def self.show(data, args = {})
|
123
|
-
# If Block of String
|
124
|
-
if data.instance_of?(String)
|
125
|
-
TTY::Pager.page data
|
126
|
-
return true
|
127
|
-
end
|
128
|
-
|
129
|
-
# If raw just print out
|
130
|
-
if args.raw
|
131
|
-
puts data.join("\n")
|
132
|
-
return true
|
133
|
-
end
|
134
|
-
|
135
|
-
# Check if content needs to paged, or if auto_height is off
|
136
|
-
if Page.skip?(args, data)
|
137
|
-
puts data.map { |entry| entry_show(args, entry) }.compact.join("\n")
|
138
|
-
return true
|
139
|
-
end
|
140
|
-
|
141
|
-
# Default Pager
|
142
|
-
TTY::Pager.page do |pager|
|
143
|
-
data.each do |entry|
|
144
|
-
output = entry_show(args, entry)
|
145
|
-
|
146
|
-
next if output.blank?
|
147
|
-
|
148
|
-
pager.write("\n#{output}") # write line to the pager
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# Entry Shower / Top Level
|
154
|
-
def self.entry_show(args, entry, key = nil)
|
155
|
-
LogBot.debug('Entry Show', entry.class) if ENV['DEBUG']
|
156
|
-
case entry
|
157
|
-
when Hash then render_table(entry, args)
|
158
|
-
when Float, Integer, Array
|
159
|
-
format_table_entry(args, entry, key)
|
160
|
-
# Ignore Special Formatting for Strings / Usually already formatted
|
161
|
-
when String
|
162
|
-
entry
|
163
|
-
else
|
164
|
-
LogBot.warn('Shell Show', "Unknown #{entry.class}")
|
165
|
-
nil
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# Format Table Entries
|
170
|
-
def self.format_table_entry(args, entry, key = nil)
|
171
|
-
formatted_entry = case entry
|
172
|
-
# Rounding
|
173
|
-
when Float, Integer || entry.numeric?
|
174
|
-
args.round ? entry.to_f.round(args.round.first.to_s.to_i).ai : entry.ai
|
175
|
-
|
176
|
-
# General Inspecting
|
177
|
-
when Hash then entry.ai(ruby19_syntax: true)
|
178
|
-
|
179
|
-
# Arrays often contain Hashes. Dangerous Recursive?
|
180
|
-
when Array
|
181
|
-
entry.map { |x| format_table_entry(args, x) }.join("\n")
|
182
|
-
|
183
|
-
when Time
|
184
|
-
entry.to_s.colorize(:light_white)
|
185
|
-
|
186
|
-
# Default String Formatting
|
187
|
-
else
|
188
|
-
StringColor.do(key, entry)
|
189
|
-
end
|
190
|
-
|
191
|
-
if args[:truncate]
|
192
|
-
entry_truncate(formatted_entry, args[:truncate].join.to_i)
|
193
|
-
else
|
194
|
-
formatted_entry
|
195
|
-
end
|
196
|
-
rescue StandardError => e
|
197
|
-
binding.pry
|
198
|
-
end
|
199
|
-
|
200
|
-
# Print the Table in a Nice way
|
201
|
-
def self.render_table(entry, args)
|
202
|
-
entry = entry.map { |k, v| [k, format_table_entry(args, v, k)] }.to_h
|
203
|
-
# Pre-format Entry
|
204
|
-
|
205
|
-
table = TTY::Table.new(header: entry.keys, rows: [entry], orientation: :vertical)
|
206
|
-
|
207
|
-
LogBot.debug('Rendering Entries') if ENV['DEBUG']
|
208
|
-
table.render(:unicode, padding: [0, 1, 0, 1], multiline: true) do |renderer|
|
209
|
-
renderer.border.style = :cyan
|
210
|
-
|
211
|
-
# renderer.filter = lambda do |val, _row_index, col_index|
|
212
|
-
# render_table_entry(val, col_index, args)
|
213
|
-
# end
|
214
|
-
end
|
215
|
-
|
216
|
-
# LogBot.debug('Finish Render Table') if ENV['DEBUG']
|
217
|
-
# Fall Back to Amazing Inspect
|
218
|
-
rescue StandardError => e
|
219
|
-
if ENV['DEBUG']
|
220
|
-
LogBot.warn('Table', message: e.message)
|
221
|
-
ap e.backtrace
|
222
|
-
end
|
223
|
-
|
224
|
-
[
|
225
|
-
entry.ai,
|
226
|
-
('_' * (TTY::Screen.width / 3)).colorize(:cyan),
|
227
|
-
"\n"
|
228
|
-
].join("\n")
|
229
|
-
end
|
230
|
-
|
231
|
-
def self.render_table_entry(val, col_index, args)
|
232
|
-
return val.to_s unless col_index == 1
|
233
|
-
|
234
|
-
format_table_entry(args, val)
|
235
|
-
end
|
236
|
-
|
237
|
-
# Main Entry Point for Filtering
|
238
|
-
def self.filter_start(log_list, filter_type, args, opts)
|
239
|
-
# Convert to Things
|
240
|
-
logs = ShellHelper.find_things(log_list, args).select(&:processed?)
|
241
|
-
|
242
|
-
# Ignore Archive/Host Dividers
|
243
|
-
if args[:combine]
|
244
|
-
results = logs.reject(&:blank?).map(&:data).flatten.compact
|
245
|
-
ShellHelper.filter(results, filter_type, args, opts)
|
246
|
-
else
|
247
|
-
# Iterate and Preserve Archive/Host Index
|
248
|
-
logs.each_with_object({}) do |log, obj|
|
249
|
-
# Ignore Empty Results / No Thing
|
250
|
-
next if log&.blank?
|
251
|
-
|
252
|
-
obj[log.friendly_name] = ShellHelper.filter(log.data, filter_type, args, opts)
|
253
|
-
|
254
|
-
obj
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
# Filter Logic
|
260
|
-
# TODO: Simplify
|
261
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
262
|
-
def self.filter(data, type = :all?, args = {}, opts = {})
|
263
|
-
results = data.clone.flatten.compact
|
264
|
-
results.select! do |row|
|
265
|
-
opts.send(type) do |opt|
|
266
|
-
filter_row_key(row, opt, args)
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# Time Filtering
|
271
|
-
results = filter_time(results, args) if args[:start] || args[:end]
|
272
|
-
|
273
|
-
# Strip Results if Slice is defined
|
274
|
-
results = filter_slice(results, args[:slice]) if args[:slice]
|
275
|
-
|
276
|
-
# Strip Results if Except is defined
|
277
|
-
results = filter_except(results, args[:except]) if args[:except]
|
278
|
-
|
279
|
-
# Remove Blank from either slice or except
|
280
|
-
results.reject!(&:empty?)
|
281
|
-
|
282
|
-
# Sort
|
283
|
-
results.sort_by! { |x| x.slice(*args[:sort]).values } if args[:sort]
|
284
|
-
|
285
|
-
# JSON Formatting
|
286
|
-
results = results.map { |x| Oj.dump(x) } if args[:json]
|
287
|
-
|
288
|
-
# Show Unique Only
|
289
|
-
results = filter_uniq(results, args[:uniq]) if args.key?(:uniq)
|
290
|
-
|
291
|
-
# Reverse
|
292
|
-
results.reverse! if args[:reverse]
|
293
|
-
|
294
|
-
# Count occurrences / Skip Results
|
295
|
-
return filter_stats(results, args[:stats]) if args[:stats]
|
296
|
-
|
297
|
-
# Pluck
|
298
|
-
results = filter_pluck(results, args[:pluck]) if args.key?(:pluck)
|
299
|
-
|
300
|
-
# Truncate
|
301
|
-
# filter_truncate(results, args[:truncate].join.to_i) if args.key?(:truncate)
|
302
|
-
|
303
|
-
# Limit
|
304
|
-
if args[:limit]
|
305
|
-
results[0..args[:limit].map(&:to_s).join.to_i - 1]
|
306
|
-
else
|
307
|
-
results
|
308
|
-
end
|
309
|
-
end
|
310
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
311
|
-
|
312
|
-
# Filter Start and End Times
|
313
|
-
# rubocop:disable Metrics/MethodLength
|
314
|
-
# TODO: This is a bit icky, simplify/dry
|
315
|
-
def self.filter_time(results, args)
|
316
|
-
if args.start
|
317
|
-
begin
|
318
|
-
time_start = Time.parse(args.start.first.to_s)
|
319
|
-
|
320
|
-
results.select! do |x|
|
321
|
-
if x.time
|
322
|
-
time_start < x.time
|
323
|
-
else
|
324
|
-
true
|
325
|
-
end
|
326
|
-
end
|
327
|
-
rescue StandardError
|
328
|
-
puts 'Unable to Process Start Time Filter'.colorize(:red)
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
if args.end
|
333
|
-
begin
|
334
|
-
time_start = Time.parse(args.end.first.to_s)
|
335
|
-
|
336
|
-
results.select! do |x|
|
337
|
-
if x.time
|
338
|
-
time_start > x.time
|
339
|
-
else
|
340
|
-
true
|
341
|
-
end
|
342
|
-
end
|
343
|
-
rescue StandardError
|
344
|
-
puts 'Unable to Process End Time Filter'.colorize(:red)
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
results
|
349
|
-
end
|
350
|
-
# rubocop:enable Metrics/MethodLength
|
351
|
-
|
352
|
-
def self.filter_except(results, except)
|
353
|
-
# Avoid Empty Results
|
354
|
-
if slice.empty?
|
355
|
-
filter_empty_arg('slice')
|
356
|
-
return results
|
357
|
-
end
|
358
|
-
|
359
|
-
results.map { |row| row.except(*except) }
|
360
|
-
end
|
361
|
-
|
362
|
-
def self.entry_truncate(entry, truncate)
|
363
|
-
# Ignore if Truncation Off
|
364
|
-
return entry if truncate.zero?
|
365
|
-
|
366
|
-
# Only truncate large strings
|
367
|
-
return entry unless entry.instance_of?(String) && entry.size > truncate
|
368
|
-
|
369
|
-
# Include '...' to indicate truncation
|
370
|
-
"#{entry.to_s[0..truncate]} #{'...'.colorize(:light_blue)}"
|
371
|
-
end
|
372
|
-
|
373
|
-
def self.filter_slice(results, slice)
|
374
|
-
# Avoid Empty Results
|
375
|
-
if slice.empty?
|
376
|
-
filter_empty_arg('slice')
|
377
|
-
return results
|
378
|
-
end
|
379
|
-
|
380
|
-
results.map { |row| row.slice(*slice) }
|
381
|
-
end
|
382
|
-
|
383
|
-
def self.filter_pluck(results, pluck)
|
384
|
-
# Avoid Empty Results
|
385
|
-
if pluck.empty?
|
386
|
-
filter_empty_arg('pluck')
|
387
|
-
return results
|
388
|
-
end
|
389
|
-
|
390
|
-
results.map { |x| x.slice(*pluck).values }.flatten
|
391
|
-
end
|
392
|
-
|
393
|
-
def self.filter_uniq(results, unique)
|
394
|
-
# Avoid Empty Results
|
395
|
-
if unique.empty?
|
396
|
-
filter_empty_arg('uniq')
|
397
|
-
return results
|
398
|
-
end
|
399
|
-
|
400
|
-
unique.map do |field|
|
401
|
-
results.uniq { |x| x[field] }
|
402
|
-
end.flatten
|
403
|
-
end
|
404
|
-
|
405
|
-
def self.filter_stats(results, stats)
|
406
|
-
# Avoid Empty Results
|
407
|
-
if stats.empty?
|
408
|
-
filter_empty_arg('stats')
|
409
|
-
return results
|
410
|
-
end
|
411
|
-
|
412
|
-
stats.map do |stat|
|
413
|
-
occurrences = filter_count_occurrences(results, stat)
|
414
|
-
occurrences.sort_by(&:last).to_h
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
# Helper to Count occurrences
|
419
|
-
def self.filter_count_occurrences(results, stat)
|
420
|
-
results.each_with_object(Hash.new(0)) do |entry, counts|
|
421
|
-
next unless entry.key? stat
|
422
|
-
|
423
|
-
counts[entry[stat]] += 1
|
424
|
-
|
425
|
-
counts
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
def self.filter_empty_arg(arg)
|
430
|
-
puts [
|
431
|
-
'Ignoring'.colorize(:light_yellow),
|
432
|
-
"--#{arg}".colorize(:cyan),
|
433
|
-
'it requires an argument'.colorize(:red)
|
434
|
-
].join(' ')
|
435
|
-
end
|
436
|
-
|
437
|
-
# Break out filter row logic into separate method
|
438
|
-
def self.filter_row_key(row, param, args)
|
439
|
-
# Ignore Other Logic if Field isn't even included
|
440
|
-
return false unless row.key? param.field
|
441
|
-
|
442
|
-
# Sensitivity Check / Check for Match
|
443
|
-
included = if args.key?(:case)
|
444
|
-
row[param.field].to_s.include? param.value.to_s
|
445
|
-
else
|
446
|
-
row[param.field].to_s.downcase.include? param.value.to_s.downcase
|
447
|
-
end
|
448
|
-
|
449
|
-
# Pivot of off include vs exclude
|
450
|
-
if param.bang
|
451
|
-
!included
|
452
|
-
else
|
453
|
-
included
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
# Total Count Helper
|
458
|
-
def self.total_count(results)
|
459
|
-
results.each do |k, v|
|
460
|
-
puts k
|
461
|
-
puts "Total: #{v.count.to_s.colorize(:blue)}"
|
462
|
-
puts
|
463
|
-
end
|
464
|
-
end
|
465
|
-
|
466
|
-
# Total Log List Manipulator
|
467
|
-
def self.prepare_list(log_list, base_list = nil)
|
468
|
-
base_list ||= GreenHat::ShellHelper::Log.list
|
469
|
-
|
470
|
-
# Assume all
|
471
|
-
log_list.push '*' if log_list.empty?
|
472
|
-
|
473
|
-
# Map for All
|
474
|
-
log_list = base_list.map(&:name) if log_list == ['*']
|
475
|
-
|
476
|
-
log_list
|
477
|
-
end
|
478
|
-
|
479
|
-
# Shortcut find things
|
480
|
-
def self.find_things(log_list, args = {})
|
481
|
-
things = log_list.uniq.flat_map do |log|
|
482
|
-
Thing.where name: log
|
483
|
-
end
|
484
|
-
|
485
|
-
# Host / Archive
|
486
|
-
things.select! { |x| x.archive? args.archive } if args.archive
|
487
|
-
|
488
|
-
things
|
489
|
-
end
|
490
|
-
|
491
|
-
# Main Entry Point for Searching
|
492
|
-
def self.search_start(log_list, filter_type, args, opts)
|
493
|
-
# Convert to Things
|
494
|
-
logs = ShellHelper.find_things(log_list, args)
|
495
|
-
|
496
|
-
logs.each_with_object({}) do |log, obj|
|
497
|
-
# Ignore Empty Results / No Thing
|
498
|
-
next if log&.data.blank?
|
499
|
-
|
500
|
-
obj[log.friendly_name] = ShellHelper.search(log.data, filter_type, args, opts)
|
501
|
-
|
502
|
-
obj
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
# Generic Search Helper / String/Regex
|
507
|
-
def self.search(data, type = :all?, args = {}, opts = {})
|
508
|
-
results = data.clone.flatten.compact
|
509
|
-
|
510
|
-
results.select! do |row|
|
511
|
-
opts.send(type) do |opt|
|
512
|
-
search_row(row, opt)
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
# Strip Results if Slice is defined
|
517
|
-
results.map! { |row| row.slice(*args[:slice]) } if args[:slice]
|
518
|
-
|
519
|
-
# Strip Results if Except is defined
|
520
|
-
results.map! { |row| row.except(*args[:except]) } if args[:except]
|
521
|
-
|
522
|
-
# Remove Blank from either slice or except
|
523
|
-
results.reject!(&:empty?)
|
524
|
-
|
525
|
-
results
|
526
|
-
end
|
527
|
-
|
528
|
-
# Break out filter row logic into separate method
|
529
|
-
def self.search_row(row, param)
|
530
|
-
# Not Included Param
|
531
|
-
included = row.to_s.include? param.value
|
532
|
-
|
533
|
-
if param.bang
|
534
|
-
!included
|
535
|
-
else
|
536
|
-
included
|
537
|
-
end
|
538
|
-
end
|
539
|
-
|
540
|
-
# Color Reader Helper
|
541
|
-
def self.pastel
|
542
|
-
@pastel ||= Pastel.new
|
543
|
-
end
|
544
|
-
|
545
|
-
# Number Helper
|
546
|
-
# https://gitlab.com/zedtux/human_size_to_number/-/blob/master/lib/human_size_to_number/helper.rb
|
547
|
-
def self.human_size_to_number(string)
|
548
|
-
size, unit = string.scan(/(\d*\.?\d+)\s?(Bytes?|KB|MB|GB|TB)/i).first
|
549
|
-
number = size.to_f
|
550
|
-
|
551
|
-
number = case unit.downcase
|
552
|
-
when 'byte', 'bytes'
|
553
|
-
number
|
554
|
-
when 'kb'
|
555
|
-
number * 1024
|
556
|
-
when 'mb'
|
557
|
-
number * 1024 * 1024
|
558
|
-
when 'gb'
|
559
|
-
number * 1024 * 1024 * 1024
|
560
|
-
when 'tb'
|
561
|
-
number * 1024 * 1024 * 1024 * 1024
|
562
|
-
end
|
563
|
-
number.round
|
564
|
-
end
|
565
|
-
|
566
|
-
# TODO: Needed?
|
567
|
-
def self.filter_and(data, params = {})
|
568
|
-
result = data.clone.flatten.compact
|
569
|
-
params.each do |k, v|
|
570
|
-
result.select! do |row|
|
571
|
-
if row.key? k.to_sym
|
572
|
-
row[k.to_sym].include? v
|
573
|
-
else
|
574
|
-
false
|
575
|
-
end
|
576
|
-
end
|
577
|
-
next
|
578
|
-
end
|
579
|
-
|
580
|
-
result
|
581
|
-
end
|
582
|
-
end
|
583
|
-
# rubocop:enable Metrics/ModuleLength
|
584
|
-
end
|