aspera-cli 4.15.0 → 4.17.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 (108) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +375 -280
  5. data/CONTRIBUTING.md +71 -18
  6. data/README.md +1978 -1656
  7. data/bin/ascli +13 -31
  8. data/bin/asession +32 -22
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/agent/alpha.rb +117 -0
  11. data/lib/aspera/agent/base.rb +61 -0
  12. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  13. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
  14. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
  15. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
  16. data/lib/aspera/agent/trsdk.rb +188 -0
  17. data/lib/aspera/api/aoc.rb +586 -0
  18. data/lib/aspera/api/ats.rb +46 -0
  19. data/lib/aspera/api/cos_node.rb +95 -0
  20. data/lib/aspera/api/node.rb +344 -0
  21. data/lib/aspera/ascmd.rb +47 -14
  22. data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
  23. data/lib/aspera/{fasp → ascp}/management.rb +14 -14
  24. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  25. data/lib/aspera/assert.rb +45 -0
  26. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  27. data/lib/aspera/cli/extended_value.rb +5 -5
  28. data/lib/aspera/cli/formatter.rb +27 -14
  29. data/lib/aspera/cli/hints.rb +7 -6
  30. data/lib/aspera/cli/main.rb +49 -29
  31. data/lib/aspera/cli/manager.rb +46 -36
  32. data/lib/aspera/cli/plugin.rb +34 -20
  33. data/lib/aspera/cli/plugin_factory.rb +61 -0
  34. data/lib/aspera/cli/plugins/alee.rb +7 -7
  35. data/lib/aspera/cli/plugins/aoc.rb +168 -132
  36. data/lib/aspera/cli/plugins/ats.rb +33 -33
  37. data/lib/aspera/cli/plugins/bss.rb +3 -4
  38. data/lib/aspera/cli/plugins/config.rb +250 -272
  39. data/lib/aspera/cli/plugins/console.rb +8 -6
  40. data/lib/aspera/cli/plugins/cos.rb +20 -19
  41. data/lib/aspera/cli/plugins/faspex.rb +71 -60
  42. data/lib/aspera/cli/plugins/faspex5.rb +212 -133
  43. data/lib/aspera/cli/plugins/node.rb +83 -75
  44. data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
  45. data/lib/aspera/cli/plugins/preview.rb +33 -31
  46. data/lib/aspera/cli/plugins/server.rb +33 -32
  47. data/lib/aspera/cli/plugins/shares.rb +39 -33
  48. data/lib/aspera/cli/sync_actions.rb +9 -9
  49. data/lib/aspera/cli/transfer_agent.rb +45 -25
  50. data/lib/aspera/cli/transfer_progress.rb +2 -3
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/colors.rb +5 -0
  53. data/lib/aspera/command_line_builder.rb +16 -14
  54. data/lib/aspera/coverage.rb +21 -0
  55. data/lib/aspera/data_repository.rb +33 -2
  56. data/lib/aspera/environment.rb +5 -4
  57. data/lib/aspera/faspex_gw.rb +13 -11
  58. data/lib/aspera/faspex_postproc.rb +6 -5
  59. data/lib/aspera/id_generator.rb +4 -2
  60. data/lib/aspera/json_rpc.rb +10 -8
  61. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  62. data/lib/aspera/keychain/macos_security.rb +29 -22
  63. data/lib/aspera/log.rb +5 -4
  64. data/lib/aspera/nagios.rb +7 -2
  65. data/lib/aspera/node_simulator.rb +213 -0
  66. data/lib/aspera/oauth/base.rb +143 -0
  67. data/lib/aspera/oauth/factory.rb +124 -0
  68. data/lib/aspera/oauth/generic.rb +34 -0
  69. data/lib/aspera/oauth/jwt.rb +51 -0
  70. data/lib/aspera/oauth/url_json.rb +31 -0
  71. data/lib/aspera/oauth/web.rb +50 -0
  72. data/lib/aspera/oauth.rb +5 -328
  73. data/lib/aspera/open_application.rb +7 -7
  74. data/lib/aspera/persistency_action_once.rb +13 -14
  75. data/lib/aspera/persistency_folder.rb +3 -2
  76. data/lib/aspera/preview/file_types.rb +53 -267
  77. data/lib/aspera/preview/generator.rb +7 -5
  78. data/lib/aspera/preview/terminal.rb +17 -7
  79. data/lib/aspera/preview/utils.rb +8 -7
  80. data/lib/aspera/proxy_auto_config.rb +6 -3
  81. data/lib/aspera/rest.rb +187 -140
  82. data/lib/aspera/rest_error_analyzer.rb +1 -0
  83. data/lib/aspera/rest_errors_aspera.rb +5 -3
  84. data/lib/aspera/resumer.rb +77 -0
  85. data/lib/aspera/secret_hider.rb +5 -2
  86. data/lib/aspera/ssh.rb +15 -8
  87. data/lib/aspera/temp_file_manager.rb +1 -1
  88. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  89. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  90. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  91. data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
  92. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
  93. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  94. data/lib/aspera/transfer/sync.rb +273 -0
  95. data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
  96. data/lib/aspera/web_server_simple.rb +12 -3
  97. data.tar.gz.sig +0 -0
  98. metadata +92 -68
  99. metadata.gz.sig +0 -0
  100. data/lib/aspera/aoc.rb +0 -606
  101. data/lib/aspera/ats_api.rb +0 -47
  102. data/lib/aspera/cos_node.rb +0 -93
  103. data/lib/aspera/fasp/agent_aspera.rb +0 -126
  104. data/lib/aspera/fasp/agent_base.rb +0 -48
  105. data/lib/aspera/fasp/agent_trsdk.rb +0 -146
  106. data/lib/aspera/fasp/resume_policy.rb +0 -77
  107. data/lib/aspera/node.rb +0 -338
  108. data/lib/aspera/sync.rb +0 -219
@@ -1,17 +1,17 @@
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'
7
7
  require 'aspera/cli/formatter'
8
8
  require 'aspera/cli/info'
9
9
  require 'aspera/cli/transfer_progress'
10
- require 'aspera/fasp/installation'
11
- require 'aspera/fasp/products'
12
- require 'aspera/fasp/parameters'
13
- require 'aspera/fasp/transfer_spec'
14
- require 'aspera/fasp/error_info'
10
+ require 'aspera/ascp/installation'
11
+ require 'aspera/ascp/products'
12
+ require 'aspera/transfer/error_info'
13
+ require 'aspera/transfer/parameters'
14
+ require 'aspera/transfer/spec'
15
15
  require 'aspera/keychain/encrypted_hash'
16
16
  require 'aspera/keychain/macos_security'
17
17
  require 'aspera/proxy_auto_config'
@@ -19,9 +19,12 @@ 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'
27
+ require 'aspera/oauth'
25
28
  require 'open3'
26
29
  require 'date'
27
30
  require 'erb'
@@ -30,31 +33,31 @@ module Aspera
30
33
  module Cli
31
34
  module Plugins
32
35
  # manage the CLI config file
33
- class Config < Aspera::Cli::Plugin
36
+ class Config < Cli::Plugin
34
37
  # folder in $HOME for application files (config, cache)
35
38
  ASPERA_HOME_FOLDER_NAME = '.aspera'
36
39
  # default config file
37
40
  DEFAULT_CONFIG_FILENAME = 'config.yaml'
41
+ DEFAULT_VAULT_FILENAME = 'vault.bin'
38
42
  # reserved preset names
39
43
  CONF_PRESET_CONFIG = 'config'
40
44
  CONF_PRESET_VERSION = 'version'
41
- CONF_PRESET_DEFAULT = 'default'
45
+ CONF_PRESET_DEFAULTS = 'default'
42
46
  CONF_PRESET_GLOBAL = 'global_common_defaults'
47
+ # special name to identify value of default
43
48
  GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
44
- CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
45
49
  CONF_GLOBAL_SYM = :config
46
50
  # folder containing custom plugins in user's config folder
47
51
  ASPERA_PLUGINS_FOLDERNAME = 'plugins'
48
52
  PERSISTENCY_FOLDER = 'persist_store'
49
- RUBY_FILE_EXT = '.rb'
50
53
  ASPERA = 'aspera'
51
54
  SERVER_COMMAND = 'server'
52
55
  APP_NAME_SDK = 'sdk'
53
56
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
54
57
  CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
55
58
  TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
56
- DEMO = 'demo'
57
- DEMO_SERVER_PRESET = 'demoserver' # cspell: disable-line
59
+ DEMO_SERVER = 'demo'
60
+ DEMO_PRESET = 'demoserver' # cspell: disable-line
58
61
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
59
62
  From: <%=from_name%> <<%=from_email%>>
60
63
  To: <<%=to%>>
@@ -77,14 +80,13 @@ module Aspera
77
80
  private_constant :DEFAULT_CONFIG_FILENAME,
78
81
  :CONF_PRESET_CONFIG,
79
82
  :CONF_PRESET_VERSION,
80
- :CONF_PRESET_DEFAULT,
83
+ :CONF_PRESET_DEFAULTS,
81
84
  :CONF_PRESET_GLOBAL,
82
85
  :ASPERA_PLUGINS_FOLDERNAME,
83
- :RUBY_FILE_EXT,
84
86
  :ASPERA,
85
- :DEMO,
86
87
  :TRANSFER_SDK_ARCHIVE_URL,
87
- :DEMO_SERVER_PRESET,
88
+ :DEMO_SERVER,
89
+ :DEMO_PRESET,
88
90
  :EMAIL_TEST_TEMPLATE,
89
91
  :EXTEND_PRESET,
90
92
  :EXTEND_VAULT,
@@ -114,23 +116,11 @@ module Aspera
114
116
  File.dirname(File.expand_path(__FILE__))
115
117
  end
116
118
 
117
- # name of englobing module
118
- # @return "Aspera::Cli::Plugins"
119
- def module_full_name
120
- return Module.nesting[2].to_s
121
- end
122
-
123
119
  # @return main folder where code is, i.e. .../lib
124
120
  # go up as many times as englobing modules (not counting class, as it is a file)
125
121
  def gem_src_root
126
- File.expand_path(module_full_name.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
127
- end
128
-
129
- # instantiate a plugin
130
- # plugins must be Capitalized
131
- def plugin_class(plugin_name_sym)
132
- # Module.nesting[2] is Aspera::Cli::Plugins
133
- return Object.const_get("#{module_full_name}::#{plugin_name_sym.to_s.capitalize}")
122
+ # Module.nesting[2] is Cli::Plugins
123
+ File.expand_path(Module.nesting[2].to_s.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
134
124
  end
135
125
 
136
126
  # deep clone hash so that it does not get modified in case of display and secret hide
@@ -141,25 +131,25 @@ module Aspera
141
131
  # return product family folder (~/.aspera)
142
132
  def module_family_folder
143
133
  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)
134
+ Aspera.assert(Dir.exist?(user_home_folder), exception_class: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
145
135
  return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
146
136
  end
147
137
 
148
138
  # return product config folder (~/.aspera/<name>)
149
139
  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?
140
+ Aspera.assert_type(app_name, String)
141
+ Aspera.assert(!app_name.empty?)
151
142
  return File.join(module_family_folder, app_name)
152
143
  end
153
144
  end # self
154
145
 
155
- 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)
146
+ def initialize(gem:, name:, help:, version:, **env)
158
147
  # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
159
- super(env)
160
- @info = params
161
- @plugins = {}
162
- @plugin_lookup_folders = []
148
+ super(**env)
149
+ @gem = gem
150
+ @name = name
151
+ @help = help
152
+ @version = version
163
153
  @use_plugin_defaults = true
164
154
  @config_presets = nil
165
155
  @config_checksum_on_disk = nil
@@ -168,6 +158,7 @@ module Aspera
168
158
  @pac_exec = nil
169
159
  @sdk_default_location = false
170
160
  @option_insecure = false
161
+ @option_warn_insecure_cert = true
171
162
  @option_ignore_cert_host_port = []
172
163
  @option_http_options = {}
173
164
  @ssl_warned_urls = []
@@ -175,7 +166,9 @@ module Aspera
175
166
  @proxy_credentials = nil
176
167
  @main_folder = nil
177
168
  @option_config_file = nil
169
+ # store is used for ruby https
178
170
  @certificate_store = nil
171
+ # paths are used for ascp
179
172
  @certificate_paths = nil
180
173
  @progress_bar = nil
181
174
  # option to set main folder
@@ -183,14 +176,14 @@ module Aspera
183
176
  :home, 'Home folder for tool',
184
177
  handler: {o: self, m: :main_folder},
185
178
  types: String,
186
- default: self.class.default_app_main_folder(app_name: @info[:name]))
179
+ default: self.class.default_app_main_folder(app_name: @name))
187
180
  options.parse_options!
188
- Log.log.debug{"#{@info[:name]} folder: #{@main_folder}"}
189
- # data persistency manager
190
- env[:persistency] = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
181
+ Log.log.debug{"#{@name} folder: #{@main_folder}"}
182
+ # data persistency manager, created by config plugin
183
+ @persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
191
184
  # set folders for plugin lookup
192
- add_plugin_lookup_folder(self.class.gem_plugins_folder)
193
- add_plugin_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
185
+ PluginFactory.instance.add_lookup_folder(self.class.gem_plugins_folder)
186
+ PluginFactory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
194
187
  # option to set config file
195
188
  options.declare(
196
189
  :config_file, 'Path to YAML file with preset configuration',
@@ -222,10 +215,10 @@ module Aspera
222
215
  options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
223
216
  options.declare(:key_path, 'Wizard: path to private key for JWT')
224
217
  # Transfer SDK options
225
- options.declare(:ascp_path, 'Path to ascp', handler: {o: Fasp::Installation.instance, m: :ascp_path})
218
+ options.declare(:ascp_path, 'Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
226
219
  options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
227
220
  options.declare(:sdk_url, 'URL to get SDK', default: TRANSFER_SDK_ARCHIVE_URL)
228
- options.declare(:sdk_folder, 'SDK folder path', handler: {o: Fasp::Installation.instance, m: :sdk_folder})
221
+ options.declare(:sdk_folder, 'SDK folder path', handler: {o: Ascp::Installation.instance, m: :sdk_folder})
229
222
  options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
230
223
  # email options
231
224
  options.declare(:smtp, 'SMTP configuration', types: Hash)
@@ -233,16 +226,17 @@ module Aspera
233
226
  options.declare(:notify_template, 'Email ERB template for notification of transfers')
234
227
  # HTTP options
235
228
  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})
229
+ options.declare(:ignore_certificate, 'Do not validate HTTPS certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
230
+ options.declare(:silent_insecure, 'Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
237
231
  options.declare(:cert_stores, 'List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
238
232
  options.declare(:http_options, 'Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
239
- options.declare(:cache_tokens, 'Save and reuse Oauth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
233
+ options.declare(:cache_tokens, 'Save and reuse OAuth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
240
234
  options.declare(:fpac, 'Proxy auto configuration script')
241
235
  options.declare(:proxy_credentials, 'HTTP proxy credentials (user and password)', types: Array)
242
236
  options.parse_options!
243
237
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
244
238
  # Check SDK folder is set or not, for compatibility, we check in two places
245
- sdk_folder = Fasp::Installation.instance.sdk_folder rescue nil
239
+ sdk_folder = Ascp::Installation.instance.sdk_folder rescue nil
246
240
  if sdk_folder.nil?
247
241
  @sdk_default_location = true
248
242
  Log.log.debug('SDK folder is not set, checking default')
@@ -252,19 +246,19 @@ module Aspera
252
246
  if !Dir.exist?(sdk_folder)
253
247
  Log.log.debug{"not exists: #{sdk_folder}"}
254
248
  # former location
255
- former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: @info[:name]), APP_NAME_SDK)
249
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: @name), APP_NAME_SDK)
256
250
  Log.log.debug{"checking: #{former_sdk_folder}"}
257
251
  sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
258
252
  end
259
253
  Log.log.debug{"using: #{sdk_folder}"}
260
- Fasp::Installation.instance.sdk_folder = sdk_folder
254
+ Ascp::Installation.instance.sdk_folder = sdk_folder
261
255
  end
262
256
  pac_script = options.get_option(:fpac)
263
257
  # create PAC executor
264
- @pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
258
+ @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
265
259
  proxy_user_pass = options.get_option(:proxy_credentials)
266
260
  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)
261
+ Aspera.assert(proxy_user_pass.length.eql?(2), exception_class: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
268
262
  @proxy_credentials = {user: proxy_user_pass[0], pass: proxy_user_pass[1]}
269
263
  @pac_exec.proxy_user = @proxy_credentials[:user]
270
264
  @pac_exec.proxy_pass = @proxy_credentials[:pass]
@@ -273,34 +267,36 @@ module Aspera
273
267
  user_agent: PROGRAM_NAME,
274
268
  session_cb: lambda{|http_session|update_http_session(http_session)},
275
269
  progress_bar: @progress_bar)
276
- Oauth.persist_mgr = persistency if @option_cache_tokens
277
- Fasp::Parameters.file_list_folder = File.join(@main_folder, 'filelists') # cspell: disable-line
278
- Aspera::RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
270
+ OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
271
+ Transfer::Parameters.file_list_folder = File.join(@main_folder, 'filelists') # cspell: disable-line
272
+ RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
279
273
  # register aspera REST call error handlers
280
- Aspera::RestErrorsAspera.register_handlers
274
+ RestErrorsAspera.register_handlers
281
275
  end
282
276
 
283
- attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_http_options
277
+ attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
284
278
  attr_reader :option_ignore_cert_host_port, :progress_bar
285
279
 
280
+ # add files, folders or default locations to the certificate store
286
281
  def trusted_cert_locations=(path_list)
287
282
  path_list = [path_list] unless path_list.is_a?(Array)
283
+ Aspera.assert_type(path_list, Array){'cert locations'}
288
284
  if @certificate_store.nil?
289
285
  Log.log.debug('Creating SSL Cert store')
290
286
  @certificate_store = OpenSSL::X509::Store.new
291
- @certificate_store.set_default_paths
292
287
  @certificate_paths = []
293
288
  end
294
289
 
295
290
  path_list.each do |path|
296
- raise 'Expecting a String for cert location' unless path.is_a?(String)
297
- Log.log.debug("Adding cert location: #{path}")
291
+ Aspera.assert_type(path, String){'Expecting a String for certificate location'}
292
+ paths_to_add = [path]
293
+ Log.log.debug{"Adding cert location: #{path}"}
298
294
  if path.eql?(ExtendedValue::DEF)
299
- path = OpenSSL::X509::DEFAULT_CERT_DIR
300
- @certificate_store.add_path(path)
301
- @certificate_paths.push(path)
302
- path = OpenSSL::X509::DEFAULT_CERT_FILE
303
- @certificate_store.add_file(path)
295
+ @certificate_store.set_default_paths
296
+ paths_to_add = [
297
+ OpenSSL::X509::DEFAULT_CERT_DIR,
298
+ OpenSSL::X509::DEFAULT_CERT_FILE
299
+ ].select{|f|File.exist?(f)}
304
300
  elsif File.file?(path)
305
301
  @certificate_store.add_file(path)
306
302
  elsif File.directory?(path)
@@ -308,39 +304,57 @@ module Aspera
308
304
  else
309
305
  raise "No such file or folder: #{path}"
310
306
  end
311
- @certificate_paths.push(path)
307
+ paths_to_add.each do |p|
308
+ pp = [File.realpath(p)]
309
+ if File.directory?(p)
310
+ pp = Dir.entries(p)
311
+ .map{|e|File.realpath(File.join(p, e))}
312
+ .select{|entry|File.file?(entry)}
313
+ end
314
+ @certificate_paths.concat(pp)
315
+ end
312
316
  end
317
+ @certificate_paths.uniq!
313
318
  end
314
319
 
315
- def trusted_cert_locations(files_only: false)
316
- locations = if @certificate_paths.nil?
317
- [OpenSSL::X509::DEFAULT_CERT_DIR, OpenSSL::X509::DEFAULT_CERT_FILE]
318
- else
319
- @certificate_paths
320
+ # returns only files
321
+ def trusted_cert_locations
322
+ locations = @certificate_paths
323
+ if locations.nil?
324
+ # compute default locations
325
+ self.trusted_cert_locations = ExtendedValue::DEF
326
+ locations = @certificate_paths
327
+ # restore defaults
328
+ @certificate_paths = @certificate_store = nil
320
329
  end
321
- locations = locations.select{|f|File.file?(f)} if files_only
322
330
  return locations
323
331
  end
324
332
 
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
@@ -370,7 +384,7 @@ module Aspera
370
384
  def check_gem_version
371
385
  latest_version =
372
386
  begin
373
- Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{@info[:gem]}/latest.json")[:data]['version']
387
+ Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{@gem}/latest.json")[:data]['version']
374
388
  rescue StandardError
375
389
  Log.log.warn('Could not retrieve latest gem version on rubygems.')
376
390
  '0'
@@ -382,10 +396,10 @@ module Aspera
382
396
  end
383
397
  end
384
398
  return {
385
- name: @info[:gem],
386
- current: Aspera::Cli::VERSION,
399
+ name: @gem,
400
+ current: Cli::VERSION,
387
401
  latest: latest_version,
388
- need_update: Gem::Version.new(Aspera::Cli::VERSION) < Gem::Version.new(latest_version)
402
+ need_update: Gem::Version.new(Cli::VERSION) < Gem::Version.new(latest_version)
389
403
  }
390
404
  end
391
405
 
@@ -418,8 +432,8 @@ module Aspera
418
432
  # retrieve structure from cloud (CDN) with all versions available
419
433
  def connect_versions
420
434
  if @connect_versions.nil?
421
- api_connect_cdn = Rest.new({base_url: CONNECT_WEB_URL})
422
- javascript = api_connect_cdn.call({operation: 'GET', subpath: CONNECT_VERSIONS})
435
+ api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
436
+ javascript = api_connect_cdn.call(operation: 'GET', subpath: CONNECT_VERSIONS)
423
437
  # get result on one line
424
438
  connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/, '')
425
439
  Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
@@ -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
+ Aspera.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
+ Aspera.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}"}
@@ -482,7 +496,7 @@ module Aspera
482
496
  end
483
497
 
484
498
  # $HOME/.aspera/`program_name`
485
- attr_reader :gem_url, :plugins
499
+ attr_reader :gem_url
486
500
  attr_accessor :option_config_file
487
501
 
488
502
  # @return the hash from name (also expands possible includes)
@@ -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
+ Aspera.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?
@@ -503,7 +517,7 @@ module Aspera
503
517
  end
504
518
 
505
519
  def option_use_product=(value)
506
- Fasp::Installation.instance.use_ascp_from_product(value)
520
+ Ascp::Installation.instance.use_ascp_from_product(value)
507
521
  end
508
522
 
509
523
  def option_use_product
@@ -511,15 +525,14 @@ 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
+ Aspera.assert_values(value.class, [String, Array]){'plugin folder'}
529
+ value = [value] if value.is_a?(String)
530
+ Aspera.assert(value.all?(String)){'plugin folder'}
531
+ value.each{|f|PluginFactory.instance.add_lookup_folder(f)}
519
532
  end
520
533
 
521
534
  def option_plugin_folder
522
- return @plugin_lookup_folders
535
+ return PluginFactory.instance.lookup_folders
523
536
  end
524
537
 
525
538
  def option_preset; 'write-only option'; end
@@ -558,18 +571,18 @@ 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
+ Aspera.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
+ Aspera.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
- @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
585
+ @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @version
573
586
  unless files_to_copy.empty?
574
587
  Log.log.warn('Copying referenced files')
575
588
  files_to_copy.each do |file|
@@ -584,7 +597,7 @@ module Aspera
584
597
  Log.log.debug{"-> #{e.class.name} : #{e}"}
585
598
  if File.exist?(@option_config_file)
586
599
  # then there is a problem with that file.
587
- new_name = "#{@option_config_file}.pre#{@info[:version]}.manual_conversion_needed"
600
+ new_name = "#{@option_config_file}.pre#{@version}.manual_conversion_needed"
588
601
  File.rename(@option_config_file, new_name)
589
602
  Log.log.warn{"Renamed config file to #{new_name}."}
590
603
  Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
@@ -592,33 +605,6 @@ module Aspera
592
605
  raise Cli::Error, e.to_s
593
606
  end
594
607
 
595
- # find plugins in defined paths
596
- def add_plugins_from_lookup_folders
597
- @plugin_lookup_folders.each do |folder|
598
- next unless File.directory?(folder)
599
- # TODO: add gem root to load path ? and require short folder ?
600
- # $LOAD_PATH.push(folder) if i[:add_path]
601
- Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
602
- add_plugin_info(File.join(folder, source))
603
- end
604
- end
605
- end
606
-
607
- def add_plugin_lookup_folder(folder)
608
- @plugin_lookup_folders.unshift(folder)
609
- end
610
-
611
- def add_plugin_info(path)
612
- raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
613
- plugin_symbol = File.basename(path, RUBY_FILE_EXT).to_sym
614
- req = path.gsub(/#{RUBY_FILE_EXT}$/o, '')
615
- if @plugins.key?(plugin_symbol)
616
- Log.log.warn{"skipping plugin already registered: #{plugin_symbol}"}
617
- return
618
- end
619
- @plugins[plugin_symbol] = {source: path, require_stanza: req}
620
- end
621
-
622
608
  # Find a plugin, and issue the "require"
623
609
  # @return [Hash] plugin info: { product: , url:, version: }
624
610
  def identify_plugins_for_url
@@ -626,33 +612,36 @@ module Aspera
626
612
  check_only = options.get_next_argument('plugin name', mandatory: false)
627
613
  check_only = check_only.to_sym unless check_only.nil?
628
614
  found_apps = []
629
- plugins.each do |plugin_name_sym, plugin_info|
615
+ my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
616
+ PluginFactory.instance.plugins.each do |plugin_name_sym, plugin_info|
630
617
  # no detection for internal plugin
631
- next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
618
+ next if plugin_name_sym.eql?(my_self_plugin_sym)
632
619
  next if check_only && !check_only.eql?(plugin_name_sym)
633
620
  # load plugin class
634
621
  require plugin_info[:require_stanza]
635
- detect_plugin_class = self.class.plugin_class(plugin_name_sym)
622
+ detect_plugin_class = PluginFactory.plugin_class(plugin_name_sym)
636
623
  # requires detection method
637
624
  next unless detect_plugin_class.respond_to?(:detect)
638
625
  detection_info = nil
639
626
  begin
627
+ Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
640
628
  detection_info = detect_plugin_class.detect(app_url)
641
629
  rescue OpenSSL::SSL::SSLError => e
642
630
  Log.log.warn(e.message)
643
631
  Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
644
632
  rescue StandardError => e
645
- Log.log.debug{"detect error: #{e}"}
633
+ Log.log.debug{"detect error: [#{e.class}] #{e}"}
646
634
  next
647
635
  end
648
636
  next if detection_info.nil?
649
- raise 'internal error' if detection_info.key?(:url) && !detection_info[:url].is_a?(String)
637
+ Aspera.assert_type(detection_info, Hash)
638
+ Aspera.assert_type(detection_info[:url], String) if detection_info.key?(:url)
650
639
  app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
651
640
  # if there is a redirect, then the detector can override the url.
652
641
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
653
642
  end # loop
654
643
  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)}
644
+ Aspera.assert(found_apps.all?{|a|a.keys.all?(Symbol)})
656
645
  return found_apps
657
646
  end
658
647
 
@@ -666,10 +655,10 @@ module Aspera
666
655
  case command
667
656
  when :list
668
657
  return {type: :object_list, data: connect_versions, fields: %w[id title version]}
669
- when :info # shows files used
658
+ when :info
670
659
  one_res.delete('links')
671
660
  return {type: :single_object, data: one_res}
672
- when :version # shows files used
661
+ when :version
673
662
  all_links = one_res['links']
674
663
  command = options.get_next_command(%i[list download open])
675
664
  if %i[download open].include?(command)
@@ -678,15 +667,15 @@ module Aspera
678
667
  raise 'no such value' if one_link.nil?
679
668
  end
680
669
  case command
681
- when :list # shows files used
670
+ when :list
682
671
  return {type: :object_list, data: all_links}
683
672
  when :download
684
- folder_dest = transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE)
673
+ folder_dest = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
685
674
  # folder_dest=self.options.get_next_argument('destination folder')
686
- api_connect_cdn = Rest.new({base_url: CONNECT_WEB_URL})
675
+ api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
687
676
  file_url = one_link['href']
688
677
  filename = file_url.gsub(%r{.*/}, '')
689
- api_connect_cdn.call({operation: 'GET', subpath: file_url, save_to_file: File.join(folder_dest, filename)})
678
+ api_connect_cdn.call(operation: 'GET', subpath: file_url, save_to_file: File.join(folder_dest, filename))
690
679
  return Main.result_status("Downloaded: #{filename}")
691
680
  when :open
692
681
  OpenApplication.instance.uri(one_link['href'])
@@ -702,79 +691,47 @@ module Aspera
702
691
  return execute_connect_action
703
692
  when :use
704
693
  ascp_path = options.get_next_argument('path to ascp')
705
- ascp_version = Fasp::Installation.instance.get_ascp_version(ascp_path)
694
+ ascp_version = Ascp::Installation.instance.get_ascp_version(ascp_path)
706
695
  formatter.display_status("ascp version: #{ascp_version}")
707
696
  set_global_default(:ascp_path, ascp_path)
708
697
  return Main.result_nothing
709
- when :show # shows files used
710
- 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
698
+ when :show
699
+ return {type: :status, data: Ascp::Installation.instance.path(:ascp)}
700
+ when :info
701
+ # collect info from ascp executable
702
+ data = Ascp::Installation.instance.ascp_info
703
+ # add command line transfer spec
751
704
  data['ts'] = transfer.updated_ts
705
+ # add keys
706
+ DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
707
+ # declare those as secrets
708
+ SecretHider::ADDITIONAL_KEYS_TO_HIDE.push(*DataRepository::ELEMENTS.map(&:to_s))
752
709
  return {type: :single_object, data: data}
753
710
  when :products
754
711
  command = options.get_next_command(%i[list use])
755
712
  case command
756
713
  when :list
757
- return {type: :object_list, data: Fasp::Products.installed_products, fields: %w[name app_root]}
714
+ return {type: :object_list, data: Ascp::Products.installed_products, fields: %w[name app_root]}
758
715
  when :use
759
716
  default_product = options.get_next_argument('product name')
760
- Fasp::Installation.instance.use_ascp_from_product(default_product)
761
- set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
717
+ Ascp::Installation.instance.use_ascp_from_product(default_product)
718
+ set_global_default(:ascp_path, Ascp::Installation.instance.path(:ascp))
762
719
  return Main.result_nothing
763
720
  end
764
721
  when :install
765
722
  # reset to default location, if older default was used
766
- Fasp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
767
- v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url, mandatory: true))
723
+ Ascp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
724
+ v = Ascp::Installation.instance.install_sdk(options.get_option(:sdk_url, mandatory: true))
768
725
  return Main.result_status("Installed version #{v}")
769
726
  when :spec
770
727
  return {
771
728
  type: :object_list,
772
- data: Fasp::Parameters.man_table,
773
- fields: [%w[name type], Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description]].flatten.freeze
729
+ data: Transfer::Parameters.man_table,
730
+ fields: [%w[name type], Transfer::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description]].flatten.freeze
774
731
  }
775
732
  when :errors
776
733
  error_data = []
777
- Fasp::ERROR_INFO.each_pair do |code, prop|
734
+ Transfer::ERROR_INFO.each_pair do |code, prop|
778
735
  error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
779
736
  end
780
737
  return {type: :object_list, data: error_data}
@@ -887,6 +844,7 @@ module Aspera
887
844
  open
888
845
  documentation
889
846
  genkey
847
+ pubkey
890
848
  remote_certificate
891
849
  gem
892
850
  plugins
@@ -918,36 +876,45 @@ module Aspera
918
876
  when :documentation
919
877
  section = options.get_next_argument('private key file path', mandatory: false)
920
878
  section = "##{section}" unless section.nil?
921
- OpenApplication.instance.uri("#{@info[:help]}#{section}")
879
+ OpenApplication.instance.uri("#{@help}#{section}")
922
880
  return Main.result_nothing
923
881
  when :genkey # generate new rsa key
924
882
  private_key_path = options.get_next_argument('private key file path')
925
883
  private_key_length = options.get_next_argument('size in bits', mandatory: false, type: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
926
884
  self.class.generate_rsa_private_key(path: private_key_path, length: private_key_length)
927
885
  return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
886
+ when :pubkey # get pub key
887
+ private_key_pem = options.get_next_argument('private key PEM value')
888
+ return Main.result_status(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
928
889
  when :remote_certificate
890
+ cert_action = options.get_next_command(%i[chain only name])
929
891
  remote_url = options.get_next_argument('remote URL')
930
- @option_insecure = true
931
- remote_certificate = Rest.start_http_session(remote_url).peer_cert
932
- remote_certificate.subject.to_a.find { |name, _, _| name == 'CN' }[1]
933
- formatter.display_status("CN=#{remote_certificate.subject.to_a.find { |name, _, _| name == 'CN' }[1] rescue ''}")
934
- return Main.result_status(remote_certificate.to_pem)
892
+ remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
893
+ raise "No certificate found for #{remote_url}" unless remote_chain&.first
894
+ case cert_action
895
+ when :chain
896
+ return Main.result_status(remote_chain.map(&:to_pem).join("\n"))
897
+ when :only
898
+ return Main.result_status(remote_chain.first.to_pem)
899
+ when :name
900
+ return Main.result_status(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
901
+ end
935
902
  when :echo # display the content of a value given on command line
936
903
  return Formatter.auto_type(options.get_next_argument('value'))
937
904
  when :flush_tokens
938
- deleted_files = Oauth.flush_tokens
905
+ deleted_files = OAuth::Factory.instance.flush_tokens
939
906
  return {type: :value_list, data: deleted_files, name: 'file'}
940
907
  when :plugins
941
908
  case options.get_next_command(%i[list create])
942
909
  when :list
943
910
  result = []
944
- @plugins.each do |name, info|
911
+ PluginFactory.instance.plugins.each do |name, info|
945
912
  require info[:require_stanza]
946
- plugin_class = self.class.plugin_class(name)
913
+ plugin_klass = PluginFactory.plugin_class(name)
947
914
  result.push({
948
915
  plugin: name,
949
- detect: Formatter.tick(plugin_class.respond_to?(:detect)),
950
- wizard: Formatter.tick(plugin_class.respond_to?(:wizard)),
916
+ detect: Formatter.tick(plugin_klass.respond_to?(:detect)),
917
+ wizard: Formatter.tick(plugin_klass.respond_to?(:wizard)),
951
918
  path: info[:source]
952
919
  })
953
920
  end
@@ -984,8 +951,7 @@ module Aspera
984
951
  return wizard_find(apps)
985
952
  when :coffee
986
953
  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))
954
+ return Main.result_picture_in_terminal(options, Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body)
989
955
  end
990
956
  OpenApplication.instance.uri(COFFEE_IMAGE)
991
957
  return Main.result_nothing
@@ -994,8 +960,8 @@ module Aspera
994
960
  when :gem
995
961
  case options.get_next_command(%i[path version name])
996
962
  when :path then return Main.result_status(self.class.gem_src_root)
997
- when :version then return Main.result_status(Aspera::Cli::VERSION)
998
- when :name then return Main.result_status(@info[:gem])
963
+ when :version then return Main.result_status(Cli::VERSION)
964
+ when :name then return Main.result_status(@gem)
999
965
  end
1000
966
  when :folder
1001
967
  return Main.result_status(@main_folder)
@@ -1014,24 +980,24 @@ module Aspera
1014
980
  when :check_update
1015
981
  return {type: :single_object, data: check_gem_version}
1016
982
  when :initdemo
1017
- if @config_presets.key?(DEMO_SERVER_PRESET)
1018
- Log.log.warn{"Demo server preset already present: #{DEMO_SERVER_PRESET}"}
983
+ if @config_presets.key?(DEMO_PRESET)
984
+ Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
1019
985
  else
1020
- Log.log.info{"Creating Demo server preset: #{DEMO_SERVER_PRESET}"}
1021
- @config_presets[DEMO_SERVER_PRESET] = {
1022
- 'url' => "ssh://#{DEMO}.asperasoft.com:33001",
986
+ Log.log.info{"Creating Demo server preset: #{DEMO_PRESET}"}
987
+ @config_presets[DEMO_PRESET] = {
988
+ 'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
1023
989
  'username' => ASPERA,
1024
- 'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + ASPERA # cspell:disable-line
990
+ 'ssAP'.downcase.reverse + 'drow'.reverse => DEMO_SERVER + ASPERA # cspell:disable-line
1025
991
  }
1026
992
  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]}"}
1030
- 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])
993
+ @config_presets[CONF_PRESET_DEFAULTS] ||= {}
994
+ if @config_presets[CONF_PRESET_DEFAULTS].key?(SERVER_COMMAND)
995
+ Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND]}"}
996
+ Log.log.warn{"Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}"} unless
997
+ DEMO_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND])
1032
998
  else
1033
- @config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND] = DEMO_SERVER_PRESET
1034
- Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
999
+ @config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
1000
+ Log.log.info{"Setting server default preset to : #{DEMO_PRESET}"}
1035
1001
  end
1036
1002
  return Main.result_status('Done')
1037
1003
  when :vault then execute_vault
@@ -1041,9 +1007,9 @@ module Aspera
1041
1007
  exception_class_name = options.get_next_argument('exception class name', mandatory: true)
1042
1008
  exception_text = options.get_next_argument('exception text', mandatory: true)
1043
1009
  exception_class = Object.const_get(exception_class_name)
1044
- raise "#{exception_class} is not an exception: #{exception_class.class}" unless exception_class <= Exception
1010
+ Aspera.assert(exception_class <= Exception){"#{exception_class} is not an exception: #{exception_class.class}"}
1045
1011
  raise exception_class, exception_text
1046
- else raise 'INTERNAL ERROR: wrong case'
1012
+ else Aspera.error_unreachable_line
1047
1013
  end
1048
1014
  end
1049
1015
 
@@ -1058,15 +1024,17 @@ module Aspera
1058
1024
  apps.find{|a|a[:product].eql?(answer)}
1059
1025
  end
1060
1026
  wiz_url = identification[:url]
1061
- Log.log.debug{Log.dump(:identification, identification, :ruby)}
1027
+ Log.log.debug{Log.dump(:identification, identification)}
1062
1028
  formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
1063
1029
  # set url for instantiation of plugin
1064
1030
  options.add_option_preset({url: wiz_url})
1065
1031
  # instantiate plugin: command line options will be known and wizard can be called
1066
- 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)
1032
+ wiz_plugin_class = PluginFactory.plugin_class(identification[:product])
1033
+ Aspera.assert(wiz_plugin_class.respond_to?(:wizard), exception_class: Cli::BadArgument) do
1034
+ "Detected: #{identification[:product]}, but this application has no wizard"
1035
+ end
1068
1036
  # instantiate plugin: command line options will be known, e.g. private_key
1069
- plugin_instance = wiz_plugin_class.new(@agents)
1037
+ plugin_instance = wiz_plugin_class.new(**init_params)
1070
1038
  wiz_params = {
1071
1039
  object: plugin_instance
1072
1040
  }
@@ -1089,7 +1057,7 @@ module Aspera
1089
1057
  formatter.display_status('Using existing key:')
1090
1058
  else
1091
1059
  formatter.display_status("Generating #{DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
1092
- Config.generate_rsa_private_key(path: private_key_path)
1060
+ self.class.generate_rsa_private_key(path: private_key_path)
1093
1061
  formatter.display_status('Created key:')
1094
1062
  end
1095
1063
  formatter.display_status(private_key_path)
@@ -1102,7 +1070,7 @@ module Aspera
1102
1070
  # finally, call the wizard
1103
1071
  wizard_result = wiz_plugin_class.wizard(**wiz_params)
1104
1072
  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)
1073
+ Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
1106
1074
  # get preset name from user or default
1107
1075
  wiz_preset_name = options.get_option(:id)
1108
1076
  if wiz_preset_name.nil?
@@ -1118,22 +1086,22 @@ module Aspera
1118
1086
  # Write configuration file
1119
1087
  formatter.display_status("Preparing preset: #{wiz_preset_name}")
1120
1088
  # init defaults if necessary
1121
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
1089
+ @config_presets[CONF_PRESET_DEFAULTS] ||= {}
1122
1090
  option_override = options.get_option(:override, mandatory: true)
1123
1091
  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])
1092
+ if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULTS].key?(identification[:product])
1125
1093
  raise Cli::Error, "Preset already exists: #{wiz_preset_name} (use --override=yes or --id=<name>)" \
1126
1094
  if !option_override && @config_presets.key?(wiz_preset_name)
1127
1095
  @config_presets[wiz_preset_name] = wizard_result[:preset_value].stringify_keys
1128
1096
  test_args = wizard_result[:test_args]
1129
1097
  if options.get_option(:default, mandatory: true)
1130
1098
  formatter.display_status("Setting config preset as default for #{identification[:product]}")
1131
- @config_presets[CONF_PRESET_DEFAULT][identification[:product].to_s] = wiz_preset_name
1099
+ @config_presets[CONF_PRESET_DEFAULTS][identification[:product].to_s] = wiz_preset_name
1132
1100
  else
1133
1101
  test_args = "-P#{wiz_preset_name} #{test_args}"
1134
1102
  end
1135
1103
  # TODO: actually test the command
1136
- return Main.result_status("You can test with:\n#{@info[:name]} #{identification[:product]} #{test_args}")
1104
+ return Main.result_status("You can test with:\n#{@name} #{identification[:product]} #{test_args}")
1137
1105
  end
1138
1106
 
1139
1107
  # @return [Hash] email server setting with defaults if not defined
@@ -1151,11 +1119,11 @@ module Aspera
1151
1119
  25
1152
1120
  end
1153
1121
  smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
1154
- smtp[:from_name] ||= smtp[:from_email].gsub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
1155
- smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/, '') if smtp.key?(:from_email)
1122
+ smtp[:from_name] ||= smtp[:from_email].sub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
1123
+ smtp[:domain] ||= smtp[:from_email].sub(/^.*@/, '') if smtp.key?(:from_email)
1156
1124
  # check minimum required
1157
1125
  %i[server port domain].each do |n|
1158
- raise "Missing mandatory smtp parameter: #{n}" unless smtp.key?(n)
1126
+ Aspera.assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
1159
1127
  end
1160
1128
  Log.log.debug{"smtp=#{smtp}"}
1161
1129
  return smtp
@@ -1169,7 +1137,7 @@ module Aspera
1169
1137
  values[:from_name] ||= mail_conf[:from_name]
1170
1138
  values[:from_email] ||= mail_conf[:from_email]
1171
1139
  %i[from_name from_email].each do |n|
1172
- raise "Missing email parameter: #{n}" unless values.key?(n)
1140
+ Aspera.assert(values.key?(n)){"Missing email parameter: #{n}"}
1173
1141
  end
1174
1142
  start_options = [mail_conf[:domain]]
1175
1143
  start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
@@ -1177,7 +1145,7 @@ module Aspera
1177
1145
  template_binding = Environment.empty_binding
1178
1146
  # add variables to binding
1179
1147
  values.each do |k, v|
1180
- raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
1148
+ Aspera.assert_type(k, Symbol)
1181
1149
  template_binding.local_variable_set(k, v)
1182
1150
  end
1183
1151
  # execute template
@@ -1211,19 +1179,19 @@ module Aspera
1211
1179
  # returns [String] name if config_presets has default
1212
1180
  # returns nil if there is no config or bypass default params
1213
1181
  def get_plugin_default_config_name(plugin_name_sym)
1214
- raise 'internal error: config_presets shall be defined' if @config_presets.nil?
1182
+ Aspera.assert(!@config_presets.nil?){'config_presets shall be defined'}
1215
1183
  if !@use_plugin_defaults
1216
1184
  Log.log.debug('skip default config')
1217
1185
  return nil
1218
1186
  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]
1187
+ if @config_presets.key?(CONF_PRESET_DEFAULTS) &&
1188
+ @config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
1189
+ default_config_name = @config_presets[CONF_PRESET_DEFAULTS][plugin_name_sym.to_s]
1222
1190
  if !@config_presets.key?(default_config_name)
1223
1191
  Log.log.error do
1224
1192
  "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
1225
1193
  'Please fix the issue: either create preset with one parameter: ' \
1226
- "(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_name_sym})."
1194
+ "(#{@name} config id #{default_config_name} init @json:'{}') or remove default (#{@name} config id default remove #{plugin_name_sym})."
1227
1195
  end
1228
1196
  end
1229
1197
  raise Cli::Error, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
@@ -1235,16 +1203,17 @@ module Aspera
1235
1203
  # TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
1236
1204
  # @return [Hash] result of execution of vault command
1237
1205
  def execute_vault
1238
- command = options.get_next_command(%i[list show create delete password])
1206
+ command = options.get_next_command(%i[info list show create delete password])
1239
1207
  case command
1208
+ when :info
1209
+ return {type: :single_object, data: vault_info}
1240
1210
  when :list
1241
1211
  return {type: :object_list, data: vault.list}
1242
1212
  when :show
1243
1213
  return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
1244
1214
  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)
1215
+ label = options.get_next_argument('label', type: String)
1216
+ info = options.get_next_argument('info', type: Hash)
1248
1217
  info = info.symbolize_keys
1249
1218
  info[:label] = label
1250
1219
  vault.set(info)
@@ -1253,7 +1222,7 @@ module Aspera
1253
1222
  vault.delete(label: options.get_next_argument('label'))
1254
1223
  return Main.result_status('Password deleted')
1255
1224
  when :password
1256
- raise 'Vault does not support password change' unless vault.respond_to?(:password=)
1225
+ Aspera.assert(vault.respond_to?(:password=)){'Vault does not support password change'}
1257
1226
  new_password = options.get_next_argument('new_password')
1258
1227
  vault.password = new_password
1259
1228
  vault.save
@@ -1263,36 +1232,45 @@ module Aspera
1263
1232
 
1264
1233
  # @return [String] value from vault matching <name>.<param>
1265
1234
  def vault_value(name)
1266
- m = name.match(/^(.+)\.(.+)$/)
1267
- raise 'vault name shall match <name>.<param>' if m.nil?
1235
+ m = name.split('.')
1236
+ raise 'vault name shall match <name>.<param>' unless m.length.eql?(2)
1268
1237
  # this raise exception if label not found:
1269
- info = vault.get(label: m[1])
1270
- value = info[m[2].to_sym]
1271
- raise "no such entry value: #{m[2]}" if value.nil?
1238
+ info = vault.get(label: m[0])
1239
+ value = info[m[1].to_sym]
1240
+ raise "no such entry value: #{m[1]}" if value.nil?
1272
1241
  return value
1273
1242
  end
1274
1243
 
1244
+ def vault_info
1245
+ info = options.get_option(:vault) || {}
1246
+ info = info.symbolize_keys
1247
+ info[:type] ||= 'file'
1248
+ info[:name] ||= (info[:type].eql?('file') ? DEFAULT_VAULT_FILENAME : PROGRAM_NAME)
1249
+ Aspera.assert(info.keys.sort == %i[name type]) {"vault info shall have exactly keys 'type' and 'name'"}
1250
+ Aspera.assert(info.values.all?(String)){'vault info shall have only string values'}
1251
+ info[:password] = options.get_option(:vault_password, mandatory: true)
1252
+ return info
1253
+ end
1254
+
1275
1255
  # @return [Object] vault, from options or cache
1276
1256
  def vault
1277
1257
  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
1258
+ info = vault_info
1259
+ case info[:type]
1283
1260
  when 'file'
1284
1261
  # 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)
1262
+ @vault = Keychain::EncryptedHash.new(
1263
+ info[:name].eql?(File.absolute_path(info[:name])) ? info[:name] : File.join(@main_folder, info[:name]),
1264
+ info[:password])
1287
1265
  when 'system'
1288
1266
  case Environment.os
1289
1267
  when Environment::OS_X
1290
- @vault = Keychain::MacosSystem.new(vault_name, vault_password)
1268
+ @vault = Keychain::MacosSystem.new(info[:name], info[:password])
1291
1269
  else
1292
1270
  raise 'not implemented for this OS'
1293
1271
  end
1294
1272
  else
1295
- raise Cli::BadArgument, "Unknown vault type: #{vault_type}"
1273
+ raise Cli::BadArgument, "Unknown vault type: #{info[:type]}"
1296
1274
  end
1297
1275
  end
1298
1276
  raise 'No vault defined' if @vault.nil?
@@ -1301,14 +1279,14 @@ module Aspera
1301
1279
 
1302
1280
  # version of URL without trailing "/" and removing default port
1303
1281
  def canonical_url(url)
1304
- url.gsub(%r{/+$}, '').gsub(%r{^(https://[^/]+):443$}, '\1')
1282
+ url.sub(%r{/+$}, '').sub(%r{^(https://[^/]+):443$}, '\1')
1305
1283
  end
1306
1284
 
1307
1285
  def lookup_preset(url:, username:)
1308
1286
  # remove extra info to maximize match
1309
1287
  url = canonical_url(url)
1310
1288
  Log.log.debug{"Lookup preset for #{username}@#{url}"}
1311
- @config_presets.each do |_k, v|
1289
+ @config_presets.each_value do |v|
1312
1290
  next unless v.is_a?(Hash)
1313
1291
  conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
1314
1292
  return self.class.protect_presets(v) if conf_url.eql?(url) && v['username'].eql?(username)