aspera-cli 4.13.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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +81 -7
  4. data/CONTRIBUTING.md +22 -6
  5. data/README.md +2038 -1080
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/dascli +1 -1
  9. data/examples/proxy.pac +1 -1
  10. data/examples/rubyc +24 -0
  11. data/lib/aspera/aoc.rb +219 -159
  12. data/lib/aspera/ascmd.rb +25 -14
  13. data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
  14. data/lib/aspera/cli/error.rb +17 -0
  15. data/lib/aspera/cli/extended_value.rb +47 -12
  16. data/lib/aspera/cli/formatter.rb +260 -179
  17. data/lib/aspera/cli/hints.rb +80 -0
  18. data/lib/aspera/cli/main.rb +104 -156
  19. data/lib/aspera/cli/manager.rb +259 -209
  20. data/lib/aspera/cli/plugin.rb +123 -63
  21. data/lib/aspera/cli/plugins/alee.rb +2 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +341 -261
  23. data/lib/aspera/cli/plugins/ats.rb +22 -21
  24. data/lib/aspera/cli/plugins/bss.rb +5 -5
  25. data/lib/aspera/cli/plugins/config.rb +578 -627
  26. data/lib/aspera/cli/plugins/console.rb +44 -6
  27. data/lib/aspera/cli/plugins/cos.rb +15 -17
  28. data/lib/aspera/cli/plugins/faspex.rb +114 -100
  29. data/lib/aspera/cli/plugins/faspex5.rb +411 -264
  30. data/lib/aspera/cli/plugins/node.rb +354 -259
  31. data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
  32. data/lib/aspera/cli/plugins/preview.rb +82 -90
  33. data/lib/aspera/cli/plugins/server.rb +79 -32
  34. data/lib/aspera/cli/plugins/shares.rb +55 -42
  35. data/lib/aspera/cli/sync_actions.rb +68 -0
  36. data/lib/aspera/cli/transfer_agent.rb +66 -73
  37. data/lib/aspera/cli/transfer_progress.rb +74 -0
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/colors.rb +12 -8
  40. data/lib/aspera/command_line_builder.rb +14 -11
  41. data/lib/aspera/cos_node.rb +3 -2
  42. data/lib/aspera/data/6 +0 -0
  43. data/lib/aspera/environment.rb +24 -9
  44. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  45. data/lib/aspera/fasp/agent_base.rb +31 -77
  46. data/lib/aspera/fasp/agent_connect.rb +25 -21
  47. data/lib/aspera/fasp/agent_direct.rb +89 -103
  48. data/lib/aspera/fasp/agent_httpgw.rb +231 -149
  49. data/lib/aspera/fasp/agent_node.rb +41 -34
  50. data/lib/aspera/fasp/agent_trsdk.rb +75 -32
  51. data/lib/aspera/fasp/error_info.rb +4 -2
  52. data/lib/aspera/fasp/faux_file.rb +52 -0
  53. data/lib/aspera/fasp/installation.rb +53 -195
  54. data/lib/aspera/fasp/management.rb +244 -0
  55. data/lib/aspera/fasp/parameters.rb +71 -37
  56. data/lib/aspera/fasp/parameters.yaml +76 -8
  57. data/lib/aspera/fasp/products.rb +162 -0
  58. data/lib/aspera/fasp/resume_policy.rb +3 -3
  59. data/lib/aspera/fasp/transfer_spec.rb +7 -6
  60. data/lib/aspera/fasp/uri.rb +26 -24
  61. data/lib/aspera/faspex_gw.rb +2 -2
  62. data/lib/aspera/faspex_postproc.rb +2 -2
  63. data/lib/aspera/hash_ext.rb +14 -4
  64. data/lib/aspera/json_rpc.rb +49 -0
  65. data/lib/aspera/keychain/macos_security.rb +13 -13
  66. data/lib/aspera/line_logger.rb +23 -0
  67. data/lib/aspera/log.rb +58 -16
  68. data/lib/aspera/node.rb +157 -92
  69. data/lib/aspera/oauth.rb +37 -19
  70. data/lib/aspera/open_application.rb +4 -4
  71. data/lib/aspera/persistency_action_once.rb +1 -1
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -2
  74. data/lib/aspera/preview/generator.rb +22 -35
  75. data/lib/aspera/preview/options.rb +2 -0
  76. data/lib/aspera/preview/terminal.rb +73 -16
  77. data/lib/aspera/preview/utils.rb +21 -28
  78. data/lib/aspera/proxy_auto_config.js +2 -2
  79. data/lib/aspera/rest.rb +136 -68
  80. data/lib/aspera/rest_call_error.rb +1 -1
  81. data/lib/aspera/rest_error_analyzer.rb +15 -14
  82. data/lib/aspera/rest_errors_aspera.rb +37 -34
  83. data/lib/aspera/secret_hider.rb +18 -15
  84. data/lib/aspera/ssh.rb +5 -2
  85. data/lib/aspera/sync.rb +127 -119
  86. data/lib/aspera/temp_file_manager.rb +10 -3
  87. data/lib/aspera/web_auth.rb +10 -7
  88. data/lib/aspera/web_server_simple.rb +9 -4
  89. data.tar.gz.sig +0 -0
  90. metadata +34 -17
  91. metadata.gz.sig +0 -0
  92. data/docs/test_env.conf +0 -186
  93. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  94. data/lib/aspera/cli/listener/logger.rb +0 -22
  95. data/lib/aspera/cli/listener/progress.rb +0 -50
  96. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  97. data/lib/aspera/cli/plugins/sync.rb +0 -44
  98. data/lib/aspera/data/7 +0 -0
  99. data/lib/aspera/fasp/listener.rb +0 -13
@@ -1,102 +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
- FIELDS_ALL = 'ALL'
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
97
 
22
- private_constant :FIELDS_ALL, :FIELDS_DEFAULT, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR,
23
- :CONF_OVERVIEW_KEYS
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
24
103
 
25
104
  class << self
26
- # special for Aspera on Cloud display node
27
- # {"param" => [{"name"=>"foo","value"=>"bar"}]} will be expanded to {"param.foo" : "bar"}
28
- def flatten_name_value_list(hash)
29
- hash.keys.each do |k| # rubocop:disable Style/HashEachMethods
30
- v = hash[k]
31
- next unless v.is_a?(Array) && v.map(&:class).uniq.eql?([Hash]) && v.map(&:keys).flatten.sort.uniq.eql?(%w[name value])
32
- v.each do |pair|
33
- hash["#{k}.#{pair['name']}"] = pair['value']
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
34
113
  end
35
- hash.delete(k)
36
114
  end
115
+ return result
37
116
  end
38
117
 
39
- def flatten_config_overview(hash_array_conf)
40
- r = []
41
- hash_array_conf.each do |config, preset|
42
- preset.each do |parameter, value|
43
- r.push(CONF_OVERVIEW_KEYS.zip([config, parameter, SecretHider.deep_remove_secret(value).to_s]).to_h)
44
- end
45
- end
46
- 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)
47
121
  end
48
122
 
49
- def simple_hash?(h)
50
- !(h.values.any?{|v|[Hash, Array].any?{|c|v.is_a?(c)}})
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
51
138
  end
52
139
 
53
- # recursive function to modify a hash
54
- # @param source [Hash] to be modified
55
- # @param expand_last [TrueClass,FalseClass] true if last level is not
56
- # @param result [Hash] new hash flattened
57
- # @param prefix [String] true if last level is not
58
- def flattened_object(source, result: {}, prefix: '', expand_last: false)
59
- Log.log.debug{"(#{expand_last})[#{simple_hash?(source)}] -#{source.values}- \n-#{source}-"}
60
- source.each do |k, v|
61
- if v.is_a?(Hash) && !(expand_last && simple_hash?(v))
62
- flattened_object(v, result: result, prefix: prefix + k.to_s + '.', expand_last: expand_last)
63
- else
64
- 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
65
146
  end
66
147
  end
67
148
  return result
68
149
  end
150
+ end # self
151
+
152
+ # initialize the formatter
153
+ def initialize
154
+ @options = {}
69
155
  end
70
156
 
71
- attr_accessor :option_flat_hash, :option_transpose_single, :option_format, :option_display, :option_fields, :option_table_style,
72
- :option_select, :option_show_secrets
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
73
165
 
74
- # adds options but does not parse
75
- def initialize(opt_mgr)
76
- @option_format = :table
77
- @option_display = :info
78
- @option_fields = FIELDS_DEFAULT
79
- @option_select = nil
80
- @option_table_style = ':.:'
81
- @option_flat_hash = true
82
- @option_transpose_single = true
83
- @option_show_secrets = false
84
- opt_mgr.set_obj_attr(:format, self, :option_format)
85
- opt_mgr.set_obj_attr(:display, self, :option_display)
86
- opt_mgr.set_obj_attr(:fields, self, :option_fields)
87
- opt_mgr.set_obj_attr(:select, self, :option_select)
88
- opt_mgr.set_obj_attr(:table_style, self, :option_table_style)
89
- opt_mgr.set_obj_attr(:flat_hash, self, :option_flat_hash)
90
- opt_mgr.set_obj_attr(:transpose_single, self, :option_transpose_single)
91
- opt_mgr.set_obj_attr(:show_secrets, self, :option_show_secrets)
92
- opt_mgr.add_opt_list(:format, DISPLAY_FORMATS, 'output format')
93
- opt_mgr.add_opt_list(:display, DISPLAY_LEVELS, 'output only some information')
94
- opt_mgr.add_opt_simple(:fields, "comma separated list of fields, or #{FIELDS_ALL}, or #{FIELDS_DEFAULT}")
95
- opt_mgr.add_opt_simple(:select, 'select only some items in lists, extended value: hash (column, value)')
96
- opt_mgr.add_opt_simple(:table_style, 'table display style')
97
- opt_mgr.add_opt_boolean(:flat_hash, 'display hash values as additional keys')
98
- opt_mgr.add_opt_boolean(:transpose_single, 'single object fields output vertically')
99
- opt_mgr.add_opt_boolean(:show_secrets, 'show secrets on command output')
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)
100
178
  end
101
179
 
102
180
  # main output method
@@ -105,8 +183,8 @@ module Aspera
105
183
  # error: always displayed on stderr
106
184
  def display_message(message_level, message)
107
185
  case message_level
108
- when :data then $stdout.puts(message) unless @option_display.eql?(:error)
109
- when :info then $stdout.puts(message) if @option_display.eql?(:info)
186
+ when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
187
+ when :info then $stdout.puts(message) if @options[:display].eql?(:info)
110
188
  when :error then $stderr.puts(message)
111
189
  else raise "wrong message_level:#{message_level}"
112
190
  end
@@ -117,156 +195,159 @@ module Aspera
117
195
  end
118
196
 
119
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)
120
201
  count_msg = "Items: #{number}/#{total}"
121
- count_msg = count_msg.bg_red unless number.to_i.eql?(total.to_i)
202
+ count_msg = count_msg.bg_red unless number.eql?(total)
122
203
  display_status(count_msg)
123
204
  end
124
205
 
125
- def result_default_fields(results, table_rows_hash_val)
126
- unless results[:fields].nil?
127
- raise "internal error: [fields] must be Array, not #{results[:fields].class}" unless results[:fields].is_a?(Array)
128
- if results[:fields].first.eql?(:all_but) && !table_rows_hash_val.empty?
129
- filter = results[:fields][1..-1]
130
- return table_rows_hash_val.first.keys.reject{|i|filter.include?(i)}
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
131
246
  end
132
- return results[:fields]
133
247
  end
134
- return ['empty'] if table_rows_hash_val.empty?
135
- return table_rows_hash_val.first.keys
248
+ return result
136
249
  end
137
250
 
138
- def result_all_fields(_results, table_rows_hash_val)
139
- raise 'internal error: must be array' unless table_rows_hash_val.is_a?(Array)
140
- # get the list of all column names used in all lines, not just first one, as all lines may have different columns
141
- return table_rows_hash_val.each_with_object({}){|v, m|v.each_key{|c|m[c] = true}; }.keys
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
142
295
  end
143
296
 
144
297
  # this method displays the results, especially the table format
145
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
146
301
  raise "INTERNAL ERROR, result must be Hash (got: #{results.class}: #{results})" unless results.is_a?(Hash)
147
- raise 'INTERNAL ERROR, result must have type' unless results.key?(:type)
302
+ raise "INTERNAL ERROR, result must have type (#{results})" unless results.key?(:type)
148
303
  raise 'INTERNAL ERROR, result must have data' unless results.key?(:data) || %i[empty nothing].include?(results[:type])
149
- res_data = results[:data]
150
- # for config overview, it is name and value
151
- is_config_overview = res_data.is_a?(Array) && !res_data.empty? && res_data.first.is_a?(Hash) && res_data.first.keys.sort.eql?(CONF_OVERVIEW_KEYS)
152
- SecretHider.deep_remove_secret(res_data, is_name_value: is_config_overview) unless @option_show_secrets || @option_display.eql?(:data)
153
- # comma separated list in string format
154
- user_asked_fields_list_str = @option_fields
155
- 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]
156
307
  when :text
157
- display_message(:data, res_data.to_s)
308
+ display_message(:data, results[:data].to_s)
158
309
  when :nagios
159
- Nagios.process(res_data)
310
+ Nagios.process(results[:data])
160
311
  when :ruby
161
- display_message(:data, PP.pp(res_data, +''))
312
+ display_message(:data, PP.pp(results[:data], +''))
162
313
  when :json
163
- display_message(:data, JSON.generate(res_data))
314
+ display_message(:data, JSON.generate(results[:data]))
164
315
  when :jsonpp
165
- display_message(:data, JSON.pretty_generate(res_data))
316
+ display_message(:data, JSON.pretty_generate(results[:data]))
166
317
  when :yaml
167
- display_message(:data, res_data.to_yaml)
318
+ display_message(:data, results[:data].to_yaml)
168
319
  when :table, :csv
169
- if !@option_transpose_single && results[:type].eql?(:single_object)
170
- results[:type] = :object_list
171
- res_data = [res_data]
172
- end
173
320
  case results[:type]
174
- when :object_list # goes to table display
175
- raise "internal error: unexpected type: #{res_data.class}, expecting Array" unless res_data.is_a?(Array)
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)
176
328
  # :object_list is an array of hash tables, where key=colum name
177
- table_rows_hash_val = res_data
178
- final_table_columns = nil
179
- if @option_flat_hash
180
- table_rows_hash_val.map!{|obj|self.class.flattened_object(obj, expand_last: results[:option_expand_last])}
181
- end
182
- final_table_columns =
183
- case user_asked_fields_list_str
184
- when FIELDS_DEFAULT then result_default_fields(results, table_rows_hash_val)
185
- when FIELDS_ALL then result_all_fields(results, table_rows_hash_val)
186
- else
187
- if user_asked_fields_list_str.start_with?('+')
188
- result_default_fields(results, table_rows_hash_val).push(*user_asked_fields_list_str.gsub(/^\+/, '').split(','))
189
- elsif user_asked_fields_list_str.start_with?('-')
190
- result_default_fields(results, table_rows_hash_val).reject{|i| user_asked_fields_list_str.gsub(/^-/, '').split(',').include?(i)}
191
- else
192
- user_asked_fields_list_str.split(',')
193
- end
194
- end
195
- when :single_object # goes to table display
196
- # :single_object is a simple hash table (can be nested)
197
- raise "internal error: expecting Hash: got #{res_data.class}: #{res_data}" unless res_data.is_a?(Hash)
198
- final_table_columns = results[:columns] || %w[key value]
199
- if @option_flat_hash
200
- res_data = self.class.flattened_object(res_data, expand_last: results[:option_expand_last])
201
- self.class.flatten_name_value_list(res_data)
202
- end
203
- asked_fields =
204
- case user_asked_fields_list_str
205
- when FIELDS_DEFAULT then results[:fields] || res_data.keys
206
- when FIELDS_ALL then res_data.keys
207
- else user_asked_fields_list_str.split(',')
208
- end
209
- table_rows_hash_val = asked_fields.map { |i| { final_table_columns.first => i, final_table_columns.last => res_data[i] } }
210
- 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
211
332
  # :value_list is a simple array of values, name of column provided in the :name
212
- final_table_columns = [results[:name]]
213
- table_rows_hash_val = res_data.map { |i| { results[:name] => i } }
333
+ display_table(results[:data].map { |i| { results[:name] => i } }, [results[:name]])
214
334
  when :empty # no table
215
- display_message(:info, 'empty')
335
+ display_message(:info, Formatter.special('empty'))
216
336
  return
217
337
  when :nothing # no result expected
218
338
  Log.log.debug('no result expected')
219
- return
220
339
  when :status # no table
221
340
  # :status displays a simple message
222
- display_message(:info, res_data)
223
- return
341
+ display_message(:info, results[:data])
224
342
  when :text # no table
225
343
  # :status displays a simple message
226
- display_message(:data, res_data)
227
- return
344
+ display_message(:data, results[:data])
228
345
  when :other_struct # no table
229
346
  # :other_struct is any other type of structure
230
- display_message(:data, PP.pp(res_data, +''))
231
- return
347
+ display_message(:data, PP.pp(results[:data], +''))
232
348
  else
233
349
  raise "unknown data type: #{results[:type]}"
234
350
  end
235
- # here we expect: table_rows_hash_val and final_table_columns
236
- raise 'no field specified' if final_table_columns.nil?
237
- if table_rows_hash_val.empty?
238
- display_message(:info, 'empty'.gray) unless @option_format.eql?(:csv)
239
- return
240
- end
241
- # convert to string with special function. here table_rows_hash_val is an array of hash
242
- table_rows_hash_val = results[:textify].call(table_rows_hash_val) if results.key?(:textify)
243
- unless @option_select.nil? || (@option_select.respond_to?(:empty?) && @option_select.empty?)
244
- raise CliBadArgument, "expecting hash for select, have #{@option_select.class}: #{@option_select}" unless @option_select.is_a?(Hash)
245
- @option_select.each{|k, v|table_rows_hash_val.select!{|i|i[k].eql?(v)}}
246
- end
247
- # convert data to string, and keep only display fields
248
- final_table_rows = table_rows_hash_val.map { |r| final_table_columns.map { |c| r[c].to_s } }
249
- # here : final_table_columns : list of column names
250
- # here: final_table_rows : array of list of value
251
- case @option_format
252
- when :table
253
- style = @option_table_style.chars
254
- # display the table !
255
- # display_message(:data,Text::Table.new(
256
- # head: final_table_columns,
257
- # rows: final_table_rows,
258
- # horizontal_boundary: style[0],
259
- # vertical_boundary: style[1],
260
- # boundary_intersection: style[2]))
261
- display_message(:data, Terminal::Table.new(
262
- headings: final_table_columns,
263
- rows: final_table_rows,
264
- border_x: style[0],
265
- border_y: style[1],
266
- border_i: style[2]))
267
- when :csv
268
- display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
269
- end
270
351
  end
271
352
  end
272
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