aspera-cli 4.17.0 → 4.18.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 (81) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -4
  3. data/CHANGELOG.md +23 -0
  4. data/CONTRIBUTING.md +15 -1
  5. data/README.md +620 -378
  6. data/bin/ascli +5 -0
  7. data/bin/asession +2 -2
  8. data/lib/aspera/agent/alpha.rb +6 -4
  9. data/lib/aspera/agent/base.rb +9 -6
  10. data/lib/aspera/agent/connect.rb +4 -4
  11. data/lib/aspera/agent/direct.rb +56 -37
  12. data/lib/aspera/agent/httpgw.rb +23 -324
  13. data/lib/aspera/agent/node.rb +19 -20
  14. data/lib/aspera/agent/trsdk.rb +19 -20
  15. data/lib/aspera/api/aoc.rb +17 -14
  16. data/lib/aspera/api/cos_node.rb +4 -4
  17. data/lib/aspera/api/httpgw.rb +339 -0
  18. data/lib/aspera/api/node.rb +34 -21
  19. data/lib/aspera/ascmd.rb +4 -3
  20. data/lib/aspera/ascp/installation.rb +15 -7
  21. data/lib/aspera/ascp/management.rb +2 -2
  22. data/lib/aspera/cli/basic_auth_plugin.rb +5 -9
  23. data/lib/aspera/cli/extended_value.rb +12 -6
  24. data/lib/aspera/cli/formatter.rb +155 -65
  25. data/lib/aspera/cli/hints.rb +18 -0
  26. data/lib/aspera/cli/main.rb +22 -29
  27. data/lib/aspera/cli/manager.rb +53 -36
  28. data/lib/aspera/cli/plugin.rb +26 -17
  29. data/lib/aspera/cli/plugin_factory.rb +31 -20
  30. data/lib/aspera/cli/plugins/alee.rb +14 -2
  31. data/lib/aspera/cli/plugins/aoc.rb +141 -131
  32. data/lib/aspera/cli/plugins/ats.rb +1 -1
  33. data/lib/aspera/cli/plugins/config.rb +52 -46
  34. data/lib/aspera/cli/plugins/console.rb +8 -5
  35. data/lib/aspera/cli/plugins/faspex.rb +27 -19
  36. data/lib/aspera/cli/plugins/faspex5.rb +222 -149
  37. data/lib/aspera/cli/plugins/faspio.rb +85 -0
  38. data/lib/aspera/cli/plugins/httpgw.rb +55 -0
  39. data/lib/aspera/cli/plugins/node.rb +86 -29
  40. data/lib/aspera/cli/plugins/orchestrator.rb +31 -29
  41. data/lib/aspera/cli/plugins/preview.rb +6 -2
  42. data/lib/aspera/cli/plugins/server.rb +5 -5
  43. data/lib/aspera/cli/plugins/shares.rb +16 -14
  44. data/lib/aspera/cli/sync_actions.rb +6 -6
  45. data/lib/aspera/cli/transfer_agent.rb +5 -4
  46. data/lib/aspera/cli/version.rb +1 -1
  47. data/lib/aspera/environment.rb +7 -6
  48. data/lib/aspera/faspex_gw.rb +5 -4
  49. data/lib/aspera/faspex_postproc.rb +2 -2
  50. data/lib/aspera/log.rb +6 -3
  51. data/lib/aspera/node_simulator.rb +2 -2
  52. data/lib/aspera/oauth/base.rb +31 -19
  53. data/lib/aspera/oauth/factory.rb +12 -13
  54. data/lib/aspera/oauth/generic.rb +1 -0
  55. data/lib/aspera/oauth/jwt.rb +18 -15
  56. data/lib/aspera/oauth/url_json.rb +8 -6
  57. data/lib/aspera/open_application.rb +5 -7
  58. data/lib/aspera/persistency_folder.rb +2 -2
  59. data/lib/aspera/preview/generator.rb +3 -3
  60. data/lib/aspera/preview/options.rb +3 -3
  61. data/lib/aspera/preview/terminal.rb +4 -4
  62. data/lib/aspera/preview/utils.rb +3 -3
  63. data/lib/aspera/proxy_auto_config.rb +5 -1
  64. data/lib/aspera/rest.rb +60 -74
  65. data/lib/aspera/rest_call_error.rb +1 -1
  66. data/lib/aspera/rest_error_analyzer.rb +2 -2
  67. data/lib/aspera/rest_errors_aspera.rb +1 -1
  68. data/lib/aspera/resumer.rb +1 -1
  69. data/lib/aspera/secret_hider.rb +2 -4
  70. data/lib/aspera/ssh.rb +1 -1
  71. data/lib/aspera/transfer/parameters.rb +39 -36
  72. data/lib/aspera/transfer/spec.rb +2 -0
  73. data/lib/aspera/transfer/sync.rb +2 -1
  74. data/lib/aspera/transfer/uri.rb +1 -1
  75. data/lib/aspera/uri_reader.rb +5 -4
  76. data/lib/aspera/web_auth.rb +1 -1
  77. data/lib/aspera/web_server_simple.rb +4 -3
  78. data.tar.gz.sig +0 -0
  79. metadata +5 -3
  80. metadata.gz.sig +0 -0
  81. data/lib/aspera/cli/plugins/bss.rb +0 -71
@@ -18,7 +18,10 @@ module Aspera
18
18
  next unless base_url.start_with?('https://')
19
19
  api = Rest.new(base_url: base_url, redirect_max: 2)
20
20
  test_endpoint = 'login'
21
- test_page = api.call(operation: 'GET', subpath: test_endpoint, url_params: {local: true})
21
+ test_page = api.call(
22
+ operation: 'GET',
23
+ subpath: test_endpoint,
24
+ query: {local: true})
22
25
  next unless test_page[:http].body.include?('Aspera Console')
23
26
  version = 'unknown'
24
27
  if (m = test_page[:http].body.match(/\(v([1-9]\..*)\)/))
@@ -102,7 +105,7 @@ module Aspera
102
105
  end
103
106
  end
104
107
  end
105
- end # Console
106
- end # Plugins
107
- end # Cli
108
- end # Aspera
108
+ end
109
+ end
110
+ end
111
+ end
@@ -179,7 +179,11 @@ module Aspera
179
179
  loop do
180
180
  # get a batch of package information
181
181
  # order: first batch is latest packages, and then in a batch ids are increasing
182
- atom_xml = api_v3.call(operation: 'GET', subpath: "#{mailbox}.atom", headers: {'Accept' => 'application/xml'}, url_params: mailbox_query)[:http].body
182
+ atom_xml = api_v3.call(
183
+ operation: 'GET',
184
+ subpath: "#{mailbox}.atom",
185
+ headers: {'Accept' => 'application/xml'},
186
+ query: mailbox_query)[:http].body
183
187
  box_data = XmlSimple.xml_in(atom_xml, {'ForceArray' => %w[entry field link to]})
184
188
  Log.log.debug{Log.dump(:box_data, box_data)}
185
189
  items = box_data.key?('entry') ? box_data['entry'] : []
@@ -246,8 +250,9 @@ module Aspera
246
250
  package_creation_data = api_public_link.call(
247
251
  operation: 'POST',
248
252
  subpath: create_path,
249
- json_params: package_create_params,
250
- headers: {'Accept' => 'text/javascript'})[:http].body
253
+ headers: {'Accept' => 'text/javascript'},
254
+ body: package_create_params,
255
+ body_type: :json)[:http].body
251
256
  # get arguments of function call
252
257
  package_creation_data.delete!("\n") # one line
253
258
  package_creation_data.gsub!(/^[^"]+\("\{/, '{') # delete header
@@ -308,8 +313,9 @@ module Aspera
308
313
  pkg_created = api_v3.call(
309
314
  operation: 'POST',
310
315
  subpath: 'send',
311
- json_params: package_create_params,
312
- headers: {'Accept' => 'application/json'}
316
+ headers: {'Accept' => 'application/json'},
317
+ body: package_create_params,
318
+ body_type: :json
313
319
  )[:data]
314
320
  if first_source.key?('id')
315
321
  # no transfer spec if remote source: handled by faspex
@@ -380,9 +386,9 @@ module Aspera
380
386
  api_public_link = Rest.new(base_url: link_data[:base_url])
381
387
  package_creation_data = api_public_link.call(
382
388
  operation: 'GET',
383
- subpath: link_data[:subpath],
384
- url_params: {passcode: link_data[:query]['passcode']},
385
- headers: {'Accept' => 'application/xml'})
389
+ subpath: link_data[:subpath],
390
+ headers: {'Accept' => 'application/xml'},
391
+ query: {passcode: link_data[:query]['passcode']})
386
392
  if !package_creation_data[:http].body.start_with?('<?xml ')
387
393
  OpenApplication.instance.uri(link_url)
388
394
  raise Cli::Error, 'Unexpected response: package not found ?'
@@ -391,7 +397,7 @@ module Aspera
391
397
  Log.log.debug{Log.dump(:package_entry, package_entry)}
392
398
  transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
393
399
  pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
394
- end # public link
400
+ end
395
401
  # prune packages already downloaded
396
402
  # TODO : remove ids from skip not present in inbox to avoid growing too big
397
403
  # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
@@ -411,10 +417,12 @@ module Aspera
411
417
  xml_payload =
412
418
  %Q(<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="#{sanitized}"/></url-list>)
413
419
  transfer_spec['token'] = api_v3.call(
414
- operation: 'POST',
415
- subpath: 'issue-token?direction=down',
416
- headers: {'Accept' => 'text/plain', 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
417
- text_body_params: xml_payload)[:http].body
420
+ operation: 'POST',
421
+ subpath: 'issue-token',
422
+ headers: {'Accept' => 'text/plain', 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
423
+ query: {'direction' => 'down'},
424
+ body: xml_payload,
425
+ body_type: :text)[:http].body
418
426
  end
419
427
  transfer_spec['direction'] = Transfer::Spec::DIRECTION_RECEIVE
420
428
  statuses = transfer.start(transfer_spec)
@@ -502,9 +510,9 @@ module Aspera
502
510
  when :address_book
503
511
  result = api_v3.call(
504
512
  operation: 'GET',
505
- subpath: 'address-book',
506
- headers: {'Accept' => 'application/json'},
507
- url_params: {'format' => 'json', 'count' => 100_000}
513
+ subpath: 'address-book',
514
+ headers: {'Accept' => 'application/json'},
515
+ query: {'format' => 'json', 'count' => 100_000}
508
516
  )[:data]
509
517
  formatter.display_status("users: #{result['itemsPerPage']}/#{result['totalResults']}, start:#{result['startIndex']}")
510
518
  users = result['entry']
@@ -526,9 +534,9 @@ module Aspera
526
534
  login_meths = api_v3.call(operation: 'GET', subpath: 'login/new', headers: {'Accept' => 'application/xrds+xml'})[:http].body
527
535
  login_methods = XmlSimple.xml_in(login_meths, {'ForceArray' => false})
528
536
  return {type: :object_list, data: login_methods['XRD']['Service']}
529
- end # command
537
+ end
530
538
  end
531
539
  end
532
540
  end
533
- end # Cli
534
- end # Aspera
541
+ end
542
+ end
@@ -10,7 +10,6 @@ require 'aspera/nagios'
10
10
  require 'aspera/environment'
11
11
  require 'aspera/assert'
12
12
  require 'securerandom'
13
- require 'tty-spinner'
14
13
 
15
14
  module Aspera
16
15
  module Cli
@@ -24,8 +23,8 @@ module Aspera
24
23
  # Faspex API v5: get transfer spec for connect
25
24
  TRANSFER_CONNECT = 'connect'
26
25
  ADMIN_RESOURCES = %i[
27
- accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
28
- metadata_profiles email_notifications alternate_addresses
26
+ accounts distribution_lists contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
27
+ metadata_profiles email_notifications alternate_addresses webhooks
29
28
  ].freeze
30
29
  # states for jobs not in final state
31
30
  JOB_RUNNING = %w[queued working].freeze
@@ -58,7 +57,7 @@ module Aspera
58
57
  path_api_detect = "#{PATH_API_V5}/#{PATH_HEALTH}"
59
58
  result = api.read(path_api_detect)
60
59
  next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
61
- # end is at -1, and substract 1 for "/"
60
+ # end is at -1, and subtract 1 for "/"
62
61
  url_length = -2 - path_api_detect.length
63
62
  # take redirect if any
64
63
  return {
@@ -237,31 +236,27 @@ module Aspera
237
236
  end
238
237
 
239
238
  def wait_for_job(job_id)
240
- spinner = nil
241
239
  loop do
242
240
  status = @api_v5.read("jobs/#{job_id}", {type: :formatted})[:data]
243
241
  return status unless JOB_RUNNING.include?(status['status'])
244
- if spinner.nil?
245
- spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
246
- spinner.start
247
- end
248
- spinner.update(title: status['status'])
249
- spinner.spin
242
+ formatter.long_operation_running(status['status'])
250
243
  sleep(0.5)
251
244
  end
252
245
  Aspera.error_unreachable_line
253
246
  end
254
247
 
255
- # Get a (full or partial) list of all entities of a given type
248
+ # Get a (full or partial) list of all entities of a given type with query: offset/limit
256
249
  # @param type [String] the type of entity to list (just a name)
257
250
  # @param query [Hash,nil] additional query parameters
258
251
  # @param real_path [String] real path if it's n ot just the type
259
252
  # @param item_list_key [String] key in the result to get the list of items
260
- def list_entities(type:, real_path: nil, query: {}, item_list_key: nil)
253
+ def list_entities(type:, real_path: nil, item_list_key: nil, query: {})
254
+ Log.log.trace1{"list_entities t=#{type} p=#{real_path} k=#{item_list_key} q=#{query}"}
261
255
  type = type.to_s if type.is_a?(Symbol)
262
256
  Aspera.assert_type(type, String)
257
+ Aspera.assert_type(query, Hash)
263
258
  item_list_key = type if item_list_key.nil?
264
- full_path = real_path.nil? ? type : real_path
259
+ real_path = type if real_path.nil?
265
260
  result = []
266
261
  offset = 0
267
262
  max_items = query.delete(MAX_ITEMS)
@@ -270,7 +265,8 @@ module Aspera
270
265
  query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
271
266
  loop do
272
267
  query['offset'] = offset
273
- page_result = @api_v5.read(full_path, query)[:data]
268
+ page_result = @api_v5.read(real_path, query)[:data]
269
+ Aspera.assert_type(page_result[item_list_key], Array)
274
270
  result.concat(page_result[item_list_key])
275
271
  # reach the limit set by user ?
276
272
  if !max_items.nil? && (result.length >= max_items)
@@ -281,12 +277,19 @@ module Aspera
281
277
  remain_pages -= 1 unless remain_pages.nil?
282
278
  break if remain_pages == 0
283
279
  offset += page_result[item_list_key].length
280
+ formatter.long_operation_running
284
281
  end
285
282
  return result
286
283
  end
287
284
 
288
285
  # lookup an entity id from its name
289
- def lookup_entity_by_field(type:, value:, field: 'name', query: :default, real_path: nil, item_list_key: nil)
286
+ # @param type [String] the type of entity to lookup, by default it is the path, and it is also the field name in result
287
+ # @param value [String] the value to lookup
288
+ # @param field [String] the field to match, by default it is 'name'
289
+ # @param real_path [String] real path if it's not just the type (override type)
290
+ # @param item_list_key [String] key in the result to get the list of items (override type)
291
+ # @param query [Hash] additional query parameters
292
+ def lookup_entity_by_field(type:, value:, field: 'name', real_path: nil, item_list_key: nil, query: :default)
290
293
  if query.eql?(:default)
291
294
  Aspera.assert(field.eql?('name')){'Default query is on name only'}
292
295
  query = {'q'=> value}
@@ -381,8 +384,9 @@ module Aspera
381
384
  operation: 'POST',
382
385
  subpath: "packages/#{pkg_id}/transfer_spec/download",
383
386
  headers: {'Accept' => 'application/json'},
384
- url_params: download_params,
385
- json_params: param_file_list
387
+ query: download_params,
388
+ body: param_file_list,
389
+ body_type: :json
386
390
  )[:data]
387
391
  # delete flag for Connect Client
388
392
  transfer_spec.delete('authentication')
@@ -400,7 +404,7 @@ module Aspera
400
404
  # browse a folder
401
405
  # @param browse_endpoint [String] the endpoint to browse
402
406
  def browse_folder(browse_endpoint)
403
- path = options.get_next_argument('folder path', mandatory: false, default: '/')
407
+ folders_to_process = [options.get_next_argument('folder path', mandatory: false, default: '/')]
404
408
  query = query_read_delete(default: {})
405
409
  query['filters'] = {} unless query.key?('filters')
406
410
  filters = query.delete('filters')
@@ -409,17 +413,16 @@ module Aspera
409
413
  max_items = query.delete('max')
410
414
  recursive = query.delete('recursive')
411
415
  all_items = []
412
- folders_to_process = [path]
413
416
  until folders_to_process.empty?
414
417
  path = folders_to_process.shift
415
- query.delete('iteration_token')
416
418
  loop do
417
419
  response = @api_v5.call(
418
420
  operation: 'POST',
419
421
  subpath: browse_endpoint,
420
- headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
421
- url_params: query,
422
- json_params: {'path' => path, 'filters' => filters})
422
+ headers: {'Accept' => 'application/json'},
423
+ query: query,
424
+ body: {'path' => path, 'filters' => filters},
425
+ body_type: :json)
423
426
  all_items.concat(response[:data]['items'])
424
427
  if recursive
425
428
  folders_to_process.concat(response[:data]['items'].select{|i|i['type'].eql?('directory')}.map{|i|i['path']})
@@ -431,7 +434,9 @@ module Aspera
431
434
  iteration_token = response[:http][HEADER_ITERATION_TOKEN]
432
435
  break if iteration_token.nil? || iteration_token.empty?
433
436
  query['iteration_token'] = iteration_token
437
+ formatter.long_operation_running(all_items.count)
434
438
  end
439
+ query.delete('iteration_token')
435
440
  end
436
441
  return {type: :object_list, data: all_items}
437
442
  end
@@ -459,9 +464,14 @@ module Aspera
459
464
  ids = package_id
460
465
  ids = [ids] unless ids.is_a?(Array)
461
466
  Aspera.assert_type(ids, Array){'Package identifier'}
462
- Aspera.assert(ids.all?(String)){'Package id shall be String'}
467
+ Aspera.assert(ids.all?(String)){"Package id(s) shall be String, but have: #{ids.map(&:class).uniq.join(', ')}"}
463
468
  # API returns 204, empty on success
464
- @api_v5.call(operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids})
469
+ @api_v5.call(
470
+ operation: 'DELETE',
471
+ subpath: 'packages',
472
+ headers: {'Accept' => 'application/json'},
473
+ body: {ids: ids},
474
+ body_type: :json)
465
475
  return Main.result_status('Package(s) deleted')
466
476
  when :receive
467
477
  return package_receive(package_id)
@@ -483,8 +493,9 @@ module Aspera
483
493
  operation: 'POST',
484
494
  subpath: "packages/#{package['id']}/transfer_spec/upload",
485
495
  headers: {'Accept' => 'application/json'},
486
- url_params: {transfer_type: TRANSFER_CONNECT},
487
- json_params: {paths: transfer.source_list}
496
+ query: {transfer_type: TRANSFER_CONNECT},
497
+ body: {paths: transfer.source_list},
498
+ body_type: :json
488
499
  )[:data]
489
500
  # well, we asked a TS for connect, but we actually want a generic one
490
501
  transfer_spec.delete('authentication')
@@ -492,7 +503,10 @@ module Aspera
492
503
  else
493
504
  # send from remote shared folder
494
505
  if (m = shared_folder.match(REGEX_LOOKUP_ID_BY_FIELD))
495
- shared_folder = lookup_entity_by_field(type: 'shared_folders', value: m[2])['id']
506
+ shared_folder = lookup_entity_by_field(
507
+ type: 'shared_folders',
508
+ field: m[1],
509
+ value: ExtendedValue.instance.evaluate(m[2]))['id']
496
510
  end
497
511
  transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
498
512
  # start remote transfer and get first status
@@ -510,7 +524,174 @@ module Aspera
510
524
  data: list_packages_with_filter,
511
525
  fields: %w[id title release_date total_bytes total_files created_time state]
512
526
  }
513
- end # case package
527
+ end
528
+ end
529
+
530
+ def execute_resource(res_type)
531
+ list_key = res_path = res_type.to_s
532
+ id_as_arg = false
533
+ display_fields = nil
534
+ adm_api = @api_v5
535
+ res_id_query = :default
536
+ delete_style = nil
537
+ available_commands = [].concat(Plugin::ALL_OPS)
538
+ case res_type
539
+ when :metadata_profiles
540
+ res_path = 'configuration/metadata_profiles'
541
+ list_key = 'profiles'
542
+ when :alternate_addresses
543
+ res_path = 'configuration/alternate_addresses'
544
+ when :distribution_lists
545
+ res_path = 'account/distribution_lists'
546
+ list_key = 'distribution_lists'
547
+ delete_style = 'ids'
548
+ when :email_notifications
549
+ list_key = false
550
+ id_as_arg = 'type'
551
+ when :accounts
552
+ display_fields = Formatter.all_but('user_profile_data_attributes')
553
+ when :oauth_clients
554
+ display_fields = Formatter.all_but('public_key')
555
+ adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
556
+ when :shared_inboxes, :workgroups
557
+ available_commands.push(:members, :saml_groups, :invite_external_collaborator)
558
+ res_id_query = {'all': true}
559
+ when :nodes
560
+ available_commands.push(:shared_folders, :browse)
561
+ end
562
+ res_command = options.get_next_command(available_commands)
563
+ case res_command
564
+ when *Plugin::ALL_OPS
565
+ return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg, delete_style: delete_style) do |field, value|
566
+ lookup_entity_by_field(
567
+ type: res_type, value: value, field: field, real_path: res_path, item_list_key: list_key, query: res_id_query)['id']
568
+ end
569
+ when :shared_folders
570
+ node_id = instance_identifier do |field, value|
571
+ lookup_entity_by_field(type: res_type, field: field, value: value)['id']
572
+ end
573
+ sh_path = "#{res_path}/#{node_id}/shared_folders"
574
+ sh_command = options.get_next_command([:user].concat(Plugin::ALL_OPS))
575
+ case sh_command
576
+ when *Plugin::ALL_OPS
577
+ return entity_command(sh_command, adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
578
+ lookup_entity_by_field(type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
579
+ end
580
+ when :user
581
+ sh_id = instance_identifier do |field, value|
582
+ lookup_entity_by_field(type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
583
+ end
584
+ user_path = "#{sh_path}/#{sh_id}/custom_access_users"
585
+ return entity_action(adm_api, user_path, item_list_key: 'users') do |field, value|
586
+ lookup_entity_by_field(type: 'users', real_path: user_path, field: field, value: value)['id']
587
+ end
588
+
589
+ end
590
+ when :browse
591
+ return browse_folder("#{res_path}/#{instance_identifier}/browse")
592
+ when :invite_external_collaborator
593
+ shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
594
+ creation_payload = value_create_modify(command: res_command, type: [Hash, String])
595
+ creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
596
+ res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
597
+ result = adm_api.create(res_path, creation_payload)[:data]
598
+ formatter.display_status(result['message'])
599
+ result = lookup_entity_by_field(
600
+ type: 'members',
601
+ real_path: "#{res_type}/#{shared_inbox_id}/members",
602
+ value: creation_payload['email_address'],
603
+ query: {})
604
+ return {type: :single_object, data: result}
605
+ when :members, :saml_groups
606
+ res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
607
+ res_prefix = "#{res_type}/#{res_id}"
608
+ res_path = "#{res_prefix}/#{res_command}"
609
+ list_key = res_command.to_s
610
+ list_key = 'groups' if res_command.eql?(:saml_groups)
611
+ sub_command = options.get_next_command(%i[create list modify delete])
612
+ if sub_command.eql?(:create) && options.get_option(:value).nil?
613
+ raise "use option 'value' to provide saml group_id and access (refer to API)" unless res_command.eql?(:members)
614
+ # first arg is one user name or list of users
615
+ users = options.get_next_argument('user id, %name:, or Array')
616
+ users = [users] unless users.is_a?(Array)
617
+ users = users.map do |user|
618
+ if (m = user.match(REGEX_LOOKUP_ID_BY_FIELD))
619
+ lookup_entity_by_field(
620
+ type: 'accounts',
621
+ field: m[1],
622
+ value: ExtendedValue.instance.evaluate(m[2]),
623
+ query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
624
+ else
625
+ # it's the user id (not member id...)
626
+ user
627
+ end
628
+ end
629
+ access = options.get_next_argument('level', mandatory: false, expected: %i[submit_only standard shared_inbox_admin], default: :standard)
630
+ # TODO: unshift to command line parameters instead of using deprecated option "value"
631
+ options.set_option(:value, {user: users.map{|u|{id: u, access: access}}})
632
+ end
633
+ return entity_command(sub_command, adm_api, res_path, item_list_key: list_key) do |field, value|
634
+ lookup_entity_by_field(
635
+ type: 'accounts',
636
+ field: field,
637
+ value: value,
638
+ query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
639
+ end
640
+ end
641
+ end
642
+
643
+ def execute_admin
644
+ command = options.get_next_command(%i[configuration smtp resource events clean_deleted].concat(ADMIN_RESOURCES).freeze)
645
+ case command
646
+ when :resource
647
+ # resource is will be deprecated
648
+ Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
649
+ return execute_resource(options.get_next_command(ADMIN_RESOURCES))
650
+ when *ADMIN_RESOURCES
651
+ return execute_resource(command)
652
+ when :clean_deleted
653
+ delete_data = value_create_modify(command: command, default: {days_before_deleting_package_records: 365})
654
+ res = @api_v5.create('internal/packages/clean_deleted', delete_data)
655
+ return {type: :single_object, data: res[:data]}
656
+ when :events
657
+ event_type = options.get_next_command(%i[application webhook])
658
+ case event_type
659
+ when :application
660
+ return {type: :object_list, data: list_entities(type: 'application_events'), fields: %w[event_type created_at application user.name]}
661
+ when :webhook
662
+ return {type: :object_list, data: list_entities(type: 'all_webhooks_events', item_list_key: 'events')}
663
+ end
664
+ when :configuration
665
+ conf_path = 'configuration'
666
+ conf_cmd = options.get_next_command(%i[show modify])
667
+ case conf_cmd
668
+ when :show
669
+ return { type: :single_object, data: @api_v5.read(conf_path)[:data] }
670
+ when :modify
671
+ return { type: :single_object, data: @api_v5.update(conf_path, value_create_modify(command: conf_cmd))[:data] }
672
+ end
673
+ when :smtp
674
+ smtp_path = 'configuration/smtp'
675
+ smtp_cmd = options.get_next_command(%i[show create modify delete test])
676
+ case smtp_cmd
677
+ when :show
678
+ return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
679
+ when :create
680
+ return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
681
+ when :modify
682
+ return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
683
+ when :delete
684
+ @api_v5.delete(smtp_path)[:data]
685
+ return Main.result_status('SMTP configuration deleted')
686
+ when :test
687
+ test_data = options.get_next_argument('Email or test data, see API')
688
+ test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
689
+ creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
690
+ result = wait_for_job(creation['job_id'])
691
+ result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
692
+ return { type: :single_object, data: result }
693
+ end
694
+ end
514
695
  end
515
696
 
516
697
  ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing invitations].freeze
@@ -546,7 +727,7 @@ module Aspera
546
727
  end
547
728
  end
548
729
  when :bearer_token
549
- return {type: :text, data: @api_v5.oauth_token}
730
+ return {type: :text, data: @api_v5.oauth.token}
550
731
  when :packages
551
732
  return package_action
552
733
  when :shared_folders
@@ -566,117 +747,7 @@ module Aspera
566
747
  return browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse")
567
748
  end
568
749
  when :admin
569
- case options.get_next_command(%i[resource smtp].freeze)
570
- when :resource
571
- res_type = options.get_next_command(ADMIN_RESOURCES)
572
- res_path = list_key = res_type.to_s
573
- id_as_arg = false
574
- display_fields = nil
575
- adm_api = @api_v5
576
- special_query = :default
577
- available_commands = [].concat(Plugin::ALL_OPS)
578
- case res_type
579
- when :metadata_profiles
580
- res_path = 'configuration/metadata_profiles'
581
- list_key = 'profiles'
582
- when :alternate_addresses
583
- res_path = 'configuration/alternate_addresses'
584
- when :email_notifications
585
- list_key = false
586
- id_as_arg = 'type'
587
- when :accounts
588
- display_fields = Formatter.all_but('user_profile_data_attributes')
589
- when :oauth_clients
590
- display_fields = Formatter.all_but('public_key')
591
- adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
592
- when :shared_inboxes, :workgroups
593
- available_commands.push(:members, :saml_groups, :invite_external_collaborator)
594
- special_query = {'all': true}
595
- when :nodes
596
- available_commands.push(:shared_folders, :browse)
597
- end
598
- res_command = options.get_next_command(available_commands)
599
- case res_command
600
- when *Plugin::ALL_OPS
601
- return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg) do |field, value|
602
- lookup_entity_by_field(
603
- type: res_type, real_path: res_path, field: field, value: value, query: special_query)['id']
604
- end
605
- when :shared_folders
606
- node_id = instance_identifier do |field, value|
607
- lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']
608
- end
609
- sh_path = "#{res_path}/#{node_id}/shared_folders"
610
- return entity_action(adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
611
- lookup_entity_by_field(
612
- type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
613
- end
614
- when :browse
615
- return browse_folder("#{res_path}/#{instance_identifier}/browse")
616
- when :invite_external_collaborator
617
- shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
618
- creation_payload = value_create_modify(command: res_command, type: [Hash, String])
619
- creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
620
- res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
621
- result = adm_api.create(res_path, creation_payload)[:data]
622
- formatter.display_status(result['message'])
623
- result = lookup_entity_by_field(
624
- type: 'members', real_path: "#{res_type}/#{shared_inbox_id}/members", value: creation_payload['email_address'],
625
- query: {})
626
- return {type: :single_object, data: result}
627
- when :members, :saml_groups
628
- res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
629
- res_prefix = "#{res_type}/#{res_id}"
630
- res_path = "#{res_prefix}/#{res_command}"
631
- list_key = res_command.to_s
632
- list_key = 'groups' if res_command.eql?(:saml_groups)
633
- sub_command = options.get_next_command(%i[create list modify delete])
634
- if sub_command.eql?(:create) && options.get_option(:value).nil?
635
- raise "use option 'value' to provide saml group_id and access (refer to API)" unless res_command.eql?(:members)
636
- # first arg is one user name or list of users
637
- users = options.get_next_argument('user id, or email, or list of')
638
- users = [users] unless users.is_a?(Array)
639
- users = users.map do |user|
640
- if (m = user.match(REGEX_LOOKUP_ID_BY_FIELD))
641
- lookup_entity_by_field(
642
- type: 'accounts', field: m[1], value: m[2],
643
- query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
644
- else
645
- # it's the user id (not member id...)
646
- user
647
- end
648
- end
649
- access = options.get_next_argument('level', mandatory: false, expected: %i[submit_only standard shared_inbox_admin], default: :standard)
650
- # TODO: unshift to command line parameters instead of using deprecated option "value"
651
- options.set_option(:value, {user: users.map{|u|{id: u, access: access}}})
652
- end
653
- return entity_command(sub_command, adm_api, res_path, item_list_key: list_key) do |field, value|
654
- lookup_entity_by_field(
655
- type: 'accounts', field: field, value: value,
656
- query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
657
- end
658
- end
659
- when :smtp
660
- smtp_path = 'configuration/smtp'
661
- smtp_cmd = options.get_next_command(%i[show create modify delete test])
662
- case smtp_cmd
663
- when :show
664
- return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
665
- when :create
666
- return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
667
- when :modify
668
- return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
669
- when :delete
670
- return { type: :single_object, data: @api_v5.delete(smtp_path)[:data] }
671
- when :test
672
- test_data = options.get_next_argument('Email or test data, see API')
673
- test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
674
- creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
675
- result = wait_for_job(creation['job_id'])
676
- result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
677
- return { type: :single_object, data: result }
678
- end
679
- end
750
+ return execute_admin
680
751
  when :invitations
681
752
  invitation_endpoint = 'invitations'
682
753
  invitation_command = options.get_next_command(%i[resend].concat(Plugin::ALL_OPS))
@@ -692,7 +763,9 @@ module Aspera
692
763
  else
693
764
  return entity_command(
694
765
  invitation_command, @api_v5, invitation_endpoint, item_list_key: invitation_endpoint,
695
- display_fields: %w[id public recipient_type recipient_name email_address])
766
+ display_fields: %w[id public recipient_type recipient_name email_address]) do |field, value|
767
+ lookup_entity_by_field(type: invitation_endpoint, field: field, value: value, query: {})['id']
768
+ end
696
769
  end
697
770
  when :gateway
698
771
  require 'aspera/faspex_gw'
@@ -714,9 +787,9 @@ module Aspera
714
787
  server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
715
788
  server.start
716
789
  return Main.result_status('Gateway terminated')
717
- end # case command
718
- end # action
719
- end # Faspex5
720
- end # Plugins
721
- end # Cli
722
- end # Aspera
790
+ end
791
+ end
792
+ end
793
+ end
794
+ end
795
+ end