aspera-cli 4.19.0 → 4.20.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 (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +20 -0
  4. data/CONTRIBUTING.md +16 -4
  5. data/README.md +344 -164
  6. data/bin/asession +26 -19
  7. data/examples/build_exec +65 -76
  8. data/examples/build_exec_rubyc +40 -0
  9. data/examples/get_proto_file.rb +7 -0
  10. data/lib/aspera/agent/alpha.rb +8 -8
  11. data/lib/aspera/agent/base.rb +2 -18
  12. data/lib/aspera/agent/connect.rb +14 -13
  13. data/lib/aspera/agent/direct.rb +23 -24
  14. data/lib/aspera/agent/httpgw.rb +2 -3
  15. data/lib/aspera/agent/node.rb +10 -10
  16. data/lib/aspera/agent/trsdk.rb +17 -20
  17. data/lib/aspera/api/alee.rb +15 -0
  18. data/lib/aspera/api/aoc.rb +126 -97
  19. data/lib/aspera/api/ats.rb +1 -1
  20. data/lib/aspera/api/cos_node.rb +1 -1
  21. data/lib/aspera/api/httpgw.rb +15 -10
  22. data/lib/aspera/api/node.rb +33 -12
  23. data/lib/aspera/ascmd.rb +56 -48
  24. data/lib/aspera/ascp/installation.rb +99 -42
  25. data/lib/aspera/ascp/management.rb +3 -2
  26. data/lib/aspera/ascp/products.rb +12 -0
  27. data/lib/aspera/assert.rb +10 -5
  28. data/lib/aspera/cli/formatter.rb +27 -17
  29. data/lib/aspera/cli/hints.rb +2 -1
  30. data/lib/aspera/cli/info.rb +12 -10
  31. data/lib/aspera/cli/main.rb +16 -13
  32. data/lib/aspera/cli/manager.rb +5 -0
  33. data/lib/aspera/cli/plugin.rb +15 -29
  34. data/lib/aspera/cli/plugins/alee.rb +3 -3
  35. data/lib/aspera/cli/plugins/aoc.rb +222 -194
  36. data/lib/aspera/cli/plugins/ats.rb +16 -14
  37. data/lib/aspera/cli/plugins/config.rb +53 -45
  38. data/lib/aspera/cli/plugins/console.rb +3 -3
  39. data/lib/aspera/cli/plugins/faspex.rb +11 -21
  40. data/lib/aspera/cli/plugins/faspex5.rb +44 -42
  41. data/lib/aspera/cli/plugins/faspio.rb +2 -2
  42. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  43. data/lib/aspera/cli/plugins/node.rb +153 -95
  44. data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
  45. data/lib/aspera/cli/plugins/preview.rb +8 -9
  46. data/lib/aspera/cli/plugins/server.rb +5 -9
  47. data/lib/aspera/cli/plugins/shares.rb +2 -2
  48. data/lib/aspera/cli/sync_actions.rb +2 -2
  49. data/lib/aspera/cli/transfer_agent.rb +12 -14
  50. data/lib/aspera/cli/transfer_progress.rb +35 -17
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/command_line_builder.rb +3 -4
  53. data/lib/aspera/coverage.rb +13 -1
  54. data/lib/aspera/environment.rb +34 -18
  55. data/lib/aspera/faspex_gw.rb +2 -2
  56. data/lib/aspera/json_rpc.rb +1 -1
  57. data/lib/aspera/keychain/macos_security.rb +7 -12
  58. data/lib/aspera/log.rb +3 -4
  59. data/lib/aspera/oauth/base.rb +39 -45
  60. data/lib/aspera/oauth/factory.rb +11 -4
  61. data/lib/aspera/oauth/generic.rb +4 -8
  62. data/lib/aspera/oauth/jwt.rb +3 -3
  63. data/lib/aspera/oauth/url_json.rb +1 -2
  64. data/lib/aspera/oauth/web.rb +5 -2
  65. data/lib/aspera/persistency_action_once.rb +16 -8
  66. data/lib/aspera/preview/utils.rb +5 -16
  67. data/lib/aspera/rest.rb +100 -76
  68. data/lib/aspera/transfer/faux_file.rb +4 -4
  69. data/lib/aspera/transfer/parameters.rb +14 -16
  70. data/lib/aspera/transfer/spec.rb +12 -12
  71. data/lib/aspera/transfer/sync.rb +1 -5
  72. data/lib/aspera/transfer/uri.rb +1 -1
  73. data/lib/aspera/uri_reader.rb +1 -1
  74. data/lib/aspera/web_auth.rb +166 -17
  75. data/lib/aspera/web_server_simple.rb +4 -3
  76. data/lib/transfer_pb.rb +84 -0
  77. data/lib/transfer_services_pb.rb +82 -0
  78. data.tar.gz.sig +0 -0
  79. metadata +24 -5
  80. metadata.gz.sig +0 -0
@@ -22,6 +22,10 @@ module Aspera
22
22
  class Node < Cli::BasicAuthPlugin
23
23
  include SyncActions
24
24
  class << self
25
+ # directory: node, container: shares
26
+ FOLDER_TYPES = %w[directory container].freeze
27
+ private_constant :FOLDER_TYPES
28
+
25
29
  def application_name
26
30
  'HSTS Node API'
27
31
  end
@@ -43,9 +47,9 @@ module Aspera
43
47
  test_endpoint = 'ping'
44
48
  result = api.call(operation: 'GET', subpath: test_endpoint)
45
49
  next unless result[:http].body.eql?('')
46
- url_length = -2 - test_endpoint.length
50
+ url_end = -2 - test_endpoint.length
47
51
  return {
48
- url: result[:http].uri.to_s[0..url_length],
52
+ url: result[:http].uri.to_s[0..url_end],
49
53
  version: 'requires authentication'
50
54
  }
51
55
  rescue StandardError => e
@@ -69,16 +73,25 @@ module Aspera
69
73
  end
70
74
 
71
75
  def declare_options(options)
76
+ return if @options_declared
77
+ @options_declared = true
72
78
  options.declare(:validator, 'Identifier of validator (optional for central)')
73
79
  options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
74
80
  options.declare(:sync_name, 'Sync name')
75
81
  options.declare(
76
82
  :default_ports, 'Use standard FASP ports or get from node api (gen4)', values: :bool, default: :yes,
77
83
  handler: {o: Api::Node, m: :use_standard_ports})
78
- options.declare(:root_id, 'File id of top folder if using bearer tokens')
84
+ options.declare(
85
+ :node_cache, 'Set to no to force actual file system read (gen4)', values: :bool,
86
+ handler: {o: Api::Node, m: :use_node_cache})
87
+ options.declare(:root_id, 'File id of top folder when using access key (override AK root id)')
79
88
  SyncActions.declare_options(options)
80
89
  options.parse_options!
81
90
  end
91
+
92
+ def gen3_entry_folder?(entry)
93
+ FOLDER_TYPES.include?(entry['type'])
94
+ end
82
95
  end
83
96
 
84
97
  # spellchecker: disable
@@ -164,7 +177,7 @@ module Aspera
164
177
  def c_result_translate_rem_prefix(response, type, success_msg, path_prefix)
165
178
  errors = []
166
179
  final_result = {type: :object_list, data: [], fields: [type, 'result']}
167
- JSON.parse(response[:http].body)['paths'].each do |p|
180
+ response['paths'].each do |p|
168
181
  result = success_msg
169
182
  if p.key?('error')
170
183
  Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
@@ -180,9 +193,6 @@ module Aspera
180
193
  return c_result_remove_prefix_path(final_result, type, path_prefix)
181
194
  end
182
195
 
183
- # directory: node, container: shares
184
- FOLDER_TYPE = %w[directory container].freeze
185
-
186
196
  def browse_gen3(prefix_path)
187
197
  folders_to_process = [get_one_argument_with_prefix(prefix_path, 'path')]
188
198
  query = options.get_option(:query, default: {})
@@ -206,26 +216,21 @@ module Aspera
206
216
  result = nil
207
217
  loop do
208
218
  # example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
209
- response = @api_node.call(
210
- operation: 'POST',
211
- subpath: 'files/browse',
212
- headers: {'Accept' => 'application/json'},
213
- body: query,
214
- body_type: :json)
219
+ response = @api_node.create('files/browse', query)
215
220
  # 'file','symbolic_link'
216
- if only_path || !FOLDER_TYPE.include?(response[:data]['self']['type'])
217
- result = { type: :single_object, data: response[:data]['self']}
221
+ if !Node.gen3_entry_folder?(response['self']) || only_path
222
+ result = { type: :single_object, data: response['self']}
218
223
  break
219
224
  end
220
- items = response[:data]['items']
221
- total_count ||= response[:data]['total_count']
225
+ items = response['items']
226
+ total_count ||= response['total_count']
222
227
  all_items.concat(items)
223
228
  if single_call
224
- formatter.display_item_count(response[:data]['item_count'], total_count)
229
+ formatter.display_item_count(response['item_count'], total_count)
225
230
  break
226
231
  end
227
232
  if recursive
228
- folders_to_process.concat(items.select{|i|FOLDER_TYPE.include?(i['type'])}.map{|i|i['path']})
233
+ folders_to_process.concat(items.select{|i|Node.gen3_entry_folder?(i)}.map{|i|i['path']})
229
234
  end
230
235
  if !max_items.nil? && (all_items.count >= max_items)
231
236
  all_items = all_items.slice(0, max_items) if all_items.count > max_items
@@ -239,6 +244,7 @@ module Aspera
239
244
  query.delete('skip')
240
245
  end
241
246
  result ||= {type: :object_list, data: all_items}
247
+ formatter.long_operation_terminated
242
248
  return c_result_remove_prefix_path(result, 'path', prefix_path)
243
249
  end
244
250
 
@@ -253,19 +259,19 @@ module Aspera
253
259
  when :search
254
260
  search_root = get_one_argument_with_prefix(prefix_path, 'search root')
255
261
  parameters = {'path' => search_root}
256
- other_options = query_option
262
+ other_options = options.get_option(:query)
257
263
  parameters.merge!(other_options) unless other_options.nil?
258
264
  resp = @api_node.create('files/search', parameters)
259
- result = { type: :object_list, data: resp[:data]['items']}
265
+ result = { type: :object_list, data: resp['items']}
260
266
  return Main.result_empty if result[:data].empty?
261
267
  result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
262
- formatter.display_item_count(resp[:data]['item_count'], resp[:data]['total_count'])
263
- formatter.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
268
+ formatter.display_item_count(resp['item_count'], resp['total_count'])
269
+ formatter.display_status("params: #{resp['parameters'].keys.map{|k|"#{k}:#{resp['parameters'][k]}"}.join(',')}")
264
270
  return c_result_remove_prefix_path(result, 'path', prefix_path)
265
271
  when :space
266
272
  path_list = get_all_arguments_with_prefix(prefix_path, 'folder path or ext.val. list')
267
273
  resp = @api_node.create('space', { 'paths' => path_list.map {|i| { path: i} } })
268
- result = { type: :object_list, data: resp[:data]['paths']}
274
+ result = { type: :object_list, data: resp['paths']}
269
275
  # return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
270
276
  return c_result_remove_prefix_path(result, 'path', prefix_path)
271
277
  when :mkdir
@@ -311,7 +317,7 @@ module Aspera
311
317
  # prepare payload for single request
312
318
  setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
313
319
  # only one request, so only one answer
314
- transfer_spec = @api_node.create('files/sync_setup', setup_payload)[:data]['transfer_specs'].first['transfer_spec']
320
+ transfer_spec = @api_node.create('files/sync_setup', setup_payload)['transfer_specs'].first['transfer_spec']
315
321
  # API returns null tag... but async does not like it
316
322
  transfer_spec.delete_if{ |_k, v| v.nil? }
317
323
  # delete this part, as the returned value contains only destination, and not sources
@@ -333,7 +339,7 @@ module Aspera
333
339
  # prepare payload for single request
334
340
  setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
335
341
  # only one request, so only one answer
336
- transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
342
+ transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)['transfer_specs'].first['transfer_spec']
337
343
  # delete this part, as the returned value contains only destination, and not sources
338
344
  transfer_spec.delete('paths') if command.eql?(:upload)
339
345
  return Main.result_transfer(transfer.start(transfer_spec))
@@ -363,13 +369,13 @@ module Aspera
363
369
  when *Plugin::ALL_OPS
364
370
  return entity_command(ak_command, @api_node, 'access_keys') do |field, value|
365
371
  raise 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
366
- @api_node.read('access_keys/self')[:data]['id']
372
+ @api_node.read('access_keys/self')['id']
367
373
  end
368
374
  when :do
369
375
  access_key_id = options.get_next_argument('access key id')
370
376
  root_file_id = options.get_option(:root_id)
371
377
  if root_file_id.nil?
372
- ak_info = @api_node.read("access_keys/#{access_key_id}")[:data]
378
+ ak_info = @api_node.read("access_keys/#{access_key_id}")
373
379
  # change API credentials if different access key
374
380
  if !access_key_id.eql?('self')
375
381
  @api_node.auth_params[:username] = ak_info['id']
@@ -381,7 +387,7 @@ module Aspera
381
387
  return execute_command_gen4(command_repo, root_file_id)
382
388
  when :set_bearer_key
383
389
  access_key_id = options.get_next_argument('access key id')
384
- access_key_id = @api_node.read('access_keys/self')[:data]['id'] if access_key_id.eql?('self')
390
+ access_key_id = @api_node.read('access_keys/self')['id'] if access_key_id.eql?('self')
385
391
  bearer_key_pem = options.get_next_argument('public or private RSA key PEM value', validation: String)
386
392
  key = OpenSSL::PKey.read(bearer_key_pem)
387
393
  key = key.public_key if key.private?
@@ -392,7 +398,7 @@ module Aspera
392
398
  when :health
393
399
  nagios = Nagios.new
394
400
  begin
395
- info = @api_node.read('info')[:data]
401
+ info = @api_node.read('info')
396
402
  nagios.add_ok('node api', 'accessible')
397
403
  nagios.check_time_offset(info['current_time'], 'node api')
398
404
  nagios.check_product_version('node api', 'entsrv', info['version'])
@@ -412,17 +418,17 @@ module Aspera
412
418
  end
413
419
  return nagios.result
414
420
  when :events
415
- events = @api_node.read('events', query_read_delete)[:data]
421
+ events = @api_node.read('events', query_read_delete)
416
422
  return { type: :object_list, data: events, fields: ->(f){!f.start_with?('data')} }
417
423
  when :info
418
- nd_info = @api_node.read('info')[:data]
424
+ nd_info = @api_node.read('info')
419
425
  return { type: :single_object, data: nd_info}
420
426
  when :slash
421
- nd_info = @api_node.read('')[:data]
427
+ nd_info = @api_node.read('')
422
428
  return { type: :single_object, data: nd_info}
423
429
  when :license
424
430
  # requires: asnodeadmin -mu <node user> --acl-add=internal --internal
425
- node_license = @api_node.read('license')[:data]
431
+ node_license = @api_node.read('license')
426
432
  if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
427
433
  Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal')
428
434
  end
@@ -432,8 +438,8 @@ module Aspera
432
438
  end
433
439
  end
434
440
 
435
- # @return [Hash] api and main file id for given path or id
436
- # Allows to specify a file by its path or by its id on the node
441
+ # Allows to specify a file by its path or by its id on the node in command line
442
+ # @return [Hash] api and main file id for given path or id in next argument
437
443
  def apifid_from_next_arg(top_file_id)
438
444
  file_path = instance_identifier(description: 'path or %id:<id>') do |attribute, value|
439
445
  raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
@@ -445,6 +451,9 @@ module Aspera
445
451
  end
446
452
 
447
453
  def execute_command_gen4(command_repo, top_file_id)
454
+ override_file_id = options.get_option(:root_id)
455
+ top_file_id = override_file_id unless override_file_id.nil?
456
+ raise 'Specify root file id with option root_id' if top_file_id.nil?
448
457
  case command_repo
449
458
  when :v3
450
459
  # NOTE: other common actions are unauthorized with user scope
@@ -453,7 +462,7 @@ module Aspera
453
462
  apifid = @api_node.resolve_api_fid(top_file_id, '')
454
463
  return Node.new(**init_params, api: apifid[:api]).execute_action(command_legacy)
455
464
  when :node_info, :bearer_token_node
456
- apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
465
+ apifid = apifid_from_next_arg(top_file_id)
457
466
  result = {
458
467
  url: apifid[:api].base_url,
459
468
  root_id: apifid[:file_id]
@@ -473,10 +482,14 @@ module Aspera
473
482
  OAuth::Factory.bearer_extract(result[:password])
474
483
  return Main.result_status(result[:password])
475
484
  when :browse
476
- apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
477
- file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
485
+ apifid = apifid_from_next_arg(top_file_id)
486
+ file_info = apifid[:api].read_with_cache("files/#{apifid[:file_id]}")
478
487
  if file_info['type'].eql?('folder')
479
- result = apifid[:api].read("files/#{apifid[:file_id]}/files", query_read_delete)
488
+ result = apifid[:api].call(
489
+ operation: 'GET',
490
+ subpath: "files/#{apifid[:file_id]}/files",
491
+ headers: Api::Node.cache_control_headers,
492
+ query: query_read_delete)
480
493
  items = result[:data]
481
494
  formatter.display_item_count(result[:data].length, result[:http]['X-Total-Count'])
482
495
  else
@@ -484,25 +497,33 @@ module Aspera
484
497
  end
485
498
  return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
486
499
  when :find
487
- apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
500
+ apifid = apifid_from_next_arg(top_file_id)
488
501
  test_block = Api::Node.file_matcher_from_argument(options)
489
502
  return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
490
503
  when :mkdir
491
504
  containing_folder_path = options.get_next_argument('path').split(Api::Node::PATH_SEPARATOR)
492
505
  new_folder = containing_folder_path.pop
493
- apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Api::Node::PATH_SEPARATOR))
494
- result = apifid[:api].create("files/#{apifid[:file_id]}/files", {name: new_folder, type: :folder})[:data]
506
+ # add trailing slash to force last link to be resolved
507
+ apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Api::Node::PATH_SEPARATOR), true)
508
+ result = apifid[:api].create("files/#{apifid[:file_id]}/files", {name: new_folder, type: :folder})
495
509
  return Main.result_status("created: #{result['name']} (id=#{result['id']})")
496
510
  when :rename
497
511
  file_path = options.get_next_argument('source path')
498
512
  apifid = @api_node.resolve_api_fid(top_file_id, file_path)
499
513
  newname = options.get_next_argument('new name')
500
- result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
514
+ result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})
501
515
  return Main.result_status("renamed to #{newname}")
502
516
  when :delete
503
517
  return do_bulk_operation(command: command_repo, descr: 'path', values: String, id_result: 'path') do |l_path|
504
- apifid = @api_node.resolve_api_fid(top_file_id, l_path)
505
- result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
518
+ apifid = if (m = l_path.match(REGEX_LOOKUP_ID_BY_FIELD))
519
+ {
520
+ api: @api_node,
521
+ file_id: m[2]
522
+ }
523
+ else
524
+ @api_node.resolve_api_fid(top_file_id, l_path)
525
+ end
526
+ result = apifid[:api].delete("files/#{apifid[:file_id]}")
506
527
  {'path' => l_path}
507
528
  end
508
529
  when :sync
@@ -522,27 +543,24 @@ module Aspera
522
543
  transfer_spec
523
544
  end
524
545
  when :upload
525
- apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND))
546
+ apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
526
547
  return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_SEND)))
527
548
  when :download
528
549
  source_paths = transfer.ts_source_paths
529
550
  # special case for AoC : all files must be in same folder
530
551
  source_folder = source_paths.shift['source']
531
552
  # if a single file: split into folder and path
532
- apifid = @api_node.resolve_api_fid(top_file_id, source_folder)
553
+ apifid = @api_node.resolve_api_fid(top_file_id, source_folder, true)
533
554
  if source_paths.empty?
534
555
  # get precise info in this element
535
- file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
556
+ file_info = apifid[:api].read("files/#{apifid[:file_id]}")
536
557
  case file_info['type']
537
558
  when 'file'
538
559
  # if the single source is a file, we need to split into folder path and filename
539
560
  src_dir_elements = source_folder.split(Api::Node::PATH_SEPARATOR)
540
561
  # filename is the last one, source folder is what remains
541
562
  source_paths = [{'source' => src_dir_elements.pop}]
542
- # add trailing / so that link is resolved, if it's a shared folder
543
- src_dir_elements.push('')
544
- source_folder = src_dir_elements.join(Api::Node::PATH_SEPARATOR)
545
- apifid = @api_node.resolve_api_fid(top_file_id, source_folder)
563
+ apifid = @api_node.resolve_api_fid(top_file_id, src_dir_elements.join(Api::Node::PATH_SEPARATOR), true)
546
564
  when 'link', 'folder'
547
565
  # single source is 'folder' or 'link'
548
566
  # TODO: add this ? , 'destination'=>file_info['name']
@@ -570,12 +588,12 @@ module Aspera
570
588
  return Main.result_status("downloaded: #{file_name}")
571
589
  when :show
572
590
  apifid = apifid_from_next_arg(top_file_id)
573
- items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
591
+ items = apifid[:api].read("files/#{apifid[:file_id]}")
574
592
  return {type: :single_object, data: items}
575
593
  when :modify
576
594
  apifid = apifid_from_next_arg(top_file_id)
577
595
  update_param = options.get_next_argument('update data', validation: Hash)
578
- apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
596
+ apifid[:api].update("files/#{apifid[:file_id]}", update_param)
579
597
  return Main.result_status('Done')
580
598
  when :thumbnail
581
599
  apifid = apifid_from_next_arg(top_file_id)
@@ -595,13 +613,14 @@ module Aspera
595
613
  # add which one to get
596
614
  list_options['file_id'] = apifid[:file_id]
597
615
  list_options['inherited'] ||= false
598
- items = apifid[:api].read('permissions', list_options)[:data]
616
+ items = apifid[:api].read('permissions', list_options)
599
617
  return {type: :object_list, data: items}
600
618
  when :delete
601
619
  return do_bulk_operation(command: command_perm, descr: 'identifier', values: :identifier) do |one_id|
602
620
  apifid[:api].delete("permissions/#{one_id}")
603
621
  # notify application of deletion
604
- the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app, types: ['permission.deleted']) unless the_app.nil?
622
+ the_app = apifid[:api].app_info
623
+ the_app&.[](:api)&.permissions_send_event(event_data: {}, app_info: the_app, types: ['permission.deleted'])
605
624
  {'id' => one_id}
606
625
  end
607
626
  when :create
@@ -611,11 +630,11 @@ module Aspera
611
630
  create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
612
631
  # add application specific tags (AoC)
613
632
  the_app = apifid[:api].app_info
614
- the_app[:api].permissions_set_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
633
+ the_app&.[](:api)&.permissions_set_create_params(perm_data: create_param, app_info: the_app)
615
634
  # create permission
616
- created_data = apifid[:api].create('permissions', create_param)[:data]
635
+ created_data = apifid[:api].create('permissions', create_param)
617
636
  # notify application of creation
618
- the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app) unless the_app.nil?
637
+ the_app&.[](:api)&.permissions_send_event(event_data: created_data, app_info: the_app)
619
638
  return { type: :single_object, data: created_data}
620
639
  else Aspera.error_unreachable_line
621
640
  end
@@ -632,14 +651,14 @@ module Aspera
632
651
  if async_name.nil?
633
652
  async_id = instance_identifier
634
653
  if async_id.eql?(SpecialValues::ALL) && %i[show delete].include?(command)
635
- async_ids = @api_node.read('async/list')[:data]['sync_ids']
654
+ async_ids = @api_node.read('async/list')['sync_ids']
636
655
  else
637
656
  Integer(async_id) # must be integer
638
657
  async_ids = [async_id]
639
658
  end
640
659
  else
641
- async_ids = @api_node.read('async/list')[:data]['sync_ids']
642
- summaries = @api_node.create('async/summary', {'syncs' => async_ids})[:data]['sync_summaries']
660
+ async_ids = @api_node.read('async/list')['sync_ids']
661
+ summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
643
662
  selected = summaries.find{|s|s['name'].eql?(async_name)}
644
663
  raise "no such sync: #{async_name}" if selected.nil?
645
664
  async_id = selected['snid']
@@ -649,19 +668,19 @@ module Aspera
649
668
  end
650
669
  case command
651
670
  when :list
652
- resp = @api_node.read('async/list')[:data]['sync_ids']
671
+ resp = @api_node.read('async/list')['sync_ids']
653
672
  return { type: :value_list, data: resp, name: 'id' }
654
673
  when :show
655
- resp = @api_node.create('async/summary', post_data)[:data]['sync_summaries']
674
+ resp = @api_node.create('async/summary', post_data)['sync_summaries']
656
675
  return Main.result_empty if resp.empty?
657
676
  return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if async_id.eql?(SpecialValues::ALL)
658
677
  return { type: :single_object, data: resp.first }
659
678
  when :delete
660
- resp = @api_node.create('async/delete', post_data)[:data]
679
+ resp = @api_node.create('async/delete', post_data)
661
680
  return { type: :single_object, data: resp, name: 'id' }
662
681
  when :bandwidth
663
682
  post_data['seconds'] = 100 # TODO: as parameter with --value
664
- resp = @api_node.create('async/bandwidth', post_data)[:data]
683
+ resp = @api_node.create('async/bandwidth', post_data)
665
684
  data = resp['bandwidth_data']
666
685
  return Main.result_empty if data.empty?
667
686
  data = data.first[async_id]['data']
@@ -671,9 +690,9 @@ module Aspera
671
690
  # filename str
672
691
  # skip int
673
692
  # status int
674
- filter = query_option
693
+ filter = options.get_option(:query)
675
694
  post_data.merge!(filter) unless filter.nil?
676
- resp = @api_node.create('async/files', post_data)[:data]
695
+ resp = @api_node.create('async/files', post_data)
677
696
  data = resp['sync_files']
678
697
  data = data.first[async_id] unless data.empty?
679
698
  iteration_data = []
@@ -696,7 +715,7 @@ module Aspera
696
715
  skip_ids_persistency&.save
697
716
  return { type: :object_list, data: data, name: 'id' }
698
717
  when :counters
699
- resp = @api_node.create('async/counters', post_data)[:data]['sync_counters'].first[async_id].last
718
+ resp = @api_node.create('async/counters', post_data)['sync_counters'].first[async_id].last
700
719
  return Main.result_empty if resp.nil?
701
720
  return { type: :single_object, data: resp }
702
721
  end
@@ -708,8 +727,8 @@ module Aspera
708
727
  # @param [String] value value of the field to search
709
728
  def ssync_lookup(field, value)
710
729
  raise Cli::BadArgument, "Only search by name is supported (#{field})" unless field.eql?('name')
711
- @api_node.read('asyncs')[:data]['ids'].each do |id|
712
- sync_info = @api_node.read("asyncs/#{id}")[:data]['configuration']
730
+ @api_node.read('asyncs')['ids'].each do |id|
731
+ sync_info = @api_node.read("asyncs/#{id}")['configuration']
713
732
  # name is unique, so we can return
714
733
  return id if sync_info[field].eql?(value)
715
734
  end
@@ -750,27 +769,27 @@ module Aspera
750
769
  return Main.result_status('Done')
751
770
  end
752
771
  parameters = nil
753
- parameters = query_option(default: {}) if %i[bandwidth counters files].include?(sync_command)
754
- return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
772
+ parameters = options.get_option(:query, default: {}) if %i[bandwidth counters files].include?(sync_command)
773
+ return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters) }
755
774
  end
756
775
  when :stream
757
776
  command = options.get_next_command(%i[list create show modify cancel])
758
777
  case command
759
778
  when :list
760
779
  resp = @api_node.read('ops/transfers', query_read_delete)
761
- return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
780
+ return { type: :object_list, data: resp, fields: %w[id status] } # TODO: useful?
762
781
  when :create
763
782
  resp = @api_node.create('streams', value_create_modify(command: command))
764
- return { type: :single_object, data: resp[:data] }
783
+ return { type: :single_object, data: resp }
765
784
  when :show
766
785
  resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
767
- return { type: :other_struct, data: resp[:data] }
786
+ return { type: :other_struct, data: resp }
768
787
  when :modify
769
788
  resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command))
770
- return { type: :other_struct, data: resp[:data] }
789
+ return { type: :other_struct, data: resp }
771
790
  when :cancel
772
791
  resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
773
- return { type: :other_struct, data: resp[:data] }
792
+ return { type: :other_struct, data: resp }
774
793
  else
775
794
  raise 'error'
776
795
  end
@@ -783,14 +802,50 @@ module Aspera
783
802
  end
784
803
  case command
785
804
  when :list
786
- transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
805
+ iteration_persistency = nil
806
+ iteration_data = []
807
+ if options.get_option(:once_only, mandatory: true)
808
+ iteration_persistency = PersistencyActionOnce.new(
809
+ manager: persistency,
810
+ data: iteration_data,
811
+ id: IdGenerator.from_list([
812
+ 'node_transfers',
813
+ options.get_option(:url, mandatory: true),
814
+ options.get_option(:username, mandatory: true)
815
+ ]))
816
+ end
817
+ transfer_filter = query_read_delete(default: {})
818
+ if transfer_filter.delete('reset')
819
+ iteration_data.clear
820
+ iteration_persistency&.save
821
+ return Main.result_status('Persistency reset')
822
+ end
823
+ max_items = transfer_filter.delete(MAX_ITEMS)
824
+ transfer_filter['iteration_token'] = iteration_persistency.data[0] unless iteration_data.empty?
825
+ transfers_data = []
826
+ loop do
827
+ result = @api_node.call(operation: 'GET', subpath: res_class_path, query: transfer_filter)
828
+ data = result[:data]
829
+ transfers_data.concat(data)
830
+ if !max_items.nil? && (transfers_data.length >= max_items)
831
+ transfers_data = transfers_data.slice(0, max_items)
832
+ break
833
+ end
834
+ link_info = result[:http]['Link']
835
+ break if iteration_persistency.nil? || data.empty? || link_info.nil?
836
+ m = link_info.match(/<([^>]+)>/)
837
+ raise "Problem with iteration: #{link_info}" if m.nil?
838
+ iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
839
+ iteration_data[0] = transfer_filter['iteration_token'] = iteration_token
840
+ end
841
+ iteration_persistency&.save
787
842
  return {
788
843
  type: :object_list,
789
844
  data: transfers_data,
790
845
  fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
791
846
  }
792
847
  when :sessions
793
- transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
848
+ transfers_data = @api_node.read(res_class_path, query_read_delete)
794
849
  sessions = transfers_data.map{|t|t['sessions']}.flatten
795
850
  sessions.each do |session|
796
851
  session['start_time'] = Time.at(session['start_time_usec'] / 1_000_000.0).utc.iso8601(0)
@@ -803,15 +858,15 @@ module Aspera
803
858
  }
804
859
  when :cancel
805
860
  resp = @api_node.cancel(one_res_path)
806
- return { type: :other_struct, data: resp[:data] }
861
+ return { type: :other_struct, data: resp }
807
862
  when :show
808
863
  resp = @api_node.read(one_res_path)
809
- return { type: :other_struct, data: resp[:data] }
864
+ return { type: :other_struct, data: resp }
810
865
  when :modify
811
866
  resp = @api_node.update(one_res_path, options.get_next_argument('update value', validation: Hash))
812
- return { type: :other_struct, data: resp[:data] }
867
+ return { type: :other_struct, data: resp }
813
868
  when :bandwidth_average
814
- transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
869
+ transfers_data = @api_node.read(res_class_path, query_read_delete)
815
870
  # collect all key dates
816
871
  bandwidth_period = {}
817
872
  dir_info = %i[avg_kbps sessions].freeze
@@ -866,12 +921,12 @@ module Aspera
866
921
  case command
867
922
  when :list
868
923
  resp = @api_node.read('rund/services')
869
- return { type: :object_list, data: resp[:data]['services'] }
924
+ return { type: :object_list, data: resp['services'] }
870
925
  when :create
871
926
  # @json:'{"type":"WATCHFOLDERD","run_as":{"user":"user1"}}'
872
927
  params = options.get_next_argument('creation data', validation: Hash)
873
928
  resp = @api_node.create('rund/services', params)
874
- return Main.result_status("#{resp[:data]['id']} created")
929
+ return Main.result_status("#{resp['id']} created")
875
930
  when :delete
876
931
  @api_node.delete("rund/services/#{service_id}")
877
932
  return Main.result_status("#{service_id} deleted")
@@ -889,36 +944,37 @@ module Aspera
889
944
  case command
890
945
  when :create
891
946
  resp = @api_node.create(res_class_path, value_create_modify(command: command))
892
- return Main.result_status("#{resp[:data]['id']} created")
947
+ return Main.result_status("#{resp['id']} created")
893
948
  when :list
894
949
  resp = @api_node.read(res_class_path, query_read_delete)
895
- return { type: :value_list, data: resp[:data]['ids'], name: 'id' }
950
+ return { type: :value_list, data: resp['ids'], name: 'id' }
896
951
  when :show
897
- return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
952
+ return { type: :single_object, data: @api_node.read(one_res_path)}
898
953
  when :modify
899
- @api_node.update(one_res_path, query_option(mandatory: true))
954
+ @api_node.update(one_res_path, options.get_option(:query, mandatory: true))
900
955
  return Main.result_status("#{one_res_id} updated")
901
956
  when :delete
902
957
  @api_node.delete(one_res_path)
903
958
  return Main.result_status("#{one_res_id} deleted")
904
959
  when :state
905
- return { type: :single_object, data: @api_node.read("#{one_res_path}/state")[:data] }
960
+ return { type: :single_object, data: @api_node.read("#{one_res_path}/state") }
906
961
  end
907
962
  when :central
908
963
  command = options.get_next_command(%i[session file])
909
964
  validator_id = options.get_option(:validator)
910
965
  validation = {'validator_id' => validator_id} unless validator_id.nil?
911
- request_data = query_option(default: {})
966
+ request_data = options.get_option(:query, default: {})
912
967
  case command
913
968
  when :session
914
969
  command = options.get_next_command([:list])
915
970
  case command
916
971
  when :list
972
+ request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
917
973
  request_data.deep_merge!({'validation' => validation}) unless validation.nil?
918
974
  resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
919
975
  return {
920
976
  type: :object_list,
921
- data: resp[:data]['session_info_result']['session_info'],
977
+ data: resp['session_info_result']['session_info'],
922
978
  fields: %w[session_uuid status transport direction bytes_transferred]
923
979
  }
924
980
  end
@@ -926,12 +982,14 @@ module Aspera
926
982
  command = options.get_next_command(%i[list modify])
927
983
  case command
928
984
  when :list
985
+ request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
929
986
  request_data.deep_merge!({'validation' => validation}) unless validation.nil?
930
- resp = @api_node.create('services/rest/transfers/v1/files', request_data)[:data]
987
+ resp = @api_node.create('services/rest/transfers/v1/files', request_data)
931
988
  resp = JSON.parse(resp) if resp.is_a?(String)
932
989
  Log.log.debug{Log.dump(:resp, resp)}
933
990
  return { type: :object_list, data: resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path]}
934
991
  when :modify
992
+ request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
935
993
  request_data.deep_merge!(validation) unless validation.nil?
936
994
  @api_node.update('services/rest/transfers/v1/files', request_data)
937
995
  return Main.result_status('updated')