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,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')