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