aspera-cli 4.15.0 → 4.17.0

Sign up to get free protection for your applications and to get access to all the features.
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)