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,12 +18,41 @@ module Aspera
18
18
  module Cli
19
19
  module Plugins
20
20
  class Aoc < Cli::BasicAuthPlugin
21
- AOC_PATH_API_CLIENTS = 'admin/api-clients'
22
21
  # default redirect for AoC web auth
23
22
  REDIRECT_LOCALHOST = 'http://localhost:12345'
24
23
  # OAuth methods supported
25
24
  STD_AUTH_TYPES = %i[web jwt].freeze
26
- private_constant :AOC_PATH_API_CLIENTS, :REDIRECT_LOCALHOST, :STD_AUTH_TYPES
25
+ # admin objects that can be manipulated
26
+ ADMIN_OBJECTS = %i[
27
+ self
28
+ organization
29
+ user
30
+ group
31
+ group_membership
32
+ client
33
+ contact
34
+ dropbox
35
+ node
36
+ operation
37
+ package
38
+ saml_configuration
39
+ workspace
40
+ workspace_membership
41
+ dropbox_membership
42
+ short_link
43
+ application
44
+ client_registration_token
45
+ client_access_key
46
+ kms_profile].freeze
47
+ # query to list fully received packages
48
+ PACKAGE_RECEIVED_BASE_QUERY = {
49
+ 'archived' => false,
50
+ 'has_content' => true,
51
+ 'received' => true,
52
+ 'completed' => true}.freeze
53
+ # options and parameters for Api::AoC.new
54
+ OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
55
+ private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW
27
56
  class << self
28
57
  def application_name
29
58
  'Aspera on Cloud'
@@ -33,12 +62,12 @@ module Aspera
33
62
  # no protocol ?
34
63
  base_url = "https://#{base_url}" unless base_url.match?(%r{^[a-z]{1,6}://})
35
64
  # only org provided ?
36
- base_url = "#{base_url}.#{Api::AoC::PROD_DOMAIN}" unless base_url.include?('.')
65
+ base_url = "#{base_url}.#{Api::SAAS_DOMAIN_PROD}" unless base_url.include?('.')
37
66
  # AoC is only https
38
67
  return nil unless base_url.start_with?('https://')
39
68
  result = Rest.new(base_url: base_url, redirect_max: 10).read('')
40
69
  # Any AoC is on this domain
41
- return nil unless result[:http].uri.host.end_with?(Api::AoC::PROD_DOMAIN)
70
+ return nil unless result[:http].uri.host.end_with?(Api::SAAS_DOMAIN_PROD)
42
71
  Log.log.debug{"AoC Main page: #{result[:http].body.include?(Api::AoC::PRODUCT_NAME)}"}
43
72
  base_url = result[:http].uri.to_s if result[:http].uri.path.include?('/public')
44
73
  # either in standard domain, or product name in page
@@ -48,6 +77,8 @@ module Aspera
48
77
  }
49
78
  end
50
79
 
80
+ # @param [String] url : url to check
81
+ # @return [Bool] true if private key is required for the url (i.e. no passcode)
51
82
  def private_key_required?(url)
52
83
  # pub link do not need private key
53
84
  return Api::AoC.link_info(url)[:token].nil?
@@ -106,7 +137,7 @@ module Aspera
106
137
  formatter.display_status('- origin: localhost')
107
138
  formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
108
139
  end
109
- OpenApplication.instance.uri("#{instance_url}/#{AOC_PATH_API_CLIENTS}")
140
+ OpenApplication.instance.uri("#{instance_url}/admin/api-clients")
110
141
  options.get_option(:client_id, mandatory: true)
111
142
  options.get_option(:client_secret, mandatory: true)
112
143
  use_browser_authentication = true
@@ -147,35 +178,8 @@ module Aspera
147
178
  }
148
179
  end
149
180
  end
150
- # special value for package id
151
- KNOWN_AOC_RES = %i[
152
- self
153
- organization
154
- user
155
- group
156
- group_membership
157
- client
158
- contact
159
- dropbox
160
- node
161
- operation
162
- package
163
- saml_configuration
164
- workspace
165
- workspace_membership
166
- dropbox_membership
167
- short_link
168
- application
169
- client_registration_token
170
- client_access_key
171
- kms_profile].freeze
172
- PACKAGE_RECEIVED_BASE_QUERY = {
173
- 'archived' => false,
174
- 'has_content' => true,
175
- 'received' => true,
176
- 'completed' => true}.freeze
177
181
 
178
- def initialize(**env)
182
+ def initialize(**_)
179
183
  super
180
184
  @cache_workspace_info = nil
181
185
  @cache_home_node_file = nil
@@ -186,17 +190,15 @@ module Aspera
186
190
  options.declare(:scope, 'OAuth scope for AoC API calls', default: Api::AoC::SCOPE_FILES_USER)
187
191
  options.declare(:redirect_uri, 'OAuth API client redirect URI')
188
192
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
189
- options.declare(:passphrase, 'RSA private key passphrase')
193
+ options.declare(:passphrase, 'RSA private key passphrase', types: String)
190
194
  options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
191
- options.declare(:new_user_option, 'New user creation option for unknown package recipients')
195
+ options.declare(:new_user_option, 'New user creation option for unknown package recipients', types: Hash)
192
196
  options.declare(:validate_metadata, 'Validate shared inbox metadata', values: :bool, default: true)
193
197
  options.parse_options!
194
198
  # add node plugin options (for manual)
195
199
  Node.declare_options(options)
196
200
  end
197
201
 
198
- OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
199
-
200
202
  def api_from_options(new_base_path)
201
203
  create_values = {subpath: new_base_path, secret_finder: config}
202
204
  # create an API object with the same options, but with a different subpath
@@ -333,12 +335,12 @@ module Aspera
333
335
  client_apfid = top_node_api.resolve_api_fid(file_id, client_folder)
334
336
  server_apfid = top_node_api.resolve_api_fid(file_id, server_folder)
335
337
  # force node as transfer agent
336
- transfer.agent_instance = Agent::Node.new({
338
+ transfer.agent_instance = Agent::Node.new(
337
339
  url: client_apfid[:api].base_url,
338
340
  username: client_apfid[:api].app_info[:node_info]['access_key'],
339
- password: client_apfid[:api].oauth_token,
341
+ password: client_apfid[:api].oauth.token,
340
342
  root_id: client_apfid[:file_id]
341
- })
343
+ )
342
344
  # additional node to node TS info
343
345
  add_ts = {
344
346
  'remote_access_key' => server_apfid[:api].app_info[:node_info]['access_key'],
@@ -350,15 +352,106 @@ module Aspera
350
352
  client_direction,
351
353
  add_ts)))
352
354
  else Aspera.error_unreachable_line
353
- end # command_repo
355
+ end
354
356
  Aspera.error_unreachable_line
355
- end # execute_nodegen4_command
357
+ end
358
+
359
+ def execute_resource_action(resource_type)
360
+ # get path on API, resource type is singular, but api is plural
361
+ resource_class_path =
362
+ case resource_type
363
+ # special cases: singleton, in admin, with x
364
+ when :self, :organization then resource_type
365
+ when :client_registration_token, :client_access_key then "admin/#{resource_type}s"
366
+ when :application then 'admin/apps_new'
367
+ when :dropbox then "#{resource_type}es"
368
+ when :kms_profile then "integrations/#{resource_type}s"
369
+ else "#{resource_type}s"
370
+ end
371
+ # build list of supported operations
372
+ singleton_object = %i[self organization].include?(resource_type)
373
+ global_operations = %i[create list]
374
+ supported_operations = %i[show modify]
375
+ supported_operations.push(:delete, *global_operations) unless singleton_object
376
+ supported_operations.push(:do) if resource_type.eql?(:node)
377
+ supported_operations.push(:set_pub_key) if resource_type.eql?(:client)
378
+ command = options.get_next_command(supported_operations)
379
+ # require identifier for non global commands
380
+ if !singleton_object && !global_operations.include?(command)
381
+ res_id = get_resource_id_from_args(resource_class_path)
382
+ resource_instance_path = "#{resource_class_path}/#{res_id}"
383
+ end
384
+ resource_instance_path = resource_class_path if singleton_object
385
+ case command
386
+ when :create
387
+ id_result = 'id'
388
+ id_result = 'token' if resource_class_path.eql?('admin/client_registration_tokens')
389
+ # TODO: report inconsistency: creation url is !=, and does not return id.
390
+ resource_class_path = 'admin/client_registration/token' if resource_class_path.eql?('admin/client_registration_tokens')
391
+ return do_bulk_operation(command: command, descr: 'creation data', id_result: id_result) do |params|
392
+ aoc_api.create(resource_class_path, params)[:data]
393
+ end
394
+ when :list
395
+ default_fields = ['id']
396
+ default_query = {}
397
+ case resource_type
398
+ when :application
399
+ default_query = {organization_apps: true}
400
+ default_fields.push('app_type', 'app_name', 'available', 'direct_authorizations_allowed', 'workspace_authorizations_allowed')
401
+ when :client, :client_access_key, :dropbox, :group, :package, :saml_configuration, :workspace then default_fields.push('name')
402
+ when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
403
+ when :contact then default_fields = %w[email name source_id source_type]
404
+ when :node then default_fields.push('name', 'host', 'access_key')
405
+ when :operation then default_fields = nil
406
+ when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
407
+ when :user then default_fields.push('name', 'email')
408
+ when :group_membership then default_fields.push(*%w[group_id member_type member_id])
409
+ when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
410
+ end
411
+ return result_list(resource_class_path, fields: default_fields, default_query: default_query)
412
+ when :show
413
+ object = aoc_api.read(resource_instance_path)[:data]
414
+ # default: show all, but certificate
415
+ fields = object.keys.reject{|k|k.eql?('certificate')}
416
+ return { type: :single_object, data: object, fields: fields }
417
+ when :modify
418
+ changes = options.get_next_argument('properties', type: Hash)
419
+ return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
420
+ aoc_api.update("#{resource_class_path}/#{one_id}", changes)
421
+ {'id' => one_id}
422
+ end
423
+ when :delete
424
+ return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
425
+ aoc_api.delete("#{resource_class_path}/#{one_id}")
426
+ {'id' => one_id}
427
+ end
428
+ when :set_pub_key
429
+ # special : reads private and generate public
430
+ the_private_key = options.get_next_argument('private_key PEM value', type: String)
431
+ the_public_key = OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
432
+ aoc_api.update(resource_instance_path, {jwt_grant_enabled: true, public_key: the_public_key})
433
+ return Main.result_success
434
+ when :do
435
+ command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
436
+ # init context
437
+ aoc_api.context(:files)
438
+ return execute_nodegen4_command(command_repo, res_id)
439
+ else Aspera.error_unexpected_value(command)
440
+ end
441
+ end
442
+
443
+ ADMIN_ACTIONS = %i[ats resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
356
444
 
357
445
  def execute_admin_action
358
446
  # upgrade scope to admin
359
447
  aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
360
- command_admin = options.get_next_command(%i[ats resource usage_reports analytics subscription auth_providers])
448
+ command_admin = options.get_next_command(ADMIN_ACTIONS)
361
449
  case command_admin
450
+ when :resource
451
+ Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
452
+ return execute_resource_action(options.get_next_argument('resource', expected: ADMIN_OBJECTS))
453
+ when *ADMIN_OBJECTS
454
+ return execute_resource_action(command_admin)
362
455
  when :auth_providers
363
456
  command_auth_prov = options.get_next_command(%i[list update])
364
457
  case command_auth_prov
@@ -480,89 +573,6 @@ module Aspera
480
573
  end
481
574
  return {type: :object_list, data: events}
482
575
  end
483
- when :resource
484
- resource_type = options.get_next_argument('resource', expected: KNOWN_AOC_RES)
485
- # get path on API, resource type is singular, but api is plural
486
- resource_class_path =
487
- case resource_type
488
- # special cases: singleton, in admin, with x
489
- when :self, :organization then resource_type
490
- when :client_registration_token, :client_access_key then "admin/#{resource_type}s"
491
- when :application then 'admin/apps_new'
492
- when :dropbox then "#{resource_type}es"
493
- when :kms_profile then "integrations/#{resource_type}s"
494
- else "#{resource_type}s"
495
- end
496
- # build list of supported operations
497
- singleton_object = %i[self organization].include?(resource_type)
498
- global_operations = %i[create list]
499
- supported_operations = %i[show modify]
500
- supported_operations.push(:delete, *global_operations) unless singleton_object
501
- supported_operations.push(:do) if resource_type.eql?(:node)
502
- supported_operations.push(:set_pub_key) if resource_type.eql?(:client)
503
- command = options.get_next_command(supported_operations)
504
- # require identifier for non global commands
505
- if !singleton_object && !global_operations.include?(command)
506
- res_id = get_resource_id_from_args(resource_class_path)
507
- resource_instance_path = "#{resource_class_path}/#{res_id}"
508
- end
509
- resource_instance_path = resource_class_path if singleton_object
510
- case command
511
- when :create
512
- id_result = 'id'
513
- id_result = 'token' if resource_class_path.eql?('admin/client_registration_tokens')
514
- # TODO: report inconsistency: creation url is !=, and does not return id.
515
- resource_class_path = 'admin/client_registration/token' if resource_class_path.eql?('admin/client_registration_tokens')
516
- return do_bulk_operation(command: command, descr: 'creation data', id_result: id_result) do |params|
517
- aoc_api.create(resource_class_path, params)[:data]
518
- end
519
- when :list
520
- default_fields = ['id']
521
- default_query = {}
522
- case resource_type
523
- when :application
524
- default_query = {organization_apps: true}
525
- default_fields.push('app_type', 'app_name', 'available', 'direct_authorizations_allowed', 'workspace_authorizations_allowed')
526
- when :client, :client_access_key, :dropbox, :group, :package, :saml_configuration, :workspace then default_fields.push('name')
527
- when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
528
- when :contact then default_fields = %w[email name source_id source_type]
529
- when :node then default_fields.push('name', 'host', 'access_key')
530
- when :operation then default_fields = nil
531
- when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
532
- when :user then default_fields.push('name', 'email')
533
- when :group_membership then default_fields.push(*%w[group_id member_type member_id])
534
- when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
535
- end
536
- return result_list(resource_class_path, fields: default_fields, default_query: default_query)
537
- when :show
538
- object = aoc_api.read(resource_instance_path)[:data]
539
- # default: show all, but certificate
540
- fields = object.keys.reject{|k|k.eql?('certificate')}
541
- return { type: :single_object, data: object, fields: fields }
542
- when :modify
543
- changes = options.get_next_argument('properties', type: Hash)
544
- return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
545
- aoc_api.update("#{resource_class_path}/#{one_id}", changes)
546
- {'id' => one_id}
547
- end
548
- when :delete
549
- return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
550
- aoc_api.delete("#{resource_class_path}/#{one_id}")
551
- {'id' => one_id}
552
- end
553
- when :set_pub_key
554
- # special : reads private and generate public
555
- the_private_key = options.get_next_argument('private_key PEM value', type: String)
556
- the_public_key = OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
557
- aoc_api.update(resource_instance_path, {jwt_grant_enabled: true, public_key: the_public_key})
558
- return Main.result_success
559
- when :do
560
- command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
561
- # init context
562
- aoc_api.context(:files)
563
- return execute_nodegen4_command(command_repo, res_id)
564
- else Aspera.error_unexpected_value(command)
565
- end
566
576
  when :usage_reports
567
577
  return result_list('usage_reports', base_query: {workspace_id: aoc_api.context(:files)[:workspace_id]})
568
578
  end
@@ -591,7 +601,7 @@ module Aspera
591
601
  when :servers
592
602
  return {type: :object_list, data: Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers')[:data]}
593
603
  when :bearer_token
594
- return {type: :text, data: aoc_api.oauth_token}
604
+ return {type: :text, data: aoc_api.oauth.token}
595
605
  when :organization
596
606
  return { type: :single_object, data: aoc_api.read('organization')[:data] }
597
607
  when :tier_restrictions
@@ -696,7 +706,7 @@ module Aspera
696
706
  ids_to_download = all_ids.reject{|id|skip_ids_data.include?(id)}
697
707
  else
698
708
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
699
- end # ExtendedValue::ALL
709
+ end
700
710
  # list here
701
711
  result_transfer = []
702
712
  formatter.display_status("found #{ids_to_download.length} package(s).")
@@ -847,8 +857,8 @@ module Aspera
847
857
  # TODO: event ?
848
858
  end
849
859
  return {type: :single_object, data: result_create_short_link}
850
- end # short_link command
851
- end # files command
860
+ end
861
+ end
852
862
  raise 'Error: shall not reach this line'
853
863
  when :automation
854
864
  Log.log.warn('BETA: work under progress')
@@ -891,7 +901,7 @@ module Aspera
891
901
  server.start
892
902
  return Main.result_status('Gateway terminated')
893
903
  else Aspera.error_unreachable_line
894
- end # action
904
+ end
895
905
  Aspera.error_unreachable_line
896
906
  end
897
907
 
@@ -100,7 +100,7 @@ module Aspera
100
100
  return Main.result_status('modified')
101
101
  when :entitlement
102
102
  ak = ats_api_pub_v1.read("access_keys/#{access_key_id}")[:data]
103
- api_bss = Api::AoC.metering_api(ak['license']['entitlement_id'], ak['license']['customer_id'])
103
+ api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
104
104
  return {type: :single_object, data: api_bss.read('entitlement')[:data]}
105
105
  when :delete
106
106
  ats_api_pub_v1.delete("access_keys/#{access_key_id}")
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # cspell:ignore initdemo genkey pubkey asperasoft
3
+ # cspell:ignore initdemo genkey pubkey asperasoft filelists
4
4
  require 'aspera/cli/basic_auth_plugin'
5
5
  require 'aspera/cli/extended_value'
6
6
  require 'aspera/cli/version'
@@ -25,6 +25,7 @@ require 'aspera/rest'
25
25
  require 'aspera/log'
26
26
  require 'aspera/assert'
27
27
  require 'aspera/oauth'
28
+ require 'openssl'
28
29
  require 'open3'
29
30
  require 'date'
30
31
  require 'erb'
@@ -141,7 +142,7 @@ module Aspera
141
142
  Aspera.assert(!app_name.empty?)
142
143
  return File.join(module_family_folder, app_name)
143
144
  end
144
- end # self
145
+ end
145
146
 
146
147
  def initialize(gem:, name:, help:, version:, **env)
147
148
  # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
@@ -163,7 +164,6 @@ module Aspera
163
164
  @option_http_options = {}
164
165
  @ssl_warned_urls = []
165
166
  @option_cache_tokens = true
166
- @proxy_credentials = nil
167
167
  @main_folder = nil
168
168
  @option_config_file = nil
169
169
  # store is used for ruby https
@@ -230,9 +230,10 @@ module Aspera
230
230
  options.declare(:silent_insecure, 'Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
231
231
  options.declare(:cert_stores, 'List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
232
232
  options.declare(:http_options, 'Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
233
+ options.declare(:http_proxy, 'URL for HTTP proxy with optional credentials', types: String, handler: {o: self, m: :option_http_proxy})
233
234
  options.declare(:cache_tokens, 'Save and reuse OAuth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
234
235
  options.declare(:fpac, 'Proxy auto configuration script')
235
- options.declare(:proxy_credentials, 'HTTP proxy credentials (user and password)', types: Array)
236
+ options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac. Array: user,password', types: Array)
236
237
  options.parse_options!
237
238
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
238
239
  # Check SDK folder is set or not, for compatibility, we check in two places
@@ -255,20 +256,21 @@ module Aspera
255
256
  end
256
257
  pac_script = options.get_option(:fpac)
257
258
  # create PAC executor
258
- @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
259
- proxy_user_pass = options.get_option(:proxy_credentials)
260
- if !proxy_user_pass.nil?
261
- Aspera.assert(proxy_user_pass.length.eql?(2), exception_class: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
262
- @proxy_credentials = {user: proxy_user_pass[0], pass: proxy_user_pass[1]}
263
- @pac_exec.proxy_user = @proxy_credentials[:user]
264
- @pac_exec.proxy_pass = @proxy_credentials[:pass]
259
+ if !pac_script.nil?
260
+ @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
261
+ proxy_user_pass = options.get_option(:proxy_credentials)
262
+ if !proxy_user_pass.nil?
263
+ Aspera.assert(proxy_user_pass.length.eql?(2), exception_class: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
264
+ @pac_exec.proxy_user = proxy_user_pass[0]
265
+ @pac_exec.proxy_pass = proxy_user_pass[1]
266
+ end
265
267
  end
266
268
  Rest.set_parameters(
267
269
  user_agent: PROGRAM_NAME,
268
270
  session_cb: lambda{|http_session|update_http_session(http_session)},
269
271
  progress_bar: @progress_bar)
270
272
  OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
271
- Transfer::Parameters.file_list_folder = File.join(@main_folder, 'filelists') # cspell: disable-line
273
+ Transfer::Parameters.file_list_folder = File.join(@main_folder, 'filelists')
272
274
  RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
273
275
  # register aspera REST call error handlers
274
276
  RestErrorsAspera.register_handlers
@@ -330,6 +332,15 @@ module Aspera
330
332
  return locations
331
333
  end
332
334
 
335
+ def option_http_proxy
336
+ return ENV['http_proxy']
337
+ end
338
+
339
+ def option_http_proxy=(value)
340
+ URI.parse(value)
341
+ ENV['http_proxy'] = value
342
+ end
343
+
333
344
  def option_ignore_cert_host_port=(url_list)
334
345
  url_list.each do |url|
335
346
  uri = URI.parse(url)
@@ -365,10 +376,6 @@ module Aspera
365
376
  http_session.verify_mode = SELF_SIGNED_CERT if http_session.use_ssl? && ignore_cert?(http_session.address, http_session.port)
366
377
  http_session.cert_store = @certificate_store if @certificate_store
367
378
  Log.log.debug{"using cert store #{http_session.cert_store} (#{@certificate_store})"} unless http_session.cert_store.nil?
368
- if @proxy_credentials
369
- http_session.proxy_user = @proxy_credentials[:user]
370
- http_session.proxy_pass = @proxy_credentials[:pass]
371
- end
372
379
  @option_http_options.each do |k, v|
373
380
  method = "#{k}=".to_sym
374
381
  # check if accessor is a method of Net::HTTP
@@ -453,7 +460,7 @@ module Aspera
453
460
  def add_plugin_default_preset(plugin_name_sym)
454
461
  default_config_name = get_plugin_default_config_name(plugin_name_sym)
455
462
  Log.log.debug{"add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}"}
456
- options.add_option_preset(preset_by_name(default_config_name), op: :unshift) unless default_config_name.nil?
463
+ options.add_option_preset(preset_by_name(default_config_name), 'default_plugin', override: false) unless default_config_name.nil?
457
464
  return nil
458
465
  end
459
466
 
@@ -539,12 +546,12 @@ module Aspera
539
546
 
540
547
  def option_preset=(value)
541
548
  case value
542
- when String
543
- options.add_option_preset(preset_by_name(value))
544
549
  when Hash
545
- options.add_option_preset(value)
550
+ options.add_option_preset(value, 'set')
551
+ when String
552
+ options.add_option_preset(preset_by_name(value), 'set_by_name')
546
553
  else
547
- raise 'Preset definition must be a String for name, or Hash for value'
554
+ raise 'Preset definition must be a String for preset name, or Hash for set of values'
548
555
  end
549
556
  end
550
557
 
@@ -613,18 +620,18 @@ module Aspera
613
620
  check_only = check_only.to_sym unless check_only.nil?
614
621
  found_apps = []
615
622
  my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
616
- PluginFactory.instance.plugins.each do |plugin_name_sym, plugin_info|
623
+ PluginFactory.instance.plugin_list.each do |plugin_name_sym|
617
624
  # no detection for internal plugin
618
625
  next if plugin_name_sym.eql?(my_self_plugin_sym)
619
626
  next if check_only && !check_only.eql?(plugin_name_sym)
620
627
  # load plugin class
621
- require plugin_info[:require_stanza]
622
- detect_plugin_class = PluginFactory.plugin_class(plugin_name_sym)
628
+ detect_plugin_class = PluginFactory.instance.plugin_class(plugin_name_sym)
623
629
  # requires detection method
624
630
  next unless detect_plugin_class.respond_to?(:detect)
625
631
  detection_info = nil
626
632
  begin
627
633
  Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
634
+ formatter.long_operation_running("#{plugin_name_sym}\r")
628
635
  detection_info = detect_plugin_class.detect(app_url)
629
636
  rescue OpenSSL::SSL::SSLError => e
630
637
  Log.log.warn(e.message)
@@ -639,7 +646,7 @@ module Aspera
639
646
  app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
640
647
  # if there is a redirect, then the detector can override the url.
641
648
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
642
- end # loop
649
+ end
643
650
  raise "No known application found at #{app_url}" if found_apps.empty?
644
651
  Aspera.assert(found_apps.all?{|a|a.keys.all?(Symbol)})
645
652
  return found_apps
@@ -726,7 +733,7 @@ module Aspera
726
733
  when :spec
727
734
  return {
728
735
  type: :object_list,
729
- data: Transfer::Parameters.man_table,
736
+ data: Transfer::Parameters.man_table(formatter),
730
737
  fields: [%w[name type], Transfer::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description]].flatten.freeze
731
738
  }
732
739
  when :errors
@@ -791,7 +798,7 @@ module Aspera
791
798
  return Main.result_status("Modified: #{@option_config_file}")
792
799
  when :update
793
800
  # get unprocessed options
794
- unprocessed_options = options.get_options_table
801
+ unprocessed_options = options.unprocessed_options_with_value
795
802
  Log.log.debug{"opts=#{unprocessed_options}"}
796
803
  @config_presets[name] ||= {}
797
804
  @config_presets[name].merge!(unprocessed_options)
@@ -853,6 +860,7 @@ module Aspera
853
860
  wizard
854
861
  detect
855
862
  coffee
863
+ image
856
864
  ascp
857
865
  email_test
858
866
  smtp_settings
@@ -908,14 +916,13 @@ module Aspera
908
916
  case options.get_next_command(%i[list create])
909
917
  when :list
910
918
  result = []
911
- PluginFactory.instance.plugins.each do |name, info|
912
- require info[:require_stanza]
913
- plugin_klass = PluginFactory.plugin_class(name)
919
+ PluginFactory.instance.plugin_list.each do |name|
920
+ plugin_class = PluginFactory.instance.plugin_class(name)
914
921
  result.push({
915
922
  plugin: name,
916
- detect: Formatter.tick(plugin_klass.respond_to?(:detect)),
917
- wizard: Formatter.tick(plugin_klass.respond_to?(:wizard)),
918
- path: info[:source]
923
+ detect: Formatter.tick(plugin_class.respond_to?(:detect)),
924
+ wizard: Formatter.tick(plugin_class.respond_to?(:wizard)),
925
+ path: PluginFactory.instance.plugin_source(name)
919
926
  })
920
927
  end
921
928
  return {type: :object_list, data: result, fields: %w[plugin detect wizard path]}
@@ -950,11 +957,9 @@ module Aspera
950
957
  } if action.eql?(:detect)
951
958
  return wizard_find(apps)
952
959
  when :coffee
953
- if OpenApplication.instance.url_method.eql?(:text)
954
- return Main.result_picture_in_terminal(options, Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body)
955
- end
956
- OpenApplication.instance.uri(COFFEE_IMAGE)
957
- return Main.result_nothing
960
+ return Main.result_image(COFFEE_IMAGE, formatter: formatter)
961
+ when :image
962
+ return Main.result_image(options.get_next_argument('image uri or blob'), formatter: formatter)
958
963
  when :ascp
959
964
  execute_action_ascp
960
965
  when :gem
@@ -1019,17 +1024,17 @@ module Aspera
1019
1024
  apps.first
1020
1025
  else
1021
1026
  formatter.display_status('Multiple applications detected, please select from:')
1022
- formatter.display_results({type: :object_list, data: apps, fields: %w[product url version]})
1027
+ formatter.display_results(type: :object_list, data: apps, fields: %w[product url version])
1023
1028
  answer = options.prompt_user_input_in_list('product', apps.map{|a|a[:product]})
1024
1029
  apps.find{|a|a[:product].eql?(answer)}
1025
1030
  end
1026
- wiz_url = identification[:url]
1027
1031
  Log.log.debug{Log.dump(:identification, identification)}
1032
+ wiz_url = identification[:url]
1028
1033
  formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
1029
1034
  # set url for instantiation of plugin
1030
- options.add_option_preset({url: wiz_url})
1035
+ options.add_option_preset({url: wiz_url}, 'wizard')
1031
1036
  # instantiate plugin: command line options will be known and wizard can be called
1032
- wiz_plugin_class = PluginFactory.plugin_class(identification[:product])
1037
+ wiz_plugin_class = PluginFactory.instance.plugin_class(identification[:product])
1033
1038
  Aspera.assert(wiz_plugin_class.respond_to?(:wizard), exception_class: Cli::BadArgument) do
1034
1039
  "Detected: #{identification[:product]}, but this application has no wizard"
1035
1040
  end
@@ -1198,7 +1203,7 @@ module Aspera
1198
1203
  return default_config_name
1199
1204
  end
1200
1205
  return nil
1201
- end # get_plugin_default_config_name
1206
+ end
1202
1207
 
1203
1208
  # TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
1204
1209
  # @return [Hash] result of execution of vault command
@@ -1208,7 +1213,7 @@ module Aspera
1208
1213
  when :info
1209
1214
  return {type: :single_object, data: vault_info}
1210
1215
  when :list
1211
- return {type: :object_list, data: vault.list}
1216
+ return {type: :object_list, data: vault.list, fields: %w(label url username password description)}
1212
1217
  when :show
1213
1218
  return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
1214
1219
  when :create
@@ -1219,8 +1224,9 @@ module Aspera
1219
1224
  vault.set(info)
1220
1225
  return Main.result_status('Password added')
1221
1226
  when :delete
1222
- vault.delete(label: options.get_next_argument('label'))
1223
- return Main.result_status('Password deleted')
1227
+ label_to_delete = options.get_next_argument('label')
1228
+ vault.delete(label: label_to_delete)
1229
+ return Main.result_status("Entry deleted: #{label_to_delete}")
1224
1230
  when :password
1225
1231
  Aspera.assert(vault.respond_to?(:password=)){'Vault does not support password change'}
1226
1232
  new_password = options.get_next_argument('new_password')