aspera-cli 4.13.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +81 -7
  4. data/CONTRIBUTING.md +22 -6
  5. data/README.md +2038 -1080
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/dascli +1 -1
  9. data/examples/proxy.pac +1 -1
  10. data/examples/rubyc +24 -0
  11. data/lib/aspera/aoc.rb +219 -159
  12. data/lib/aspera/ascmd.rb +25 -14
  13. data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
  14. data/lib/aspera/cli/error.rb +17 -0
  15. data/lib/aspera/cli/extended_value.rb +47 -12
  16. data/lib/aspera/cli/formatter.rb +260 -179
  17. data/lib/aspera/cli/hints.rb +80 -0
  18. data/lib/aspera/cli/main.rb +104 -156
  19. data/lib/aspera/cli/manager.rb +259 -209
  20. data/lib/aspera/cli/plugin.rb +123 -63
  21. data/lib/aspera/cli/plugins/alee.rb +2 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +341 -261
  23. data/lib/aspera/cli/plugins/ats.rb +22 -21
  24. data/lib/aspera/cli/plugins/bss.rb +5 -5
  25. data/lib/aspera/cli/plugins/config.rb +578 -627
  26. data/lib/aspera/cli/plugins/console.rb +44 -6
  27. data/lib/aspera/cli/plugins/cos.rb +15 -17
  28. data/lib/aspera/cli/plugins/faspex.rb +114 -100
  29. data/lib/aspera/cli/plugins/faspex5.rb +411 -264
  30. data/lib/aspera/cli/plugins/node.rb +354 -259
  31. data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
  32. data/lib/aspera/cli/plugins/preview.rb +82 -90
  33. data/lib/aspera/cli/plugins/server.rb +79 -32
  34. data/lib/aspera/cli/plugins/shares.rb +55 -42
  35. data/lib/aspera/cli/sync_actions.rb +68 -0
  36. data/lib/aspera/cli/transfer_agent.rb +66 -73
  37. data/lib/aspera/cli/transfer_progress.rb +74 -0
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/colors.rb +12 -8
  40. data/lib/aspera/command_line_builder.rb +14 -11
  41. data/lib/aspera/cos_node.rb +3 -2
  42. data/lib/aspera/data/6 +0 -0
  43. data/lib/aspera/environment.rb +24 -9
  44. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  45. data/lib/aspera/fasp/agent_base.rb +31 -77
  46. data/lib/aspera/fasp/agent_connect.rb +25 -21
  47. data/lib/aspera/fasp/agent_direct.rb +89 -103
  48. data/lib/aspera/fasp/agent_httpgw.rb +231 -149
  49. data/lib/aspera/fasp/agent_node.rb +41 -34
  50. data/lib/aspera/fasp/agent_trsdk.rb +75 -32
  51. data/lib/aspera/fasp/error_info.rb +4 -2
  52. data/lib/aspera/fasp/faux_file.rb +52 -0
  53. data/lib/aspera/fasp/installation.rb +53 -195
  54. data/lib/aspera/fasp/management.rb +244 -0
  55. data/lib/aspera/fasp/parameters.rb +71 -37
  56. data/lib/aspera/fasp/parameters.yaml +76 -8
  57. data/lib/aspera/fasp/products.rb +162 -0
  58. data/lib/aspera/fasp/resume_policy.rb +3 -3
  59. data/lib/aspera/fasp/transfer_spec.rb +7 -6
  60. data/lib/aspera/fasp/uri.rb +26 -24
  61. data/lib/aspera/faspex_gw.rb +2 -2
  62. data/lib/aspera/faspex_postproc.rb +2 -2
  63. data/lib/aspera/hash_ext.rb +14 -4
  64. data/lib/aspera/json_rpc.rb +49 -0
  65. data/lib/aspera/keychain/macos_security.rb +13 -13
  66. data/lib/aspera/line_logger.rb +23 -0
  67. data/lib/aspera/log.rb +58 -16
  68. data/lib/aspera/node.rb +157 -92
  69. data/lib/aspera/oauth.rb +37 -19
  70. data/lib/aspera/open_application.rb +4 -4
  71. data/lib/aspera/persistency_action_once.rb +1 -1
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -2
  74. data/lib/aspera/preview/generator.rb +22 -35
  75. data/lib/aspera/preview/options.rb +2 -0
  76. data/lib/aspera/preview/terminal.rb +73 -16
  77. data/lib/aspera/preview/utils.rb +21 -28
  78. data/lib/aspera/proxy_auto_config.js +2 -2
  79. data/lib/aspera/rest.rb +136 -68
  80. data/lib/aspera/rest_call_error.rb +1 -1
  81. data/lib/aspera/rest_error_analyzer.rb +15 -14
  82. data/lib/aspera/rest_errors_aspera.rb +37 -34
  83. data/lib/aspera/secret_hider.rb +18 -15
  84. data/lib/aspera/ssh.rb +5 -2
  85. data/lib/aspera/sync.rb +127 -119
  86. data/lib/aspera/temp_file_manager.rb +10 -3
  87. data/lib/aspera/web_auth.rb +10 -7
  88. data/lib/aspera/web_server_simple.rb +9 -4
  89. data.tar.gz.sig +0 -0
  90. metadata +34 -17
  91. metadata.gz.sig +0 -0
  92. data/docs/test_env.conf +0 -186
  93. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  94. data/lib/aspera/cli/listener/logger.rb +0 -22
  95. data/lib/aspera/cli/listener/progress.rb +0 -50
  96. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  97. data/lib/aspera/cli/plugins/sync.rb +0 -44
  98. data/lib/aspera/data/7 +0 -0
  99. data/lib/aspera/fasp/listener.rb +0 -13
@@ -1,25 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore initdemo genkey asperasoft
3
4
  require 'aspera/cli/basic_auth_plugin'
4
5
  require 'aspera/cli/extended_value'
5
6
  require 'aspera/cli/version'
6
7
  require 'aspera/cli/formatter'
7
8
  require 'aspera/cli/info'
9
+ require 'aspera/cli/transfer_progress'
8
10
  require 'aspera/fasp/installation'
11
+ require 'aspera/fasp/products'
9
12
  require 'aspera/fasp/parameters'
10
13
  require 'aspera/fasp/transfer_spec'
11
14
  require 'aspera/fasp/error_info'
15
+ require 'aspera/keychain/encrypted_hash'
16
+ require 'aspera/keychain/macos_security'
12
17
  require 'aspera/proxy_auto_config'
13
18
  require 'aspera/open_application'
14
19
  require 'aspera/persistency_action_once'
15
20
  require 'aspera/id_generator'
16
- require 'aspera/keychain/encrypted_hash'
17
- require 'aspera/keychain/macos_security'
18
- require 'aspera/aoc'
21
+ require 'aspera/persistency_folder'
22
+ require 'aspera/line_logger'
19
23
  require 'aspera/rest'
20
- require 'xmlsimple'
21
- require 'base64'
22
- require 'net/smtp'
24
+ require 'aspera/log'
23
25
  require 'open3'
24
26
  require 'date'
25
27
  require 'erb'
@@ -38,27 +40,21 @@ module Aspera
38
40
  CONF_PRESET_VERSION = 'version'
39
41
  CONF_PRESET_DEFAULT = 'default'
40
42
  CONF_PRESET_GLOBAL = 'global_common_defaults'
43
+ GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
41
44
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
42
45
  CONF_GLOBAL_SYM = :config
43
- # old tool name
44
- PROGRAM_NAME_V1 = 'aslmcli'
45
- PROGRAM_NAME_V2 = 'mlia'
46
- # default redirect for AoC web auth
47
- DEFAULT_REDIRECT = 'http://localhost:12345'
48
46
  # folder containing custom plugins in user's config folder
49
47
  ASPERA_PLUGINS_FOLDERNAME = 'plugins'
48
+ PERSISTENCY_FOLDER = 'persist_store'
50
49
  RUBY_FILE_EXT = '.rb'
51
- AOC_COMMAND_V1 = 'files'
52
- AOC_COMMAND_V2 = 'aspera'
53
- AOC_COMMAND_V3 = 'aoc'
54
- AOC_COMMAND_CURRENT = AOC_COMMAND_V3
50
+ ASPERA = 'aspera'
55
51
  SERVER_COMMAND = 'server'
52
+ APP_NAME_SDK = 'sdk'
56
53
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
57
- CONNECT_VERSIONS = 'connectversions.js'
54
+ CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
58
55
  TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
59
56
  DEMO = 'demo'
60
- DEMO_SERVER_PRESET = 'demoserver'
61
- AOC_PATH_API_CLIENTS = 'admin/api-clients'
57
+ DEMO_SERVER_PRESET = 'demoserver' # cspell: disable-line
62
58
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
63
59
  From: <%=from_name%> <<%=from_email%>>
64
60
  To: <<%=to%>>
@@ -67,135 +63,308 @@ module Aspera
67
63
  This email was sent to test #{PROGRAM_NAME}.
68
64
  END_OF_TEMPLATE
69
65
  # special extended values
70
- EXTV_INCLUDE_PRESETS = :incps
71
- EXTV_PRESET = :preset
72
- EXTV_VAULT = :vault
66
+ EXTEND_PRESET = :preset
67
+ EXTEND_VAULT = :vault
73
68
  PRESET_DIG_SEPARATOR = '.'
74
69
  DEFAULT_CHECK_NEW_VERSION_DAYS = 7
75
- DEFAULT_PRIV_KEY_FILENAME = 'aspera_aoc_key' # pragma: allowlist secret
76
- DEFAULT_PRIVKEY_LENGTH = 4096
70
+ DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
71
+ DEFAULT_PRIV_KEY_LENGTH = 4096
77
72
  COFFEE_IMAGE = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
73
+ WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
74
+ GEM_CHECK_DATE_FMT = '%Y/%m/%d'
75
+ # for testing only
76
+ SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
78
77
  private_constant :DEFAULT_CONFIG_FILENAME,
79
78
  :CONF_PRESET_CONFIG,
80
79
  :CONF_PRESET_VERSION,
81
80
  :CONF_PRESET_DEFAULT,
82
81
  :CONF_PRESET_GLOBAL,
83
- :PROGRAM_NAME_V1,
84
- :PROGRAM_NAME_V2,
85
- :DEFAULT_REDIRECT,
86
82
  :ASPERA_PLUGINS_FOLDERNAME,
87
83
  :RUBY_FILE_EXT,
88
- :AOC_COMMAND_V1,
89
- :AOC_COMMAND_V2,
90
- :AOC_COMMAND_V3,
91
- :AOC_COMMAND_CURRENT,
84
+ :ASPERA,
92
85
  :DEMO,
93
86
  :TRANSFER_SDK_ARCHIVE_URL,
94
- :AOC_PATH_API_CLIENTS,
95
87
  :DEMO_SERVER_PRESET,
96
88
  :EMAIL_TEST_TEMPLATE,
97
- :EXTV_INCLUDE_PRESETS,
98
- :EXTV_PRESET,
99
- :EXTV_VAULT,
89
+ :EXTEND_PRESET,
90
+ :EXTEND_VAULT,
100
91
  :DEFAULT_CHECK_NEW_VERSION_DAYS,
101
92
  :DEFAULT_PRIV_KEY_FILENAME,
102
93
  :SERVER_COMMAND,
103
94
  :PRESET_DIG_SEPARATOR,
104
- :COFFEE_IMAGE
95
+ :COFFEE_IMAGE,
96
+ :WIZARD_RESULT_KEYS,
97
+ :SELF_SIGNED_CERT,
98
+ :PERSISTENCY_FOLDER,
99
+ :DEFAULT_PRIV_KEY_LENGTH
100
+
101
+ class << self
102
+ def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
103
+ require 'openssl'
104
+ priv_key = OpenSSL::PKey::RSA.new(length)
105
+ File.write(path, priv_key.to_s)
106
+ File.write("#{path}.pub", priv_key.public_key.to_s)
107
+ Environment.restrict_file_access(path)
108
+ Environment.restrict_file_access("#{path}.pub")
109
+ nil
110
+ end
111
+
112
+ # folder containing plugins in the gem's main folder
113
+ def gem_plugins_folder
114
+ File.dirname(File.expand_path(__FILE__))
115
+ end
116
+
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
+ # @return main folder where code is, i.e. .../lib
124
+ # go up as many times as englobing modules (not counting class, as it is a file)
125
+ 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}")
134
+ end
135
+
136
+ # deep clone hash so that it does not get modified in case of display and secret hide
137
+ def protect_presets(val)
138
+ return JSON.parse(JSON.generate(val))
139
+ end
140
+
141
+ # return product family folder (~/.aspera)
142
+ def module_family_folder
143
+ 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)
145
+ return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
146
+ end
147
+
148
+ # return product config folder (~/.aspera/<name>)
149
+ 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?
151
+ return File.join(module_family_folder, app_name)
152
+ end
153
+ end # self
154
+
105
155
  def initialize(env, params)
106
- raise 'env and params must be Hash' unless env.is_a?(Hash) && params.is_a?(Hash)
107
- raise 'missing param' unless %i[name help version gem].sort.eql?(params.keys.sort)
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)
158
+ # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
108
159
  super(env)
109
160
  @info = params
110
- @main_folder = default_app_main_folder
111
161
  @plugins = {}
112
162
  @plugin_lookup_folders = []
113
163
  @use_plugin_defaults = true
114
164
  @config_presets = nil
165
+ @config_checksum_on_disk = nil
115
166
  @connect_versions = nil
116
167
  @vault = nil
117
- @conf_file_default = File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
118
- @option_config_file = @conf_file_default
119
168
  @pac_exec = nil
169
+ @sdk_default_location = false
170
+ @option_insecure = false
171
+ @option_ignore_cert_host_port = []
172
+ @option_http_options = {}
173
+ @ssl_warned_urls = []
174
+ @option_cache_tokens = true
175
+ @proxy_credentials = nil
176
+ @main_folder = nil
177
+ @option_config_file = nil
178
+ @certificate_store = nil
179
+ @certificate_paths = nil
180
+ @progress_bar = nil
181
+ # option to set main folder
182
+ options.declare(
183
+ :home, 'Home folder for tool',
184
+ handler: {o: self, m: :main_folder},
185
+ types: String,
186
+ default: self.class.default_app_main_folder(app_name: @info[:name]))
187
+ options.parse_options!
120
188
  Log.log.debug{"#{@info[:name]} folder: #{@main_folder}"}
121
- # set folder for FASP SDK
189
+ # data persistency manager
190
+ env[:persistency] = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
191
+ # set folders for plugin lookup
122
192
  add_plugin_lookup_folder(self.class.gem_plugins_folder)
123
193
  add_plugin_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
124
- # do file parameter first
125
- options.set_obj_attr(:config_file, self, :option_config_file)
126
- options.add_opt_simple(:config_file, "read parameters from file in YAML format, current=#{@option_config_file}")
194
+ # option to set config file
195
+ options.declare(
196
+ :config_file, 'Path to YAML file with preset configuration',
197
+ handler: {o: self, m: :option_config_file},
198
+ default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME))
127
199
  options.parse_options!
128
- # read correct file (set @config_presets)
200
+ # read config file (set @config_presets)
129
201
  read_config_file
130
202
  # add preset handler (needed for smtp)
131
- ExtendedValue.instance.set_handler(EXTV_PRESET, lambda{|v|preset_by_name(v)})
132
- ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS, lambda{|v|expanded_with_preset_includes(v)})
133
- ExtendedValue.instance.set_handler(EXTV_VAULT, lambda{|v|vault_value(v)})
203
+ ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{|v|preset_by_name(v)})
204
+ ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{|v|vault_value(v)})
134
205
  # load defaults before it can be overridden
135
206
  add_plugin_default_preset(CONF_GLOBAL_SYM)
207
+ # vault options
208
+ options.declare(:secret, 'Secret for access keys')
209
+ options.declare(:vault, 'Vault for secrets', types: Hash)
210
+ options.declare(:vault_password, 'Vault password')
136
211
  options.parse_options!
137
- options.set_obj_attr(:ascp_path, Fasp::Installation.instance, :ascp_path)
138
- options.set_obj_attr(:sdk_folder, Fasp::Installation.instance, :sdk_folder)
139
- options.set_obj_attr(:use_product, self, :option_use_product)
140
- options.set_obj_attr(:preset, self, :option_preset)
141
- options.set_obj_attr(:plugin_folder, self, :option_plugin_folder)
142
- options.add_opt_switch(:no_default, '-N', 'do not load default configuration for plugin') { @use_plugin_defaults = false }
143
- options.add_opt_boolean(:override, 'Wizard: override existing value')
144
- options.add_opt_boolean(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id')
145
- options.add_opt_boolean(:default, 'Wizard: set as default configuration for specified plugin (also: update)')
146
- options.add_opt_boolean(:test_mode, 'Wizard: skip private key check step')
147
- options.add_opt_simple(:preset, '-PVALUE', 'load the named option preset from current config file')
148
- options.add_opt_simple(:pkeypath, 'Wizard: path to private key for JWT')
149
- options.add_opt_simple(:ascp_path, 'Path to ascp')
150
- options.add_opt_simple(:use_product, 'Use ascp from specified product')
151
- options.add_opt_simple(:smtp, 'SMTP configuration (extended value: hash)')
152
- options.add_opt_simple(:fpac, 'Proxy auto configuration script')
153
- options.add_opt_simple(:proxy_credentials, 'HTTP proxy credentials (Array with user and password)')
154
- options.add_opt_simple(:secret, 'Secret for access keys')
155
- options.add_opt_simple(:vault, 'Vault for secrets')
156
- options.add_opt_simple(:vault_password, 'Vault password')
157
- options.add_opt_simple(:sdk_url, 'URL to get SDK')
158
- options.add_opt_simple(:sdk_folder, 'SDK folder path')
159
- options.add_opt_simple(:notif_to, 'Email recipient for notification of transfers')
160
- options.add_opt_simple(:notif_template, 'Email ERB template for notification of transfers')
161
- options.add_opt_simple(:version_check_days, Integer, 'Period in days to check new version (zero to disable)')
162
- options.add_opt_simple(:plugin_folder, 'Folder where to find additional plugins')
163
- options.set_option(:use_generic_client, true)
164
- options.set_option(:test_mode, false)
165
- options.set_option(:default, true)
166
- options.set_option(:version_check_days, DEFAULT_CHECK_NEW_VERSION_DAYS)
167
- options.set_option(:sdk_url, TRANSFER_SDK_ARCHIVE_URL)
168
- options.set_option(:sdk_folder, File.join(@main_folder, 'sdk'))
169
- options.set_option(:override, :no)
212
+ # declare generic plugin options only after handlers are declared
213
+ Plugin.declare_generic_options(options)
214
+ # configuration options
215
+ options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N') { @use_plugin_defaults = false }
216
+ options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
217
+ options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
218
+ options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
219
+ # wizard options
220
+ options.declare(:override, 'Wizard: override existing value', values: :bool, default: :no)
221
+ options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', values: :bool, default: true)
222
+ options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
223
+ options.declare(:key_path, 'Wizard: path to private key for JWT')
224
+ # Transfer SDK options
225
+ options.declare(:ascp_path, 'Path to ascp', handler: {o: Fasp::Installation.instance, m: :ascp_path})
226
+ options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
227
+ 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})
229
+ options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
230
+ # email options
231
+ options.declare(:smtp, 'SMTP configuration', types: Hash)
232
+ options.declare(:notify_to, 'Email recipient for notification of transfers')
233
+ options.declare(:notify_template, 'Email ERB template for notification of transfers')
234
+ # HTTP options
235
+ 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})
237
+ options.declare(:cert_stores, 'List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
238
+ 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})
240
+ options.declare(:fpac, 'Proxy auto configuration script')
241
+ options.declare(:proxy_credentials, 'HTTP proxy credentials (user and password)', types: Array)
170
242
  options.parse_options!
243
+ @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
244
+ # Check SDK folder is set or not, for compatibility, we check in two places
245
+ sdk_folder = Fasp::Installation.instance.sdk_folder rescue nil
246
+ if sdk_folder.nil?
247
+ @sdk_default_location = true
248
+ Log.log.debug('SDK folder is not set, checking default')
249
+ # new location
250
+ sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK)
251
+ Log.log.debug{"checking: #{sdk_folder}"}
252
+ if !Dir.exist?(sdk_folder)
253
+ Log.log.debug{"not exists: #{sdk_folder}"}
254
+ # former location
255
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: @info[:name]), APP_NAME_SDK)
256
+ Log.log.debug{"checking: #{former_sdk_folder}"}
257
+ sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
258
+ end
259
+ Log.log.debug{"using: #{sdk_folder}"}
260
+ Fasp::Installation.instance.sdk_folder = sdk_folder
261
+ end
171
262
  pac_script = options.get_option(:fpac)
172
263
  # create PAC executor
173
264
  @pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
174
- proxy_creds = options.get_option(:proxy_credentials)
175
- if !proxy_creds.nil?
176
- raise CliBadArgument, 'proxy credentials shall be an array (#{proxy_creds.class})' unless proxy_creds.is_a?(Array)
177
- raise CliBadArgument, 'proxy credentials shall have two elements (#{proxy_creds.length})' unless proxy_creds.length.eql?(2)
178
- @pac_exec.proxy_user = Rest.proxy_user = proxy_creds[0]
179
- @pac_exec.proxy_pass = Rest.proxy_pass = proxy_creds[1]
265
+ proxy_user_pass = options.get_option(:proxy_credentials)
266
+ 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)
268
+ @proxy_credentials = {user: proxy_user_pass[0], pass: proxy_user_pass[1]}
269
+ @pac_exec.proxy_user = @proxy_credentials[:user]
270
+ @pac_exec.proxy_pass = @proxy_credentials[:pass]
271
+ end
272
+ Rest.set_parameters(
273
+ user_agent: PROGRAM_NAME,
274
+ session_cb: lambda{|http_session|update_http_session(http_session)},
275
+ 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')
279
+ # register aspera REST call error handlers
280
+ Aspera::RestErrorsAspera.register_handlers
281
+ end
282
+
283
+ attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_http_options
284
+ attr_reader :option_ignore_cert_host_port, :progress_bar
285
+
286
+ def trusted_cert_locations=(path_list)
287
+ path_list = [path_list] unless path_list.is_a?(Array)
288
+ if @certificate_store.nil?
289
+ Log.log.debug('Creating SSL Cert store')
290
+ @certificate_store = OpenSSL::X509::Store.new
291
+ @certificate_store.set_default_paths
292
+ @certificate_paths = []
180
293
  end
294
+
295
+ 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}")
298
+ 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)
304
+ elsif File.file?(path)
305
+ @certificate_store.add_file(path)
306
+ elsif File.directory?(path)
307
+ @certificate_store.add_path(path)
308
+ else
309
+ raise "No such file or folder: #{path}"
310
+ end
311
+ @certificate_paths.push(path)
312
+ end
313
+ end
314
+
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
+ end
321
+ locations = locations.select{|f|File.file?(f)} if files_only
322
+ return locations
181
323
  end
182
324
 
183
- # env var name to override the app's main folder
184
- # default main folder is $HOME/<vendor main app folder>/<program name>
185
- def conf_dir_env_var
186
- return "#{@info[:name]}_home".upcase
325
+ def option_ignore_cert_host_port=(url_list)
326
+ url_list.each do |url|
327
+ uri = URI.parse(url)
328
+ @option_ignore_cert_host_port.push([uri.host, uri.port].freeze)
329
+ end
187
330
  end
188
331
 
189
- def default_app_main_folder
190
- # find out application main folder
191
- app_folder = ENV[conf_dir_env_var]
192
- # if env var undefined or empty
193
- if app_folder.nil? || app_folder.empty?
194
- user_home_folder = Dir.home
195
- raise CliError, "Home folder does not exist: #{user_home_folder}. Check your user environment or use #{conf_dir_env_var}." unless Dir.exist?(user_home_folder)
196
- app_folder = File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME, @info[:name])
332
+ def ignore_cert?(address, port)
333
+ 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)
342
+ end
343
+ return true
344
+ end
345
+
346
+ # called every time a new REST HTTP session is opened to set user-provided options
347
+ # @param http_session [Net::HTTP] the newly created HTTP/S session object
348
+ def update_http_session(http_session)
349
+ http_session.set_debug_output(LineLogger.new(:trace2)) if Log.instance.logger.trace2?
350
+ # Rest.io_http_session(http_session).debug_output = Log.log
351
+ http_session.verify_mode = SELF_SIGNED_CERT if http_session.use_ssl? && ignore_cert?(http_session.address, http_session.port)
352
+ http_session.cert_store = @certificate_store if @certificate_store
353
+ Log.log.debug{"using cert store #{http_session.cert_store} (#{@certificate_store})"} unless http_session.cert_store.nil?
354
+ if @proxy_credentials
355
+ http_session.proxy_user = @proxy_credentials[:user]
356
+ http_session.proxy_pass = @proxy_credentials[:pass]
357
+ end
358
+ @option_http_options.each do |k, v|
359
+ method = "#{k}=".to_sym
360
+ # check if accessor is a method of Net::HTTP
361
+ # continue_timeout= read_timeout= write_timeout=
362
+ if http_session.respond_to?(method)
363
+ http_session.send(method, v)
364
+ else
365
+ Log.log.error{"no such HTTP session attribute: #{k}"}
366
+ end
197
367
  end
198
- return app_folder
199
368
  end
200
369
 
201
370
  def check_gem_version
@@ -208,7 +377,7 @@ module Aspera
208
377
  end
209
378
  if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(RUBY_FUTURE_MINIMUM_VERSION)
210
379
  Log.log.warn do
211
- "Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, "\
380
+ "Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, " \
212
381
  "you are using #{Environment.ruby_version}"
213
382
  end
214
383
  end
@@ -222,8 +391,7 @@ module Aspera
222
391
 
223
392
  def periodic_check_newer_gem_version
224
393
  # get verification period
225
- delay_days = options.get_option(:version_check_days, is_type: :mandatory)
226
- Log.log.info{"check days: #{delay_days}"}
394
+ delay_days = options.get_option(:version_check_days, mandatory: true)
227
395
  # check only if not zero day
228
396
  return if delay_days.eql?(0)
229
397
  # get last date from persistency
@@ -234,17 +402,11 @@ module Aspera
234
402
  id: 'version_last_check')
235
403
  # get persisted date or nil
236
404
  current_date = Date.today
237
- last_check_days =
238
- begin
239
- current_date - Date.strptime(last_check_array.first, '%Y/%m/%d')
240
- rescue StandardError
241
- # negative value will force check
242
- -1
243
- end
244
- Log.log.debug{"days elapsed: #{last_check_days}"}
245
- return if last_check_days < delay_days
405
+ last_check_days = (current_date - Date.strptime(last_check_array.first, GEM_CHECK_DATE_FMT)) rescue nil
406
+ Log.log.debug{"gem check new version: #{delay_days}, #{last_check_days}, #{current_date}, #{last_check_array}"}
407
+ return if !last_check_days.nil? && last_check_days < delay_days
246
408
  # generate timestamp
247
- last_check_array[0] = current_date.strftime('%Y/%m/%d')
409
+ last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
248
410
  check_date_persist.save
249
411
  # compare this version and the one on internet
250
412
  check_data = check_gem_version
@@ -263,7 +425,7 @@ module Aspera
263
425
  Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
264
426
  # get javascript object only
265
427
  found = connect_versions_javascript.match(/^.*? = (.*);/)
266
- raise CliError, 'Problem when getting connect versions from internet' if found.nil?
428
+ raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
267
429
  all_data = JSON.parse(found[1])
268
430
  @connect_versions = all_data['entries']
269
431
  end
@@ -281,66 +443,45 @@ module Aspera
281
443
  return nil
282
444
  end
283
445
 
284
- private
285
-
286
- def generate_rsa_private_key(private_key_path, length)
287
- require 'openssl'
288
- priv_key = OpenSSL::PKey::RSA.new(length)
289
- File.write(private_key_path, priv_key.to_s)
290
- File.write(private_key_path + '.pub', priv_key.public_key.to_s)
291
- Environment.restrict_file_access(private_key_path)
292
- Environment.restrict_file_access(private_key_path + '.pub')
293
- nil
294
- end
295
-
296
- class << self
297
- # folder containing plugins in the gem's main folder
298
- def gem_plugins_folder
299
- File.dirname(File.expand_path(__FILE__))
300
- end
301
-
302
- # name of englobing module
303
- # @return "Aspera::Cli::Plugins"
304
- def module_full_name
305
- return Module.nesting[2].to_s
446
+ # get the default global preset, or init a new one
447
+ 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)
306
452
  end
453
+ return global_default_preset_name
454
+ end
307
455
 
308
- # @return main folder where code is, i.e. .../lib
309
- # go up as many times as englobing modules (not counting class, as it is a file)
310
- def gem_src_root
311
- File.expand_path(module_full_name.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
456
+ 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)
458
+ param_name = param_name.to_s
459
+ selected_preset = @config_presets[preset]
460
+ if selected_preset.nil?
461
+ Log.log.debug{"No such preset name: #{preset}, initializing"}
462
+ selected_preset = @config_presets[preset] = {}
312
463
  end
313
-
314
- # instantiate a plugin
315
- # plugins must be Capitalized
316
- def plugin_class(plugin_name_sym)
317
- # Module.nesting[2] is Aspera::Cli::Plugins
318
- return Object.const_get("#{module_full_name}::#{plugin_name_sym.to_s.capitalize}")
464
+ raise "expecting Hash for #{preset}.#{param_name}" unless selected_preset.is_a?(Hash)
465
+ if selected_preset.key?(param_name)
466
+ if selected_preset[param_name].eql?(param_value)
467
+ Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
468
+ return
469
+ end
470
+ Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
319
471
  end
472
+ selected_preset[param_name] = param_value
473
+ formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
474
+ nil
320
475
  end
321
476
 
322
477
  # set parameter and value in global config
323
478
  # creates one if none already created
324
479
  # @return preset name that contains global default
325
480
  def set_global_default(key, value)
326
- # get default preset if it exists
327
- global_default_preset_name = get_plugin_default_config_name(CONF_GLOBAL_SYM)
328
- if global_default_preset_name.nil?
329
- global_default_preset_name = CONF_PRESET_GLOBAL
330
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
331
- @config_presets[CONF_PRESET_DEFAULT][CONF_GLOBAL_SYM.to_s] = global_default_preset_name
332
- end
333
- @config_presets[global_default_preset_name] ||= {}
334
- @config_presets[global_default_preset_name][key.to_s] = value
335
- formatter.display_status("Updated: #{global_default_preset_name}: #{key} <- #{value}")
336
- save_presets_to_config_file
337
- return global_default_preset_name
481
+ set_preset_key(global_default_preset, key, value)
338
482
  end
339
483
 
340
- public
341
-
342
484
  # $HOME/.aspera/`program_name`
343
- attr_reader :main_folder
344
485
  attr_reader :gem_url, :plugins
345
486
  attr_accessor :option_config_file
346
487
 
@@ -348,40 +489,17 @@ module Aspera
348
489
  # @param config_name name of the preset in config file
349
490
  # @param include_path used to detect and avoid include loops
350
491
  def preset_by_name(config_name, include_path=[])
351
- raise CliError, 'loop in include' if include_path.include?(config_name)
492
+ raise Cli::Error, 'loop in include' if include_path.include?(config_name)
352
493
  include_path = include_path.clone # avoid messing up if there are multiple branches
353
494
  current = @config_presets
354
495
  config_name.split(PRESET_DIG_SEPARATOR).each do |name|
355
- raise CliError, "Expecting Hash for sub key: #{include_path} (#{current.class})" unless current.is_a?(Hash)
496
+ raise Cli::Error, "Expecting Hash for sub key: #{include_path} (#{current.class})" unless current.is_a?(Hash)
356
497
  include_path.push(name)
357
498
  current = current[name]
358
- raise CliError, "No such config preset: #{include_path}" if current.nil?
359
- end
360
- case current
361
- when Hash then return expanded_with_preset_includes(current, include_path)
362
- when String then return ExtendedValue.instance.evaluate(current)
363
- else return current
499
+ raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
364
500
  end
365
- end
366
-
367
- # @return the hash value with 'incps' keys expanded to include other presets
368
- # @param hash_val
369
- # @param include_path to avoid inclusion loop
370
- def expanded_with_preset_includes(hash_val, include_path=[])
371
- raise CliError, "#{EXTV_INCLUDE_PRESETS} requires a Hash, have #{hash_val.class}" unless hash_val.is_a?(Hash)
372
- if hash_val.key?(EXTV_INCLUDE_PRESETS)
373
- memory = hash_val.clone
374
- includes = memory[EXTV_INCLUDE_PRESETS]
375
- memory.delete(EXTV_INCLUDE_PRESETS)
376
- hash_val = {}
377
- raise "#{EXTV_INCLUDE_PRESETS} must be an Array" unless includes.is_a?(Array)
378
- raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map(&:class).uniq.eql?([String])
379
- includes.each do |preset_name|
380
- hash_val.merge!(preset_by_name(preset_name, include_path))
381
- end
382
- hash_val.merge!(memory)
383
- end
384
- return hash_val
501
+ current = self.class.protect_presets(current) unless current.is_a?(String)
502
+ return ExtendedValue.instance.evaluate(current)
385
503
  end
386
504
 
387
505
  def option_use_product=(value)
@@ -417,97 +535,46 @@ module Aspera
417
535
  end
418
536
  end
419
537
 
420
- def convert_preset_path(old_name, new_name, files_to_copy)
421
- old_subpath = File.join('', ASPERA_HOME_FOLDER_NAME, old_name, '')
422
- new_subpath = File.join('', ASPERA_HOME_FOLDER_NAME, new_name, '')
423
- # convert possible keys located in config folder
424
- @config_presets.values.select{|p|p.is_a?(Hash)}.each do |preset|
425
- preset.values.select{|v|v.is_a?(String) && v.include?(old_subpath)}.each do |value|
426
- old_val = value.clone
427
- included_path = File.expand_path(old_val.gsub(/^@file:/, ''))
428
- files_to_copy.push(included_path) unless files_to_copy.include?(included_path) || !File.exist?(included_path)
429
- value.gsub!(old_subpath, new_subpath)
430
- Log.log.warn{"Converted config value: #{old_val} -> #{value}"}
431
- end
432
- end
433
- end
434
-
435
- def convert_preset_plugin_name(old_name, new_name)
436
- default_preset = @config_presets[CONF_PRESET_DEFAULT]
437
- return unless default_preset.is_a?(Hash) && default_preset.key?(old_name)
438
- default_preset[new_name] = default_preset[old_name]
439
- default_preset.delete(old_name)
440
- Log.log.warn{"Converted plugin default: #{old_name} -> #{new_name}"}
538
+ def config_checksum
539
+ JSON.generate(@config_presets).hash
441
540
  end
442
541
 
443
542
  # read config file and validate format
444
- # tries to convert from older version if possible and required
445
543
  def read_config_file
446
544
  Log.log.debug{"config file is: #{@option_config_file}".red}
447
- conf_file_v1 = File.join(Dir.home, ASPERA_HOME_FOLDER_NAME, PROGRAM_NAME_V1, DEFAULT_CONFIG_FILENAME)
448
- conf_file_v2 = File.join(Dir.home, ASPERA_HOME_FOLDER_NAME, PROGRAM_NAME_V2, DEFAULT_CONFIG_FILENAME)
449
545
  # files search for configuration, by default the one given by user
450
546
  search_files = [@option_config_file]
451
- # if default file, then also look for older versions
452
- search_files.push(conf_file_v2, conf_file_v1) if @option_config_file.eql?(@conf_file_default)
453
547
  # find first existing file (or nil)
454
548
  conf_file_to_load = search_files.find{|f| File.exist?(f)}
455
- # require save if old version of file
456
- save_required = !@option_config_file.eql?(conf_file_to_load)
457
549
  # if no file found, create default config
458
550
  if conf_file_to_load.nil?
459
- Log.log.warn{"No config file found. Creating empty configuration file: #{@option_config_file}"}
460
- @config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => @info[:version]}}
551
+ Log.log.warn{"No config file found. New configuration file: #{@option_config_file}"}
552
+ @config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => 'new file'}}
553
+ # @config_checksum_on_disk is nil
461
554
  else
462
555
  Log.log.debug{"loading #{@option_config_file}"}
463
556
  @config_presets = YAML.load_file(conf_file_to_load)
557
+ @config_checksum_on_disk = config_checksum
464
558
  end
465
559
  files_to_copy = []
466
- Log.log.debug{"Available_presets: #{@config_presets}"}
560
+ Log.log.debug{Log.dump('Available_presets', @config_presets)}
467
561
  raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
468
562
  # check there is at least the config section
469
- if !@config_presets.key?(CONF_PRESET_CONFIG)
470
- raise "Cannot find key: #{CONF_PRESET_CONFIG}"
471
- end
563
+ raise "Cannot find key: #{CONF_PRESET_CONFIG}" unless @config_presets.key?(CONF_PRESET_CONFIG)
472
564
  version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
473
- if version.nil?
474
- raise 'No version found in config section.'
475
- end
476
- # oldest compatible conf file format, update to latest version when an incompatible change is made
477
- # check compatibility of version of conf file
478
- config_tested_version = '0.4.5'
479
- if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
480
- raise "Unsupported config file version #{version}. Expecting min version #{config_tested_version}"
481
- end
482
- config_tested_version = '0.6.15'
483
- if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
484
- convert_preset_plugin_name(AOC_COMMAND_V1, AOC_COMMAND_V2)
485
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
486
- save_required = true
487
- end
488
- config_tested_version = '0.8.10'
489
- if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
490
- convert_preset_path(PROGRAM_NAME_V1, PROGRAM_NAME_V2, files_to_copy)
491
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
492
- save_required = true
493
- end
494
- config_tested_version = '1.0'
495
- if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
496
- convert_preset_plugin_name(AOC_COMMAND_V2, AOC_COMMAND_V3)
497
- convert_preset_path(PROGRAM_NAME_V2, @info[:name], files_to_copy)
498
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
499
- save_required = true
500
- end
565
+ raise 'No version found in config section.' if version.nil?
501
566
  Log.log.debug{"conf version: #{version}"}
502
- # Place new compatibility code here
503
- if save_required
504
- Log.log.warn('Saving automatic conversion.')
505
- @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
506
- save_presets_to_config_file
567
+ # VVV if there are any conversion needed, those happen here.
568
+ # 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)
570
+ # ^^^ Place new compatibility code before this line
571
+ # set version to current
572
+ @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
573
+ unless files_to_copy.empty?
507
574
  Log.log.warn('Copying referenced files')
508
575
  files_to_copy.each do |file|
509
576
  FileUtils.cp(file, @main_folder)
510
- Log.log.warn{"..#{file} -> #{@main_folder}"}
577
+ Log.log.warn{"#{file} -> #{@main_folder}"}
511
578
  end
512
579
  end
513
580
  rescue Psych::SyntaxError => e
@@ -522,7 +589,7 @@ module Aspera
522
589
  Log.log.warn{"Renamed config file to #{new_name}."}
523
590
  Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
524
591
  end
525
- raise CliError, e.to_s
592
+ raise Cli::Error, e.to_s
526
593
  end
527
594
 
528
595
  # find plugins in defined paths
@@ -552,42 +619,41 @@ module Aspera
552
619
  @plugins[plugin_symbol] = {source: path, require_stanza: req}
553
620
  end
554
621
 
555
- def identify_plugin_for_url(url)
622
+ # Find a plugin, and issue the "require"
623
+ # @return [Hash] plugin info: { product: , url:, version: }
624
+ def identify_plugins_for_url
625
+ app_url = options.get_next_argument('url', mandatory: true)
626
+ check_only = options.get_next_argument('plugin name', mandatory: false)
627
+ check_only = check_only.to_sym unless check_only.nil?
628
+ found_apps = []
556
629
  plugins.each do |plugin_name_sym, plugin_info|
557
630
  # no detection for internal plugin
558
631
  next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
632
+ next if check_only && !check_only.eql?(plugin_name_sym)
559
633
  # load plugin class
560
634
  require plugin_info[:require_stanza]
561
- c = self.class.plugin_class(plugin_name_sym)
562
- next unless c.respond_to?(:detect)
563
- current_url = url
635
+ detect_plugin_class = self.class.plugin_class(plugin_name_sym)
636
+ # requires detection method
637
+ next unless detect_plugin_class.respond_to?(:detect)
564
638
  detection_info = nil
565
- # first try : direct
566
639
  begin
567
- detection_info = c.detect(current_url)
640
+ detection_info = detect_plugin_class.detect(app_url)
568
641
  rescue OpenSSL::SSL::SSLError => e
569
642
  Log.log.warn(e.message)
570
- Log.log.warn('Use option --insecure=yes to ignore certificate') if e.message.include?('cert')
643
+ Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
571
644
  rescue StandardError => e
572
- Log.log.debug{"Cannot detect #{plugin_name_sym} : #{e.class}/#{e.message}"}
573
- end
574
- # second try : is there a redirect ?
575
- if detection_info.nil?
576
- begin
577
- # TODO: check if redirect ?
578
- new_url = Rest.new(base_url: url).call(operation: 'GET', subpath: '', redirect_max: 1)[:http].uri.to_s
579
- unless url.eql?(new_url)
580
- detection_info = c.detect(new_url)
581
- current_url = new_url
582
- end
583
- rescue StandardError => e
584
- Log.log.debug{"Cannot detect #{plugin_name_sym} : #{e.message}"}
585
- end
645
+ Log.log.debug{"detect error: #{e}"}
646
+ next
586
647
  end
648
+ next if detection_info.nil?
649
+ raise 'internal error' if detection_info.key?(:url) && !detection_info[:url].is_a?(String)
650
+ app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
587
651
  # if there is a redirect, then the detector can override the url.
588
- return {product: plugin_name_sym, url: current_url}.merge(detection_info) unless detection_info.nil?
652
+ found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
589
653
  end # loop
590
- raise "No known application found at #{url}"
654
+ 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)}
656
+ return found_apps
591
657
  end
592
658
 
593
659
  def execute_connect_action
@@ -595,7 +661,7 @@ module Aspera
595
661
  if %i[info version].include?(command)
596
662
  connect_id = options.get_next_argument('id or title')
597
663
  one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
598
- raise CliNoSuchId.new(:connect, connect_id) if one_res.nil?
664
+ raise Cli::NoSuchIdentifier.new(:connect, connect_id) if one_res.nil?
599
665
  end
600
666
  case command
601
667
  when :list
@@ -638,21 +704,14 @@ module Aspera
638
704
  ascp_path = options.get_next_argument('path to ascp')
639
705
  ascp_version = Fasp::Installation.instance.get_ascp_version(ascp_path)
640
706
  formatter.display_status("ascp version: #{ascp_version}")
641
- preset_name = set_global_default(:ascp_path, ascp_path)
642
- return Main.result_status("Saved to default global preset #{preset_name}")
707
+ set_global_default(:ascp_path, ascp_path)
708
+ return Main.result_nothing
643
709
  when :show # shows files used
644
710
  return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
645
711
  when :info # shows files used
646
- data = Fasp::Installation::FILES.each_with_object({}) do |v, m|
647
- m[v.to_s] =
648
- begin
649
- Fasp::Installation.instance.path(v)
650
- rescue => e
651
- e.message
652
- end
653
- end
712
+ data = Fasp::Installation.instance.file_paths
654
713
  # read PATHs from ascp directly, and pvcl modules as well
655
- Open3.popen3(Fasp::Installation.instance.path(:ascp), '-DDL-') do |_stdin, _stdout, stderr, thread|
714
+ Open3.popen3(data['ascp'], '-DDL-') do |_stdin, _stdout, stderr, thread|
656
715
  last_line = ''
657
716
  while (line = stderr.gets)
658
717
  line.chomp!
@@ -660,8 +719,11 @@ module Aspera
660
719
  case line
661
720
  when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
662
721
  data[Regexp.last_match(1)] = Regexp.last_match(3)
663
- when /^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$/
664
- data[Regexp.last_match(2)] = "#{Regexp.last_match(4)} #{Regexp.last_match(1)} v#{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]}")
665
727
  when %r{^DBG License result \(/license/(\S+)\): (.+)$}
666
728
  data[Regexp.last_match(1)] = Regexp.last_match(2)
667
729
  when /^LOG (.+) version ([0-9.]+)$/
@@ -675,21 +737,34 @@ module Aspera
675
737
  raise last_line
676
738
  end
677
739
  end
678
- data['keypass'] = Fasp::Installation.instance.bypass_pass
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
751
+ data['ts'] = transfer.updated_ts
679
752
  return {type: :single_object, data: data}
680
753
  when :products
681
754
  command = options.get_next_command(%i[list use])
682
755
  case command
683
756
  when :list
684
- return {type: :object_list, data: Fasp::Installation.instance.installed_products, fields: %w[name app_root]}
757
+ return {type: :object_list, data: Fasp::Products.installed_products, fields: %w[name app_root]}
685
758
  when :use
686
759
  default_product = options.get_next_argument('product name')
687
760
  Fasp::Installation.instance.use_ascp_from_product(default_product)
688
- preset_name = set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
689
- return Main.result_status("Saved to default global preset #{preset_name}")
761
+ set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
762
+ return Main.result_nothing
690
763
  end
691
764
  when :install
692
- v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url, is_type: :mandatory))
765
+ # 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))
693
768
  return Main.result_status("Installed version #{v}")
694
769
  when :spec
695
770
  return {
@@ -718,24 +793,23 @@ module Aspera
718
793
  def execute_preset(action: nil, name: nil)
719
794
  action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
720
795
  name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
796
+ name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
721
797
  # those operations require existing option
722
798
  raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
723
- selected_preset = @config_presets[name]
724
799
  case action
725
800
  when :list
726
801
  return {type: :value_list, data: @config_presets.keys, name: 'name'}
727
802
  when :overview
728
- return {type: :object_list, data: Formatter.flatten_config_overview(@config_presets)}
803
+ # display process modifies the value (hide secrets): we do not want to save removed secrets
804
+ return {type: :config_over, data: self.class.protect_presets(@config_presets)}
729
805
  when :show
730
- raise "no such config: #{name}" if selected_preset.nil?
731
- return {type: :single_object, data: selected_preset}
806
+ return {type: :single_object, data: self.class.protect_presets(@config_presets[name])}
732
807
  when :delete
733
808
  @config_presets.delete(name)
734
- save_presets_to_config_file
735
809
  return Main.result_status("Deleted: #{name}")
736
810
  when :get
737
811
  param_name = options.get_next_argument('parameter name')
738
- value = selected_preset[param_name]
812
+ value = @config_presets[name][param_name]
739
813
  raise "no such option in preset #{name} : #{param_name}" if value.nil?
740
814
  case value
741
815
  when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
@@ -743,30 +817,20 @@ module Aspera
743
817
  return {type: :single_object, data: value}
744
818
  when :unset
745
819
  param_name = options.get_next_argument('parameter name')
746
- selected_preset.delete(param_name)
747
- save_presets_to_config_file
820
+ @config_presets[name].delete(param_name)
748
821
  return Main.result_status("Removed: #{name}: #{param_name}")
749
822
  when :set
750
823
  param_name = options.get_next_argument('parameter name')
751
- param_value = options.get_next_argument('parameter value')
752
824
  param_name = Manager.option_line_to_name(param_name)
753
- if !@config_presets.key?(name)
754
- Log.log.debug{"no such config name: #{name}, initializing"}
755
- selected_preset = @config_presets[name] = {}
756
- end
757
- if selected_preset.key?(param_name)
758
- Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
759
- end
760
- selected_preset[param_name] = param_value
761
- save_presets_to_config_file
762
- return Main.result_status("Updated: #{name}: #{param_name} <- #{param_value}")
825
+ param_value = options.get_next_argument('parameter value')
826
+ set_preset_key(name, param_name, param_value)
827
+ return Main.result_nothing
763
828
  when :initialize
764
829
  config_value = options.get_next_argument('extended value', type: Hash)
765
830
  if @config_presets.key?(name)
766
831
  Log.log.warn{"configuration already exists: #{name}, overwriting"}
767
832
  end
768
833
  @config_presets[name] = config_value
769
- save_presets_to_config_file
770
834
  return Main.result_status("Modified: #{@option_config_file}")
771
835
  when :update
772
836
  # get unprocessed options
@@ -774,9 +838,6 @@ module Aspera
774
838
  Log.log.debug{"opts=#{unprocessed_options}"}
775
839
  @config_presets[name] ||= {}
776
840
  @config_presets[name].merge!(unprocessed_options)
777
- # fix bug in 4.4 (creating key "true" in "default" preset)
778
- @config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
779
- save_presets_to_config_file
780
841
  return Main.result_status("Updated: #{name}")
781
842
  when :ask
782
843
  options.ask_missing_mandatory = :yes
@@ -785,12 +846,11 @@ module Aspera
785
846
  option_value = options.get_interactive(:option, option_name)
786
847
  @config_presets[name][option_name] = option_value
787
848
  end
788
- save_presets_to_config_file
789
849
  return Main.result_status("Updated: #{name}")
790
850
  when :lookup
791
- BasicAuthPlugin.register_options(@agents)
792
- url = options.get_option(:url, is_type: :mandatory)
793
- user = options.get_option(:username, is_type: :mandatory)
851
+ BasicAuthPlugin.declare_options(options)
852
+ url = options.get_option(:url, mandatory: true)
853
+ user = options.get_option(:username, mandatory: true)
794
854
  result = lookup_preset(url: url, username: user)
795
855
  raise 'no such config found' if result.nil?
796
856
  return {type: :single_object, data: result}
@@ -823,17 +883,16 @@ module Aspera
823
883
  end
824
884
 
825
885
  ACTIONS = %i[
826
- id
827
886
  preset
828
887
  open
829
888
  documentation
830
889
  genkey
890
+ remote_certificate
831
891
  gem
832
- plugin
892
+ plugins
833
893
  flush_tokens
834
894
  echo
835
895
  wizard
836
- export_to_cli
837
896
  detect
838
897
  coffee
839
898
  ascp
@@ -844,19 +903,13 @@ module Aspera
844
903
  file
845
904
  check_update
846
905
  initdemo
847
- vault].concat(PRESET_GBL_ACTIONS).freeze
906
+ vault
907
+ throw].freeze
848
908
 
849
- # "config" plugin
909
+ # Main action procedure for plugin
850
910
  def execute_action
851
911
  action = options.get_next_command(ACTIONS)
852
912
  case action
853
- when *PRESET_GBL_ACTIONS # older syntax
854
- Log.log.warn{"This syntax is deprecated, use command: preset #{action}"}
855
- return execute_preset(action: action)
856
- when :id # older syntax
857
- identifier = options.get_next_argument('config name')
858
- Log.log.warn{"This syntax is deprecated, use command: preset <verb> #{identifier}"}
859
- return execute_preset(name: identifier)
860
913
  when :preset # newer syntax
861
914
  return execute_preset
862
915
  when :open
@@ -864,31 +917,45 @@ module Aspera
864
917
  return Main.result_nothing
865
918
  when :documentation
866
919
  section = options.get_next_argument('private key file path', mandatory: false)
867
- section = '#' + section unless section.nil?
920
+ section = "##{section}" unless section.nil?
868
921
  OpenApplication.instance.uri("#{@info[:help]}#{section}")
869
922
  return Main.result_nothing
870
923
  when :genkey # generate new rsa key
871
924
  private_key_path = options.get_next_argument('private key file path')
872
- private_key_length = options.get_next_argument('size in bits', mandatory: false) || DEFAULT_PRIVKEY_LENGTH
873
- generate_rsa_private_key(private_key_path, private_key_length)
874
- return Main.result_status('Generated key: ' + private_key_path)
925
+ private_key_length = options.get_next_argument('size in bits', mandatory: false, type: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
926
+ self.class.generate_rsa_private_key(path: private_key_path, length: private_key_length)
927
+ return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
928
+ when :remote_certificate
929
+ 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)
875
935
  when :echo # display the content of a value given on command line
876
- result = {type: :other_struct, data: options.get_next_argument('value')}
877
- # special for csv
878
- result[:type] = :object_list if result[:data].is_a?(Array) && result[:data].first.is_a?(Hash)
879
- result[:type] = :single_object if result[:data].is_a?(Hash)
880
- return result
936
+ return Formatter.auto_type(options.get_next_argument('value'))
881
937
  when :flush_tokens
882
938
  deleted_files = Oauth.flush_tokens
883
939
  return {type: :value_list, data: deleted_files, name: 'file'}
884
- when :plugin
940
+ when :plugins
885
941
  case options.get_next_command(%i[list create])
886
942
  when :list
887
- return {type: :object_list, data: @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } }, fields: %w[plugin path]}
943
+ result = []
944
+ @plugins.each do |name, info|
945
+ require info[:require_stanza]
946
+ plugin_class = self.class.plugin_class(name)
947
+ result.push({
948
+ plugin: name,
949
+ detect: Formatter.tick(plugin_class.respond_to?(:detect)),
950
+ wizard: Formatter.tick(plugin_class.respond_to?(:wizard)),
951
+ path: info[:source]
952
+ })
953
+ end
954
+ return {type: :object_list, data: result, fields: %w[plugin detect wizard path]}
888
955
  when :create
889
956
  plugin_name = options.get_next_argument('name', expected: :single).downcase
890
- plugin_folder = options.get_next_argument('folder', expected: :single, mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
891
- plugin_file = File.join(plugin_folder, "#{plugin_name}.rb")
957
+ destination_folder = options.get_next_argument('folder', expected: :single, mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
958
+ plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
892
959
  content = <<~END_OF_PLUGIN_CODE
893
960
  require 'aspera/cli/plugin'
894
961
  module Aspera
@@ -905,81 +972,20 @@ module Aspera
905
972
  File.write(plugin_file, content)
906
973
  return Main.result_status("Created #{plugin_file}")
907
974
  end
908
- when :wizard
975
+ when :detect, :wizard
909
976
  # interactive mode
910
977
  options.ask_missing_mandatory = true
911
- # register url option
912
- BasicAuthPlugin.register_options(@agents)
913
- params = {}
914
- # get from option, or ask
915
- params[:instance_url] = options.get_option(:url, is_type: :mandatory)
916
- # allow user to tell the preset name
917
- params[:preset_name] = options.get_option(:id)
918
- # allow user to specify type of application
919
- params[:application] = options.get_option(:value)
920
- params[:application] = params[:application].nil? ? identify_plugin_for_url(params[:instance_url])[:product] : params[:application].to_sym
921
- params[:plugin_name] = params[:application]
922
- params[:test_args] = '<replace per app>'
923
- case params[:application]
924
- when :faspex5
925
- wizard_faspex5(params)
926
- when :aoc
927
- wizard_aoc(params)
928
- else
929
- raise CliBadArgument, "Supports only: aoc. Detected: #{params[:application]}"
930
- end # product
931
- if params[:option_default]
932
- formatter.display_status("Setting config preset as default for #{params[:plugin_name]}")
933
- @config_presets[CONF_PRESET_DEFAULT][params[:plugin_name]] = params[:preset_name]
934
- else
935
- params[:test_args] = "-P#{params[:preset_name]} #{params[:test_args]}"
936
- end
937
- formatter.display_status('Saving config file.')
938
- save_presets_to_config_file
939
- return Main.result_status("Done.\nYou can test with:\n#{@info[:name]} #{params[:test_args]}")
940
- when :export_to_cli # this method shall be deprecated in the future: it was used to export configuration to "aspera.exe" CLI
941
- formatter.display_status('Exporting: Aspera on Cloud')
942
- require 'aspera/cli/plugins/aoc'
943
- # need url / username
944
- add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
945
- # instantiate AoC plugin
946
- self.class.plugin_class(AOC_COMMAND_CURRENT).new(@agents) # TODO: is this line needed ? get options ?
947
- url = options.get_option(:url, is_type: :mandatory)
948
- cli_conf_file = Fasp::Installation.instance.cli_conf_file
949
- data = JSON.parse(File.read(cli_conf_file))
950
- organization, instance_domain = AoC.parse_url(url)
951
- key_basename = 'org_' + organization + '.pem'
952
- key_file = File.join(File.dirname(File.dirname(cli_conf_file)), 'etc', key_basename)
953
- File.write(key_file, options.get_option(:private_key, is_type: :mandatory))
954
- new_conf = {
955
- 'organization' => organization,
956
- 'hostname' => [organization, instance_domain].join('.'),
957
- 'privateKeyFilename' => key_basename,
958
- 'username' => options.get_option(:username, is_type: :mandatory)
959
- }
960
- new_conf['clientId'] = options.get_option(:client_id)
961
- new_conf['clientSecret'] = options.get_option(:client_secret)
962
- if new_conf['clientId'].nil?
963
- new_conf['clientId'], new_conf['clientSecret'] = AoC.get_client_info
964
- end
965
- entry = data['AoCAccounts'].find{|i|i['organization'].eql?(organization)}
966
- if entry.nil?
967
- data['AoCAccounts'].push(new_conf)
968
- formatter.display_status("Creating new aoc entry: #{organization}")
969
- else
970
- formatter.display_status("Updating existing aoc entry: #{organization}")
971
- entry.merge!(new_conf)
972
- end
973
- File.write(cli_conf_file, JSON.pretty_generate(data))
974
- return Main.result_status("Updated: #{cli_conf_file}")
975
- when :detect
976
- # need url / username
977
- BasicAuthPlugin.register_options(@agents)
978
- return {type: :single_object, data: identify_plugin_for_url(options.get_option(:url, is_type: :mandatory))}
978
+ # detect plugins by url and optional query
979
+ apps = identify_plugins_for_url.freeze
980
+ return {
981
+ type: :object_list,
982
+ data: apps
983
+ } if action.eql?(:detect)
984
+ return wizard_find(apps)
979
985
  when :coffee
980
986
  if OpenApplication.instance.url_method.eql?(:text)
981
987
  require 'aspera/preview/terminal'
982
- return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body, reserved_lines: 3))
988
+ return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body))
983
989
  end
984
990
  OpenApplication.instance.uri(COFFEE_IMAGE)
985
991
  return Main.result_nothing
@@ -1002,7 +1008,7 @@ module Aspera
1002
1008
  return {type: :single_object, data: email_settings}
1003
1009
  when :proxy_check
1004
1010
  # ensure fpac was provided
1005
- options.get_option(:fpac, is_type: :mandatory)
1011
+ options.get_option(:fpac, mandatory: true)
1006
1012
  server_url = options.get_next_argument('server url')
1007
1013
  return Main.result_status(@pac_exec.find_proxy_for_url(server_url))
1008
1014
  when :check_update
@@ -1013,9 +1019,9 @@ module Aspera
1013
1019
  else
1014
1020
  Log.log.info{"Creating Demo server preset: #{DEMO_SERVER_PRESET}"}
1015
1021
  @config_presets[DEMO_SERVER_PRESET] = {
1016
- 'url' => 'ssh://' + DEMO + '.asperasoft.com:33001',
1017
- 'username' => AOC_COMMAND_V2,
1018
- 'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + AOC_COMMAND_V2
1022
+ 'url' => "ssh://#{DEMO}.asperasoft.com:33001",
1023
+ 'username' => ASPERA,
1024
+ 'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + ASPERA # cspell:disable-line
1019
1025
  }
1020
1026
  end
1021
1027
  @config_presets[CONF_PRESET_DEFAULT] ||= {}
@@ -1027,40 +1033,138 @@ module Aspera
1027
1033
  @config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND] = DEMO_SERVER_PRESET
1028
1034
  Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
1029
1035
  end
1030
- save_presets_to_config_file
1031
1036
  return Main.result_status('Done')
1032
1037
  when :vault then execute_vault
1038
+ when :throw
1039
+ # :type [String]
1040
+ options
1041
+ exception_class_name = options.get_next_argument('exception class name', mandatory: true)
1042
+ exception_text = options.get_next_argument('exception text', mandatory: true)
1043
+ exception_class = Object.const_get(exception_class_name)
1044
+ raise "#{exception_class} is not an exception: #{exception_class.class}" unless exception_class <= Exception
1045
+ raise exception_class, exception_text
1033
1046
  else raise 'INTERNAL ERROR: wrong case'
1034
1047
  end
1035
1048
  end
1036
1049
 
1037
- # @return email server setting with defaults if not defined
1050
+ def wizard_find(apps)
1051
+ identification = if apps.length.eql?(1)
1052
+ Log.log.debug{"Detected: #{identification}"}
1053
+ apps.first
1054
+ else
1055
+ formatter.display_status('Multiple applications detected, please select from:')
1056
+ formatter.display_results({type: :object_list, data: apps, fields: %w[product url version]})
1057
+ answer = options.prompt_user_input_in_list('product', apps.map{|a|a[:product]})
1058
+ apps.find{|a|a[:product].eql?(answer)}
1059
+ end
1060
+ wiz_url = identification[:url]
1061
+ Log.log.debug{Log.dump(:identification, identification, :ruby)}
1062
+ formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
1063
+ # set url for instantiation of plugin
1064
+ options.add_option_preset({url: wiz_url})
1065
+ # 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)
1068
+ # instantiate plugin: command line options will be known, e.g. private_key
1069
+ plugin_instance = wiz_plugin_class.new(@agents)
1070
+ wiz_params = {
1071
+ object: plugin_instance
1072
+ }
1073
+ # is private key needed ?
1074
+ if options.known_options.key?(:private_key) &&
1075
+ (!wiz_plugin_class.respond_to?(:private_key_required?) || wiz_plugin_class.private_key_required?(wiz_url))
1076
+ # lets see if path to priv key is provided
1077
+ private_key_path = options.get_option(:key_path)
1078
+ # give a chance to provide
1079
+ if private_key_path.nil?
1080
+ formatter.display_status('Please provide the path to your private RSA key, or nothing to generate one:')
1081
+ private_key_path = options.get_option(:key_path, mandatory: true).to_s
1082
+ # private_key_path = File.expand_path(private_key_path)
1083
+ end
1084
+ # else generate path
1085
+ if private_key_path.empty?
1086
+ private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
1087
+ end
1088
+ if File.exist?(private_key_path)
1089
+ formatter.display_status('Using existing key:')
1090
+ else
1091
+ formatter.display_status("Generating #{DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
1092
+ Config.generate_rsa_private_key(path: private_key_path)
1093
+ formatter.display_status('Created key:')
1094
+ end
1095
+ formatter.display_status(private_key_path)
1096
+ private_key_pem = File.read(private_key_path)
1097
+ options.set_option(:private_key, private_key_pem)
1098
+ wiz_params[:private_key_path] = private_key_path
1099
+ wiz_params[:pub_key_pem] = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
1100
+ end
1101
+ Log.log.debug{Log.dump(:wiz_params, wiz_params)}
1102
+ # finally, call the wizard
1103
+ wizard_result = wiz_plugin_class.wizard(**wiz_params)
1104
+ 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)
1106
+ # get preset name from user or default
1107
+ wiz_preset_name = options.get_option(:id)
1108
+ if wiz_preset_name.nil?
1109
+ elements = [
1110
+ identification[:product],
1111
+ URI.parse(wiz_url).host
1112
+ ]
1113
+ elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link)
1114
+ wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
1115
+ end
1116
+ # test mode does not change conf file
1117
+ return {type: :single_object, data: wizard_result} if options.get_option(:test_mode)
1118
+ # Write configuration file
1119
+ formatter.display_status("Preparing preset: #{wiz_preset_name}")
1120
+ # init defaults if necessary
1121
+ @config_presets[CONF_PRESET_DEFAULT] ||= {}
1122
+ option_override = options.get_option(:override, mandatory: true)
1123
+ 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])
1125
+ raise Cli::Error, "Preset already exists: #{wiz_preset_name} (use --override=yes or --id=<name>)" \
1126
+ if !option_override && @config_presets.key?(wiz_preset_name)
1127
+ @config_presets[wiz_preset_name] = wizard_result[:preset_value].stringify_keys
1128
+ test_args = wizard_result[:test_args]
1129
+ if options.get_option(:default, mandatory: true)
1130
+ formatter.display_status("Setting config preset as default for #{identification[:product]}")
1131
+ @config_presets[CONF_PRESET_DEFAULT][identification[:product].to_s] = wiz_preset_name
1132
+ else
1133
+ test_args = "-P#{wiz_preset_name} #{test_args}"
1134
+ end
1135
+ # TODO: actually test the command
1136
+ return Main.result_status("You can test with:\n#{@info[:name]} #{identification[:product]} #{test_args}")
1137
+ end
1138
+
1139
+ # @return [Hash] email server setting with defaults if not defined
1038
1140
  def email_settings
1039
- smtp = options.get_option(:smtp, is_type: :mandatory)
1141
+ smtp = options.get_option(:smtp, mandatory: true)
1040
1142
  # change string keys into symbol keys
1041
- smtp = smtp.keys.each_with_object({}){|v, m|m[v.to_sym] = smtp[v]; }
1143
+ smtp = smtp.symbolize_keys
1042
1144
  # defaults
1043
- smtp[:tls] ||= true
1044
- smtp[:port] ||= smtp[:tls] ? 587 : 25
1145
+ smtp[:tls] = !smtp[:ssl] unless smtp.key?(:tls)
1146
+ smtp[:port] ||= if smtp[:tls]
1147
+ 587
1148
+ elsif smtp[:ssl]
1149
+ 465
1150
+ else
1151
+ 25
1152
+ end
1045
1153
  smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
1046
1154
  smtp[:from_name] ||= smtp[:from_email].gsub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
1047
1155
  smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/, '') if smtp.key?(:from_email)
1048
1156
  # check minimum required
1049
1157
  %i[server port domain].each do |n|
1050
- raise "Missing smtp parameter: #{n}" unless smtp.key?(n)
1158
+ raise "Missing mandatory smtp parameter: #{n}" unless smtp.key?(n)
1051
1159
  end
1052
1160
  Log.log.debug{"smtp=#{smtp}"}
1053
1161
  return smtp
1054
1162
  end
1055
1163
 
1056
- # create a clean binding (ruby variable environment)
1057
- def empty_binding
1058
- Kernel.binding
1059
- end
1060
-
1164
+ # send email using ERB template
1061
1165
  def send_email_template(email_template_default: nil, values: {})
1062
- values[:to] ||= options.get_option(:notif_to, is_type: :mandatory)
1063
- notif_template = options.get_option(:notif_template, is_type: email_template_default.nil? ? :mandatory : :optional) || email_template_default
1166
+ values[:to] ||= options.get_option(:notify_to, mandatory: true)
1167
+ notify_template = options.get_option(:notify_template, mandatory: email_template_default.nil?) || email_template_default
1064
1168
  mail_conf = email_settings
1065
1169
  values[:from_name] ||= mail_conf[:from_name]
1066
1170
  values[:from_email] ||= mail_conf[:from_email]
@@ -1070,56 +1174,66 @@ module Aspera
1070
1174
  start_options = [mail_conf[:domain]]
1071
1175
  start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
1072
1176
  # create a binding with only variables defined in values
1073
- template_binding = empty_binding
1177
+ template_binding = Environment.empty_binding
1074
1178
  # add variables to binding
1075
1179
  values.each do |k, v|
1076
1180
  raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
1077
1181
  template_binding.local_variable_set(k, v)
1078
1182
  end
1079
1183
  # execute template
1080
- msg_with_headers = ERB.new(notif_template).result(template_binding)
1081
- Log.dump(:msg_with_headers, msg_with_headers)
1184
+ msg_with_headers = ERB.new(notify_template).result(template_binding)
1185
+ Log.log.debug{Log.dump(:msg_with_headers, msg_with_headers)}
1186
+ require 'net/smtp'
1082
1187
  smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
1083
1188
  smtp.enable_starttls if mail_conf[:tls]
1189
+ smtp.enable_tls if mail_conf[:ssl]
1084
1190
  smtp.start(*start_options) do |smtp_session|
1085
1191
  smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
1086
1192
  end
1193
+ nil
1087
1194
  end
1088
1195
 
1089
- def save_presets_to_config_file
1196
+ # Save current configuration to config file
1197
+ # return true if file was saved
1198
+ def save_config_file_if_needed
1090
1199
  raise 'no configuration loaded' if @config_presets.nil?
1091
- FileUtils.mkdir_p(@main_folder) unless Dir.exist?(@main_folder)
1092
- Log.log.debug{"Writing #{@option_config_file}"}
1093
- File.write(@option_config_file, @config_presets.to_yaml)
1200
+ current_checksum = config_checksum
1201
+ return false if @config_checksum_on_disk.eql?(current_checksum)
1202
+ FileUtils.mkdir_p(@main_folder)
1094
1203
  Environment.restrict_file_access(@main_folder)
1095
- Environment.restrict_file_access(@option_config_file)
1204
+ Log.log.info{"Writing #{@option_config_file}"}
1205
+ formatter.display_status('Saving config file.')
1206
+ Environment.write_file_restricted(@option_config_file, force: true) {@config_presets.to_yaml}
1207
+ @config_checksum_on_disk = current_checksum
1208
+ return true
1096
1209
  end
1097
1210
 
1098
1211
  # returns [String] name if config_presets has default
1099
1212
  # returns nil if there is no config or bypass default params
1100
- def get_plugin_default_config_name(plugin_sym)
1213
+ def get_plugin_default_config_name(plugin_name_sym)
1101
1214
  raise 'internal error: config_presets shall be defined' if @config_presets.nil?
1102
1215
  if !@use_plugin_defaults
1103
1216
  Log.log.debug('skip default config')
1104
1217
  return nil
1105
1218
  end
1106
1219
  if @config_presets.key?(CONF_PRESET_DEFAULT) &&
1107
- @config_presets[CONF_PRESET_DEFAULT].key?(plugin_sym.to_s)
1108
- default_config_name = @config_presets[CONF_PRESET_DEFAULT][plugin_sym.to_s]
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]
1109
1222
  if !@config_presets.key?(default_config_name)
1110
1223
  Log.log.error do
1111
- "Default config name [#{default_config_name}] specified for plugin [#{plugin_sym}], but it does not exist in config file.\n"\
1112
- 'Please fix the issue: either create preset with one parameter: '\
1113
- "(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_sym})."
1224
+ "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
1225
+ '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})."
1114
1227
  end
1115
1228
  end
1116
- raise CliError, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
1229
+ raise Cli::Error, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
1117
1230
  return default_config_name
1118
1231
  end
1119
1232
  return nil
1120
1233
  end # get_plugin_default_config_name
1121
1234
 
1122
- ALLOWED_KEYS = %i[password username description].freeze
1235
+ # TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
1236
+ # @return [Hash] result of execution of vault command
1123
1237
  def execute_vault
1124
1238
  command = options.get_next_command(%i[list show create delete password])
1125
1239
  case command
@@ -1147,21 +1261,22 @@ module Aspera
1147
1261
  end
1148
1262
  end
1149
1263
 
1264
+ # @return [String] value from vault matching <name>.<param>
1150
1265
  def vault_value(name)
1151
1266
  m = name.match(/^(.+)\.(.+)$/)
1152
1267
  raise 'vault name shall match <name>.<param>' if m.nil?
1268
+ # this raise exception if label not found:
1153
1269
  info = vault.get(label: m[1])
1154
- # raise "no such vault entry: #{m[1]}" if info.nil?
1155
1270
  value = info[m[2].to_sym]
1156
1271
  raise "no such entry value: #{m[2]}" if value.nil?
1157
1272
  return value
1158
1273
  end
1159
1274
 
1275
+ # @return [Object] vault, from options or cache
1160
1276
  def vault
1161
1277
  if @vault.nil?
1162
1278
  vault_info = options.get_option(:vault) || {'type' => 'file', 'name' => 'vault.bin'}
1163
- vault_password = options.get_option(:vault_password, is_type: :mandatory)
1164
- raise 'vault must be Hash' unless vault_info.is_a?(Hash)
1279
+ vault_password = options.get_option(:vault_password, mandatory: true)
1165
1280
  vault_type = vault_info['type'] || 'file'
1166
1281
  vault_name = vault_info['name'] || (vault_type.eql?('file') ? 'vault.bin' : PROGRAM_NAME)
1167
1282
  case vault_type
@@ -1173,12 +1288,11 @@ module Aspera
1173
1288
  case Environment.os
1174
1289
  when Environment::OS_X
1175
1290
  @vault = Keychain::MacosSystem.new(vault_name, vault_password)
1176
- when Environment::OS_WINDOWS, Environment::OS_LINUX, Environment::OS_AIX
1177
- raise 'not implemented'
1178
- else raise 'Error, OS not supported'
1291
+ else
1292
+ raise 'not implemented for this OS'
1179
1293
  end
1180
1294
  else
1181
- raise CliBadArgument, "Unknown vault type: #{vault_type}"
1295
+ raise Cli::BadArgument, "Unknown vault type: #{vault_type}"
1182
1296
  end
1183
1297
  end
1184
1298
  raise 'No vault defined' if @vault.nil?
@@ -1197,7 +1311,7 @@ module Aspera
1197
1311
  @config_presets.each do |_k, v|
1198
1312
  next unless v.is_a?(Hash)
1199
1313
  conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
1200
- return v if conf_url.eql?(url) && v['username'].eql?(username)
1314
+ return self.class.protect_presets(v) if conf_url.eql?(url) && v['username'].eql?(username)
1201
1315
  end
1202
1316
  nil
1203
1317
  end
@@ -1214,169 +1328,6 @@ module Aspera
1214
1328
  end
1215
1329
  return secret
1216
1330
  end
1217
-
1218
- def wizard_aoc(params)
1219
- formatter.display_status('Detected: Aspera on Cloud'.bold)
1220
- params[:plugin_name] = AOC_COMMAND_CURRENT
1221
- organization = AoC.parse_url(params[:instance_url]).first
1222
- # if not defined by user, generate name
1223
- params[:preset_name] = [params[:application], organization].join('_') if params[:preset_name].nil?
1224
- formatter.display_status("Preparing preset: #{params[:preset_name]}")
1225
- # init defaults if necessary
1226
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
1227
- option_override = options.get_option(:override, is_type: :mandatory)
1228
- params[:option_default] = options.get_option(:default, is_type: :mandatory)
1229
- Log.log.error{"override=#{option_override} -> #{option_override.class}"}
1230
- raise CliError, "A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1231
- if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].key?(params[:plugin_name])
1232
- raise CliError, "Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1233
- if !option_override && @config_presets.key?(params[:preset_name])
1234
- # lets see if path to priv key is provided
1235
- private_key_path = options.get_option(:pkeypath)
1236
- # give a chance to provide
1237
- if private_key_path.nil?
1238
- formatter.display_status('Please provide path to your private RSA key, or empty to generate one:')
1239
- private_key_path = options.get_option(:pkeypath, is_type: :mandatory).to_s
1240
- end
1241
- # else generate path
1242
- if private_key_path.empty?
1243
- private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
1244
- end
1245
- if File.exist?(private_key_path)
1246
- formatter.display_status('Using existing key:')
1247
- else
1248
- formatter.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
1249
- generate_rsa_private_key(private_key_path, DEFAULT_PRIVKEY_LENGTH)
1250
- formatter.display_status('Created:')
1251
- end
1252
- formatter.display_status(private_key_path)
1253
- pub_key_pem = OpenSSL::PKey::RSA.new(File.read(private_key_path)).public_key.to_s
1254
- # declare command line options for AoC
1255
- require 'aspera/cli/plugins/aoc'
1256
- # make username mandatory for jwt, this triggers interactive input
1257
- options.get_option(:username, is_type: :mandatory)
1258
- # instantiate AoC plugin, so that command line options are known
1259
- aoc_api = self.class.plugin_class(params[:plugin_name]).new(@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path})).aoc_api
1260
- auto_set_pub_key = false
1261
- auto_set_jwt = false
1262
- use_browser_authentication = false
1263
- if options.get_option(:use_generic_client)
1264
- formatter.display_status('Using global client_id.')
1265
- formatter.display_status('Please Login to your Aspera on Cloud instance.'.red)
1266
- formatter.display_status('Navigate to your "Account Settings"'.red)
1267
- formatter.display_status('Check or update the value of "Public Key" to be:'.red.blink)
1268
- formatter.display_status(pub_key_pem.to_s)
1269
- if !options.get_option(:test_mode)
1270
- formatter.display_status('Once updated or validated, press enter.')
1271
- OpenApplication.instance.uri(params[:instance_url])
1272
- $stdin.gets
1273
- end
1274
- else
1275
- formatter.display_status('Using organization specific client_id.')
1276
- if options.get_option(:client_id).nil? || options.get_option(:client_secret, is_type: :optional).nil?
1277
- formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
1278
- formatter.display_status('Go to: Apps->Admin->Organization->Integrations')
1279
- formatter.display_status('Create or check if there is an existing integration named:')
1280
- formatter.display_status("- name: #{@info[:name]}")
1281
- formatter.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
1282
- formatter.display_status('- origin: localhost')
1283
- formatter.display_status('Once created or identified,')
1284
- formatter.display_status('Please enter:'.red)
1285
- end
1286
- OpenApplication.instance.uri("#{params[:instance_url]}/#{AOC_PATH_API_CLIENTS}")
1287
- options.get_option(:client_id, is_type: :mandatory)
1288
- options.get_option(:client_secret, is_type: :mandatory)
1289
- use_browser_authentication = true
1290
- end
1291
- if use_browser_authentication
1292
- formatter.display_status('We will use web authentication to bootstrap.')
1293
- auto_set_pub_key = true
1294
- auto_set_jwt = true
1295
- aoc_api.oauth.generic_parameters[:grant_method] = :web
1296
- aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
1297
- aoc_api.oauth.specific_parameters[:redirect_uri] = DEFAULT_REDIRECT
1298
- end
1299
- myself = aoc_api.read('self')[:data]
1300
- if auto_set_pub_key
1301
- raise CliError, 'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
1302
- formatter.display_status('Updating profile with new key')
1303
- aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
1304
- end
1305
- if auto_set_jwt
1306
- formatter.display_status('Enabling JWT for client')
1307
- aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
1308
- end
1309
- formatter.display_status("Creating new config preset: #{params[:preset_name]}")
1310
- @config_presets[params[:preset_name]] = {
1311
- :url.to_s => options.get_option(:url),
1312
- :username.to_s => myself['email'],
1313
- :auth.to_s => :jwt.to_s,
1314
- :private_key.to_s => '@file:' + private_key_path
1315
- }
1316
- # set only if non nil
1317
- %i[client_id client_secret].each do |s|
1318
- o = options.get_option(s)
1319
- @config_presets[params[:preset_name]][s.to_s] = o unless o.nil?
1320
- end
1321
- params[:test_args] = "#{params[:plugin_name]} user profile show"
1322
- end
1323
-
1324
- def wizard_faspex5(params)
1325
- formatter.display_status('Detected: Faspex v5'.bold)
1326
- # if not defined by user, generate unique name
1327
- params[:preset_name] = [params[:application]].concat(URI.parse(params[:instance_url]).host.gsub(/[^a-z0-9.]/, '').split('.')).join('_') \
1328
- if params[:preset_name].nil?
1329
- formatter.display_status("Preparing preset: #{params[:preset_name]}")
1330
- # init defaults if necessary
1331
- @config_presets[CONF_PRESET_DEFAULT] ||= {}
1332
- option_override = options.get_option(:override, is_type: :mandatory)
1333
- params[:option_default] = options.get_option(:default, is_type: :mandatory)
1334
- Log.log.error{"override=#{option_override} -> #{option_override.class}"}
1335
- raise CliError, "A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1336
- if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].key?(params[:plugin_name])
1337
- raise CliError, "Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1338
- if !option_override && @config_presets.key?(params[:preset_name])
1339
- # lets see if path to priv key is provided
1340
- private_key_path = options.get_option(:pkeypath)
1341
- # give a chance to provide
1342
- if private_key_path.nil?
1343
- formatter.display_status('Please provide path to your private RSA key, or empty to generate one:')
1344
- private_key_path = options.get_option(:pkeypath, is_type: :mandatory).to_s
1345
- end
1346
- # else generate path
1347
- if private_key_path.empty?
1348
- private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
1349
- end
1350
- if File.exist?(private_key_path)
1351
- formatter.display_status('Using existing key:')
1352
- else
1353
- formatter.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
1354
- generate_rsa_private_key(private_key_path, DEFAULT_PRIVKEY_LENGTH)
1355
- formatter.display_status('Created:')
1356
- end
1357
- formatter.display_status(private_key_path)
1358
- pub_key_pem = OpenSSL::PKey::RSA.new(File.read(private_key_path)).public_key.to_s
1359
- # declare command line options for AoC
1360
- require 'aspera/cli/plugins/faspex5'
1361
- self.class.plugin_class(params[:plugin_name]).new(@agents.merge({skip_basic_auth_options: true}))
1362
- formatter.display_status('Please login to Faspex 5.'.red)
1363
- OpenApplication.instance.uri(params[:instance_url])
1364
- formatter.display_status('Navigate to: 𓃑 → Admin → Configurations → API clients')
1365
- formatter.display_status('Create a client with:')
1366
- formatter.display_status('- JWT enabled')
1367
- formatter.display_status('- The following public key:')
1368
- formatter.display_status(pub_key_pem.to_s)
1369
- formatter.display_status('Once created, copy the following parameters:')
1370
- @config_presets[params[:preset_name]] = {
1371
- :url.to_s => options.get_option(:url),
1372
- :username.to_s => options.get_option(:username),
1373
- :auth.to_s => :jwt.to_s,
1374
- :private_key.to_s => '@file:' + private_key_path,
1375
- :client_id.to_s => options.get_option(:client_id, is_type: :mandatory),
1376
- :client_secret.to_s => options.get_option(:client_secret, is_type: :mandatory)
1377
- }
1378
- params[:test_args] = "#{params[:plugin_name]} user profile show"
1379
- end
1380
1331
  end
1381
1332
  end
1382
1333
  end