aspera-cli 4.22.0 → 4.24.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 (114) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +405 -364
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +1856 -961
  6. data/bin/ascli +2 -1
  7. data/bin/asession +4 -4
  8. data/lib/aspera/agent/base.rb +4 -0
  9. data/lib/aspera/agent/connect.rb +20 -18
  10. data/lib/aspera/agent/desktop.rb +14 -11
  11. data/lib/aspera/agent/direct.rb +39 -31
  12. data/lib/aspera/agent/httpgw.rb +2 -2
  13. data/lib/aspera/agent/node.rb +9 -11
  14. data/lib/aspera/agent/transferd.rb +18 -11
  15. data/lib/aspera/api/aoc.rb +53 -43
  16. data/lib/aspera/api/cos_node.rb +7 -5
  17. data/lib/aspera/api/httpgw.rb +23 -22
  18. data/lib/aspera/api/node.rb +104 -22
  19. data/lib/aspera/ascmd.rb +35 -21
  20. data/lib/aspera/ascp/installation.rb +43 -43
  21. data/lib/aspera/ascp/management.rb +5 -4
  22. data/lib/aspera/assert.rb +55 -24
  23. data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
  24. data/lib/aspera/cli/error.rb +1 -1
  25. data/lib/aspera/cli/extended_value.rb +28 -29
  26. data/lib/aspera/cli/formatter.rb +191 -168
  27. data/lib/aspera/cli/hints.rb +38 -4
  28. data/lib/aspera/cli/main.rb +139 -108
  29. data/lib/aspera/cli/manager.rb +51 -31
  30. data/lib/aspera/cli/plugin.rb +149 -78
  31. data/lib/aspera/cli/plugin_factory.rb +2 -2
  32. data/lib/aspera/cli/plugins/aoc.rb +217 -88
  33. data/lib/aspera/cli/plugins/ats.rb +15 -13
  34. data/lib/aspera/cli/plugins/config.rb +105 -227
  35. data/lib/aspera/cli/plugins/console.rb +49 -18
  36. data/lib/aspera/cli/plugins/cos.rb +4 -4
  37. data/lib/aspera/cli/plugins/faspex.rb +45 -51
  38. data/lib/aspera/cli/plugins/faspex5.rb +162 -163
  39. data/lib/aspera/cli/plugins/faspio.rb +6 -5
  40. data/lib/aspera/cli/plugins/httpgw.rb +2 -2
  41. data/lib/aspera/cli/plugins/node.rb +233 -247
  42. data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
  43. data/lib/aspera/cli/plugins/preview.rb +26 -29
  44. data/lib/aspera/cli/plugins/server.rb +29 -28
  45. data/lib/aspera/cli/plugins/shares.rb +40 -28
  46. data/lib/aspera/cli/sync_actions.rb +101 -80
  47. data/lib/aspera/cli/transfer_agent.rb +55 -58
  48. data/lib/aspera/cli/transfer_progress.rb +29 -20
  49. data/lib/aspera/cli/version.rb +1 -1
  50. data/lib/aspera/cli/wizard.rb +160 -0
  51. data/lib/aspera/colors.rb +13 -8
  52. data/lib/aspera/command_line_builder.rb +28 -22
  53. data/lib/aspera/command_line_converter.rb +31 -0
  54. data/lib/aspera/data_repository.rb +1 -0
  55. data/lib/aspera/environment.rb +144 -100
  56. data/lib/aspera/faspex_gw.rb +1 -1
  57. data/lib/aspera/faspex_postproc.rb +3 -2
  58. data/lib/aspera/hash_ext.rb +1 -1
  59. data/lib/aspera/id_generator.rb +10 -10
  60. data/lib/aspera/keychain/base.rb +18 -0
  61. data/lib/aspera/keychain/encrypted_hash.rb +6 -12
  62. data/lib/aspera/keychain/factory.rb +9 -3
  63. data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
  64. data/lib/aspera/keychain/macos_security.rb +13 -13
  65. data/lib/aspera/log.rb +70 -20
  66. data/lib/aspera/nagios.rb +5 -6
  67. data/lib/aspera/node_simulator.rb +12 -7
  68. data/lib/aspera/oauth/base.rb +6 -2
  69. data/lib/aspera/oauth/factory.rb +25 -18
  70. data/lib/aspera/oauth/jwt.rb +13 -1
  71. data/lib/aspera/oauth/url_json.rb +3 -3
  72. data/lib/aspera/oauth/web.rb +5 -3
  73. data/lib/aspera/persistency_folder.rb +2 -2
  74. data/lib/aspera/preview/file_types.rb +43 -35
  75. data/lib/aspera/preview/generator.rb +26 -13
  76. data/lib/aspera/preview/terminal.rb +10 -7
  77. data/lib/aspera/preview/utils.rb +11 -9
  78. data/lib/aspera/products/connect.rb +2 -1
  79. data/lib/aspera/products/desktop.rb +1 -1
  80. data/lib/aspera/products/other.rb +2 -2
  81. data/lib/aspera/products/transferd.rb +8 -6
  82. data/lib/aspera/proxy_auto_config.rb +1 -1
  83. data/lib/aspera/rest.rb +46 -28
  84. data/lib/aspera/rest_call_error.rb +1 -1
  85. data/lib/aspera/rest_error_analyzer.rb +1 -0
  86. data/lib/aspera/resumer.rb +1 -1
  87. data/lib/aspera/secret_hider.rb +46 -40
  88. data/lib/aspera/ssh.rb +14 -4
  89. data/lib/aspera/sync/args.schema.yaml +102 -0
  90. data/lib/aspera/sync/conf.schema.yaml +701 -0
  91. data/lib/aspera/sync/database.rb +83 -0
  92. data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
  93. data/lib/aspera/temp_file_manager.rb +4 -2
  94. data/lib/aspera/timer_limiter.rb +7 -5
  95. data/lib/aspera/transfer/error.rb +1 -1
  96. data/lib/aspera/transfer/error_info.rb +1 -2
  97. data/lib/aspera/transfer/faux_file.rb +11 -10
  98. data/lib/aspera/transfer/parameters.rb +6 -5
  99. data/lib/aspera/transfer/spec.rb +15 -1
  100. data/lib/aspera/transfer/spec.schema.yaml +316 -293
  101. data/lib/aspera/transfer/spec_doc.rb +34 -16
  102. data/lib/aspera/transfer/uri.rb +5 -5
  103. data/lib/aspera/uri_reader.rb +14 -10
  104. data/lib/aspera/web_auth.rb +2 -2
  105. data/lib/aspera/web_server_simple.rb +2 -2
  106. data.tar.gz.sig +0 -0
  107. metadata +15 -15
  108. metadata.gz.sig +0 -0
  109. data/examples/dascli +0 -30
  110. data/examples/get_proto_file.rb +0 -8
  111. data/examples/proxy.pac +0 -60
  112. data/lib/aspera/transfer/convert.rb +0 -29
  113. data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
  114. data/lib/aspera/transfer/sync_session.schema.yaml +0 -79
@@ -11,81 +11,15 @@ require 'terminal-table'
11
11
  require 'tty-spinner'
12
12
  require 'yaml'
13
13
  require 'pp'
14
+ require 'csv'
14
15
  require 'word_wrap'
15
16
 
16
17
  module Aspera
17
18
  module Cli
18
- # This class is used to transform a complex structure into a simple hash
19
- class Flattener
20
- def initialize(formatter)
21
- @result = nil
22
- @formatter = formatter
23
- end
24
-
25
- # General method
26
- def flatten(something)
27
- Aspera.assert_type(something, Hash)
28
- @result = {}
29
- flatten_any(something, '')
30
- return @result
31
- end
32
-
33
- private
34
-
35
- # Recursive function to flatten any type
36
- # @param something [Object] to be flattened
37
- # @param name [String] name of englobing key
38
- def flatten_any(something, name)
39
- if something.is_a?(Hash)
40
- flattened_hash(something, name)
41
- elsif something.is_a?(Array)
42
- flatten_array(something, name)
43
- elsif something.is_a?(String) && something.empty?
44
- @result[name] = @formatter.special_format('empty string')
45
- elsif something.nil?
46
- @result[name] = @formatter.special_format('null')
47
- # elsif something.eql?(true) || something.eql?(false)
48
- # @result[name] = something
49
- else
50
- @result[name] = something
51
- end
52
- end
53
-
54
- # Recursive function to flatten an array
55
- # @param array [Array] to be flattened
56
- # @param name [String] name of englobing key
57
- def flatten_array(array, name)
58
- if array.empty?
59
- @result[name] = @formatter.special_format('empty list')
60
- elsif array.all?(String)
61
- @result[name] = array.join("\n")
62
- elsif array.all?{ |i| i.is_a?(Hash) && i.keys.eql?(%w[name])}
63
- @result[name] = array.map(&:values).join(', ')
64
- elsif array.all?{ |i| i.is_a?(Hash) && i.keys.sort.eql?(%w[name value])}
65
- flattened_hash(array.each_with_object({}){ |i, h| h[i['name']] = i['value']}, name)
66
- else
67
- array.each_with_index{ |item, index| flatten_any(item, "#{name}.#{index}")}
68
- end
69
- nil
70
- end
71
-
72
- # Recursive function to flatten a Hash
73
- # @param hash [Hash] to be flattened
74
- # @param name [String] name of englobing key
75
- def flattened_hash(hash, name)
76
- prefix = name.empty? ? '' : "#{name}."
77
- hash.each do |k, v|
78
- flatten_any(v, "#{prefix}#{k}")
79
- end
80
- end
81
- end
82
-
83
- # Take care of output
19
+ # Take care of CLI output on terminal
84
20
  class Formatter
85
21
  # remove a fields from the list
86
22
  FIELDS_LESS = '-'
87
- CSV_RECORD_SEPARATOR = "\n"
88
- CSV_FIELD_SEPARATOR = ','
89
23
  # supported output formats
90
24
  DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv image].freeze
91
25
  # user output levels
@@ -93,34 +27,113 @@ module Aspera
93
27
  # column names for single object display in table
94
28
  SINGLE_OBJECT_COLUMN_NAMES = %i[field value].freeze
95
29
 
96
- private_constant :FIELDS_LESS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES
30
+ private_constant :FIELDS_LESS, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES
97
31
  # prefix to display error messages in user messages (terminal)
98
32
  ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
99
33
  WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
100
34
  HINT_FLASH = 'HINT:'.bg_green.gray.blink.freeze
101
35
 
102
36
  class << self
103
- def all_but(list)
104
- list = [list] unless list.is_a?(Array)
105
- return list.map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
106
- end
107
-
37
+ # nicer display for boolean
38
+ # used by spec_doc
108
39
  def tick(yes)
109
40
  result =
110
- if Environment.instance.terminal_supports_unicode?
111
- if yes
112
- "\u2713"
113
- else
114
- "\u2717"
115
- end
116
- elsif yes
117
- 'Y'
41
+ if Environment.terminal_supports_unicode?
42
+ yes ? "\u2713" : "\u2717"
118
43
  else
119
- ' '
44
+ yes ? 'Y' : ' '
120
45
  end
121
46
  return result.green if yes
122
47
  return result.red
123
48
  end
49
+
50
+ # Highlight special values on terminal
51
+ # empty values are dim
52
+ # used by spec_doc
53
+ def special_format(what)
54
+ result = "<#{what}>"
55
+ return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
56
+ end
57
+
58
+ # for transfer spec table, build line for display in terminal
59
+ # used by spec_doc
60
+ def check_row(row)
61
+ row.each_key do |k|
62
+ row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
63
+ end
64
+ end
65
+
66
+ # used by spec_doc
67
+ def keyword_highlight(value)
68
+ value.bold
69
+ end
70
+
71
+ # replace empty values with a readable version
72
+ def enhance_display_values_hash(input_hash)
73
+ stack = [input_hash]
74
+ until stack.empty?
75
+ current = stack.pop
76
+ current.each do |key, value|
77
+ case value
78
+ when NilClass
79
+ current[key] = special_format('null')
80
+ when String
81
+ current[key] = special_format('empty string') if value.empty?
82
+ when Array
83
+ if value.empty?
84
+ current[key] = special_format('empty list')
85
+ else
86
+ value.each do |item|
87
+ stack.push(item) if item.is_a?(Hash)
88
+ end
89
+ end
90
+ when Hash
91
+ if value.empty?
92
+ current[key] = special_format('empty dict')
93
+ else
94
+ stack.push(value)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ # Flatten a Hash into single level hash
102
+ def flatten_hash(input)
103
+ Aspera.assert_type(input, Hash)
104
+ return input if input.empty?
105
+ flat = {}
106
+ stack = [[nil, input]]
107
+ until stack.empty?
108
+ prefix, current = stack.pop
109
+ if current.respond_to?(:empty?) && current.empty?
110
+ flat[prefix] = current
111
+ next
112
+ end
113
+ case current
114
+ when Hash
115
+ current.reverse_each{ |k, v| stack.push([[prefix, k].compact.join('.'), v])}
116
+ when Array
117
+ if current.all?(String)
118
+ flat[prefix] = current.join("\n")
119
+ elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
120
+ flat[prefix] = current.map{ |i| i['name']}.join(', ')
121
+ elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
122
+ stack.push([prefix, current.each_with_object({}){ |i, h| h[i['name']] = i['value']}])
123
+ else
124
+ current.each_with_index.reverse_each{ |v, k| stack.push([[prefix, k].compact.join('.'), v])}
125
+ end
126
+ else
127
+ flat[prefix] = current
128
+ end
129
+ end
130
+ flat
131
+ end
132
+
133
+ def all_but(list)
134
+ list = [list] unless list.is_a?(Array)
135
+ return list.map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
136
+ end
124
137
  end
125
138
 
126
139
  # initialize the formatter
@@ -129,19 +142,6 @@ module Aspera
129
142
  @spinner = nil
130
143
  end
131
144
 
132
- # Highlight special values on terminal
133
- def special_format(what)
134
- result = "<#{what}>"
135
- return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
136
- end
137
-
138
- # for transfer spec table, build line for display
139
- def check_row(row)
140
- row.each_key do |k|
141
- row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
142
- end
143
- end
144
-
145
145
  # call this after REST calls if several api calls are expected
146
146
  def long_operation_running(title = '')
147
147
  return unless Environment.terminal?
@@ -159,7 +159,7 @@ module Aspera
159
159
  end
160
160
 
161
161
  def declare_options(options)
162
- default_table_style = if Environment.instance.terminal_supports_unicode?
162
+ default_table_style = if Environment.terminal_supports_unicode?
163
163
  {border: :unicode_round}
164
164
  else
165
165
  {}
@@ -170,20 +170,22 @@ module Aspera
170
170
  options.declare(
171
171
  :fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
172
172
  types: [String, Array, Regexp, Proc],
173
- default: SpecialValues::DEF)
173
+ default: SpecialValues::DEF
174
+ )
174
175
  options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
175
- options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
176
+ options.declare(:table_style, '(Table) Display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
176
177
  options.declare(:flat_hash, '(Table) Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
177
178
  options.declare(
178
179
  :multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', values: %i[no yes single],
179
- handler: {o: self, m: :option_handler}, default: :no)
180
+ handler: {o: self, m: :option_handler}, default: :no
181
+ )
180
182
  options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
181
183
  options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
182
184
  end
183
185
 
184
186
  # method accessed by option manager
185
187
  # options are: format, output, display, fields, select, table_style, flat_hash, multi_single
186
- def option_handler(option_symbol, operation, value=nil)
188
+ def option_handler(option_symbol, operation, value = nil)
187
189
  Aspera.assert_values(operation, %i[set get])
188
190
  case operation
189
191
  when :set
@@ -214,11 +216,11 @@ module Aspera
214
216
  # info: additional info, displayed if level==info
215
217
  # error: always displayed on stderr
216
218
  def display_message(message_level, message, hide_secrets: true)
217
- message = SecretHider.hide_secrets_in_string(message) if hide_secrets && message.is_a?(String) && hide_secrets?
219
+ message = SecretHider.instance.hide_secrets_in_string(message) if hide_secrets && message.is_a?(String) && hide_secrets?
218
220
  case message_level
219
221
  when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
220
222
  when :info then $stdout.puts(message) if @options[:display].eql?(:info)
221
- when :error then $stderr.puts(message)
223
+ when :error then $stderr.puts(message) # rubocop:disable Style/StderrPuts
222
224
  else Aspera.error_unexpected_value(message_level)
223
225
  end
224
226
  end
@@ -242,7 +244,7 @@ module Aspera
242
244
 
243
245
  # hides secrets in Hash or Array
244
246
  def hide_secrets(data)
245
- SecretHider.deep_remove_secret(data) if hide_secrets?
247
+ SecretHider.instance.deep_remove_secret(data) if hide_secrets?
246
248
  end
247
249
 
248
250
  # this method displays the results, especially the table format
@@ -252,13 +254,14 @@ module Aspera
252
254
  # @param fields [Array<String>] list of fields to display
253
255
  # @param name [String] name of the column to display
254
256
  def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
255
- Log.log.debug{"display_results: #{type} class=#{data.class}"}
256
- Log.log.trace1{"display_results:data=#{data}"}
257
+ Log.log.debug{"display_results: type=#{type} class=#{data.class}"}
258
+ Log.log.trace1{"display_results: data=#{data}"}
257
259
  Aspera.assert_type(type, Symbol){'result must have type'}
258
260
  Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
259
261
  display_item_count(data.length, total) unless total.nil?
260
262
  hide_secrets(data)
261
- data = SecretHider.hide_secrets_in_string(data) if data.is_a?(String) && hide_secrets?
263
+ data = SecretHider.instance.hide_secrets_in_string(data) if data.is_a?(String) && hide_secrets?
264
+ @options[:format] = :image if type.eql?(:image)
262
265
  case @options[:format]
263
266
  when :text
264
267
  display_message(:data, data.to_s)
@@ -273,87 +276,90 @@ module Aspera
273
276
  when :yaml
274
277
  display_message(:data, YAML.dump(filter_list_on_fields(data)))
275
278
  when :image
276
- # assume it is an url
277
- url = data
279
+ # if object or list, then must be a single
278
280
  case type
279
281
  when :single_object, :object_list
280
- url = [url] if type.eql?(:single_object)
281
- raise 'image display requires a single result' unless url.length == 1
282
- fields = compute_fields(url, fields)
283
- raise 'select a field to display' unless fields.length == 1
284
- url = url.first
285
- raise 'no such field' unless url.key?(fields.first)
286
- url = url[fields.first]
282
+ data = [data] if type.eql?(:single_object)
283
+ raise BadArgument, 'image display requires a single result' unless data.length == 1
284
+ fields = compute_fields(data, fields)
285
+ raise BadArgument, 'select a single field to display' unless fields.length == 1
286
+ data = data.first
287
+ raise BadArgument, 'no such field' unless data.key?(fields.first)
288
+ data = data[fields.first]
289
+ end
290
+ Aspera.assert_type(data, String){'URL or blob for image'}
291
+ # Check if URL
292
+ data =
293
+ begin
294
+ # just validate
295
+ URI.parse(data)
296
+ if Environment.instance.url_method.eql?(:text)
297
+ UriReader.read(data)
298
+ else
299
+ Environment.instance.open_uri(data)
300
+ display_message(:info, "Opened URL in browser: #{data}")
301
+ :done
302
+ end
303
+ rescue URI::InvalidURIError
304
+ data
305
+ end
306
+ # try base64
307
+ data = begin
308
+ Base64.strict_decode64(data)
309
+ rescue
310
+ data
287
311
  end
288
- raise "not url: #{url.class} #{url}" unless url.is_a?(String)
289
- display_message(:data, status_image(url))
312
+ # here, data is the image blob
313
+ display_message(:data, Preview::Terminal.build(data, **@options[:image].symbolize_keys)) unless data.eql?(:done)
290
314
  when :table, :csv
291
315
  case type
292
- when :object_list, :single_object
293
- obj_list = data
294
- if type.eql?(:single_object)
295
- obj_list = [obj_list]
316
+ when :single_object
317
+ # :single_object is a Hash, where key=colum name
318
+ Aspera.assert_type(data, Hash)
319
+ if data.empty?
320
+ display_message(:data, self.class.special_format('empty dict'))
321
+ else
322
+ data = self.class.flatten_hash(data) if @options[:flat_hash]
323
+ display_table([data], compute_fields([data], fields), single: true)
296
324
  end
297
- Aspera.assert_type(obj_list, Array)
298
- Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
299
- # :object_list is an array of hash tables, where key=colum name
300
- obj_list = obj_list.map{ |obj| Flattener.new(self).flatten(obj)} if @options[:flat_hash]
301
- display_table(obj_list, compute_fields(obj_list, fields), single: type.eql?(:single_object))
325
+ when :object_list
326
+ # :object_list is an Array of Hash, where key=colum name
327
+ Aspera.assert_type(data, Array)
328
+ Aspera.assert(data.all?(Hash)){"expecting Array of Hash: #{data.inspect}"}
329
+ data = data.map{ |obj| self.class.flatten_hash(obj)} if @options[:flat_hash]
330
+ display_table(data, compute_fields(data, fields), single: type.eql?(:single_object))
302
331
  when :value_list
303
- # :value_list is a simple array of values, name of column provided in the :name
332
+ # :value_list is a simple array of values, name of column provided in `name`
304
333
  display_table(data.map{ |i| {name => i}}, [name])
305
- when :empty # no table
306
- display_message(:info, special_format('empty'))
334
+ when :special # no table
335
+ if data.eql?(:nothing)
336
+ Log.log.debug('no result expected')
337
+ return
338
+ end
339
+ display_message(:info, self.class.special_format(data.to_s))
307
340
  return
308
- when :nothing # no result expected
309
- Log.log.debug('no result expected')
310
341
  when :status # no table
311
342
  # :status displays a simple message
312
343
  display_message(:info, data)
313
344
  when :text # no table
314
345
  # :status displays a simple message
315
346
  display_message(:data, data)
316
- else
317
- raise "unknown data type: #{type}"
347
+ else Aspera.error_unexpected_value(type){'data type'}
318
348
  end
319
- else
320
- raise "not expected: #{@options[:format]}"
349
+ else Aspera.error_unexpected_value(@options[:format]){'format'}
321
350
  end
322
351
  end
323
-
324
- # @return text suitable to display an image from url
325
- # @param blob [String] either a URL or image data
326
- def status_image(blob)
327
- # check if URL
328
- begin
329
- URI.parse(blob)
330
- url = blob
331
- unless Environment.instance.url_method.eql?(:text)
332
- Environment.instance.open_uri(url)
333
- return ''
334
- end
335
- blob = UriReader.read(url)
336
- rescue URI::InvalidURIError
337
- nil
338
- end
339
- # try base64
340
- begin
341
- blob = Base64.strict_decode64(blob)
342
- rescue
343
- nil
344
- end
345
- return Preview::Terminal.build(blob, **@options[:image].symbolize_keys)
346
- end
347
352
  #==========================================================================================
348
353
 
349
354
  private
350
355
 
356
+ # @return all fields of all objects in list of objects
351
357
  def all_fields(data)
352
358
  data.each_with_object({}){ |v, m| v.each_key{ |c| m[c] = true}}.keys
353
359
  end
354
360
 
355
361
  # @return the list of fields to display
356
- # @param data [Array<Hash>] data to display
362
+ # @param data [Array<Hash>] data to display
357
363
  # @param default [Array<String>, Proc] list of fields to display by default (may contain special values)
358
364
  def compute_fields(data, default)
359
365
  Log.log.debug{"compute_fields: data:#{data.class} default:#{default.class} #{default}"}
@@ -412,7 +418,11 @@ module Aspera
412
418
  def filter_columns_on_select(data)
413
419
  case @options[:select]
414
420
  when Proc
415
- data.select!{ |i| @options[:select].call(i)}
421
+ begin
422
+ data.select!{ |i| @options[:select].call(i)}
423
+ rescue Exception => e # rubocop:disable Lint/RescueException
424
+ raise Cli::BadArgument, "Error in user-provided ruby lambda code during select: #{e.message}"
425
+ end
416
426
  when Hash
417
427
  @options[:select].each{ |k, v| data.select!{ |i| i[k].eql?(v)}}
418
428
  end
@@ -423,12 +433,13 @@ module Aspera
423
433
  # @param fields [Array] list of column names
424
434
  def display_table(object_array, fields, single: false)
425
435
  Aspera.assert(!fields.nil?){'missing fields parameter'}
426
- filter_columns_on_select(object_array)
427
436
  if object_array.empty?
428
437
  # no display for csv
429
- display_message(:info, special_format('empty')) if @options[:format].eql?(:table)
438
+ display_message(:info, self.class.special_format('empty')) if @options[:format].eql?(:table)
430
439
  return
431
440
  end
441
+ filter_columns_on_select(object_array)
442
+ object_array.each{ |i| self.class.enhance_display_values_hash(i)}
432
443
  # if table has only one element, and only one field, display the value
433
444
  if object_array.length == 1 && fields.length == 1
434
445
  Log.log.debug("display_table: single element, field: #{fields.first}")
@@ -441,7 +452,7 @@ module Aspera
441
452
  fields = all_fields(object_array)
442
453
  single = false
443
454
  end
444
- Log.log.debug{Log.dump(:object_array, object_array)}
455
+ Log.dump(:object_array, object_array)
445
456
  # convert data to string, and keep only display fields
446
457
  final_table_rows = object_array.map{ |r| fields.map{ |c| r[c].to_s}}
447
458
  # remove empty rows
@@ -456,17 +467,29 @@ module Aspera
456
467
  display_message(:data, Terminal::Table.new(
457
468
  headings: SINGLE_OBJECT_COLUMN_NAMES,
458
469
  rows: fields.zip(row),
459
- style: @options[:table_style]&.symbolize_keys))
470
+ style: @options[:table_style].symbolize_keys
471
+ ))
460
472
  end
461
473
  else
462
474
  # display the table ! as single table
463
475
  display_message(:data, Terminal::Table.new(
464
476
  headings: fields,
465
477
  rows: final_table_rows,
466
- style: @options[:table_style]&.symbolize_keys))
478
+ style: @options[:table_style].symbolize_keys
479
+ ))
467
480
  end
468
481
  when :csv
469
- display_message(:data, final_table_rows.map{ |t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
482
+ params = @options[:table_style].symbolize_keys
483
+ # delete default
484
+ params.delete(:border)
485
+ add_headers = params.delete(:headers)
486
+ output = CSV.generate(**params) do |csv|
487
+ csv << fields if add_headers
488
+ final_table_rows.each do |row|
489
+ csv << row
490
+ end
491
+ end
492
+ display_message(:data, output)
470
493
  else
471
494
  raise "not expected: #{@options[:format]}"
472
495
  end
@@ -3,6 +3,7 @@
3
3
  require 'aspera/transfer/error'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/log'
6
+ require 'aspera/ssh'
6
7
  require 'aspera/assert'
7
8
  require 'aspera/cli/info'
8
9
  require 'net/ssh'
@@ -36,8 +37,9 @@ module Aspera
36
37
  exception: OpenSSL::SSL::SSLError,
37
38
  match: /(does not match the server certificate|certificate verify failed)/,
38
39
  remediation: [
39
- 'You can ignore SSL errors with option:',
40
- '--insecure=yes'
40
+ 'You can ignore SSL errors with either of the following options:',
41
+ '--insecure=yes (global skip)',
42
+ '--ignore-certificate=@list,https://<address>[:<port>] (selective skip)'
41
43
  ]
42
44
  },
43
45
  {
@@ -64,14 +66,46 @@ module Aspera
64
66
  remediation: [
65
67
  'If remote node is Cloud Pak For Integration',
66
68
  'Make sure that a LoadBalancer is active on cluster',
67
- 'Check the external address of Aspera tcp-proxy pod'
69
+ 'Check the external address of Aspera tcp-proxy Pod'
70
+ ]
71
+ },
72
+ {
73
+ exception: Aspera::RestCallError,
74
+ match: /Invalid subject\./,
75
+ remediation: [
76
+ 'It seems that this user name is not registered on the server',
77
+ 'Check the user name and try again'
78
+ ]
79
+ },
80
+ {
81
+ exception: OpenSSL::Cipher::CipherError,
82
+ match: /authentication tag already generated by cipher/,
83
+ remediation: [
84
+ 'If using JRuby, refer to aspera-cli documentation.',
85
+ 'Look for "unsupported algorithm".'
86
+ ]
87
+ },
88
+ {
89
+ exception: OpenSSL::SSL::SSLError,
90
+ match: /unexpected eof while reading/,
91
+ remediation: [
92
+ 'Look at parameter ssl_options in http_options.',
93
+ 'Refer to the manual for more information.',
94
+ %q{Try: --http-options=@json:'{"ssl_options":["IGNORE_UNEXPECTED_EOF"]}'}
95
+ ]
96
+ },
97
+ {
98
+ exception: Aspera::Ssh::Error,
99
+ match: /Could not chdir to home directory/,
100
+ remediation: [
101
+ 'home not created in Windows?'
68
102
  ]
69
103
  }
70
104
  ]
71
-
72
105
  private_constant :ERROR_HINTS
73
106
 
74
107
  class << self
108
+ # @param error [Exception] exception object
75
109
  def hint_for(error, formatter)
76
110
  ERROR_HINTS.each do |hint|
77
111
  next unless error.is_a?(hint[:exception])