aspera-cli 4.13.0 → 4.14.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 (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +28 -5
  4. data/CONTRIBUTING.md +17 -1
  5. data/README.md +782 -401
  6. data/examples/dascli +1 -1
  7. data/examples/rubyc +24 -0
  8. data/lib/aspera/aoc.rb +21 -32
  9. data/lib/aspera/ascmd.rb +1 -0
  10. data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
  11. data/lib/aspera/cli/formatter.rb +17 -25
  12. data/lib/aspera/cli/main.rb +21 -27
  13. data/lib/aspera/cli/manager.rb +128 -114
  14. data/lib/aspera/cli/plugin.rb +87 -38
  15. data/lib/aspera/cli/plugins/alee.rb +2 -2
  16. data/lib/aspera/cli/plugins/aoc.rb +216 -102
  17. data/lib/aspera/cli/plugins/ats.rb +16 -18
  18. data/lib/aspera/cli/plugins/bss.rb +3 -3
  19. data/lib/aspera/cli/plugins/config.rb +177 -367
  20. data/lib/aspera/cli/plugins/console.rb +4 -6
  21. data/lib/aspera/cli/plugins/cos.rb +12 -13
  22. data/lib/aspera/cli/plugins/faspex.rb +17 -18
  23. data/lib/aspera/cli/plugins/faspex5.rb +332 -216
  24. data/lib/aspera/cli/plugins/node.rb +171 -142
  25. data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
  26. data/lib/aspera/cli/plugins/preview.rb +38 -60
  27. data/lib/aspera/cli/plugins/server.rb +22 -15
  28. data/lib/aspera/cli/plugins/shares.rb +24 -33
  29. data/lib/aspera/cli/plugins/sync.rb +3 -3
  30. data/lib/aspera/cli/transfer_agent.rb +29 -26
  31. data/lib/aspera/cli/version.rb +1 -1
  32. data/lib/aspera/colors.rb +9 -7
  33. data/lib/aspera/data/6 +0 -0
  34. data/lib/aspera/environment.rb +7 -3
  35. data/lib/aspera/fasp/agent_connect.rb +5 -0
  36. data/lib/aspera/fasp/agent_direct.rb +5 -5
  37. data/lib/aspera/fasp/agent_httpgw.rb +138 -60
  38. data/lib/aspera/fasp/agent_trsdk.rb +2 -0
  39. data/lib/aspera/fasp/error_info.rb +2 -0
  40. data/lib/aspera/fasp/installation.rb +18 -19
  41. data/lib/aspera/fasp/parameters.rb +18 -17
  42. data/lib/aspera/fasp/parameters.yaml +2 -1
  43. data/lib/aspera/fasp/resume_policy.rb +3 -3
  44. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  45. data/lib/aspera/fasp/uri.rb +23 -21
  46. data/lib/aspera/faspex_postproc.rb +1 -1
  47. data/lib/aspera/hash_ext.rb +12 -2
  48. data/lib/aspera/keychain/macos_security.rb +13 -13
  49. data/lib/aspera/log.rb +1 -0
  50. data/lib/aspera/node.rb +62 -80
  51. data/lib/aspera/oauth.rb +1 -1
  52. data/lib/aspera/persistency_action_once.rb +1 -1
  53. data/lib/aspera/preview/terminal.rb +61 -15
  54. data/lib/aspera/preview/utils.rb +3 -3
  55. data/lib/aspera/proxy_auto_config.js +2 -2
  56. data/lib/aspera/rest.rb +37 -0
  57. data/lib/aspera/secret_hider.rb +6 -1
  58. data/lib/aspera/ssh.rb +1 -1
  59. data/lib/aspera/sync.rb +2 -0
  60. data.tar.gz.sig +0 -0
  61. metadata +3 -4
  62. metadata.gz.sig +0 -0
  63. data/docs/test_env.conf +0 -186
  64. data/lib/aspera/data/7 +0 -0
@@ -72,15 +72,10 @@ module Aspera
72
72
  end
73
73
 
74
74
  def register_node_options(env)
75
- env[:options].add_opt_simple(:validator, 'identifier of validator (optional for central)')
76
- env[:options].add_opt_simple(:asperabrowserurl, 'URL for simple aspera web ui')
77
- env[:options].add_opt_simple(:sync_name, 'sync name')
78
- env[:options].add_opt_simple(:path, 'file or folder path for gen4 operation "file"')
79
- env[:options].add_opt_list(:token_type, %i[aspera basic hybrid], 'type of token used for transfers')
80
- env[:options].add_opt_boolean(:default_ports, 'use standard FASP ports or get from node api (gen4)')
81
- env[:options].set_option(:asperabrowserurl, 'https://asperabrowser.mybluemix.net')
82
- env[:options].set_option(:token_type, :aspera)
83
- env[:options].set_option(:default_ports, :yes)
75
+ env[:options].declare(:validator, 'Identifier of validator (optional for central)')
76
+ env[:options].declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
77
+ env[:options].declare(:sync_name, 'Sync name')
78
+ env[:options].declare(:default_ports, 'Use standard FASP ports or get from node api (gen4)', values: :bool, default: :yes)
84
79
  env[:options].parse_options!
85
80
  Aspera::Node.use_standard_ports = env[:options].get_option(:default_ports)
86
81
  end
@@ -117,7 +112,7 @@ module Aspera
117
112
  NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
118
113
 
119
114
  # commands for execute_command_gen4
120
- COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download file v3].concat(NODE4_READ_ACTIONS).freeze
115
+ COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download show modify permission thumbnail v3].concat(NODE4_READ_ACTIONS).freeze
121
116
 
122
117
  COMMANDS_COS = %i[upload download info access_key api_details transfer].freeze
123
118
  COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
@@ -131,23 +126,23 @@ module Aspera
131
126
  if env.key?(:node_api)
132
127
  # this can be Aspera::Node or Aspera::Rest (shares)
133
128
  env[:node_api]
134
- elsif options.get_option(:password, is_type: :mandatory).start_with?('Bearer ')
129
+ elsif options.get_option(:password, mandatory: true).start_with?('Bearer ')
135
130
  # info is provided like node_info of aoc
136
131
  Aspera::Node.new(params: {
137
- base_url: options.get_option(:url, is_type: :mandatory),
132
+ base_url: options.get_option(:url, mandatory: true),
138
133
  headers: {
139
- Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, is_type: :mandatory),
140
- 'Authorization' => options.get_option(:password, is_type: :mandatory)
134
+ Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, mandatory: true),
135
+ 'Authorization' => options.get_option(:password, mandatory: true)
141
136
  }
142
137
  })
143
138
  else
144
139
  # this is normal case
145
140
  Aspera::Node.new(params: {
146
- base_url: options.get_option(:url, is_type: :mandatory),
141
+ base_url: options.get_option(:url, mandatory: true),
147
142
  auth: {
148
143
  type: :basic,
149
- username: options.get_option(:username, is_type: :mandatory),
150
- password: options.get_option(:password, is_type: :mandatory)
144
+ username: options.get_option(:username, mandatory: true),
145
+ password: options.get_option(:password, mandatory: true)
151
146
  }})
152
147
  end
153
148
  end
@@ -227,7 +222,7 @@ module Aspera
227
222
  when :search
228
223
  search_root = get_next_arg_add_prefix(prefix_path, 'search root')
229
224
  parameters = {'path' => search_root}
230
- other_options = options.get_option(:value)
225
+ other_options = value_or_query(allowed_types: Hash)
231
226
  parameters.merge!(other_options) unless other_options.nil?
232
227
  resp = @api_node.create('files/search', parameters)
233
228
  result = { type: :object_list, data: resp[:data]['items']}
@@ -285,40 +280,22 @@ module Aspera
285
280
  node_sync = SyncSpecGen3.new(@api_node)
286
281
  return Plugins::Sync.new(@agents, sync_spec: node_sync).execute_action
287
282
  when :upload, :download
288
- token_type = options.get_option(:token_type)
289
- # nil if Shares 1.x
290
- token_type = :aspera if token_type.nil?
291
- case token_type
292
- when :aspera, :hybrid
293
- # empty transfer spec for authorization request
294
- request_transfer_spec = {}
295
- # set requested paths depending on direction
296
- request_transfer_spec[:paths] = if command.eql?(:download)
297
- transfer.ts_source_paths
298
- else
299
- [{ destination: transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND) }]
300
- end
301
- # add fixed parameters if any (for COS)
302
- @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
303
- # prepare payload for single request
304
- setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
305
- # only one request, so only one answer
306
- transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
307
- # delete this part, as the returned value contains only destination, and not sources
308
- transfer_spec.delete('paths') if command.eql?(:upload)
309
- when :basic
310
- raise 'shall have auth' unless @api_node.params[:auth].is_a?(Hash)
311
- raise 'shall be basic auth' unless @api_node.params[:auth][:type].eql?(:basic)
312
- transfer_spec = {}.merge(Aspera::Fasp::TransferSpec::AK_TSPEC_BASE)
313
- transfer_spec['remote_host'] = URI.parse(@api_node.params[:base_url]).host
314
- Fasp::TransferSpec.action_to_direction(transfer_spec, command)
315
- transfer_spec['destination_root'] = transfer.destination_folder(transfer_spec['direction'])
316
- @api_node.add_tspec_info(transfer_spec) if @api_node.respond_to?(:add_tspec_info)
317
- else raise "ERROR: token_type #{tt}"
318
- end
319
- if %i[basic hybrid].include?(token_type)
320
- @api_node.ts_basic_token(transfer_spec)
283
+ # empty transfer spec for authorization request
284
+ request_transfer_spec = {}
285
+ # set requested paths depending on direction
286
+ request_transfer_spec[:paths] = if command.eql?(:download)
287
+ transfer.ts_source_paths
288
+ else
289
+ [{ destination: transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND) }]
321
290
  end
291
+ # add fixed parameters if any (for COS)
292
+ @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
293
+ # prepare payload for single request
294
+ setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
295
+ # only one request, so only one answer
296
+ transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
297
+ # delete this part, as the returned value contains only destination, and not sources
298
+ transfer_spec.delete('paths') if command.eql?(:upload)
322
299
  return Main.result_transfer(transfer.start(transfer_spec))
323
300
  end
324
301
  raise 'INTERNAL ERROR'
@@ -367,7 +344,7 @@ module Aspera
367
344
  end
368
345
  return nagios.result
369
346
  when :events
370
- events = @api_node.read('events', options.get_option(:value))[:data]
347
+ events = @api_node.read('events', query_read_delete)[:data]
371
348
  return { type: :object_list, data: events}
372
349
  when :info
373
350
  nd_info = @api_node.read('info')[:data]
@@ -383,66 +360,17 @@ module Aspera
383
360
  return { type: :single_object, data: @api_node.params }
384
361
  end
385
362
  end
386
- GEN4_FILE_COMMANDS = %i[show modify permission thumbnail].freeze
387
- def execute_node_gen4_file_command(command_node_file, top_file_id)
388
- file_path = options.get_option(:path)
389
- apifid =
390
- if file_path.nil?
391
- {api: @api_node, file_id: instance_identifier}
392
- else
393
- @api_node.resolve_api_fid(top_file_id, file_path) # TODO: allow follow link ?
394
- end
395
- case command_node_file
396
- when :show
397
- items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
398
- return {type: :single_object, data: items}
399
- when :modify
400
- update_param = options.get_next_argument('update data', type: Hash)
401
- apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
402
- return Main.result_status('Done')
403
- when :thumbnail
404
- result = apifid[:api].call(
405
- operation: 'GET',
406
- subpath: "files/#{apifid[:file_id]}/preview",
407
- headers: {'Accept' => 'image/png'}
408
- )
409
- require 'aspera/preview/terminal'
410
- return Main.result_status(Preview::Terminal.build(result[:http].body, reserved_lines: 3))
411
- when :permission
412
- command_perm = options.get_next_command(%i[list create delete])
413
- case command_perm
414
- when :list
415
- # generic options : TODO: as arg ? option_url_query
416
- list_options ||= {'include' => [Rest::ARRAY_PARAMS, 'access_level', 'permission_count']}
417
- # add which one to get
418
- list_options['file_id'] = apifid[:file_id]
419
- list_options['inherited'] ||= false
420
- items = apifid[:api].read('permissions', list_options)[:data]
421
- return {type: :object_list, data: items}
422
- when :delete
423
- perm_id = instance_identifier
424
- return do_bulk_operation(perm_id, 'deleted') do |one_id|
425
- # TODO: notify event ?
426
- apifid[:api].delete("permissions/#{perm_id}")
427
- {'id' => one_id}
428
- end
429
- when :create
430
- create_param = options.get_next_argument('creation data', type: Hash)
431
- raise 'no file_id' if create_param.key?('file_id')
432
- create_param['file_id'] = apifid[:file_id]
433
- create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
434
- # add application specific tags (AoC)
435
- the_app = apifid[:api].app_info
436
- the_app[:api].permissions_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
437
- # create permission
438
- created_data = apifid[:api].create('permissions', create_param)[:data]
439
- # notify application of creation
440
- the_app[:api].permissions_create_event(created_data: created_data, app_info: the_app) unless the_app.nil?
441
- return { type: :single_object, data: created_data}
442
- else raise "internal error:shall not reach here (#{command_perm})"
443
- end
444
- else raise "internal error:shall not reach here (#{command_node_file})"
363
+
364
+ # @return [Hash] api and main file id for given path or id
365
+ # Allows to specify a file by its path or by its id on the node
366
+ def apifid_from_next_arg(top_file_id)
367
+ file_path = instance_identifier(description: 'path or id') do |attribute, value|
368
+ raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
369
+ # directly return result for method
370
+ return {api: @api_node, file_id: value}
445
371
  end
372
+ # there was no selector, so it is a path
373
+ return @api_node.resolve_api_fid(top_file_id, file_path)
446
374
  end
447
375
 
448
376
  def execute_command_gen4(command_repo, top_file_id)
@@ -476,7 +404,7 @@ module Aspera
476
404
  apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
477
405
  file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
478
406
  if file_info['type'].eql?('folder')
479
- result = apifid[:api].read("files/#{apifid[:file_id]}/files", options.get_option(:value))
407
+ result = apifid[:api].read("files/#{apifid[:file_id]}/files", old_query_read_delete)
480
408
  items = result[:data]
481
409
  formatter.display_item_count(result[:data].length, result[:http]['X-Total-Count'])
482
410
  else
@@ -485,7 +413,7 @@ module Aspera
485
413
  return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
486
414
  when :find
487
415
  apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
488
- test_block = Aspera::Node.file_matcher(options.get_option(:value))
416
+ test_block = Aspera::Node.file_matcher(value_or_query(allowed_types: String))
489
417
  return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
490
418
  when :mkdir
491
419
  containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
@@ -495,10 +423,10 @@ module Aspera
495
423
  return Main.result_status("created: #{result['name']} (id=#{result['id']})")
496
424
  when :rename
497
425
  file_path = options.get_next_argument('source path')
498
- newname = options.get_next_argument('new name')
499
426
  apifid = @api_node.resolve_api_fid(top_file_id, file_path)
427
+ newname = options.get_next_argument('new name')
500
428
  result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
501
- return Main.result_status("renamed #{file_path} to #{newname}")
429
+ return Main.result_status("renamed to #{newname}")
502
430
  when :delete
503
431
  return do_bulk_operation(options.get_next_argument('path'), 'deleted', id_result: 'path') do |l_path|
504
432
  raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
@@ -555,9 +483,60 @@ module Aspera
555
483
  subpath: "files/#{apifid[:file_id]}/content",
556
484
  save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE), file_name))
557
485
  return Main.result_status("downloaded: #{file_name}")
558
- when :file
559
- command_node_file = options.get_next_command(GEN4_FILE_COMMANDS)
560
- return execute_node_gen4_file_command(command_node_file, top_file_id)
486
+ when :show
487
+ apifid = apifid_from_next_arg(top_file_id)
488
+ items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
489
+ return {type: :single_object, data: items}
490
+ when :modify
491
+ apifid = apifid_from_next_arg(top_file_id)
492
+ update_param = options.get_next_argument('update data', type: Hash)
493
+ apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
494
+ return Main.result_status('Done')
495
+ when :thumbnail
496
+ apifid = apifid_from_next_arg(top_file_id)
497
+ result = apifid[:api].call(
498
+ operation: 'GET',
499
+ subpath: "files/#{apifid[:file_id]}/preview",
500
+ headers: {'Accept' => 'image/png'}
501
+ )
502
+ require 'aspera/preview/terminal'
503
+ return Main.result_status(Preview::Terminal.build(result[:http].body, reserved_lines: 3))
504
+ when :permission
505
+ apifid = apifid_from_next_arg(top_file_id)
506
+ command_perm = options.get_next_command(%i[list create delete])
507
+ case command_perm
508
+ when :list
509
+ # generic options : TODO: as arg ? query_read_delete
510
+ list_options ||= {'include' => Rest.array_params(%w[access_level permission_count])}
511
+ # add which one to get
512
+ list_options['file_id'] = apifid[:file_id]
513
+ list_options['inherited'] ||= false
514
+ items = apifid[:api].read('permissions', list_options)[:data]
515
+ return {type: :object_list, data: items}
516
+ when :delete
517
+ perm_id = instance_identifier
518
+ return do_bulk_operation(perm_id, 'deleted') do |one_id|
519
+ # TODO: notify event ?
520
+ apifid[:api].delete("permissions/#{perm_id}")
521
+ # notify application of deletion
522
+ the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app, types: ['permission.deleted']) unless the_app.nil?
523
+ {'id' => one_id}
524
+ end
525
+ when :create
526
+ create_param = options.get_next_argument('creation data', type: Hash)
527
+ raise 'no file_id' if create_param.key?('file_id')
528
+ create_param['file_id'] = apifid[:file_id]
529
+ create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
530
+ # add application specific tags (AoC)
531
+ the_app = apifid[:api].app_info
532
+ the_app[:api].permissions_set_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
533
+ # create permission
534
+ created_data = apifid[:api].create('permissions', create_param)[:data]
535
+ # notify application of creation
536
+ the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app) unless the_app.nil?
537
+ return { type: :single_object, data: created_data}
538
+ else raise "internal error:shall not reach here (#{command_perm})"
539
+ end
561
540
  else raise "INTERNAL ERROR: no case for #{command_repo}"
562
541
  end # command_repo
563
542
  # raise 'INTERNAL ERROR: missing return'
@@ -610,21 +589,21 @@ module Aspera
610
589
  # filename str
611
590
  # skip int
612
591
  # status int
613
- filter = options.get_option(:value)
592
+ filter = value_or_query(allowed_types: Hash)
614
593
  post_data.merge!(filter) unless filter.nil?
615
594
  resp = @api_node.create('async/files', post_data)[:data]
616
595
  data = resp['sync_files']
617
596
  data = data.first[async_id] unless data.empty?
618
597
  iteration_data = []
619
598
  skip_ids_persistency = nil
620
- if options.get_option(:once_only, is_type: :mandatory)
599
+ if options.get_option(:once_only, mandatory: true)
621
600
  skip_ids_persistency = PersistencyActionOnce.new(
622
601
  manager: @agents[:persistency],
623
602
  data: iteration_data,
624
603
  id: IdGenerator.from_list([
625
604
  'sync_files',
626
- options.get_option(:url, is_type: :mandatory),
627
- options.get_option(:username, is_type: :mandatory),
605
+ options.get_option(:url, mandatory: true),
606
+ options.get_option(:username, mandatory: true),
628
607
  async_id]))
629
608
  unless iteration_data.first.nil?
630
609
  data.select!{|l| l['fnid'].to_i > iteration_data.first}
@@ -659,15 +638,18 @@ module Aspera
659
638
  when :async then return execute_async # former API
660
639
  when :ssync
661
640
  # newer API
662
- sync_command = options.get_next_command(%i[bandwidth counters files start state stop summary].concat(Plugin::ALL_OPS) - %i[modify])
641
+ sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
663
642
  case sync_command
664
643
  when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids')
665
644
  else
666
- parameters = options.get_option(:value)
667
645
  asyncs_id = instance_identifier
646
+ parameters = nil
668
647
  if %i[start stop].include?(sync_command)
669
648
  @api_node.create("asyncs/#{asyncs_id}/#{sync_command}", parameters)
670
- return Main.result_status('ok')
649
+ return Main.result_status('Done')
650
+ end
651
+ if %i[bandwidth counters files].include?(sync_command)
652
+ parameters = value_or_query(allowed_types: Hash, mandatory: false) || {}
671
653
  end
672
654
  return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
673
655
  end
@@ -675,16 +657,16 @@ module Aspera
675
657
  command = options.get_next_command(%i[list create show modify cancel])
676
658
  case command
677
659
  when :list
678
- resp = @api_node.read('ops/transfers', options.get_option(:value))
660
+ resp = @api_node.read('ops/transfers', old_query_read_delete)
679
661
  return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
680
662
  when :create
681
- resp = @api_node.create('streams', options.get_option(:value, is_type: :mandatory))
663
+ resp = @api_node.create('streams', value_create_modify(command: command, type: Hash))
682
664
  return { type: :single_object, data: resp[:data] }
683
665
  when :show
684
666
  resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
685
667
  return { type: :other_struct, data: resp[:data] }
686
668
  when :modify
687
- resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", options.get_option(:value, is_type: :mandatory))
669
+ resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command, type: Hash))
688
670
  return { type: :other_struct, data: resp[:data] }
689
671
  when :cancel
690
672
  resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
@@ -693,21 +675,21 @@ module Aspera
693
675
  raise 'error'
694
676
  end
695
677
  when :transfer
696
- command = options.get_next_command(%i[list cancel show])
678
+ command = options.get_next_command(%i[list cancel show modify bandwidth_average])
697
679
  res_class_path = 'ops/transfers'
698
- if %i[cancel show].include?(command)
680
+ if %i[cancel show modify].include?(command)
699
681
  one_res_id = instance_identifier
700
682
  one_res_path = "#{res_class_path}/#{one_res_id}"
701
683
  end
702
684
  case command
703
685
  when :list
704
686
  # could use ? subpath: 'transfers'
705
- query = options.get_option(:value) || options.get_option(:query)
687
+ query = query_read_delete
706
688
  raise 'Query must be a Hash' unless query.nil? || query.is_a?(Hash)
707
- resp = @api_node.read(res_class_path, query)
689
+ transfers_data = @api_node.read(res_class_path, query)[:data]
708
690
  return {
709
691
  type: :object_list,
710
- data: resp[:data],
692
+ data: transfers_data,
711
693
  fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
712
694
  }
713
695
  when :cancel
@@ -716,6 +698,54 @@ module Aspera
716
698
  when :show
717
699
  resp = @api_node.read(one_res_path)
718
700
  return { type: :other_struct, data: resp[:data] }
701
+ when :modify
702
+ resp = @api_node.update(one_res_path, options.get_next_argument('update value', type: Hash))
703
+ return { type: :other_struct, data: resp[:data] }
704
+ when :bandwidth_average
705
+ transfers_data = @api_node.read(res_class_path, query)[:data]
706
+ # collect all key dates
707
+ bandwidth_period = {}
708
+ dir_info = %i[avg_kbps sessions].freeze
709
+ transfers_data.each do |transfer|
710
+ session = transfer
711
+ # transfer['sessions'].each do |session|
712
+ next if session['avg_rate_kbps'].zero?
713
+ bandwidth_period[session['start_time_usec']] = 0
714
+ bandwidth_period[session['end_time_usec']] = 0
715
+ # end
716
+ end
717
+ result = []
718
+ # all dates sorted numerically
719
+ all_dates = bandwidth_period.keys.sort
720
+ all_dates.each_with_index do |start_date, index|
721
+ end_date = all_dates[index + 1]
722
+ # do not process last one
723
+ break if end_date.nil?
724
+ # init data for this period
725
+ period_bandwidth = Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).each_with_object({}) do |direction, h|
726
+ h[direction] = dir_info.each_with_object({}) do |k2, h2|
727
+ h2[k2] = 0
728
+ end
729
+ end
730
+ # find all transfers that were active at this time
731
+ transfers_data.each do |transfer|
732
+ session = transfer
733
+ # transfer['sessions'].each do |session|
734
+ # skip if not information for this period
735
+ next if session['avg_rate_kbps'].zero?
736
+ # skip if not in this period
737
+ next if session['start_time_usec'] >= end_date || session['end_time_usec'] <= start_date
738
+ info = period_bandwidth[transfer['start_spec']['direction'].to_sym]
739
+ info[:avg_kbps] += session['avg_rate_kbps']
740
+ info[:sessions] += 1
741
+ # end
742
+ end
743
+ next if Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).all? do |dir|
744
+ period_bandwidth[dir][:sessions].zero?
745
+ end
746
+ result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
747
+ end
748
+ return { type: :object_list, data: result }
719
749
  else
720
750
  raise 'error'
721
751
  end
@@ -749,15 +779,15 @@ module Aspera
749
779
  @api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
750
780
  case command
751
781
  when :create
752
- resp = @api_node.create(res_class_path, options.get_option(:value, is_type: :mandatory))
782
+ resp = @api_node.create(res_class_path, value_create_modify(command: command, type: Hash))
753
783
  return Main.result_status("#{resp[:data]['id']} created")
754
784
  when :list
755
- resp = @api_node.read(res_class_path, options.get_option(:value))
785
+ resp = @api_node.read(res_class_path, old_query_read_delete)
756
786
  return { type: :value_list, data: resp[:data]['ids'], name: 'id' }
757
787
  when :show
758
788
  return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
759
789
  when :modify
760
- @api_node.update(one_res_path, options.get_option(:value, is_type: :mandatory))
790
+ @api_node.update(one_res_path, value_or_query(mandatory: true, allowed_types: Hash))
761
791
  return Main.result_status("#{one_res_id} updated")
762
792
  when :delete
763
793
  @api_node.delete(one_res_path)
@@ -769,8 +799,7 @@ module Aspera
769
799
  command = options.get_next_command(%i[session file])
770
800
  validator_id = options.get_option(:validator)
771
801
  validation = {'validator_id' => validator_id} unless validator_id.nil?
772
- request_data = options.get_option(:value)
773
- request_data ||= {}
802
+ request_data = value_create_modify(default: {}, type: Hash)
774
803
  case command
775
804
  when :session
776
805
  command = options.get_next_command([:list])
@@ -801,16 +830,16 @@ module Aspera
801
830
  end
802
831
  when :asperabrowser
803
832
  browse_params = {
804
- 'nodeUser' => options.get_option(:username, is_type: :mandatory),
805
- 'nodePW' => options.get_option(:password, is_type: :mandatory),
806
- 'nodeURL' => options.get_option(:url, is_type: :mandatory)
833
+ 'nodeUser' => options.get_option(:username, mandatory: true),
834
+ 'nodePW' => options.get_option(:password, mandatory: true),
835
+ 'nodeURL' => options.get_option(:url, mandatory: true)
807
836
  }
808
837
  # encode parameters so that it looks good in url
809
838
  encoded_params = Base64.strict_encode64(Zlib::Deflate.deflate(JSON.generate(browse_params))).gsub(/=+$/, '').tr('+/', '-_').reverse
810
839
  OpenApplication.instance.uri(options.get_option(:asperabrowserurl) + '?goto=' + encoded_params)
811
840
  return Main.result_status('done')
812
841
  when :basic_token
813
- return Main.result_status(Rest.basic_creds(options.get_option(:username, is_type: :mandatory), options.get_option(:password, is_type: :mandatory)))
842
+ return Main.result_status(Rest.basic_creds(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true)))
814
843
  end # case command
815
844
  raise 'ERROR: shall not reach this line'
816
845
  end # execute_action
@@ -9,15 +9,11 @@ module Aspera
9
9
  class Orchestrator < Aspera::Cli::BasicAuthPlugin
10
10
  def initialize(env)
11
11
  super(env)
12
- options.add_opt_simple(:params, 'parameters hash table, use @json:{"param":"value"}')
13
- options.add_opt_simple(:result, "specify result value as: 'work step:parameter'")
14
- options.add_opt_boolean(:synchronous, 'work step:parameter expected as result')
15
- options.add_opt_list(:ret_style, %i[header arg ext], 'how return type is requested in api')
16
- options.add_opt_list(:auth_style, %i[arg_pass head_basic apikey], 'authentication type')
17
- options.set_option(:params, {})
18
- options.set_option(:synchronous, :no)
19
- options.set_option(:ret_style, :arg)
20
- options.set_option(:auth_style, :head_basic)
12
+ options.declare(:params, 'Start parameters', types: Hash, default: {})
13
+ options.declare(:result, "Specify result value as: 'work step:parameter'")
14
+ options.declare(:synchronous, 'Work step:parameter expected as result', values: :bool, default: :no)
15
+ options.declare(:ret_style, 'How return type is requested in api', values: %i[header arg ext], default: :arg)
16
+ options.declare(:auth_style, 'Authentication type', values: %i[arg_pass head_basic apikey], default: :head_basic)
21
17
  options.parse_options!
22
18
  end
23
19
 
@@ -49,7 +45,7 @@ module Aspera
49
45
  call_args[:subpath] = "#{opt[:prefix]}/#{call_args[:subpath]}" unless opt[:prefix].nil?
50
46
  # specify id if necessary
51
47
  call_args[:subpath] = "#{call_args[:subpath]}/#{opt[:id]}" if opt.key?(:id)
52
- call_type = options.get_option(:ret_style, is_type: :mandatory)
48
+ call_type = options.get_option(:ret_style, mandatory: true)
53
49
  call_type = opt[:ret_style] if opt.key?(:ret_style)
54
50
  format = 'json'
55
51
  format = opt[:format] if opt.key?(:format)
@@ -73,19 +69,19 @@ module Aspera
73
69
  end
74
70
 
75
71
  def execute_action
76
- rest_params = {base_url: options.get_option(:url, is_type: :mandatory)}
77
- case options.get_option(:auth_style, is_type: :mandatory)
72
+ rest_params = {base_url: options.get_option(:url, mandatory: true)}
73
+ case options.get_option(:auth_style, mandatory: true)
78
74
  when :arg_pass
79
75
  rest_params[:auth] = {
80
76
  type: :url,
81
77
  url_creds: {
82
- 'login' => options.get_option(:username, is_type: :mandatory),
83
- 'password' => options.get_option(:password, is_type: :mandatory) }}
78
+ 'login' => options.get_option(:username, mandatory: true),
79
+ 'password' => options.get_option(:password, mandatory: true) }}
84
80
  when :head_basic
85
81
  rest_params[:auth] = {
86
82
  type: :basic,
87
- username: options.get_option(:username, is_type: :mandatory),
88
- password: options.get_option(:password, is_type: :mandatory) }
83
+ username: options.get_option(:username, mandatory: true),
84
+ password: options.get_option(:password, mandatory: true) }
89
85
  when :apikey
90
86
  raise 'Not implemented'
91
87
  end
@@ -150,11 +146,12 @@ module Aspera
150
146
  call_params = {format: :json}
151
147
  override_accept = nil
152
148
  # set external parameters if any
153
- self.options.get_option(:params, is_type: :mandatory).each do |name, value|
149
+ # TODO: make not an option, but a parameter
150
+ self.options.get_option(:params, mandatory: true).each do |name, value|
154
151
  call_params["external_parameters[#{name}]"] = value
155
152
  end
156
153
  # synchronous call ?
157
- call_params['synchronous'] = true if self.options.get_option(:synchronous, is_type: :mandatory)
154
+ call_params['synchronous'] = true if self.options.get_option(:synchronous, mandatory: true)
158
155
  # expected result for synchro call ?
159
156
  expected = self.options.get_option(:result)
160
157
  unless expected.nil?