aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -1,95 +1,193 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# cspell:ignore jsonpp
|
3
4
|
require 'aspera/secret_hider'
|
5
|
+
require 'aspera/environment'
|
6
|
+
require 'aspera/log'
|
7
|
+
require 'aspera/assert'
|
4
8
|
require 'terminal-table'
|
5
9
|
require 'yaml'
|
6
10
|
require 'pp'
|
7
11
|
|
8
12
|
module Aspera
|
9
13
|
module Cli
|
14
|
+
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
15
|
+
# This class is used to transform a complex structure into a simple hash
|
16
|
+
class Flattener
|
17
|
+
def initialize
|
18
|
+
@result = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# General method
|
22
|
+
def flatten(something)
|
23
|
+
assert_type(something, Hash)
|
24
|
+
@result = {}
|
25
|
+
flatten_any(something, '')
|
26
|
+
return @result
|
27
|
+
end
|
28
|
+
|
29
|
+
# Special method for configuration overview
|
30
|
+
def config_over(something)
|
31
|
+
@result = []
|
32
|
+
something.each do |config, preset|
|
33
|
+
preset.each do |parameter, value|
|
34
|
+
@result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
return @result
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Recursive function to flatten any type
|
43
|
+
# @param something [Object] to be flattened
|
44
|
+
# @param name [String] name of englobing key
|
45
|
+
def flatten_any(something, name)
|
46
|
+
if something.is_a?(Hash)
|
47
|
+
flattened_hash(something, name)
|
48
|
+
elsif something.is_a?(Array)
|
49
|
+
flatten_array(something, name)
|
50
|
+
elsif something.is_a?(String) && something.empty?
|
51
|
+
@result[name] = Formatter.special('empty string')
|
52
|
+
elsif something.nil?
|
53
|
+
@result[name] = Formatter.special('null')
|
54
|
+
# elsif something.eql?(true) || something.eql?(false)
|
55
|
+
# @result[name] = something
|
56
|
+
else
|
57
|
+
@result[name] = something
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Recursive function to flatten an array
|
62
|
+
# @param array [Array] to be flattened
|
63
|
+
# @param name [String] name of englobing key
|
64
|
+
def flatten_array(array, name)
|
65
|
+
if array.empty?
|
66
|
+
@result[name] = Formatter.special('empty list')
|
67
|
+
elsif array.all?(String)
|
68
|
+
@result[name] = array.join("\n")
|
69
|
+
elsif array.all?{|i| i.is_a?(Hash) && i.keys.eql?(%w[name])}
|
70
|
+
@result[name] = array.map(&:values).join(', ')
|
71
|
+
elsif array.all?{|i| i.is_a?(Hash) && i.keys.sort.eql?(%w[name value])}
|
72
|
+
flattened_hash(array.each_with_object({}){|i, h|h[i['name']] = i['value']}, name)
|
73
|
+
else
|
74
|
+
array.each_with_index { |item, index| flatten_any(item, "#{name}.#{index}")}
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Recursive function to flatten a Hash
|
80
|
+
# @param hash [Hash] to be flattened
|
81
|
+
# @param name [String] name of englobing key
|
82
|
+
def flattened_hash(hash, name)
|
83
|
+
prefix = name.empty? ? '' : "#{name}."
|
84
|
+
hash.each do |k, v|
|
85
|
+
flatten_any(v, "#{prefix}#{k}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end # class
|
89
|
+
|
10
90
|
# Take care of output
|
11
91
|
class Formatter
|
12
|
-
|
13
|
-
FIELDS_DEFAULT = 'DEF'
|
92
|
+
FIELDS_LESS = '-'
|
14
93
|
CSV_RECORD_SEPARATOR = "\n"
|
15
94
|
CSV_FIELD_SEPARATOR = ','
|
16
95
|
# supported output formats
|
17
96
|
DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv].freeze
|
18
97
|
# user output levels
|
19
98
|
DISPLAY_LEVELS = %i[info data error].freeze
|
20
|
-
|
21
|
-
KEY_VALUE = %w[key value].freeze
|
99
|
+
RESULT_PARAMS = %i[type data total fields name].freeze
|
22
100
|
|
23
|
-
private_constant :
|
24
|
-
|
101
|
+
private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR
|
102
|
+
# prefix to display error messages in user messages (terminal)
|
103
|
+
ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
|
104
|
+
WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
|
105
|
+
HINT_FLASH = 'HINT:'.bg_green.gray.blink.freeze
|
25
106
|
|
26
107
|
class << self
|
27
|
-
# special
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
108
|
+
# Highlight special values
|
109
|
+
def special(what, use_colors: $stdout.isatty)
|
110
|
+
result = "<#{what}>"
|
111
|
+
if use_colors
|
112
|
+
result = if %w[null empty].any?{|s|what.include?(s)}
|
113
|
+
result.dim
|
114
|
+
else
|
115
|
+
result.reverse_color
|
35
116
|
end
|
36
|
-
hash.delete(k)
|
37
117
|
end
|
118
|
+
return result
|
38
119
|
end
|
39
120
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
preset.each do |parameter, value|
|
44
|
-
r.push(CONF_OVERVIEW_KEYS.zip([config, parameter, SecretHider.deep_remove_secret(value).to_s]).to_h)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
return r
|
121
|
+
def all_but(list)
|
122
|
+
list = [list] unless list.is_a?(Array)
|
123
|
+
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(ExtendedValue::ALL)
|
48
124
|
end
|
49
125
|
|
50
|
-
def
|
51
|
-
|
126
|
+
def tick(yes)
|
127
|
+
result =
|
128
|
+
if Environment.use_unicode?
|
129
|
+
if yes
|
130
|
+
"\u2713"
|
131
|
+
else
|
132
|
+
"\u2717"
|
133
|
+
end
|
134
|
+
elsif yes
|
135
|
+
'Y'
|
136
|
+
else
|
137
|
+
' '
|
138
|
+
end
|
139
|
+
return result.green if yes
|
140
|
+
return result.red
|
52
141
|
end
|
53
142
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
Log.log.debug{"(#{expand_last})[#{simple_hash?(source)}] -#{source.values}- \n-#{source}-"}
|
61
|
-
source.each do |k, v|
|
62
|
-
if v.is_a?(Hash) && !(expand_last && simple_hash?(v))
|
63
|
-
flattened_object(v, result: result, prefix: prefix + k.to_s + '.', expand_last: expand_last)
|
64
|
-
else
|
65
|
-
result[prefix + k.to_s] = v
|
143
|
+
def auto_type(data)
|
144
|
+
result = {type: :other_struct, data: data}
|
145
|
+
result[:type] = :single_object if result[:data].is_a?(Hash)
|
146
|
+
if result[:data].is_a?(Array)
|
147
|
+
if result[:data].all?(Hash)
|
148
|
+
result[:type] = :object_list
|
66
149
|
end
|
67
150
|
end
|
68
151
|
return result
|
69
152
|
end
|
153
|
+
end # self
|
154
|
+
|
155
|
+
# initialize the formatter
|
156
|
+
def initialize
|
157
|
+
@options = {}
|
70
158
|
end
|
71
159
|
|
72
|
-
|
73
|
-
|
160
|
+
def option_handler(option_symbol, operation, value=nil)
|
161
|
+
assert_values(operation, %i[set get])
|
162
|
+
case operation
|
163
|
+
when :set
|
164
|
+
@options[option_symbol] = value
|
165
|
+
if option_symbol.eql?(:output)
|
166
|
+
$stdout = if value.eql?('-')
|
167
|
+
STDOUT # rubocop:disable Style/GlobalStdStream
|
168
|
+
else
|
169
|
+
File.open(value, 'w')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
when :get then return @options[option_symbol]
|
173
|
+
else error_unreachable_line
|
174
|
+
end
|
175
|
+
nil
|
176
|
+
end
|
74
177
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
opt_mgr.declare(:select, 'Select only some items in lists: column, value', types: Hash, handler: {o: self, m: :option_select})
|
89
|
-
opt_mgr.declare(:table_style, 'Table display style', handler: {o: self, m: :option_table_style})
|
90
|
-
opt_mgr.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_flat_hash})
|
91
|
-
opt_mgr.declare(:transpose_single, 'Single object fields output vertically', values: :bool, handler: {o: self, m: :option_transpose_single})
|
92
|
-
opt_mgr.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_show_secrets})
|
178
|
+
def declare_options(options)
|
179
|
+
options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
180
|
+
options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
|
181
|
+
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
182
|
+
options.declare(
|
183
|
+
:fields, "Comma separated list of: fields, or #{ExtendedValue::ALL}, or #{ExtendedValue::DEF}", handler: {o: self, m: :option_handler},
|
184
|
+
types: [String, Array, Regexp, Proc],
|
185
|
+
default: ExtendedValue::DEF)
|
186
|
+
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
187
|
+
options.declare(:table_style, 'Table display style', handler: {o: self, m: :option_handler}, default: ':.:')
|
188
|
+
options.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
189
|
+
options.declare(:transpose_single, 'Single object fields output vertically', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
190
|
+
options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
|
93
191
|
end
|
94
192
|
|
95
193
|
# main output method
|
@@ -98,10 +196,10 @@ module Aspera
|
|
98
196
|
# error: always displayed on stderr
|
99
197
|
def display_message(message_level, message)
|
100
198
|
case message_level
|
101
|
-
when :data then $stdout.puts(message) unless @
|
102
|
-
when :info then $stdout.puts(message) if @
|
199
|
+
when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
|
200
|
+
when :info then $stdout.puts(message) if @options[:display].eql?(:info)
|
103
201
|
when :error then $stderr.puts(message)
|
104
|
-
else
|
202
|
+
else error_unexpected_value(message_level)
|
105
203
|
end
|
106
204
|
end
|
107
205
|
|
@@ -110,155 +208,159 @@ module Aspera
|
|
110
208
|
end
|
111
209
|
|
112
210
|
def display_item_count(number, total)
|
211
|
+
number = number.to_i
|
212
|
+
total = total.to_i
|
213
|
+
return if total.eql?(0) && number.eql?(0)
|
113
214
|
count_msg = "Items: #{number}/#{total}"
|
114
|
-
count_msg = count_msg.bg_red unless number.
|
215
|
+
count_msg = count_msg.bg_red unless number.eql?(total)
|
115
216
|
display_status(count_msg)
|
116
217
|
end
|
117
218
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
219
|
+
def all_fields(data)
|
220
|
+
data.each_with_object({}){|v, m|v.each_key{|c|m[c] = true}}.keys
|
221
|
+
end
|
222
|
+
|
223
|
+
# this method computes the list of fields to display
|
224
|
+
# data: array of hash
|
225
|
+
# default: list of fields to display by default (may contain special values)
|
226
|
+
def compute_fields(data, default)
|
227
|
+
Log.log.debug{"compute_fields: data:#{data.class} default:#{default.class} #{default}"}
|
228
|
+
request =
|
229
|
+
case @options[:fields]
|
230
|
+
when NilClass then [ExtendedValue::DEF]
|
231
|
+
when String then @options[:fields].split(',')
|
232
|
+
when Array then @options[:fields]
|
233
|
+
when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
|
234
|
+
when Proc then return all_fields(data).select{|i|@options[:fields].call(i)}
|
235
|
+
else error_unexpected_value(@options[:fields])
|
236
|
+
end
|
237
|
+
result = []
|
238
|
+
until request.empty?
|
239
|
+
item = request.shift
|
240
|
+
removal = false
|
241
|
+
if item[0].eql?(FIELDS_LESS)
|
242
|
+
removal = true
|
243
|
+
item = item[1..-1]
|
244
|
+
end
|
245
|
+
case item
|
246
|
+
when ExtendedValue::ALL
|
247
|
+
# get the list of all column names used in all lines, not just first one, as all lines may have different columns
|
248
|
+
request.unshift(*all_fields(data))
|
249
|
+
when ExtendedValue::DEF
|
250
|
+
default = all_fields(data).select{|i|default.call(i)} if default.is_a?(Proc)
|
251
|
+
default = all_fields(data) if default.nil?
|
252
|
+
request.unshift(*default)
|
253
|
+
else
|
254
|
+
if removal
|
255
|
+
result = result.reject{|i|i.eql?(item)}
|
256
|
+
else
|
257
|
+
result.push(item)
|
258
|
+
end
|
124
259
|
end
|
125
|
-
return results[:fields]
|
126
260
|
end
|
127
|
-
return
|
128
|
-
return table_rows_hash_val.first.keys
|
261
|
+
return result
|
129
262
|
end
|
130
263
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
264
|
+
# this method displays a table
|
265
|
+
# object_array: array of hash
|
266
|
+
# fields: list of column names
|
267
|
+
def display_table(object_array, fields)
|
268
|
+
assert(!fields.nil?){'missing fields parameter'}
|
269
|
+
case @options[:select]
|
270
|
+
when Proc
|
271
|
+
object_array.select!{|i|@options[:select].call(i)}
|
272
|
+
when Hash
|
273
|
+
@options[:select].each{|k, v|object_array.select!{|i|i[k].eql?(v)}}
|
274
|
+
end
|
275
|
+
if object_array.empty?
|
276
|
+
# no display for csv
|
277
|
+
display_message(:info, Formatter.special('empty')) if @options[:format].eql?(:table)
|
278
|
+
return
|
279
|
+
end
|
280
|
+
if object_array.length == 1 && fields.length == 1
|
281
|
+
display_message(:data, object_array.first[fields.first])
|
282
|
+
return
|
283
|
+
end
|
284
|
+
# Special case if only one row (it could be object_list or single_object)
|
285
|
+
if @options[:transpose_single] && object_array.length == 1
|
286
|
+
new_columns = %i[key value]
|
287
|
+
single = object_array.first
|
288
|
+
object_array = fields.map { |i| new_columns.zip([i, single[i]]).to_h }
|
289
|
+
fields = new_columns
|
290
|
+
end
|
291
|
+
Log.log.debug{Log.dump(:object_array, object_array)}
|
292
|
+
# convert data to string, and keep only display fields
|
293
|
+
final_table_rows = object_array.map { |r| fields.map { |c| r[c].to_s } }
|
294
|
+
# here : fields : list of column names
|
295
|
+
case @options[:format]
|
296
|
+
when :table
|
297
|
+
style = @options[:table_style].chars
|
298
|
+
# display the table !
|
299
|
+
display_message(:data, Terminal::Table.new(
|
300
|
+
headings: fields,
|
301
|
+
rows: final_table_rows,
|
302
|
+
border_x: style[0],
|
303
|
+
border_y: style[1],
|
304
|
+
border_i: style[2]))
|
305
|
+
when :csv
|
306
|
+
display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
|
307
|
+
end
|
135
308
|
end
|
136
309
|
|
137
310
|
# this method displays the results, especially the table format
|
138
311
|
def display_results(results)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
#
|
144
|
-
|
145
|
-
SecretHider.deep_remove_secret(
|
146
|
-
|
147
|
-
user_asked_fields_list_str = @option_fields
|
148
|
-
case @option_format
|
312
|
+
assert_type(results, Hash)
|
313
|
+
assert((results.keys - RESULT_PARAMS).empty?){"result unsupported key: #{results.keys - RESULT_PARAMS}"}
|
314
|
+
assert(results.key?(:type)){"result must have type (#{results})"}
|
315
|
+
assert(results.key?(:data) || %i[empty nothing].include?(results[:type])){'result must have data'}
|
316
|
+
Log.log.debug{"display_results: #{results[:data].class} #{results[:type]}"}
|
317
|
+
display_item_count(results[:data].length, results[:total]) if results.key?(:total)
|
318
|
+
SecretHider.deep_remove_secret(results[:data]) unless @options[:show_secrets] || @options[:display].eql?(:data)
|
319
|
+
case @options[:format]
|
149
320
|
when :text
|
150
|
-
display_message(:data,
|
321
|
+
display_message(:data, results[:data].to_s)
|
151
322
|
when :nagios
|
152
|
-
Nagios.process(
|
323
|
+
Nagios.process(results[:data])
|
153
324
|
when :ruby
|
154
|
-
display_message(:data, PP.pp(
|
325
|
+
display_message(:data, PP.pp(results[:data], +''))
|
155
326
|
when :json
|
156
|
-
display_message(:data, JSON.generate(
|
327
|
+
display_message(:data, JSON.generate(results[:data]))
|
157
328
|
when :jsonpp
|
158
|
-
display_message(:data, JSON.pretty_generate(
|
329
|
+
display_message(:data, JSON.pretty_generate(results[:data]))
|
159
330
|
when :yaml
|
160
|
-
display_message(:data,
|
331
|
+
display_message(:data, results[:data].to_yaml)
|
161
332
|
when :table, :csv
|
162
|
-
if !@option_transpose_single && results[:type].eql?(:single_object)
|
163
|
-
results[:type] = :object_list
|
164
|
-
res_data = [res_data]
|
165
|
-
end
|
166
333
|
case results[:type]
|
167
|
-
when :
|
168
|
-
|
334
|
+
when :config_over
|
335
|
+
display_table(Flattener.new.config_over(results[:data]), CONF_OVERVIEW_KEYS)
|
336
|
+
when :object_list, :single_object
|
337
|
+
obj_list = results[:data]
|
338
|
+
obj_list = [obj_list] if results[:type].eql?(:single_object)
|
339
|
+
assert_type(obj_list, Array)
|
340
|
+
assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
|
169
341
|
# :object_list is an array of hash tables, where key=colum name
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
table_rows_hash_val.map!{|obj|self.class.flattened_object(obj, expand_last: results[:option_expand_last])}
|
174
|
-
end
|
175
|
-
final_table_columns =
|
176
|
-
case user_asked_fields_list_str
|
177
|
-
when FIELDS_DEFAULT then result_default_fields(results, table_rows_hash_val)
|
178
|
-
when FIELDS_ALL then result_all_fields(results, table_rows_hash_val)
|
179
|
-
else
|
180
|
-
if user_asked_fields_list_str.start_with?('+')
|
181
|
-
result_default_fields(results, table_rows_hash_val).push(*user_asked_fields_list_str.gsub(/^\+/, '').split(','))
|
182
|
-
elsif user_asked_fields_list_str.start_with?('-')
|
183
|
-
result_default_fields(results, table_rows_hash_val).reject{|i| user_asked_fields_list_str.gsub(/^-/, '').split(',').include?(i)}
|
184
|
-
else
|
185
|
-
user_asked_fields_list_str.split(',')
|
186
|
-
end
|
187
|
-
end
|
188
|
-
when :single_object # goes to table display
|
189
|
-
# :single_object is a simple hash table (can be nested)
|
190
|
-
raise "internal error: expecting Hash: got #{res_data.class}: #{res_data}" unless res_data.is_a?(Hash)
|
191
|
-
final_table_columns = results[:columns] || KEY_VALUE
|
192
|
-
if @option_flat_hash
|
193
|
-
res_data = self.class.flattened_object(res_data, expand_last: results[:option_expand_last])
|
194
|
-
self.class.flatten_name_value_list(res_data)
|
195
|
-
end
|
196
|
-
asked_fields =
|
197
|
-
case user_asked_fields_list_str
|
198
|
-
when FIELDS_DEFAULT then results[:fields] || res_data.keys
|
199
|
-
when FIELDS_ALL then res_data.keys
|
200
|
-
else user_asked_fields_list_str.split(',')
|
201
|
-
end
|
202
|
-
table_rows_hash_val = asked_fields.map { |i| { final_table_columns.first => i, final_table_columns.last => res_data[i] } }
|
203
|
-
# if only one row, and columns are key/value, then display the value only
|
204
|
-
if table_rows_hash_val.length == 1 && final_table_columns.eql?(KEY_VALUE)
|
205
|
-
display_message(:data, res_data.values.first)
|
206
|
-
return
|
207
|
-
end
|
208
|
-
when :value_list # goes to table display
|
342
|
+
obj_list = obj_list.map{|obj|Flattener.new.flatten(obj)} if @options[:flat_hash]
|
343
|
+
display_table(obj_list, compute_fields(obj_list, results[:fields]))
|
344
|
+
when :value_list
|
209
345
|
# :value_list is a simple array of values, name of column provided in the :name
|
210
|
-
|
211
|
-
table_rows_hash_val = res_data.map { |i| { results[:name] => i } }
|
346
|
+
display_table(results[:data].map { |i| { results[:name] => i } }, [results[:name]])
|
212
347
|
when :empty # no table
|
213
|
-
display_message(:info, 'empty')
|
348
|
+
display_message(:info, Formatter.special('empty'))
|
214
349
|
return
|
215
350
|
when :nothing # no result expected
|
216
351
|
Log.log.debug('no result expected')
|
217
|
-
return
|
218
352
|
when :status # no table
|
219
353
|
# :status displays a simple message
|
220
|
-
display_message(:info,
|
221
|
-
return
|
354
|
+
display_message(:info, results[:data])
|
222
355
|
when :text # no table
|
223
356
|
# :status displays a simple message
|
224
|
-
display_message(:data,
|
225
|
-
return
|
357
|
+
display_message(:data, results[:data])
|
226
358
|
when :other_struct # no table
|
227
359
|
# :other_struct is any other type of structure
|
228
|
-
display_message(:data, PP.pp(
|
229
|
-
return
|
360
|
+
display_message(:data, PP.pp(results[:data], +''))
|
230
361
|
else
|
231
362
|
raise "unknown data type: #{results[:type]}"
|
232
363
|
end
|
233
|
-
# here we expect: table_rows_hash_val and final_table_columns
|
234
|
-
raise 'no field specified' if final_table_columns.nil?
|
235
|
-
if table_rows_hash_val.empty?
|
236
|
-
display_message(:info, 'empty'.gray) if @option_format.eql?(:table)
|
237
|
-
return
|
238
|
-
end
|
239
|
-
# convert to string with special function. here table_rows_hash_val is an array of hash
|
240
|
-
table_rows_hash_val = results[:textify].call(table_rows_hash_val) if results.key?(:textify)
|
241
|
-
unless @option_select.nil? || (@option_select.respond_to?(:empty?) && @option_select.empty?)
|
242
|
-
raise CliBadArgument, "expecting hash for select, have #{@option_select.class}: #{@option_select}" unless @option_select.is_a?(Hash)
|
243
|
-
@option_select.each{|k, v|table_rows_hash_val.select!{|i|i[k].eql?(v)}}
|
244
|
-
end
|
245
|
-
# convert data to string, and keep only display fields
|
246
|
-
final_table_rows = table_rows_hash_val.map { |r| final_table_columns.map { |c| r[c].to_s } }
|
247
|
-
# here : final_table_columns : list of column names
|
248
|
-
# here: final_table_rows : array of list of value
|
249
|
-
case @option_format
|
250
|
-
when :table
|
251
|
-
style = @option_table_style.chars
|
252
|
-
# display the table !
|
253
|
-
display_message(:data, Terminal::Table.new(
|
254
|
-
headings: final_table_columns,
|
255
|
-
rows: final_table_rows,
|
256
|
-
border_x: style[0],
|
257
|
-
border_y: style[1],
|
258
|
-
border_i: style[2]))
|
259
|
-
when :csv
|
260
|
-
display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
|
261
|
-
end
|
262
364
|
end
|
263
365
|
end
|
264
366
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/fasp/error'
|
4
|
+
require 'aspera/rest'
|
5
|
+
require 'aspera/log'
|
6
|
+
require 'aspera/assert'
|
7
|
+
require 'net/ssh'
|
8
|
+
require 'openssl'
|
9
|
+
|
10
|
+
module Aspera
|
11
|
+
module Cli
|
12
|
+
# Provide hints on errors
|
13
|
+
class Hints
|
14
|
+
# Well know issues that users may get
|
15
|
+
ERROR_HINTS = [
|
16
|
+
{
|
17
|
+
exception: Fasp::Error,
|
18
|
+
match: 'Remote host is not who we expected',
|
19
|
+
remediation: [
|
20
|
+
'For this specific error, refer to:',
|
21
|
+
"#{SRC_URL}#error-remote-host-is-not-who-we-expected",
|
22
|
+
'Add this to arguments:',
|
23
|
+
%q{--ts=@json:'{"sshfp":null}'"}
|
24
|
+
]
|
25
|
+
},
|
26
|
+
{
|
27
|
+
exception: Aspera::RestCallError,
|
28
|
+
match: /Signature has expired/,
|
29
|
+
remediation: [
|
30
|
+
'There is too much time difference between your computer and the server',
|
31
|
+
'Check your local time: is time synchronization enabled?'
|
32
|
+
]
|
33
|
+
},
|
34
|
+
{
|
35
|
+
exception: OpenSSL::SSL::SSLError,
|
36
|
+
match: /(does not match the server certificate|certificate verify failed)/,
|
37
|
+
remediation: [
|
38
|
+
'You can ignore SSL errors with option:',
|
39
|
+
'--insecure=yes'
|
40
|
+
]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
exception: OpenSSL::PKey::RSAError,
|
44
|
+
match: /Neither PUB key nor PRIV key/,
|
45
|
+
remediation: [
|
46
|
+
'option: private_key expects a key PEM value, not path to file',
|
47
|
+
'if you provide a path: prefix with @file:',
|
48
|
+
'e.g. --private-key=@file:/path/to/key.pem'
|
49
|
+
]
|
50
|
+
}
|
51
|
+
]
|
52
|
+
|
53
|
+
private_constant :ERROR_HINTS
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def hint_for(error, formatter)
|
57
|
+
ERROR_HINTS.each do |hint|
|
58
|
+
next unless error.is_a?(hint[:exception])
|
59
|
+
message = error.message
|
60
|
+
matches = hint[:match]
|
61
|
+
matches = [matches] unless matches.is_a?(Array)
|
62
|
+
matches.each do |m|
|
63
|
+
assert_values(m.class, [String, Regexp])
|
64
|
+
case m
|
65
|
+
when String
|
66
|
+
next unless message.eql?(m)
|
67
|
+
when Regexp
|
68
|
+
next unless message.match?(m)
|
69
|
+
else error_unexpected_value(m)
|
70
|
+
end
|
71
|
+
remediation = hint[:remediation]
|
72
|
+
remediation = [remediation] unless remediation.is_a?(Array)
|
73
|
+
remediation.each{|r|formatter.display_message(:error, "#{Formatter::HINT_FLASH} #{r}")}
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|