aspera-cli 4.20.0 → 4.21.2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +41 -3
  4. data/CONTRIBUTING.md +69 -142
  5. data/README.md +687 -461
  6. data/bin/ascli +5 -14
  7. data/bin/asession +3 -5
  8. data/examples/get_proto_file.rb +4 -3
  9. data/examples/proxy.pac +20 -20
  10. data/lib/aspera/agent/base.rb +2 -0
  11. data/lib/aspera/agent/connect.rb +20 -2
  12. data/lib/aspera/agent/{alpha.rb → desktop.rb} +12 -18
  13. data/lib/aspera/agent/direct.rb +30 -31
  14. data/lib/aspera/agent/node.rb +1 -11
  15. data/lib/aspera/agent/{trsdk.rb → transferd.rb} +37 -51
  16. data/lib/aspera/api/alee.rb +1 -1
  17. data/lib/aspera/api/aoc.rb +13 -8
  18. data/lib/aspera/api/cos_node.rb +1 -1
  19. data/lib/aspera/api/node.rb +49 -32
  20. data/lib/aspera/ascp/installation.rb +98 -77
  21. data/lib/aspera/ascp/management.rb +27 -6
  22. data/lib/aspera/cli/extended_value.rb +9 -3
  23. data/lib/aspera/cli/formatter.rb +155 -154
  24. data/lib/aspera/cli/info.rb +2 -1
  25. data/lib/aspera/cli/main.rb +12 -0
  26. data/lib/aspera/cli/manager.rb +4 -4
  27. data/lib/aspera/cli/plugin.rb +2 -2
  28. data/lib/aspera/cli/plugins/aoc.rb +134 -73
  29. data/lib/aspera/cli/plugins/config.rb +114 -83
  30. data/lib/aspera/cli/plugins/cos.rb +1 -0
  31. data/lib/aspera/cli/plugins/faspex.rb +4 -2
  32. data/lib/aspera/cli/plugins/faspex5.rb +29 -14
  33. data/lib/aspera/cli/plugins/node.rb +51 -41
  34. data/lib/aspera/cli/transfer_progress.rb +2 -0
  35. data/lib/aspera/cli/version.rb +1 -1
  36. data/lib/aspera/command_line_builder.rb +1 -1
  37. data/lib/aspera/coverage.rb +5 -3
  38. data/lib/aspera/environment.rb +59 -16
  39. data/lib/aspera/faspex_postproc.rb +3 -5
  40. data/lib/aspera/hash_ext.rb +2 -12
  41. data/lib/aspera/node_simulator.rb +230 -112
  42. data/lib/aspera/oauth/base.rb +40 -48
  43. data/lib/aspera/oauth/factory.rb +41 -2
  44. data/lib/aspera/oauth/jwt.rb +4 -1
  45. data/lib/aspera/persistency_action_once.rb +1 -1
  46. data/lib/aspera/persistency_folder.rb +20 -2
  47. data/lib/aspera/preview/generator.rb +13 -10
  48. data/lib/aspera/preview/options.rb +2 -2
  49. data/lib/aspera/preview/terminal.rb +1 -1
  50. data/lib/aspera/preview/utils.rb +11 -6
  51. data/lib/aspera/products/connect.rb +82 -0
  52. data/lib/aspera/products/desktop.rb +30 -0
  53. data/lib/aspera/products/other.rb +82 -0
  54. data/lib/aspera/products/transferd.rb +61 -0
  55. data/lib/aspera/rest.rb +22 -17
  56. data/lib/aspera/secret_hider.rb +9 -2
  57. data/lib/aspera/ssh.rb +31 -24
  58. data/lib/aspera/temp_file_manager.rb +5 -4
  59. data/lib/aspera/transfer/parameters.rb +2 -1
  60. data/lib/aspera/transfer/spec.yaml +22 -20
  61. data/lib/aspera/transfer/sync.rb +1 -5
  62. data/lib/aspera/transfer/uri.rb +2 -2
  63. data/lib/aspera/uri_reader.rb +18 -1
  64. data/lib/transferd_pb.rb +86 -0
  65. data/lib/transferd_services_pb.rb +84 -0
  66. data.tar.gz.sig +0 -0
  67. metadata +13 -166
  68. metadata.gz.sig +0 -0
  69. data/examples/build_exec +0 -74
  70. data/examples/build_exec_rubyc +0 -40
  71. data/lib/aspera/ascp/products.rb +0 -168
  72. data/lib/transfer_pb.rb +0 -84
  73. data/lib/transfer_services_pb.rb +0 -82
@@ -9,11 +9,10 @@ require 'aspera/cli/formatter'
9
9
  require 'aspera/cli/info'
10
10
  require 'aspera/cli/transfer_progress'
11
11
  require 'aspera/ascp/installation'
12
- require 'aspera/ascp/products'
12
+ require 'aspera/products/transferd'
13
13
  require 'aspera/transfer/error_info'
14
14
  require 'aspera/transfer/parameters'
15
15
  require 'aspera/transfer/spec'
16
- require 'aspera/keychain/encrypted_hash'
17
16
  require 'aspera/keychain/macos_security'
18
17
  require 'aspera/proxy_auto_config'
19
18
  require 'aspera/environment'
@@ -54,9 +53,7 @@ module Aspera
54
53
  PERSISTENCY_FOLDER = 'persist_store'
55
54
  ASPERA = 'aspera'
56
55
  SERVER_COMMAND = 'server'
57
- APP_NAME_SDK = 'sdk'
58
- CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
59
- CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
56
+ DIR_SDK = 'sdk'
60
57
  DEMO_SERVER = 'demo'
61
58
  DEMO_PRESET = 'demoserver' # cspell: disable-line
62
59
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
@@ -78,6 +75,7 @@ module Aspera
78
75
  GEM_CHECK_DATE_FMT = '%Y/%m/%d'
79
76
  # for testing only
80
77
  SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
78
+ CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
81
79
  private_constant :DEFAULT_CONFIG_FILENAME,
82
80
  :CONF_PRESET_CONFIG,
83
81
  :CONF_PRESET_VERSION,
@@ -98,7 +96,8 @@ module Aspera
98
96
  :WIZARD_RESULT_KEYS,
99
97
  :SELF_SIGNED_CERT,
100
98
  :PERSISTENCY_FOLDER,
101
- :DEFAULT_PRIV_KEY_LENGTH
99
+ :DEFAULT_PRIV_KEY_LENGTH,
100
+ :CONF_OVERVIEW_KEYS
102
101
 
103
102
  class << self
104
103
  def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
@@ -145,11 +144,10 @@ module Aspera
145
144
 
146
145
  def initialize(**env)
147
146
  # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
148
- super(**env)
147
+ super
149
148
  @use_plugin_defaults = true
150
149
  @config_presets = nil
151
150
  @config_checksum_on_disk = nil
152
- @connect_versions = nil
153
151
  @vault = nil
154
152
  @pac_exec = nil
155
153
  @sdk_default_location = false
@@ -212,8 +210,9 @@ module Aspera
212
210
  # Transfer SDK options
213
211
  options.declare(:ascp_path, 'Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
214
212
  options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
215
- options.declare(:sdk_url, 'URL to get SDK', default: SpecialValues::DEF)
216
- options.declare(:sdk_folder, 'SDK folder path', handler: {o: Ascp::Installation.instance, m: :sdk_folder})
213
+ options.declare(:sdk_url, 'URL to get Aspera Transfer Daemon', default: SpecialValues::DEF)
214
+ options.declare(:locations_url, 'URL to get locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
215
+ options.declare(:sdk_folder, 'SDK folder path', handler: {o: Products::Transferd, m: :sdk_directory})
217
216
  options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
218
217
  # email options
219
218
  options.declare(:smtp, 'SMTP configuration', types: Hash)
@@ -232,22 +231,22 @@ module Aspera
232
231
  options.parse_options!
233
232
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
234
233
  # Check SDK folder is set or not, for compatibility, we check in two places
235
- sdk_folder = Ascp::Installation.instance.sdk_folder rescue nil
236
- if sdk_folder.nil?
234
+ sdk_dir = Products::Transferd.sdk_directory rescue nil
235
+ if sdk_dir.nil?
237
236
  @sdk_default_location = true
238
237
  Log.log.debug('SDK folder is not set, checking default')
239
238
  # new location
240
- sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK)
241
- Log.log.debug{"checking: #{sdk_folder}"}
242
- if !Dir.exist?(sdk_folder)
243
- Log.log.debug{"not exists: #{sdk_folder}"}
239
+ sdk_dir = self.class.default_app_main_folder(app_name: DIR_SDK)
240
+ Log.log.debug{"checking: #{sdk_dir}"}
241
+ if !Dir.exist?(sdk_dir)
242
+ Log.log.debug{"not exists: #{sdk_dir}"}
244
243
  # former location
245
- former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), APP_NAME_SDK)
244
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), DIR_SDK)
246
245
  Log.log.debug{"checking: #{former_sdk_folder}"}
247
- sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
246
+ sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
248
247
  end
249
- Log.log.debug{"using: #{sdk_folder}"}
250
- Ascp::Installation.instance.sdk_folder = sdk_folder
248
+ Log.log.debug{"using: #{sdk_dir}"}
249
+ Products::Transferd.sdk_directory = sdk_dir
251
250
  end
252
251
  pac_script = options.get_option(:fpac)
253
252
  # create PAC executor
@@ -414,7 +413,7 @@ module Aspera
414
413
 
415
414
  def periodic_check_newer_gem_version
416
415
  # get verification period
417
- delay_days = options.get_option(:version_check_days, mandatory: true)
416
+ delay_days = options.get_option(:version_check_days, mandatory: true).to_i
418
417
  # check only if not zero day
419
418
  return if delay_days.eql?(0)
420
419
  # get last date from persistency
@@ -438,23 +437,6 @@ module Aspera
438
437
  end if check_data[:need_update]
439
438
  end
440
439
 
441
- # retrieve structure from cloud (CDN) with all versions available
442
- def connect_versions
443
- if @connect_versions.nil?
444
- api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
445
- javascript = api_connect_cdn.call(operation: 'GET', subpath: CONNECT_VERSIONS)
446
- # get result on one line
447
- connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/, '')
448
- Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
449
- # get javascript object only
450
- found = connect_versions_javascript.match(/^.*? = (.*);/)
451
- raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
452
- all_data = JSON.parse(found[1])
453
- @connect_versions = all_data['entries']
454
- end
455
- return @connect_versions
456
- end
457
-
458
440
  # loads default parameters of plugin if no -P parameter
459
441
  # and if there is a section defined for the plugin in the "default" section
460
442
  # try to find: conf[conf["default"][plugin_str]]
@@ -658,34 +640,31 @@ module Aspera
658
640
  command = options.get_next_command(%i[list info version])
659
641
  if %i[info version].include?(command)
660
642
  connect_id = options.get_next_argument('id or title')
661
- one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
643
+ one_res = Products::Connect.instance.versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
662
644
  raise Cli::NoSuchIdentifier.new(:connect, connect_id) if one_res.nil?
663
645
  end
664
646
  case command
665
647
  when :list
666
- return {type: :object_list, data: connect_versions, fields: %w[id title version]}
648
+ return Main.result_object_list(Products::Connect.instance.versions, fields: %w[id title version])
667
649
  when :info
668
650
  one_res.delete('links')
669
- return {type: :single_object, data: one_res}
651
+ return Main.result_single_object(one_res)
670
652
  when :version
671
653
  all_links = one_res['links']
672
654
  command = options.get_next_command(%i[list download open])
673
655
  if %i[download open].include?(command)
674
656
  link_title = options.get_next_argument('title or rel')
675
657
  one_link = all_links.find {|i| i['title'].eql?(link_title) || i['rel'].eql?(link_title)}
676
- raise 'no such value' if one_link.nil?
658
+ raise "no such value: #{link_title}" if one_link.nil?
677
659
  end
678
660
  case command
679
661
  when :list
680
- return {type: :object_list, data: all_links}
662
+ return Main.result_object_list(all_links)
681
663
  when :download
682
- folder_dest = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
683
- # folder_dest=self.options.get_next_argument('destination folder')
684
- api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
685
- file_url = one_link['href']
686
- filename = file_url.gsub(%r{.*/}, '')
687
- api_connect_cdn.call(operation: 'GET', subpath: file_url, save_to_file: File.join(folder_dest, filename))
688
- return Main.result_status("Downloaded: #{filename}")
664
+ archive_path = one_link['href']
665
+ save_to_path = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), archive_path.gsub(%r{.*/}, ''))
666
+ Products::Connect.instance.cdn_api.call(operation: 'GET', subpath: archive_path, save_to_file: save_to_path)
667
+ return Main.result_status("Downloaded: #{save_to_path}")
689
668
  when :open
690
669
  Environment.instance.open_uri(one_link['href'])
691
670
  return Main.result_status("Opened: #{one_link['href']}")
@@ -700,11 +679,12 @@ module Aspera
700
679
  return execute_connect_action
701
680
  when :use
702
681
  ascp_path = options.get_next_argument('path to ascp')
682
+ Ascp::Installation.instance.ascp_path = ascp_path
703
683
  formatter.display_status("ascp version: #{Ascp::Installation.instance.get_ascp_version(ascp_path)}")
704
684
  set_global_default(:ascp_path, ascp_path)
705
685
  return Main.result_nothing
706
686
  when :show
707
- return {type: :status, data: Ascp::Installation.instance.path(:ascp)}
687
+ return Main.result_status(Ascp::Installation.instance.path(:ascp))
708
688
  when :info
709
689
  # collect info from ascp executable
710
690
  data = Ascp::Installation.instance.ascp_info
@@ -713,13 +693,13 @@ module Aspera
713
693
  # add keys
714
694
  DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
715
695
  # declare those as secrets
716
- SecretHider::ADDITIONAL_KEYS_TO_HIDE.push(*DataRepository::ELEMENTS.map(&:to_s))
717
- return {type: :single_object, data: data}
696
+ SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
697
+ return Main.result_single_object(data)
718
698
  when :products
719
699
  command = options.get_next_command(%i[list use])
720
700
  case command
721
701
  when :list
722
- return {type: :object_list, data: Ascp::Products.installed_products, fields: %w[name app_root]}
702
+ return Main.result_object_list(Ascp::Installation.instance.installed_products, fields: %w[name app_root])
723
703
  when :use
724
704
  default_product = options.get_next_argument('product name')
725
705
  Ascp::Installation.instance.use_ascp_from_product(default_product)
@@ -728,23 +708,44 @@ module Aspera
728
708
  end
729
709
  when :install
730
710
  # reset to default location, if older default was used
731
- Ascp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
732
- n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true))
711
+ Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: DIR_SDK) if @sdk_default_location
712
+ version = options.get_next_argument('transferd version', mandatory: false)
713
+ n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
733
714
  return Main.result_status("Installed #{n} version #{v}")
734
715
  when :spec
735
- return {
736
- type: :object_list,
737
- data: Transfer::Parameters.man_table(formatter),
716
+ return Main.result_object_list(
717
+ Transfer::Parameters.man_table(formatter),
738
718
  fields: [%w[name type], Transfer::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description]].flatten.freeze
739
- }
719
+ )
740
720
  when :errors
741
721
  error_data = []
742
722
  Transfer::ERROR_INFO.each_pair do |code, prop|
743
723
  error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
744
724
  end
745
- return {type: :object_list, data: error_data}
725
+ return Main.result_object_list(error_data)
726
+ else Aspera.error_unexpected_value(command)
727
+ end
728
+ Aspera.error_unreachable_line
729
+ end
730
+
731
+ def execute_action_transferd
732
+ command = options.get_next_command(%i[list install])
733
+ case command
734
+ when :install
735
+ # reset to default location, if older default was used
736
+ Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: DIR_SDK) if @sdk_default_location
737
+ version = options.get_next_argument('transferd version', mandatory: false)
738
+ n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
739
+ return Main.result_status("Installed #{n} version #{v}")
740
+ when :list
741
+ sdk_list = Ascp::Installation.instance.sdk_locations
742
+ return Main.result_object_list(
743
+ sdk_list,
744
+ fields: sdk_list.first.keys - ['url']
745
+ )
746
+ else Aspera.error_unexpected_value(command)
746
747
  end
747
- raise "unexpected case: #{command}"
748
+ Aspera.error_unreachable_line
748
749
  end
749
750
 
750
751
  # legacy actions available globally
@@ -763,12 +764,20 @@ module Aspera
763
764
  raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
764
765
  case action
765
766
  when :list
766
- return {type: :value_list, data: @config_presets.keys, name: 'name'}
767
+ return Main.result_value_list(@config_presets.keys, 'name')
767
768
  when :overview
768
769
  # display process modifies the value (hide secrets): we do not want to save removed secrets
769
- return {type: :config_over, data: self.class.deep_clone(@config_presets)}
770
+ data = self.class.deep_clone(@config_presets)
771
+ formatter.hide_secrets(data)
772
+ result = []
773
+ data.each do |config, preset|
774
+ preset.each do |parameter, value|
775
+ result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
776
+ end
777
+ end
778
+ return Main.result_object_list(result, fields: CONF_OVERVIEW_KEYS)
770
779
  when :show
771
- return {type: :single_object, data: self.class.deep_clone(@config_presets[name])}
780
+ return Main.result_single_object(self.class.deep_clone(@config_presets[name]))
772
781
  when :delete
773
782
  @config_presets.delete(name)
774
783
  return Main.result_status("Deleted: #{name}")
@@ -779,7 +788,7 @@ module Aspera
779
788
  case value
780
789
  when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
781
790
  end
782
- return {type: :single_object, data: value}
791
+ return Main.result_single_object(value)
783
792
  when :unset
784
793
  param_name = options.get_next_argument('parameter name')
785
794
  @config_presets[name].delete(param_name)
@@ -818,7 +827,7 @@ module Aspera
818
827
  user = options.get_option(:username, mandatory: true)
819
828
  result = lookup_preset(url: url, username: user)
820
829
  raise 'no such config found' if result.nil?
821
- return {type: :single_object, data: result}
830
+ return Main.result_single_object(result)
822
831
  when :secure
823
832
  identifier = options.get_next_argument('config name', mandatory: false)
824
833
  preset_names = identifier.nil? ? @config_presets.keys : [identifier]
@@ -856,13 +865,15 @@ module Aspera
856
865
  remote_certificate
857
866
  gem
858
867
  plugins
859
- flush_tokens
868
+ tokens
860
869
  echo
870
+ download
861
871
  wizard
862
872
  detect
863
873
  coffee
864
874
  image
865
875
  ascp
876
+ transferd
866
877
  email_test
867
878
  smtp_settings
868
879
  proxy_check
@@ -912,9 +923,27 @@ module Aspera
912
923
  end
913
924
  when :echo # display the content of a value given on command line
914
925
  return Formatter.auto_type(options.get_next_argument('value', validation: nil))
915
- when :flush_tokens
916
- deleted_files = OAuth::Factory.instance.flush_tokens
917
- return {type: :value_list, data: deleted_files, name: 'file'}
926
+ when :download
927
+ file_url = options.get_next_argument('source URL').chomp
928
+ file_dest = options.get_next_argument('file path', mandatory: false)
929
+ if file_dest.nil?
930
+ file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, ''))
931
+ end
932
+ formatter.display_status("Downloading: #{file_url}")
933
+ Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
934
+ return Main.result_status("Saved to: #{file_dest}")
935
+ when :tokens
936
+ require 'aspera/api/node'
937
+ case options.get_next_command(%i{flush list show})
938
+ when :flush
939
+ return Main.result_value_list(OAuth::Factory.instance.flush_tokens, name: 'file')
940
+ when :list
941
+ return Main.result_object_list(OAuth::Factory.instance.persisted_tokens)
942
+ when :show
943
+ data = OAuth::Factory.instance.get_token_info(instance_identifier)
944
+ raise Cli::Error, 'No such identifier' if data.nil?
945
+ return Main.result_single_object(data)
946
+ end
918
947
  when :plugins
919
948
  case options.get_next_command(%i[list create])
920
949
  when :list
@@ -928,7 +957,7 @@ module Aspera
928
957
  path: PluginFactory.instance.plugin_source(name)
929
958
  })
930
959
  end
931
- return {type: :object_list, data: result, fields: %w[plugin detect wizard path]}
960
+ return Main.result_object_list(result, fields: %w[plugin detect wizard path])
932
961
  when :create
933
962
  plugin_name = options.get_next_argument('name').downcase
934
963
  destination_folder = options.get_next_argument('folder', mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
@@ -954,10 +983,7 @@ module Aspera
954
983
  options.ask_missing_mandatory = true
955
984
  # detect plugins by url and optional query
956
985
  apps = identify_plugins_for_url.freeze
957
- return {
958
- type: :object_list,
959
- data: apps
960
- } if action.eql?(:detect)
986
+ return Main.result_object_list(apps) if action.eql?(:detect)
961
987
  return wizard_find(apps)
962
988
  when :coffee
963
989
  return Main.result_image(COFFEE_IMAGE, formatter: formatter)
@@ -965,6 +991,8 @@ module Aspera
965
991
  return Main.result_image(options.get_next_argument('image uri or blob'), formatter: formatter)
966
992
  when :ascp
967
993
  execute_action_ascp
994
+ when :transferd
995
+ execute_action_transferd
968
996
  when :gem
969
997
  case options.get_next_command(%i[path version name])
970
998
  when :path then return Main.result_status(self.class.gem_src_root)
@@ -979,14 +1007,14 @@ module Aspera
979
1007
  send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
980
1008
  return Main.result_nothing
981
1009
  when :smtp_settings
982
- return {type: :single_object, data: email_settings}
1010
+ return Main.result_single_object(email_settings)
983
1011
  when :proxy_check
984
1012
  # ensure fpac was provided
985
1013
  options.get_option(:fpac, mandatory: true)
986
1014
  server_url = options.get_next_argument('server url')
987
1015
  return Main.result_status(@pac_exec.find_proxy_for_url(server_url))
988
1016
  when :check_update
989
- return {type: :single_object, data: check_gem_version}
1017
+ return Main.result_single_object(check_gem_version)
990
1018
  when :initdemo
991
1019
  if @config_presets.key?(DEMO_PRESET)
992
1020
  Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
@@ -1085,7 +1113,7 @@ module Aspera
1085
1113
  wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
1086
1114
  end
1087
1115
  # test mode does not change conf file
1088
- return {type: :single_object, data: wizard_result} if options.get_option(:test_mode)
1116
+ return Main.result_single_object(wizard_result) if options.get_option(:test_mode)
1089
1117
  # Write configuration file
1090
1118
  formatter.display_status("Preparing preset: #{wiz_preset_name}")
1091
1119
  # init defaults if necessary
@@ -1196,7 +1224,8 @@ module Aspera
1196
1224
  Log.log.error do
1197
1225
  "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
1198
1226
  'Please fix the issue: either create preset with one parameter: ' \
1199
- "(#{Info::CMD_NAME} config id #{default_config_name} init @json:'{}') or remove default (#{Info::CMD_NAME} config id default remove #{plugin_name_sym})."
1227
+ "(#{Info::CMD_NAME} config id #{default_config_name} init @json:'{}') " \
1228
+ "or remove default (#{Info::CMD_NAME} config id default remove #{plugin_name_sym})."
1200
1229
  end
1201
1230
  end
1202
1231
  raise Cli::Error, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
@@ -1211,11 +1240,11 @@ module Aspera
1211
1240
  command = options.get_next_command(%i[info list show create delete password])
1212
1241
  case command
1213
1242
  when :info
1214
- return {type: :single_object, data: vault_info}
1243
+ return Main.result_single_object(vault_info)
1215
1244
  when :list
1216
- return {type: :object_list, data: vault.list, fields: %w(label url username password description)}
1245
+ return Main.result_object_list(vault.list, fields: %w(label url username password description))
1217
1246
  when :show
1218
- return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
1247
+ return Main.result_single_object(vault.get(label: options.get_next_argument('label')))
1219
1248
  when :create
1220
1249
  label = options.get_next_argument('label', validation: String)
1221
1250
  info = options.get_next_argument('info', validation: Hash)
@@ -1264,6 +1293,8 @@ module Aspera
1264
1293
  info = vault_info
1265
1294
  case info[:type]
1266
1295
  when 'file'
1296
+ # this module requires comilation, so it is optinal
1297
+ require 'aspera/keychain/encrypted_hash'
1267
1298
  # absolute_path? introduced in ruby 2.7
1268
1299
  @vault = Keychain::EncryptedHash.new(
1269
1300
  info[:name].eql?(File.absolute_path(info[:name])) ? info[:name] : File.join(@main_folder, info[:name]),
@@ -19,6 +19,7 @@ module Aspera
19
19
  options.declare(:region, 'Storage region')
20
20
  options.declare(:identity, "Authentication URL (#{Api::CosNode::IBM_CLOUD_TOKEN_URL})", default: Api::CosNode::IBM_CLOUD_TOKEN_URL)
21
21
  options.parse_options!
22
+ Node.declare_options(options)
22
23
  end
23
24
 
24
25
  ACTIONS = %i[node].freeze
@@ -305,7 +305,7 @@ module Aspera
305
305
  # authenticated user
306
306
  delivery_info['sources'] ||= [{'paths' => []}]
307
307
  first_source = delivery_info['sources'].first
308
- first_source['paths'].push(*transfer.source_list)
308
+ first_source['paths'].concat(transfer.source_list)
309
309
  source_id = instance_identifier(as_option: :remote_source) do |field, value|
310
310
  Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
311
311
  source_list = api_v3.read('source_shares')['items']
@@ -441,7 +441,9 @@ module Aspera
441
441
  Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
442
442
  self.class.get_source_id_by_name(value, source_list)
443
443
  end.to_i
444
- source_name = source_list.find{|i|i['id'].eql?(source_id)}['name']
444
+ selected_source = source_list.find{|i|i['id'].eql?(source_id)}
445
+ raise 'No such source' if selected_source.nil?
446
+ source_name = selected_source['name']
445
447
  source_hash = options.get_option(:storage, mandatory: true)
446
448
  # check value of option
447
449
  Aspera.assert_type(source_hash, Hash, exception_class: Cli::Error){'storage option'}
@@ -228,24 +228,25 @@ module Aspera
228
228
  config.progress_bar&.event(:transfer, session_id: id, info: status['bytes_written'].to_i)
229
229
  end
230
230
  if status_list.include?(status['upload_status'])
231
- # if status['upload_status'].eql?('completed')
232
231
  config.progress_bar&.event(:end, session_id: id)
233
232
  return status
234
- # end
235
233
  end
236
234
  sleep(1.0)
237
235
  end
238
236
  end
239
237
 
238
+ # @param [Srting] job identifier
239
+ # @return [Hash] result of API call for job status
240
240
  def wait_for_job(job_id)
241
+ result = nil
241
242
  loop do
242
- status = @api_v5.read("jobs/#{job_id}", {type: :formatted})
243
- return status unless JOB_RUNNING.include?(status['status'])
244
- formatter.long_operation_running(status['status'])
243
+ result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
244
+ break unless JOB_RUNNING.include?(result['status'])
245
+ formatter.long_operation_running(result['status'])
245
246
  sleep(0.5)
246
247
  end
247
248
  formatter.long_operation_terminated
248
- Aspera.error_unreachable_line
249
+ return result
249
250
  end
250
251
 
251
252
  # Get a (full or partial) list of all entities of a given type with query: offset/limit
@@ -253,9 +254,10 @@ module Aspera
253
254
  # @param query [Hash,nil] additional query parameters
254
255
  # @param real_path [String] real path if it's n ot just the type
255
256
  # @param item_list_key [String] key in the result to get the list of items
256
- def list_entities(type:, real_path: nil, item_list_key: nil, query: {})
257
+ def list_entities(type:, real_path: nil, item_list_key: nil, query: nil)
257
258
  Log.log.trace1{"list_entities t=#{type} p=#{real_path} k=#{item_list_key} q=#{query}"}
258
259
  type = type.to_s if type.is_a?(Symbol)
260
+ query = {} if query.nil?
259
261
  Aspera.assert_type(type, String)
260
262
  Aspera.assert_type(query, Hash)
261
263
  item_list_key = type if item_list_key.nil?
@@ -463,7 +465,8 @@ module Aspera
463
465
  end
464
466
  return browse_folder("packages/#{package_id}/files/#{location}")
465
467
  when :status
466
- status = wait_package_status(package_id, status_list: nil)
468
+ status_list = options.get_next_argument('list of states, or nothing', mandatory: false, validation: Array)
469
+ status = wait_package_status(package_id, status_list: status_list)
467
470
  return {type: :single_object, data: status}
468
471
  when :delete
469
472
  ids = package_id
@@ -555,9 +558,10 @@ module Aspera
555
558
  id_as_arg = 'type'
556
559
  when :accounts
557
560
  display_fields = Formatter.all_but('user_profile_data_attributes')
561
+ available_commands.push(:reset_password)
558
562
  when :oauth_clients
559
563
  display_fields = Formatter.all_but('public_key')
560
- adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
564
+ adm_api = Rest.new(**@api_v5.params, base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}")
561
565
  when :shared_inboxes, :workgroups
562
566
  available_commands.push(:members, :saml_groups, :invite_external_collaborator)
563
567
  res_id_query = {'all': true}
@@ -595,7 +599,11 @@ module Aspera
595
599
 
596
600
  end
597
601
  when :browse
598
- return browse_folder("#{res_path}/#{instance_identifier}/browse")
602
+ node_id = instance_identifier do |field, value|
603
+ lookup_entity_by_field(
604
+ type: res_type, value: value, field: field, real_path: res_path, item_list_key: list_key, query: res_id_query)['id']
605
+ end
606
+ return browse_folder("#{res_path}/#{node_id}/browse")
599
607
  when :invite_external_collaborator
600
608
  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']}
601
609
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
@@ -642,7 +650,12 @@ module Aspera
642
650
  value: value,
643
651
  query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
644
652
  end
653
+ when :reset_password
654
+ contact_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
655
+ adm_api.create("#{res_type}/#{contact_id}/reset_password", {})
656
+ return Main.result_status('password reset, user shall check email')
645
657
  end
658
+ Aspera.error_unreachable_line
646
659
  end
647
660
 
648
661
  def execute_admin
@@ -655,16 +668,18 @@ module Aspera
655
668
  when *ADMIN_RESOURCES
656
669
  return execute_resource(command)
657
670
  when :clean_deleted
658
- delete_data = value_create_modify(command: command, default: {days_before_deleting_package_records: 365})
671
+ delete_data = value_create_modify(command: command, default: {})
672
+ delete_data = @api_v5.read('configuration').slice('days_before_deleting_package_records') if delete_data.empty?
659
673
  res = @api_v5.create('internal/packages/clean_deleted', delete_data)
660
674
  return {type: :single_object, data: res}
661
675
  when :events
662
676
  event_type = options.get_next_command(%i[application webhook])
663
677
  case event_type
664
678
  when :application
665
- return {type: :object_list, data: list_entities(type: 'application_events'), fields: %w[event_type created_at application user.name]}
679
+ return {type: :object_list, data: list_entities(type: 'application_events', query: query_read_delete),
680
+ fields: %w[event_type created_at application user.name]}
666
681
  when :webhook
667
- return {type: :object_list, data: list_entities(type: 'all_webhooks_events', item_list_key: 'events')}
682
+ return {type: :object_list, data: list_entities(type: 'all_webhooks_events', query: query_read_delete, item_list_key: 'events')}
668
683
  end
669
684
  when :configuration
670
685
  conf_path = 'configuration'
@@ -732,7 +747,7 @@ module Aspera
732
747
  end
733
748
  end
734
749
  when :bearer_token
735
- return {type: :text, data: @api_v5.oauth.token}
750
+ return {type: :text, data: @api_v5.oauth.authorization}
736
751
  when :packages
737
752
  return package_action
738
753
  when :shared_folders