aspera-cli 4.13.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
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