aspera-cli 4.15.0 → 4.16.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 (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +292 -228
  5. data/CONTRIBUTING.md +69 -18
  6. data/README.md +1102 -952
  7. data/bin/ascli +13 -31
  8. data/bin/asession +3 -1
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/aoc.rb +28 -33
  11. data/lib/aspera/ascmd.rb +3 -6
  12. data/lib/aspera/assert.rb +45 -0
  13. data/lib/aspera/cli/extended_value.rb +5 -5
  14. data/lib/aspera/cli/formatter.rb +26 -13
  15. data/lib/aspera/cli/hints.rb +4 -3
  16. data/lib/aspera/cli/main.rb +16 -3
  17. data/lib/aspera/cli/manager.rb +45 -36
  18. data/lib/aspera/cli/plugin.rb +20 -13
  19. data/lib/aspera/cli/plugins/aoc.rb +103 -73
  20. data/lib/aspera/cli/plugins/ats.rb +4 -3
  21. data/lib/aspera/cli/plugins/config.rb +114 -119
  22. data/lib/aspera/cli/plugins/cos.rb +2 -2
  23. data/lib/aspera/cli/plugins/faspex.rb +23 -19
  24. data/lib/aspera/cli/plugins/faspex5.rb +75 -43
  25. data/lib/aspera/cli/plugins/node.rb +28 -15
  26. data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
  27. data/lib/aspera/cli/plugins/preview.rb +9 -7
  28. data/lib/aspera/cli/plugins/server.rb +6 -3
  29. data/lib/aspera/cli/plugins/shares.rb +30 -26
  30. data/lib/aspera/cli/sync_actions.rb +9 -9
  31. data/lib/aspera/cli/transfer_agent.rb +21 -14
  32. data/lib/aspera/cli/transfer_progress.rb +2 -3
  33. data/lib/aspera/cli/version.rb +1 -1
  34. data/lib/aspera/command_line_builder.rb +13 -11
  35. data/lib/aspera/cos_node.rb +3 -2
  36. data/lib/aspera/coverage.rb +22 -0
  37. data/lib/aspera/data_repository.rb +33 -2
  38. data/lib/aspera/environment.rb +4 -2
  39. data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
  40. data/lib/aspera/fasp/agent_base.rb +17 -7
  41. data/lib/aspera/fasp/agent_direct.rb +88 -84
  42. data/lib/aspera/fasp/agent_httpgw.rb +4 -3
  43. data/lib/aspera/fasp/agent_node.rb +3 -2
  44. data/lib/aspera/fasp/agent_trsdk.rb +79 -37
  45. data/lib/aspera/fasp/installation.rb +51 -12
  46. data/lib/aspera/fasp/management.rb +11 -6
  47. data/lib/aspera/fasp/parameters.rb +53 -47
  48. data/lib/aspera/fasp/resume_policy.rb +7 -5
  49. data/lib/aspera/fasp/sync.rb +273 -0
  50. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  51. data/lib/aspera/fasp/uri.rb +2 -2
  52. data/lib/aspera/faspex_gw.rb +11 -8
  53. data/lib/aspera/faspex_postproc.rb +6 -5
  54. data/lib/aspera/id_generator.rb +3 -1
  55. data/lib/aspera/json_rpc.rb +10 -8
  56. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  57. data/lib/aspera/keychain/macos_security.rb +15 -13
  58. data/lib/aspera/log.rb +4 -3
  59. data/lib/aspera/nagios.rb +7 -2
  60. data/lib/aspera/node.rb +17 -16
  61. data/lib/aspera/node_simulator.rb +214 -0
  62. data/lib/aspera/oauth.rb +22 -19
  63. data/lib/aspera/persistency_action_once.rb +13 -14
  64. data/lib/aspera/persistency_folder.rb +3 -2
  65. data/lib/aspera/preview/file_types.rb +53 -267
  66. data/lib/aspera/preview/generator.rb +7 -5
  67. data/lib/aspera/preview/terminal.rb +14 -5
  68. data/lib/aspera/preview/utils.rb +8 -7
  69. data/lib/aspera/proxy_auto_config.rb +6 -3
  70. data/lib/aspera/rest.rb +29 -13
  71. data/lib/aspera/rest_error_analyzer.rb +1 -0
  72. data/lib/aspera/rest_errors_aspera.rb +2 -0
  73. data/lib/aspera/secret_hider.rb +5 -2
  74. data/lib/aspera/ssh.rb +10 -8
  75. data/lib/aspera/temp_file_manager.rb +1 -1
  76. data/lib/aspera/web_server_simple.rb +2 -1
  77. data.tar.gz.sig +0 -0
  78. metadata +96 -45
  79. metadata.gz.sig +0 -0
  80. data/lib/aspera/sync.rb +0 -219
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # cspell:ignore initdemo genkey asperasoft
3
+ # cspell:ignore initdemo genkey pubkey asperasoft
4
4
  require 'aspera/cli/basic_auth_plugin'
5
5
  require 'aspera/cli/extended_value'
6
6
  require 'aspera/cli/version'
@@ -19,9 +19,11 @@ require 'aspera/open_application'
19
19
  require 'aspera/persistency_action_once'
20
20
  require 'aspera/id_generator'
21
21
  require 'aspera/persistency_folder'
22
+ require 'aspera/data_repository'
22
23
  require 'aspera/line_logger'
23
24
  require 'aspera/rest'
24
25
  require 'aspera/log'
26
+ require 'aspera/assert'
25
27
  require 'open3'
26
28
  require 'date'
27
29
  require 'erb'
@@ -35,11 +37,13 @@ module Aspera
35
37
  ASPERA_HOME_FOLDER_NAME = '.aspera'
36
38
  # default config file
37
39
  DEFAULT_CONFIG_FILENAME = 'config.yaml'
40
+ DEFAULT_VAULT_FILENAME = 'vault.bin'
38
41
  # reserved preset names
39
42
  CONF_PRESET_CONFIG = 'config'
40
43
  CONF_PRESET_VERSION = 'version'
41
- CONF_PRESET_DEFAULT = 'default'
44
+ CONF_PRESET_DEFAULTS = 'default'
42
45
  CONF_PRESET_GLOBAL = 'global_common_defaults'
46
+ # special name to identify value of default
43
47
  GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
44
48
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
45
49
  CONF_GLOBAL_SYM = :config
@@ -77,7 +81,7 @@ module Aspera
77
81
  private_constant :DEFAULT_CONFIG_FILENAME,
78
82
  :CONF_PRESET_CONFIG,
79
83
  :CONF_PRESET_VERSION,
80
- :CONF_PRESET_DEFAULT,
84
+ :CONF_PRESET_DEFAULTS,
81
85
  :CONF_PRESET_GLOBAL,
82
86
  :ASPERA_PLUGINS_FOLDERNAME,
83
87
  :RUBY_FILE_EXT,
@@ -141,20 +145,22 @@ module Aspera
141
145
  # return product family folder (~/.aspera)
142
146
  def module_family_folder
143
147
  user_home_folder = Dir.home
144
- raise Cli::Error, "Home folder does not exist: #{user_home_folder}. Check your user environment." unless Dir.exist?(user_home_folder)
148
+ assert(Dir.exist?(user_home_folder), exception_class: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
145
149
  return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
146
150
  end
147
151
 
148
152
  # return product config folder (~/.aspera/<name>)
149
153
  def default_app_main_folder(app_name:)
150
- raise 'app_name must be a non-empty String' unless app_name.is_a?(String) && !app_name.empty?
154
+ assert_type(app_name, String)
155
+ assert(!app_name.empty?)
151
156
  return File.join(module_family_folder, app_name)
152
157
  end
153
158
  end # self
154
159
 
155
160
  def initialize(env, params)
156
- raise 'Internal Error: env and params must be Hash' unless env.is_a?(Hash) && params.is_a?(Hash)
157
- raise 'Internal Error: missing param' unless %i[gem help name version].eql?(params.keys.sort)
161
+ assert_type(env, Hash)
162
+ assert_type(params, Hash)
163
+ assert(%i[gem help name version].eql?(params.keys.sort)){'missing param'}
158
164
  # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
159
165
  super(env)
160
166
  @info = params
@@ -168,6 +174,7 @@ module Aspera
168
174
  @pac_exec = nil
169
175
  @sdk_default_location = false
170
176
  @option_insecure = false
177
+ @option_warn_insecure_cert = true
171
178
  @option_ignore_cert_host_port = []
172
179
  @option_http_options = {}
173
180
  @ssl_warned_urls = []
@@ -233,7 +240,8 @@ module Aspera
233
240
  options.declare(:notify_template, 'Email ERB template for notification of transfers')
234
241
  # HTTP options
235
242
  options.declare(:insecure, 'Do not validate any HTTPS certificate', values: :bool, handler: {o: self, m: :option_insecure}, default: :no)
236
- options.declare(:ignore_certificate, 'List of HTTPS url where to no validate certificate', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
243
+ options.declare(:ignore_certificate, 'Do not validate HTTPS certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
244
+ options.declare(:silent_insecure, 'Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
237
245
  options.declare(:cert_stores, 'List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
238
246
  options.declare(:http_options, 'Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
239
247
  options.declare(:cache_tokens, 'Save and reuse Oauth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
@@ -264,7 +272,7 @@ module Aspera
264
272
  @pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
265
273
  proxy_user_pass = options.get_option(:proxy_credentials)
266
274
  if !proxy_user_pass.nil?
267
- raise Cli::BadArgument, "proxy_credentials shall have two elements (#{proxy_user_pass.length})" unless proxy_user_pass.length.eql?(2)
275
+ assert(proxy_user_pass.length.eql?(2), exception_class: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
268
276
  @proxy_credentials = {user: proxy_user_pass[0], pass: proxy_user_pass[1]}
269
277
  @pac_exec.proxy_user = @proxy_credentials[:user]
270
278
  @pac_exec.proxy_pass = @proxy_credentials[:pass]
@@ -280,7 +288,7 @@ module Aspera
280
288
  Aspera::RestErrorsAspera.register_handlers
281
289
  end
282
290
 
283
- attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_http_options
291
+ attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
284
292
  attr_reader :option_ignore_cert_host_port, :progress_bar
285
293
 
286
294
  def trusted_cert_locations=(path_list)
@@ -293,7 +301,7 @@ module Aspera
293
301
  end
294
302
 
295
303
  path_list.each do |path|
296
- raise 'Expecting a String for cert location' unless path.is_a?(String)
304
+ assert_type(path, String){'Expecting a String for cert location'}
297
305
  Log.log.debug("Adding cert location: #{path}")
298
306
  if path.eql?(ExtendedValue::DEF)
299
307
  path = OpenSSL::X509::DEFAULT_CERT_DIR
@@ -325,22 +333,28 @@ module Aspera
325
333
  def option_ignore_cert_host_port=(url_list)
326
334
  url_list.each do |url|
327
335
  uri = URI.parse(url)
336
+ raise "Expecting https scheme: #{url}" unless uri.scheme.eql?('https')
328
337
  @option_ignore_cert_host_port.push([uri.host, uri.port].freeze)
329
338
  end
330
339
  end
331
340
 
332
341
  def ignore_cert?(address, port)
333
342
  endpoint = [address, port].freeze
334
- Log.log.debug{"ignore cert? #{endpoint}"}
335
- return false unless @option_insecure || @option_ignore_cert_host_port.any?(endpoint)
336
- base_url = "https://#{address}:#{port}"
337
- if !@ssl_warned_urls.include?(base_url)
338
- formatter.display_message(
339
- :error,
340
- "#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production.")
341
- @ssl_warned_urls.push(base_url)
343
+ ignore_cert = false
344
+ if @option_insecure || @option_ignore_cert_host_port.any?(endpoint)
345
+ ignore_cert = true
346
+ if @option_warn_insecure_cert
347
+ base_url = "https://#{address}:#{port}"
348
+ if !@ssl_warned_urls.include?(base_url)
349
+ formatter.display_message(
350
+ :error,
351
+ "#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production.")
352
+ @ssl_warned_urls.push(base_url)
353
+ end
354
+ end
342
355
  end
343
- return true
356
+ Log.log.debug{"ignore cert? #{endpoint} -> #{ignore_cert}"}
357
+ return ignore_cert
344
358
  end
345
359
 
346
360
  # called every time a new REST HTTP session is opened to set user-provided options
@@ -443,25 +457,25 @@ module Aspera
443
457
  return nil
444
458
  end
445
459
 
446
- # get the default global preset, or init a new one
460
+ # get the default global preset, or set default one
447
461
  def global_default_preset
448
- global_default_preset_name = get_plugin_default_config_name(CONF_GLOBAL_SYM)
449
- if global_default_preset_name.nil?
450
- global_default_preset_name = CONF_PRESET_GLOBAL.to_s
451
- set_preset_key(CONF_PRESET_DEFAULT, CONF_GLOBAL_SYM, global_default_preset_name)
462
+ result = get_plugin_default_config_name(CONF_GLOBAL_SYM)
463
+ if result.nil?
464
+ result = CONF_PRESET_GLOBAL
465
+ set_preset_key(CONF_PRESET_DEFAULTS, CONF_GLOBAL_SYM, result)
452
466
  end
453
- return global_default_preset_name
467
+ return result
454
468
  end
455
469
 
456
470
  def set_preset_key(preset, param_name, param_value)
457
- raise "Parameter name must be a String or Symbol, not #{param_name.class}" unless [String, Symbol].include?(param_name.class)
471
+ assert_values(param_name.class, [String, Symbol]){'parameter'}
458
472
  param_name = param_name.to_s
459
473
  selected_preset = @config_presets[preset]
460
474
  if selected_preset.nil?
461
475
  Log.log.debug{"No such preset name: #{preset}, initializing"}
462
476
  selected_preset = @config_presets[preset] = {}
463
477
  end
464
- raise "expecting Hash for #{preset}.#{param_name}" unless selected_preset.is_a?(Hash)
478
+ assert_type(selected_preset, Hash){"#{preset}.#{param_name}"}
465
479
  if selected_preset.key?(param_name)
466
480
  if selected_preset[param_name].eql?(param_value)
467
481
  Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
@@ -493,7 +507,7 @@ module Aspera
493
507
  include_path = include_path.clone # avoid messing up if there are multiple branches
494
508
  current = @config_presets
495
509
  config_name.split(PRESET_DIG_SEPARATOR).each do |name|
496
- raise Cli::Error, "Expecting Hash for sub key: #{include_path} (#{current.class})" unless current.is_a?(Hash)
510
+ assert_type(current, Hash, exception_class: Cli::Error){"sub key: #{include_path}"}
497
511
  include_path.push(name)
498
512
  current = current[name]
499
513
  raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
@@ -511,11 +525,10 @@ module Aspera
511
525
  end
512
526
 
513
527
  def option_plugin_folder=(value)
514
- case value
515
- when String then add_plugin_lookup_folder(value)
516
- when Array then value.each{|f|add_plugin_lookup_folder(f)}
517
- else raise "folder shall be Array or String, not #{value.class}"
518
- end
528
+ assert_values(value.class, [String, Array]){'plugin folder'}
529
+ value = [value] if value.is_a?(String)
530
+ assert(value.all?(String)){'plugin folder'}
531
+ value.each{|f|add_plugin_lookup_folder(f)}
519
532
  end
520
533
 
521
534
  def option_plugin_folder
@@ -558,15 +571,15 @@ module Aspera
558
571
  end
559
572
  files_to_copy = []
560
573
  Log.log.debug{Log.dump('Available_presets', @config_presets)}
561
- raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
574
+ assert_type(@config_presets, Hash){'config file YAML'}
562
575
  # check there is at least the config section
563
- raise "Cannot find key: #{CONF_PRESET_CONFIG}" unless @config_presets.key?(CONF_PRESET_CONFIG)
576
+ assert(@config_presets.key?(CONF_PRESET_CONFIG)){"Cannot find key: #{CONF_PRESET_CONFIG}"}
564
577
  version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
565
578
  raise 'No version found in config section.' if version.nil?
566
579
  Log.log.debug{"conf version: #{version}"}
567
580
  # VVV if there are any conversion needed, those happen here.
568
581
  # fix bug in 4.4 (creating key "true" in "default" preset)
569
- @config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
582
+ @config_presets[CONF_PRESET_DEFAULTS].delete(true) if @config_presets[CONF_PRESET_DEFAULTS].is_a?(Hash)
570
583
  # ^^^ Place new compatibility code before this line
571
584
  # set version to current
572
585
  @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
@@ -646,13 +659,14 @@ module Aspera
646
659
  next
647
660
  end
648
661
  next if detection_info.nil?
649
- raise 'internal error' if detection_info.key?(:url) && !detection_info[:url].is_a?(String)
662
+ assert_type(detection_info, Hash)
663
+ assert_type(detection_info[:url], String) if detection_info.key?(:url)
650
664
  app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
651
665
  # if there is a redirect, then the detector can override the url.
652
666
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
653
667
  end # loop
654
668
  raise "No known application found at #{app_url}" if found_apps.empty?
655
- raise 'Internal error' unless found_apps.all?{|a|a.keys.all?(Symbol)}
669
+ assert(found_apps.all?{|a|a.keys.all?(Symbol)})
656
670
  return found_apps
657
671
  end
658
672
 
@@ -666,10 +680,10 @@ module Aspera
666
680
  case command
667
681
  when :list
668
682
  return {type: :object_list, data: connect_versions, fields: %w[id title version]}
669
- when :info # shows files used
683
+ when :info
670
684
  one_res.delete('links')
671
685
  return {type: :single_object, data: one_res}
672
- when :version # shows files used
686
+ when :version
673
687
  all_links = one_res['links']
674
688
  command = options.get_next_command(%i[list download open])
675
689
  if %i[download open].include?(command)
@@ -678,7 +692,7 @@ module Aspera
678
692
  raise 'no such value' if one_link.nil?
679
693
  end
680
694
  case command
681
- when :list # shows files used
695
+ when :list
682
696
  return {type: :object_list, data: all_links}
683
697
  when :download
684
698
  folder_dest = transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE)
@@ -706,49 +720,17 @@ module Aspera
706
720
  formatter.display_status("ascp version: #{ascp_version}")
707
721
  set_global_default(:ascp_path, ascp_path)
708
722
  return Main.result_nothing
709
- when :show # shows files used
723
+ when :show
710
724
  return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
711
- when :info # shows files used
712
- data = Fasp::Installation.instance.file_paths
713
- # read PATHs from ascp directly, and pvcl modules as well
714
- Open3.popen3(data['ascp'], '-DDL-') do |_stdin, _stdout, stderr, thread|
715
- last_line = ''
716
- while (line = stderr.gets)
717
- line.chomp!
718
- last_line = line
719
- case line
720
- when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
721
- data[Regexp.last_match(1)] = Regexp.last_match(3)
722
- when /^DBG Added module group:"(?<module>[^"]+)" name:"(?<scheme>[^"]+)", version:"(?<version>[^"]+)" interface:"(?<interface>[^"]+)"$/
723
- c = Regexp.last_match.named_captures.symbolize_keys
724
- data[c[:interface]] ||= {}
725
- data[c[:interface]][c[:module]] ||= []
726
- data[c[:interface]][c[:module]].push("#{c[:scheme]} v#{c[:version]}")
727
- when %r{^DBG License result \(/license/(\S+)\): (.+)$}
728
- data[Regexp.last_match(1)] = Regexp.last_match(2)
729
- when /^LOG (.+) version ([0-9.]+)$/
730
- data['product_name'] = Regexp.last_match(1)
731
- data['product_version'] = Regexp.last_match(2)
732
- when /^LOG Initializing FASP version ([^,]+),/
733
- data['ascp_version'] = Regexp.last_match(1)
734
- end
735
- end
736
- if !thread.value.exitstatus.eql?(1) && !data.key?('root')
737
- raise last_line
738
- end
739
- end
740
- # ascp's openssl directory
741
- ascp_file = data['ascp']
742
- File.binread(ascp_file).scan(/[\x20-\x7E]{4,}/) do |match|
743
- if (m = match.match(/OPENSSLDIR.*"(.*)"/))
744
- data['openssldir'] = m[1]
745
- end
746
- end if File.file?(ascp_file)
747
- data['uuid'] = Fasp::Installation.instance.ssh_cert_uuid
748
- # log is "-" no need to display
749
- data.delete('log')
750
- # show command line transfer spec
725
+ when :info
726
+ # collect info from ascp executable
727
+ data = Fasp::Installation.instance.ascp_info
728
+ # add command line transfer spec
751
729
  data['ts'] = transfer.updated_ts
730
+ # add keys
731
+ DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
732
+ # declare those as secrets
733
+ SecretHider::ADDITIONAL_KEYS_TO_HIDE.push(*DataRepository::ELEMENTS.map(&:to_s))
752
734
  return {type: :single_object, data: data}
753
735
  when :products
754
736
  command = options.get_next_command(%i[list use])
@@ -887,6 +869,7 @@ module Aspera
887
869
  open
888
870
  documentation
889
871
  genkey
872
+ pubkey
890
873
  remote_certificate
891
874
  gem
892
875
  plugins
@@ -925,6 +908,9 @@ module Aspera
925
908
  private_key_length = options.get_next_argument('size in bits', mandatory: false, type: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
926
909
  self.class.generate_rsa_private_key(path: private_key_path, length: private_key_length)
927
910
  return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
911
+ when :pubkey # get pub key
912
+ private_key_pem = options.get_next_argument('private key PEM value')
913
+ return Main.result_status(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
928
914
  when :remote_certificate
929
915
  remote_url = options.get_next_argument('remote URL')
930
916
  @option_insecure = true
@@ -984,8 +970,7 @@ module Aspera
984
970
  return wizard_find(apps)
985
971
  when :coffee
986
972
  if OpenApplication.instance.url_method.eql?(:text)
987
- require 'aspera/preview/terminal'
988
- return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body))
973
+ return Main.result_picture_in_terminal(options, Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body)
989
974
  end
990
975
  OpenApplication.instance.uri(COFFEE_IMAGE)
991
976
  return Main.result_nothing
@@ -1024,13 +1009,13 @@ module Aspera
1024
1009
  'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + ASPERA # cspell:disable-line
1025
1010
  }
1026
1011
  end
1027
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
1028
- if @config_presets[CONF_PRESET_DEFAULT].key?(SERVER_COMMAND)
1029
- Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND]}"}
1012
+ @config_presets[CONF_PRESET_DEFAULTS] ||= {}
1013
+ if @config_presets[CONF_PRESET_DEFAULTS].key?(SERVER_COMMAND)
1014
+ Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND]}"}
1030
1015
  Log.log.warn{"Use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}"} unless
1031
- DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND])
1016
+ DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND])
1032
1017
  else
1033
- @config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND] = DEMO_SERVER_PRESET
1018
+ @config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND] = DEMO_SERVER_PRESET
1034
1019
  Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
1035
1020
  end
1036
1021
  return Main.result_status('Done')
@@ -1041,9 +1026,9 @@ module Aspera
1041
1026
  exception_class_name = options.get_next_argument('exception class name', mandatory: true)
1042
1027
  exception_text = options.get_next_argument('exception text', mandatory: true)
1043
1028
  exception_class = Object.const_get(exception_class_name)
1044
- raise "#{exception_class} is not an exception: #{exception_class.class}" unless exception_class <= Exception
1029
+ assert(exception_class <= Exception){"#{exception_class} is not an exception: #{exception_class.class}"}
1045
1030
  raise exception_class, exception_text
1046
- else raise 'INTERNAL ERROR: wrong case'
1031
+ else error_unreachable_line
1047
1032
  end
1048
1033
  end
1049
1034
 
@@ -1064,7 +1049,7 @@ module Aspera
1064
1049
  options.add_option_preset({url: wiz_url})
1065
1050
  # instantiate plugin: command line options will be known and wizard can be called
1066
1051
  wiz_plugin_class = self.class.plugin_class(identification[:product])
1067
- raise Cli::BadArgument, "Detected: #{identification[:product]}, but this application has no wizard" unless wiz_plugin_class.respond_to?(:wizard)
1052
+ assert(wiz_plugin_class.respond_to?(:wizard), exception_class: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
1068
1053
  # instantiate plugin: command line options will be known, e.g. private_key
1069
1054
  plugin_instance = wiz_plugin_class.new(@agents)
1070
1055
  wiz_params = {
@@ -1089,7 +1074,7 @@ module Aspera
1089
1074
  formatter.display_status('Using existing key:')
1090
1075
  else
1091
1076
  formatter.display_status("Generating #{DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
1092
- Config.generate_rsa_private_key(path: private_key_path)
1077
+ self.class.generate_rsa_private_key(path: private_key_path)
1093
1078
  formatter.display_status('Created key:')
1094
1079
  end
1095
1080
  formatter.display_status(private_key_path)
@@ -1102,7 +1087,7 @@ module Aspera
1102
1087
  # finally, call the wizard
1103
1088
  wizard_result = wiz_plugin_class.wizard(**wiz_params)
1104
1089
  Log.log.debug{"wizard result: #{wizard_result}"}
1105
- raise "Internal error: missing or extra keys in wizard result: #{wizard_result.keys}" unless WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)
1090
+ assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
1106
1091
  # get preset name from user or default
1107
1092
  wiz_preset_name = options.get_option(:id)
1108
1093
  if wiz_preset_name.nil?
@@ -1118,17 +1103,17 @@ module Aspera
1118
1103
  # Write configuration file
1119
1104
  formatter.display_status("Preparing preset: #{wiz_preset_name}")
1120
1105
  # init defaults if necessary
1121
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
1106
+ @config_presets[CONF_PRESET_DEFAULTS] ||= {}
1122
1107
  option_override = options.get_option(:override, mandatory: true)
1123
1108
  raise Cli::Error, "A default configuration already exists for plugin '#{identification[:product]}' (use --override=yes or --default=no)" \
1124
- if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULT].key?(identification[:product])
1109
+ if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULTS].key?(identification[:product])
1125
1110
  raise Cli::Error, "Preset already exists: #{wiz_preset_name} (use --override=yes or --id=<name>)" \
1126
1111
  if !option_override && @config_presets.key?(wiz_preset_name)
1127
1112
  @config_presets[wiz_preset_name] = wizard_result[:preset_value].stringify_keys
1128
1113
  test_args = wizard_result[:test_args]
1129
1114
  if options.get_option(:default, mandatory: true)
1130
1115
  formatter.display_status("Setting config preset as default for #{identification[:product]}")
1131
- @config_presets[CONF_PRESET_DEFAULT][identification[:product].to_s] = wiz_preset_name
1116
+ @config_presets[CONF_PRESET_DEFAULTS][identification[:product].to_s] = wiz_preset_name
1132
1117
  else
1133
1118
  test_args = "-P#{wiz_preset_name} #{test_args}"
1134
1119
  end
@@ -1155,7 +1140,7 @@ module Aspera
1155
1140
  smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/, '') if smtp.key?(:from_email)
1156
1141
  # check minimum required
1157
1142
  %i[server port domain].each do |n|
1158
- raise "Missing mandatory smtp parameter: #{n}" unless smtp.key?(n)
1143
+ assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
1159
1144
  end
1160
1145
  Log.log.debug{"smtp=#{smtp}"}
1161
1146
  return smtp
@@ -1169,7 +1154,7 @@ module Aspera
1169
1154
  values[:from_name] ||= mail_conf[:from_name]
1170
1155
  values[:from_email] ||= mail_conf[:from_email]
1171
1156
  %i[from_name from_email].each do |n|
1172
- raise "Missing email parameter: #{n}" unless values.key?(n)
1157
+ assert(values.key?(n)){"Missing email parameter: #{n}"}
1173
1158
  end
1174
1159
  start_options = [mail_conf[:domain]]
1175
1160
  start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
@@ -1177,7 +1162,7 @@ module Aspera
1177
1162
  template_binding = Environment.empty_binding
1178
1163
  # add variables to binding
1179
1164
  values.each do |k, v|
1180
- raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
1165
+ assert_type(k, Symbol)
1181
1166
  template_binding.local_variable_set(k, v)
1182
1167
  end
1183
1168
  # execute template
@@ -1211,14 +1196,14 @@ module Aspera
1211
1196
  # returns [String] name if config_presets has default
1212
1197
  # returns nil if there is no config or bypass default params
1213
1198
  def get_plugin_default_config_name(plugin_name_sym)
1214
- raise 'internal error: config_presets shall be defined' if @config_presets.nil?
1199
+ assert(!@config_presets.nil?){'config_presets shall be defined'}
1215
1200
  if !@use_plugin_defaults
1216
1201
  Log.log.debug('skip default config')
1217
1202
  return nil
1218
1203
  end
1219
- if @config_presets.key?(CONF_PRESET_DEFAULT) &&
1220
- @config_presets[CONF_PRESET_DEFAULT].key?(plugin_name_sym.to_s)
1221
- default_config_name = @config_presets[CONF_PRESET_DEFAULT][plugin_name_sym.to_s]
1204
+ if @config_presets.key?(CONF_PRESET_DEFAULTS) &&
1205
+ @config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
1206
+ default_config_name = @config_presets[CONF_PRESET_DEFAULTS][plugin_name_sym.to_s]
1222
1207
  if !@config_presets.key?(default_config_name)
1223
1208
  Log.log.error do
1224
1209
  "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
@@ -1235,16 +1220,17 @@ module Aspera
1235
1220
  # TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
1236
1221
  # @return [Hash] result of execution of vault command
1237
1222
  def execute_vault
1238
- command = options.get_next_command(%i[list show create delete password])
1223
+ command = options.get_next_command(%i[info list show create delete password])
1239
1224
  case command
1225
+ when :info
1226
+ return {type: :single_object, data: vault_info}
1240
1227
  when :list
1241
1228
  return {type: :object_list, data: vault.list}
1242
1229
  when :show
1243
1230
  return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
1244
1231
  when :create
1245
- label = options.get_next_argument('label')
1246
- info = options.get_next_argument('info Hash')
1247
- raise 'info must be Hash' unless info.is_a?(Hash)
1232
+ label = options.get_next_argument('label', type: String)
1233
+ info = options.get_next_argument('info', type: Hash)
1248
1234
  info = info.symbolize_keys
1249
1235
  info[:label] = label
1250
1236
  vault.set(info)
@@ -1253,7 +1239,7 @@ module Aspera
1253
1239
  vault.delete(label: options.get_next_argument('label'))
1254
1240
  return Main.result_status('Password deleted')
1255
1241
  when :password
1256
- raise 'Vault does not support password change' unless vault.respond_to?(:password=)
1242
+ assert(vault.respond_to?(:password=)){'Vault does not support password change'}
1257
1243
  new_password = options.get_next_argument('new_password')
1258
1244
  vault.password = new_password
1259
1245
  vault.save
@@ -1272,27 +1258,36 @@ module Aspera
1272
1258
  return value
1273
1259
  end
1274
1260
 
1261
+ def vault_info
1262
+ info = options.get_option(:vault) || {}
1263
+ info = info.symbolize_keys
1264
+ info[:type] ||= 'file'
1265
+ info[:name] ||= (info[:type].eql?('file') ? DEFAULT_VAULT_FILENAME : PROGRAM_NAME)
1266
+ assert(info.keys.sort == %i[name type]) {"vault info shall have exactly keys 'type' and 'name'"}
1267
+ assert(info.values.all?(String)){'vault info shall have only string values'}
1268
+ info[:password] = options.get_option(:vault_password, mandatory: true)
1269
+ return info
1270
+ end
1271
+
1275
1272
  # @return [Object] vault, from options or cache
1276
1273
  def vault
1277
1274
  if @vault.nil?
1278
- vault_info = options.get_option(:vault) || {'type' => 'file', 'name' => 'vault.bin'}
1279
- vault_password = options.get_option(:vault_password, mandatory: true)
1280
- vault_type = vault_info['type'] || 'file'
1281
- vault_name = vault_info['name'] || (vault_type.eql?('file') ? 'vault.bin' : PROGRAM_NAME)
1282
- case vault_type
1275
+ info = vault_info
1276
+ case info[:type]
1283
1277
  when 'file'
1284
1278
  # absolute_path? introduced in ruby 2.7
1285
- vault_path = vault_name.eql?(File.absolute_path(vault_name)) ? vault_name : File.join(@main_folder, vault_name)
1286
- @vault = Keychain::EncryptedHash.new(vault_path, vault_password)
1279
+ @vault = Keychain::EncryptedHash.new(
1280
+ info[:name].eql?(File.absolute_path(info[:name])) ? info[:name] : File.join(@main_folder, info[:name]),
1281
+ info[:password])
1287
1282
  when 'system'
1288
1283
  case Environment.os
1289
1284
  when Environment::OS_X
1290
- @vault = Keychain::MacosSystem.new(vault_name, vault_password)
1285
+ @vault = Keychain::MacosSystem.new(info[:name], info[:password])
1291
1286
  else
1292
1287
  raise 'not implemented for this OS'
1293
1288
  end
1294
1289
  else
1295
- raise Cli::BadArgument, "Unknown vault type: #{vault_type}"
1290
+ raise Cli::BadArgument, "Unknown vault type: #{info[:type]}"
1296
1291
  end
1297
1292
  end
1298
1293
  raise 'No vault defined' if @vault.nil?
@@ -3,6 +3,7 @@
3
3
  require 'aspera/cli/plugin'
4
4
  require 'aspera/cli/plugins/node'
5
5
  require 'aspera/cos_node'
6
+ require 'aspera/assert'
6
7
 
7
8
  module Aspera
8
9
  module Cli
@@ -30,8 +31,7 @@ module Aspera
30
31
  # get service credentials, Hash, e.g. @json:@file:...
31
32
  service_credentials = options.get_option(:service_credentials)
32
33
  storage_endpoint = options.get_option(:endpoint)
33
- raise Cli::BadArgument, 'one of: endpoint or service_credentials is required' if service_credentials.nil? && storage_endpoint.nil?
34
- raise Cli::BadArgument, 'endpoint and service_credentials are mutually exclusive' unless service_credentials.nil? || storage_endpoint.nil?
34
+ assert(service_credentials.nil? ^ storage_endpoint.nil?, exception_class: Cli::BadArgument){'endpoint and service_credentials are mutually exclusive'}
35
35
  if service_credentials.nil?
36
36
  service_api_key = options.get_option(:apikey, mandatory: true)
37
37
  instance_id = options.get_option(:crn, mandatory: true)