aspera-cli 4.24.2 → 4.25.0.pre

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 (65) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1064 -758
  4. data/CONTRIBUTING.md +43 -100
  5. data/README.md +671 -419
  6. data/lib/aspera/api/aoc.rb +71 -43
  7. data/lib/aspera/api/cos_node.rb +3 -2
  8. data/lib/aspera/api/faspex.rb +6 -5
  9. data/lib/aspera/api/node.rb +10 -12
  10. data/lib/aspera/ascmd.rb +1 -2
  11. data/lib/aspera/ascp/installation.rb +53 -39
  12. data/lib/aspera/assert.rb +25 -3
  13. data/lib/aspera/cli/error.rb +4 -2
  14. data/lib/aspera/cli/extended_value.rb +84 -60
  15. data/lib/aspera/cli/formatter.rb +55 -22
  16. data/lib/aspera/cli/main.rb +21 -14
  17. data/lib/aspera/cli/manager.rb +348 -247
  18. data/lib/aspera/cli/plugins/alee.rb +3 -3
  19. data/lib/aspera/cli/plugins/aoc.rb +70 -14
  20. data/lib/aspera/cli/plugins/base.rb +57 -49
  21. data/lib/aspera/cli/plugins/config.rb +69 -84
  22. data/lib/aspera/cli/plugins/console.rb +13 -8
  23. data/lib/aspera/cli/plugins/cos.rb +1 -1
  24. data/lib/aspera/cli/plugins/faspex.rb +32 -26
  25. data/lib/aspera/cli/plugins/faspex5.rb +45 -43
  26. data/lib/aspera/cli/plugins/faspio.rb +5 -5
  27. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  28. data/lib/aspera/cli/plugins/node.rb +131 -120
  29. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  30. data/lib/aspera/cli/plugins/orchestrator.rb +114 -32
  31. data/lib/aspera/cli/plugins/preview.rb +26 -46
  32. data/lib/aspera/cli/plugins/server.rb +6 -8
  33. data/lib/aspera/cli/plugins/shares.rb +27 -32
  34. data/lib/aspera/cli/sync_actions.rb +49 -38
  35. data/lib/aspera/cli/transfer_agent.rb +16 -34
  36. data/lib/aspera/cli/version.rb +1 -1
  37. data/lib/aspera/cli/wizard.rb +8 -5
  38. data/lib/aspera/command_line_builder.rb +20 -17
  39. data/lib/aspera/coverage.rb +1 -1
  40. data/lib/aspera/environment.rb +41 -34
  41. data/lib/aspera/faspex_gw.rb +1 -1
  42. data/lib/aspera/keychain/factory.rb +1 -2
  43. data/lib/aspera/markdown.rb +31 -0
  44. data/lib/aspera/nagios.rb +6 -5
  45. data/lib/aspera/oauth/base.rb +17 -27
  46. data/lib/aspera/oauth/factory.rb +1 -1
  47. data/lib/aspera/oauth/url_json.rb +2 -1
  48. data/lib/aspera/preview/file_types.rb +23 -37
  49. data/lib/aspera/products/connect.rb +3 -3
  50. data/lib/aspera/rest.rb +51 -39
  51. data/lib/aspera/rest_error_analyzer.rb +4 -4
  52. data/lib/aspera/ssh.rb +5 -2
  53. data/lib/aspera/ssl.rb +41 -0
  54. data/lib/aspera/sync/conf.schema.yaml +182 -34
  55. data/lib/aspera/sync/database.rb +2 -1
  56. data/lib/aspera/sync/operations.rb +125 -69
  57. data/lib/aspera/transfer/parameters.rb +3 -4
  58. data/lib/aspera/transfer/spec.rb +2 -3
  59. data/lib/aspera/transfer/spec.schema.yaml +48 -18
  60. data/lib/aspera/transfer/spec_doc.rb +14 -14
  61. data/lib/aspera/uri_reader.rb +1 -1
  62. data/lib/transferd_pb.rb +2 -2
  63. data.tar.gz.sig +0 -0
  64. metadata +19 -6
  65. metadata.gz.sig +3 -2
@@ -21,6 +21,30 @@ module Aspera
21
21
  class Node < BasicAuth
22
22
  include SyncActions
23
23
 
24
+ # Processing of paths in arguments and results
25
+ # Used only by Faspex4 to browse packages
26
+ class NodePathPrefix
27
+ def initialize(path)
28
+ @root = path
29
+ end
30
+
31
+ # get next path argument from command line, and add prefix
32
+ def add_to_path(path_arg)
33
+ File.join(@root, path_arg)
34
+ end
35
+
36
+ # get remaining path arguments from command line, and add prefix
37
+ def add_to_paths!(path_args)
38
+ path_args.map!{ |p| add_to_path(p)}
39
+ end
40
+
41
+ def remove_in_object_list!(obj_list)
42
+ obj_list.each do |item|
43
+ item['path'] = item['path'][@root.length..-1] if item['path'].start_with?(@root)
44
+ end
45
+ end
46
+ end
47
+
24
48
  class << self
25
49
  # directory: node, container: shares
26
50
  FOLDER_TYPES = %w[directory container].freeze
@@ -45,12 +69,12 @@ module Aspera
45
69
  next unless base_url.match?('https?://')
46
70
  api = Rest.new(base_url: base_url)
47
71
  test_endpoint = 'ping'
48
- result = api.call(operation: 'GET', subpath: test_endpoint)
49
- next unless result[:http].body.eql?('')
72
+ http = api.read(test_endpoint, ret: :resp)
73
+ next unless http.body.eql?('')
50
74
  # also remove "/"
51
75
  url_end = -2 - test_endpoint.length
52
76
  return {
53
- url: result[:http].uri.to_s[0..url_end],
77
+ url: http.uri.to_s[0..url_end],
54
78
  version: 'requires authentication'
55
79
  }
56
80
  rescue StandardError => e
@@ -68,11 +92,11 @@ module Aspera
68
92
  options.declare(:validator, 'Identifier of validator (optional for central)')
69
93
  options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
70
94
  options.declare(
71
- :default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', values: :bool, default: :yes,
95
+ :default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', allowed: Allowed::TYPES_BOOLEAN, default: true,
72
96
  handler: {o: Api::Node, m: :use_standard_ports}
73
97
  )
74
98
  options.declare(
75
- :node_cache, 'Gen4: Set to no to force actual file system read', values: :bool,
99
+ :node_cache, 'Gen4: Set to no to force actual file system read', allowed: Allowed::TYPES_BOOLEAN,
76
100
  handler: {o: Api::Node, m: :use_node_cache}
77
101
  )
78
102
  options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
@@ -114,7 +138,7 @@ module Aspera
114
138
  SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
115
139
 
116
140
  # Actions in execute_command_gen3
117
- COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport]
141
+ COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport spec]
118
142
 
119
143
  BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
120
144
 
@@ -142,9 +166,9 @@ module Aspera
142
166
  GEN4_LS_FIELDS = %w[name type recursive_size size modified_time access_level].freeze
143
167
 
144
168
  # @param api [Rest] an existing API object for the Node API
145
- # @param prefix_path [String,nil] for Faspex 4, allows browsing a package
169
+ # @param prefix_path [String,nil] for Faspex 4, allows browsing a package without full path in node (removes storage prefix)
146
170
  def initialize(context:, api: nil, prefix_path: nil)
147
- @prefix_path = prefix_path
171
+ @prefixer = prefix_path ? NodePathPrefix.new(prefix_path) : nil
148
172
  super(context: context, basic_options: api.nil?)
149
173
  Node.declare_options(options)
150
174
  return if context.only_manual?
@@ -171,43 +195,12 @@ module Aspera
171
195
  end
172
196
  end
173
197
 
174
- # reduce the path from a result on given named column
175
- def c_result_remove_prefix_path(result, column)
176
- return result if @prefix_path.nil?
177
- case result[:type]
178
- when :object_list
179
- result[:data].each do |item|
180
- item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
181
- end
182
- when :single_object
183
- item = result[:data]
184
- item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
185
- end
186
- return result
187
- end
188
-
189
- # translates paths results into CLI result, and removes prefix
190
- def c_result_translate_rem_prefix(response, type, success_msg)
191
- errors = []
192
- final_result = {type: :object_list, data: [], fields: [type, 'result']}
193
- response['paths'].each do |p|
194
- result = success_msg
195
- if p.key?('error')
196
- Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
197
- result = p['error']['user_message']
198
- errors.push([p['path'], p['error']['user_message']])
199
- end
200
- final_result[:data].push({type => p['path'], 'result' => result})
201
- end
202
- # one error make all fail
203
- raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ') unless errors.empty?
204
- return c_result_remove_prefix_path(final_result, type)
205
- end
206
-
207
198
  # Gen3 API
208
199
  def browse_gen3
209
- folders_to_process = [get_one_argument_with_prefix('path')]
210
- query = options.get_option(:query, default: {})
200
+ folders_to_process = options.get_next_argument('path', validation: String)
201
+ folders_to_process = @prefixer.add_to_path(folders_to_process) unless @prefixer.nil?
202
+ folders_to_process = [folders_to_process]
203
+ query = options.get_option(:query) || {}
211
204
  # special parameter: max number of entries in result
212
205
  max_items = query.delete(MAX_ITEMS)
213
206
  # special parameter: recursive browsing
@@ -225,14 +218,13 @@ module Aspera
225
218
  query['path'] = path
226
219
  offset = 0
227
220
  total_count = nil
228
- result = nil
229
221
  loop do
230
222
  # example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
231
223
  response = @api_node.create('files/browse', query)
232
224
  # 'file','symbolic_link'
233
225
  if !Node.gen3_entry_folder?(response['self']) || only_path
234
- result = {type: :single_object, data: response['self']}
235
- break
226
+ @prefixer&.remove_in_object_list!([response['self']])
227
+ return Main.result_single_object(response['self'])
236
228
  end
237
229
  items = response['items']
238
230
  total_count ||= response['total_count']
@@ -253,9 +245,10 @@ module Aspera
253
245
  end
254
246
  query.delete('skip')
255
247
  end
256
- result ||= {type: :object_list, data: all_items}
248
+ @prefixer&.remove_in_object_list!(all_items)
249
+ return Main.result_object_list(all_items)
250
+ ensure
257
251
  formatter.long_operation_terminated
258
- return c_result_remove_prefix_path(result, 'path')
259
252
  end
260
253
 
261
254
  # Create async transfer spec request from direction and folders
@@ -294,48 +287,57 @@ module Aspera
294
287
  case command
295
288
  when :delete
296
289
  # TODO: add query for recursive
297
- paths_to_delete = get_all_arguments_with_prefix('file list')
290
+ paths_to_delete = options.get_next_argument('file list', multiple: true)
291
+ @prefixer&.add_to_paths!(paths_to_delete)
298
292
  resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
299
- return c_result_translate_rem_prefix(resp, 'file', 'deleted')
293
+ return cli_result_from_paths_response(resp, 'file deleted')
300
294
  when :search
301
- search_root = get_one_argument_with_prefix('search root')
295
+ search_root = options.get_next_argument('search root', validation: String)
296
+ search_root = @prefixer.add_to_path(search_root) unless @prefixer.nil?
302
297
  parameters = {'path' => search_root}
303
298
  other_options = options.get_option(:query)
304
299
  parameters.merge!(other_options) unless other_options.nil?
305
300
  resp = @api_node.create('files/search', parameters)
306
- result = {type: :object_list, data: resp['items']}
307
- return Main.result_empty if result[:data].empty?
308
- result[:fields] = result[:data].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
301
+ return Main.result_empty if resp['items'].empty?
302
+ fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
309
303
  formatter.display_item_count(resp['item_count'], resp['total_count'])
310
304
  formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
311
- return c_result_remove_prefix_path(result, 'path')
305
+ @prefixer&.remove_in_object_list!(resp['items'])
306
+ return Main.result_object_list(resp['items'], fields: fields)
312
307
  when :space
313
- path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
308
+ path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
309
+ @prefixer&.add_to_paths!(path_list)
314
310
  resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
315
- result = {type: :object_list, data: resp['paths']}
316
- # return c_result_translate_rem_prefix(resp,'folder','created',@prefix_path)
317
- return c_result_remove_prefix_path(result, 'path')
311
+ @prefixer&.remove_in_object_list!(resp['paths'])
312
+ return Main.result_object_list(resp['paths'])
318
313
  when :mkdir
319
- path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
314
+ path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
315
+ @prefixer&.add_to_paths!(path_list)
320
316
  resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
321
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
317
+ return cli_result_from_paths_response(resp, 'folder created')
322
318
  when :mklink
323
- target = get_one_argument_with_prefix('target')
324
- one_path = get_one_argument_with_prefix('link path')
319
+ target = options.get_next_argument('target', validation: String)
320
+ target = @prefixer.add_to_path(target) unless @prefixer.nil?
321
+ one_path = options.get_next_argument('link path', validation: String)
322
+ one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
325
323
  resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
326
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
324
+ return cli_result_from_paths_response(resp, 'link created')
327
325
  when :mkfile
328
- one_path = get_one_argument_with_prefix('file path')
326
+ one_path = options.get_next_argument('file path', validation: String)
327
+ one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
329
328
  contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
330
329
  resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
331
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
330
+ return cli_result_from_paths_response(resp, 'file created')
332
331
  when :rename
333
332
  # TODO: multiple ?
334
- path_base = get_one_argument_with_prefix('path_base')
335
- path_src = get_one_argument_with_prefix('path_src')
336
- path_dst = get_one_argument_with_prefix('path_dst')
333
+ path_base = options.get_next_argument('path_base', validation: String)
334
+ path_base = @prefixer.add_to_path(path_base) unless @prefixer.nil?
335
+ path_src = options.get_next_argument('path_src', validation: String)
336
+ path_src = @prefixer.add_to_path(path_src) unless @prefixer.nil?
337
+ path_dst = options.get_next_argument('path_dst', validation: String)
338
+ path_dst = @prefixer.add_to_path(path_dst) unless @prefixer.nil?
337
339
  resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
338
- return c_result_translate_rem_prefix(resp, 'entry', 'moved')
340
+ return cli_result_from_paths_response(resp, 'entry moved')
339
341
  when :browse
340
342
  return browse_gen3
341
343
  when :sync
@@ -377,15 +379,15 @@ module Aspera
377
379
  transfer_spec.delete('paths') if command.eql?(:upload)
378
380
  return Main.result_transfer(transfer.start(transfer_spec))
379
381
  when :cat
380
- remote_path = get_one_argument_with_prefix('remote path')
382
+ remote_path = options.get_next_argument('remote path', validation: String)
383
+ remote_path = @prefixer.add_to_path(remote_path) unless @prefixer.nil?
381
384
  File.basename(remote_path)
382
- result = @api_node.call(
383
- operation: 'GET',
384
- subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents"
385
- )
386
- return Main.result_text(result[:http].body)
385
+ http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
386
+ return Main.result_text(http.body)
387
387
  when :transport
388
388
  return Main.result_single_object(@api_node.transport_params)
389
+ when :spec
390
+ return Main.result_single_object(@api_node.base_spec)
389
391
  end
390
392
  Aspera.error_unreachable_line
391
393
  end
@@ -438,7 +440,7 @@ module Aspera
438
440
  nagios.add_ok('node api', 'accessible')
439
441
  nagios.check_time_offset(info['current_time'], 'node api')
440
442
  nagios.check_product_version('node api', 'entsrv', info['version'])
441
- rescue RestCallError => e
443
+ rescue StandardError => e
442
444
  nagios.add_critical('node api', e.to_s)
443
445
  end
444
446
  begin
@@ -447,13 +449,14 @@ module Aspera
447
449
  subpath: 'services/soap/Transfer-201210',
448
450
  content_type: Rest::MIME_TEXT,
449
451
  body: CENTRAL_SOAP_API_TEST,
450
- headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'}
451
- )[:http].body
452
+ headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
453
+ ret: :resp
454
+ ).body
452
455
  nagios.add_ok('central', 'accessible by node')
453
456
  rescue StandardError => e
454
457
  nagios.add_critical('central', e.to_s)
455
458
  end
456
- return nagios.result
459
+ Main.result_object_list(nagios.status_list)
457
460
  when :events
458
461
  events = @api_node.read('events', query_read_delete)
459
462
  return Main.result_object_list(events, fields: ->(f){!f.start_with?('data')})
@@ -513,8 +516,8 @@ module Aspera
513
516
  else Aspera.error_unreachable_line
514
517
  end
515
518
  return Main.result_single_object(result) if command_repo.eql?(:node_info)
516
- # check format of bearer token
517
- OAuth::Factory.bearer_token(result[:password])
519
+ raise BadArgument, 'Cannot get bearer token if authenticating with secret' unless apifid[:api].auth_params[:type].eql?(:oauth2)
520
+ Aspera.assert(OAuth::Factory.bearer_auth?(result[:password])){'Not using bearer token auth'}
518
521
  return Main.result_text(result[:password])
519
522
  when :browse
520
523
  apifid = apifid_from_next_arg(top_file_id)
@@ -531,7 +534,7 @@ module Aspera
531
534
  when :mkdir, :mklink, :mkfile
532
535
  containing_folder_path, new_item = Api::Node.split_folder(options.get_next_argument('path'))
533
536
  apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
534
- query = options.get_option(:query, mandatory: false)
537
+ query = options.get_option(:query)
535
538
  check_exists = true
536
539
  payload = {name: new_item}
537
540
  if query
@@ -570,10 +573,11 @@ module Aspera
570
573
  return Main.result_status("renamed to #{newname}")
571
574
  when :delete
572
575
  return do_bulk_operation(command: command_repo, descr: 'path', values: String, id_result: 'path') do |l_path|
573
- apifid = if (m = l_path.match(REGEX_LOOKUP_ID_BY_FIELD))
576
+ apifid = if (m = Base.percent_selector(l_path))
577
+ Aspera.assert_values(m[:field], ['id'], type: BadIdentifier)
574
578
  {
575
579
  api: @api_node,
576
- file_id: m[2]
580
+ file_id: m[:value]
577
581
  }
578
582
  else
579
583
  @api_node.resolve_api_fid(top_file_id, l_path)
@@ -602,11 +606,8 @@ module Aspera
602
606
  return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
603
607
  when :cat
604
608
  apifid = apifid_from_next_arg(top_file_id)
605
- result = apifid[:api].call(
606
- operation: 'GET',
607
- subpath: "files/#{apifid[:file_id]}/content"
608
- )
609
- return Main.result_text(result[:http].body)
609
+ http = apifid[:api].read("files/#{apifid[:file_id]}/content", ret: :resp)
610
+ return Main.result_text(http.body)
610
611
  when :show
611
612
  apifid = apifid_from_next_arg(top_file_id)
612
613
  items = apifid[:api].read("files/#{apifid[:file_id]}")
@@ -618,12 +619,8 @@ module Aspera
618
619
  return Main.result_status('Done')
619
620
  when :thumbnail
620
621
  apifid = apifid_from_next_arg(top_file_id)
621
- result = apifid[:api].call(
622
- operation: 'GET',
623
- subpath: "files/#{apifid[:file_id]}/preview",
624
- headers: {'Accept' => 'image/png'}
625
- )
626
- return Main.result_image(result[:http].body)
622
+ http = apifid[:api].read("files/#{apifid[:file_id]}/preview", headers: {'Accept' => 'image/png'}, ret: :resp)
623
+ return Main.result_image(http.body)
627
624
  when :permission
628
625
  apifid = apifid_from_next_arg(top_file_id)
629
626
  command_perm = options.get_next_command(%i[list show create delete])
@@ -677,7 +674,7 @@ module Aspera
677
674
  async_ids = @api_node.read('async/list')['sync_ids']
678
675
  summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
679
676
  selected = summaries.find{ |s| s['name'].eql?(value)}
680
- raise Cli::BadIdentifier.new('sync', "#{field}=#{value}") if selected.nil?
677
+ raise Cli::BadIdentifier.new('sync', value, field: field) if selected.nil?
681
678
  return selected['snid']
682
679
  end
683
680
 
@@ -762,7 +759,7 @@ module Aspera
762
759
  # name is unique, so we can return
763
760
  return id if sync_info[field].eql?(value)
764
761
  end
765
- raise Cli::BadIdentifier.new('ssync', "#{field}=#{value}")
762
+ raise Cli::BadIdentifier.new('ssync', value, field: field)
766
763
  end
767
764
 
768
765
  WATCH_FOLDER_MUL = %i[create list].freeze
@@ -789,7 +786,7 @@ module Aspera
789
786
  when :show
790
787
  return Main.result_single_object(@api_node.read(one_res_path))
791
788
  when :modify
792
- @api_node.update(one_res_path, options.get_option(:query, mandatory: true))
789
+ @api_node.update(one_res_path, value_create_modify(command: 'watch_folder'))
793
790
  return Main.result_status("#{one_res_id} updated")
794
791
  when :delete
795
792
  @api_node.delete(one_res_path)
@@ -837,12 +834,12 @@ module Aspera
837
834
  operation: 'POST',
838
835
  subpath: "asyncs/#{asyncs_id}/#{sync_command}",
839
836
  content_type: Rest::MIME_TEXT,
840
- body: ''
841
- )[:http].body
837
+ body: '',
838
+ ret: :resp
839
+ ).body
842
840
  return Main.result_status('Done')
843
841
  end
844
- parameters = nil
845
- parameters = options.get_option(:query, default: {}) if %i[bandwidth counters files].include?(sync_command)
842
+ parameters = options.get_option(:query) || {} if %i[bandwidth counters files].include?(sync_command)
846
843
  return Main.result_single_object(@api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters))
847
844
  end
848
845
  when :stream
@@ -895,8 +892,9 @@ module Aspera
895
892
  when :sessions
896
893
  transfers_data = @api_node.read('ops/transfers', query_read_delete)
897
894
  sessions = transfers_data.flat_map{ |t| t['sessions']}
895
+ start_end = %i[start end].freeze
898
896
  sessions.each do |session|
899
- %i[start end].each do |what|
897
+ start_end.each do |what|
900
898
  session["#{what}_time"] = session["#{what}_time_usec"] ? Time.at(session["#{what}_time_usec"] / 1_000_000.0).utc.iso8601(0) : nil
901
899
  end
902
900
  end
@@ -979,7 +977,7 @@ module Aspera
979
977
  command = options.get_next_command(%i[session file])
980
978
  validator_id = options.get_option(:validator)
981
979
  validation = {'validator_id' => validator_id} unless validator_id.nil?
982
- request_data = options.get_option(:query, default: {})
980
+ request_data = options.get_option(:query) || {}
983
981
  case command
984
982
  when :session
985
983
  command = options.get_next_command([:list])
@@ -1112,18 +1110,31 @@ module Aspera
1112
1110
 
1113
1111
  private
1114
1112
 
1115
- # get remaining path arguments from command line, and add prefix
1116
- def get_all_arguments_with_prefix(name)
1117
- path_args = options.get_next_argument(name, multiple: true)
1118
- return path_args if @prefix_path.nil?
1119
- return path_args.map{ |p| File.join(@prefix_path, p)}
1113
+ # Response has key `paths`.
1114
+ # From those, check if there is an error
1115
+ # @return [Array] of Hash with 2 keys: `path` and `result`
1116
+ def response_to_result(response, success_msg)
1117
+ errors = []
1118
+ obj_list = []
1119
+ response['paths'].each do |p|
1120
+ result = success_msg
1121
+ if p.key?('error')
1122
+ Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
1123
+ result = p['error']['user_message']
1124
+ errors.push([p['path'], p['error']['user_message']])
1125
+ end
1126
+ obj_list.push({'path' => p['path'], 'result' => result})
1127
+ end
1128
+ # one error make all fail
1129
+ raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ') unless errors.empty?
1130
+ obj_list
1120
1131
  end
1121
1132
 
1122
- # get next path argument from command line, and add prefix
1123
- def get_one_argument_with_prefix(name)
1124
- path_arg = options.get_next_argument(name, validation: String)
1125
- return path_arg if @prefix_path.nil?
1126
- return File.join(@prefix_path, path_arg)
1133
+ # Translates paths results into CLI result, and removes prefix
1134
+ def cli_result_from_paths_response(response, success_msg)
1135
+ obj_list = response_to_result(response, success_msg)
1136
+ @prefixer&.remove_in_object_list!(obj_list)
1137
+ return Main.result_object_list(obj_list, fields: %w[path result])
1127
1138
  end
1128
1139
 
1129
1140
  # Executes the provided API call in loop
@@ -1140,13 +1151,13 @@ module Aspera
1140
1151
  item_list = []
1141
1152
  query_token[:iteration_token] = iteration[0] unless iteration.nil?
1142
1153
  loop do
1143
- result = api.call(**call_args, query: query_token)
1144
- Aspera.assert_type(result[:data], Array){"Expected data to be an Array, got: #{result[:data].class}"}
1154
+ data, http = api.call(**call_args, query: query_token, ret: :both)
1155
+ Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
1145
1156
  # no data
1146
- break if result[:data].empty?
1157
+ break if data.empty?
1147
1158
  # get next iteration token from link
1148
1159
  next_iteration_token = nil
1149
- link_info = result[:http]['Link']
1160
+ link_info = http['Link']
1150
1161
  unless link_info.nil?
1151
1162
  m = link_info.match(/<([^>]+)>/)
1152
1163
  Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
@@ -1155,7 +1166,7 @@ module Aspera
1155
1166
  # same as last iteration: stop
1156
1167
  break if next_iteration_token&.eql?(query_token[:iteration_token])
1157
1168
  query_token[:iteration_token] = next_iteration_token
1158
- item_list.concat(result[:data])
1169
+ item_list.concat(data)
1159
1170
  if max&.<=(item_list.length)
1160
1171
  item_list = item_list.slice(0, max)
1161
1172
  break
@@ -13,7 +13,7 @@ module Aspera
13
13
  AUTH_OPTIONS = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password].freeze
14
14
  def initialize(**_)
15
15
  super
16
- options.declare(:auth, 'OAuth type of authentication', values: AUTH_TYPES, default: :jwt)
16
+ options.declare(:auth, 'OAuth type of authentication', allowed: AUTH_TYPES, default: :jwt)
17
17
  options.declare(:client_id, 'OAuth client identifier')
18
18
  options.declare(:client_secret, 'OAuth client secret')
19
19
  options.declare(:redirect_uri, 'OAuth (Web) redirect URI for web authentication')