aspera-cli 4.17.0 → 4.18.0

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