aspera-cli 4.13.0 → 4.14.0

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