aspera-cli 4.23.0 → 4.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +37 -1
- data/CONTRIBUTING.md +86 -29
- data/README.md +2109 -1300
- data/bin/ascli +2 -1
- data/bin/asession +4 -4
- data/lib/aspera/agent/base.rb +4 -0
- data/lib/aspera/agent/connect.rb +20 -18
- data/lib/aspera/agent/desktop.rb +14 -11
- data/lib/aspera/agent/direct.rb +39 -31
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +9 -11
- data/lib/aspera/agent/transferd.rb +18 -11
- data/lib/aspera/api/aoc.rb +44 -31
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +15 -18
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +22 -16
- data/lib/aspera/ascp/installation.rb +37 -40
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +54 -23
- data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
- data/lib/aspera/cli/error.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +28 -29
- data/lib/aspera/cli/formatter.rb +191 -168
- data/lib/aspera/cli/hints.rb +29 -3
- data/lib/aspera/cli/main.rb +138 -107
- data/lib/aspera/cli/manager.rb +50 -30
- data/lib/aspera/cli/plugin.rb +148 -77
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +189 -70
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +100 -214
- data/lib/aspera/cli/plugins/console.rb +49 -18
- data/lib/aspera/cli/plugins/cos.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +45 -51
- data/lib/aspera/cli/plugins/faspex5.rb +164 -165
- data/lib/aspera/cli/plugins/faspio.rb +6 -5
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +144 -162
- data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
- data/lib/aspera/cli/plugins/preview.rb +26 -29
- data/lib/aspera/cli/plugins/server.rb +28 -28
- data/lib/aspera/cli/plugins/shares.rb +40 -28
- data/lib/aspera/cli/sync_actions.rb +101 -80
- data/lib/aspera/cli/transfer_agent.rb +51 -50
- data/lib/aspera/cli/transfer_progress.rb +29 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +157 -0
- data/lib/aspera/colors.rb +13 -8
- data/lib/aspera/command_line_builder.rb +28 -22
- data/lib/aspera/command_line_converter.rb +31 -0
- data/lib/aspera/environment.rb +145 -101
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +3 -2
- data/lib/aspera/hash_ext.rb +1 -1
- data/lib/aspera/id_generator.rb +10 -10
- data/lib/aspera/keychain/base.rb +18 -0
- data/lib/aspera/keychain/encrypted_hash.rb +6 -12
- data/lib/aspera/keychain/factory.rb +9 -3
- data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +91 -19
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +5 -3
- data/lib/aspera/oauth/factory.rb +24 -18
- data/lib/aspera/oauth/jwt.rb +13 -1
- data/lib/aspera/oauth/url_json.rb +3 -3
- data/lib/aspera/oauth/web.rb +5 -3
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -3
- data/lib/aspera/preview/generator.rb +25 -12
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +8 -6
- data/lib/aspera/proxy_auto_config.rb +1 -1
- data/lib/aspera/rest.rb +29 -22
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +13 -3
- data/lib/aspera/sync/args.schema.yaml +102 -0
- data/lib/aspera/sync/conf.schema.yaml +701 -0
- data/lib/aspera/sync/database.rb +83 -0
- data/lib/aspera/sync/operations.rb +296 -0
- data/lib/aspera/temp_file_manager.rb +3 -2
- data/lib/aspera/transfer/error.rb +1 -1
- data/lib/aspera/transfer/error_info.rb +1 -2
- data/lib/aspera/transfer/faux_file.rb +11 -10
- data/lib/aspera/transfer/parameters.rb +6 -5
- data/lib/aspera/transfer/spec.rb +15 -1
- data/lib/aspera/transfer/spec.schema.yaml +316 -293
- data/lib/aspera/transfer/spec_doc.rb +34 -16
- data/lib/aspera/transfer/uri.rb +5 -5
- data/lib/aspera/uri_reader.rb +14 -10
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +15 -13
- metadata.gz.sig +0 -0
- data/lib/aspera/transfer/async_conf.schema.yaml +0 -716
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync.rb +0 -232
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -20
- data/lib/aspera/transfer/sync_session.schema.yaml +0 -86
@@ -8,6 +8,7 @@ require 'aspera/cli/version'
|
|
8
8
|
require 'aspera/cli/formatter'
|
9
9
|
require 'aspera/cli/info'
|
10
10
|
require 'aspera/cli/transfer_progress'
|
11
|
+
require 'aspera/cli/wizard'
|
11
12
|
require 'aspera/ascp/installation'
|
12
13
|
require 'aspera/products/transferd'
|
13
14
|
require 'aspera/transfer/error_info'
|
@@ -23,6 +24,7 @@ require 'aspera/persistency_folder'
|
|
23
24
|
require 'aspera/data_repository'
|
24
25
|
require 'aspera/line_logger'
|
25
26
|
require 'aspera/rest'
|
27
|
+
require 'aspera/oauth/jwt'
|
26
28
|
require 'aspera/log'
|
27
29
|
require 'aspera/assert'
|
28
30
|
require 'aspera/oauth'
|
@@ -68,10 +70,7 @@ module Aspera
|
|
68
70
|
EXTEND_VAULT = :vault
|
69
71
|
PRESET_DIG_SEPARATOR = '.'
|
70
72
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
71
|
-
|
72
|
-
DEFAULT_PRIV_KEY_LENGTH = 4096
|
73
|
-
COFFEE_IMAGE = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
74
|
-
WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
|
73
|
+
COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
75
74
|
GEM_CHECK_DATE_FMT = '%Y/%m/%d'
|
76
75
|
# for testing only
|
77
76
|
SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
|
@@ -90,28 +89,15 @@ module Aspera
|
|
90
89
|
:EXTEND_PRESET,
|
91
90
|
:EXTEND_VAULT,
|
92
91
|
:DEFAULT_CHECK_NEW_VERSION_DAYS,
|
93
|
-
:DEFAULT_PRIV_KEY_FILENAME,
|
94
92
|
:SERVER_COMMAND,
|
95
93
|
:PRESET_DIG_SEPARATOR,
|
96
|
-
:
|
97
|
-
:WIZARD_RESULT_KEYS,
|
94
|
+
:COFFEE_IMAGE_URL,
|
98
95
|
:SELF_SIGNED_CERT,
|
99
96
|
:PERSISTENCY_FOLDER,
|
100
|
-
:DEFAULT_PRIV_KEY_LENGTH,
|
101
97
|
:CONF_OVERVIEW_KEYS,
|
102
98
|
:SMTP_CONF_PARAMS
|
103
99
|
|
104
100
|
class << self
|
105
|
-
def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
|
106
|
-
require 'openssl'
|
107
|
-
priv_key = OpenSSL::PKey::RSA.new(length)
|
108
|
-
File.write(path, priv_key.to_s)
|
109
|
-
File.write("#{path}.pub", priv_key.public_key.to_s)
|
110
|
-
Environment.restrict_file_access(path)
|
111
|
-
Environment.restrict_file_access("#{path}.pub")
|
112
|
-
nil
|
113
|
-
end
|
114
|
-
|
115
101
|
# folder containing plugins in the gem's main folder
|
116
102
|
def gem_plugins_folder
|
117
103
|
File.dirname(File.expand_path(__FILE__))
|
@@ -132,7 +118,7 @@ module Aspera
|
|
132
118
|
# return product family folder (~/.aspera)
|
133
119
|
def module_family_folder
|
134
120
|
user_home_folder = Dir.home
|
135
|
-
Aspera.assert(Dir.exist?(user_home_folder),
|
121
|
+
Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
|
136
122
|
return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
|
137
123
|
end
|
138
124
|
|
@@ -171,11 +157,12 @@ module Aspera
|
|
171
157
|
:home, 'Home folder for tool',
|
172
158
|
handler: {o: self, m: :main_folder},
|
173
159
|
types: String,
|
174
|
-
default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
|
160
|
+
default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
|
161
|
+
)
|
175
162
|
options.parse_options!
|
176
163
|
Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
|
177
164
|
# data persistency manager, created by config plugin, set for global object
|
178
|
-
|
165
|
+
context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
|
179
166
|
# set folders for plugin lookup
|
180
167
|
PluginFactory.instance.add_lookup_folder(self.class.gem_plugins_folder)
|
181
168
|
PluginFactory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
@@ -183,7 +170,8 @@ module Aspera
|
|
183
170
|
options.declare(
|
184
171
|
:config_file, 'Path to YAML file with preset configuration',
|
185
172
|
handler: {o: self, m: :option_config_file},
|
186
|
-
default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
173
|
+
default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
174
|
+
)
|
187
175
|
options.parse_options!
|
188
176
|
# read config file (set @config_presets)
|
189
177
|
read_config_file
|
@@ -204,11 +192,8 @@ module Aspera
|
|
204
192
|
options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
|
205
193
|
options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
|
206
194
|
options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
|
207
|
-
# wizard options
|
208
|
-
|
209
|
-
options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', values: :bool, default: true)
|
210
|
-
options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
|
211
|
-
options.declare(:key_path, 'Wizard: path to private key for JWT')
|
195
|
+
# declare wizard options
|
196
|
+
@wizard = Wizard.new(self, @main_folder)
|
212
197
|
# Transfer SDK options
|
213
198
|
options.declare(:ascp_path, 'Ascp: Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
|
214
199
|
options.declare(:use_product, 'Ascp: Use ascp from specified product', handler: {o: self, m: :option_use_product})
|
@@ -223,7 +208,7 @@ module Aspera
|
|
223
208
|
# HTTP options
|
224
209
|
options.declare(:insecure, 'HTTP/S: Do not validate any certificate', values: :bool, handler: {o: self, m: :option_insecure}, default: :no)
|
225
210
|
options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
|
226
|
-
options.declare(:
|
211
|
+
options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
|
227
212
|
options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
|
228
213
|
options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
|
229
214
|
options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', types: String, handler: {o: self, m: :option_http_proxy})
|
@@ -256,7 +241,7 @@ module Aspera
|
|
256
241
|
@pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
|
257
242
|
proxy_user_pass = options.get_option(:proxy_credentials)
|
258
243
|
if !proxy_user_pass.nil?
|
259
|
-
Aspera.assert(proxy_user_pass.length.eql?(2),
|
244
|
+
Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
|
260
245
|
@pac_exec.proxy_user = proxy_user_pass[0]
|
261
246
|
@pac_exec.proxy_pass = proxy_user_pass[1]
|
262
247
|
end
|
@@ -362,7 +347,8 @@ module Aspera
|
|
362
347
|
if !@ssl_warned_urls.include?(base_url)
|
363
348
|
formatter.display_message(
|
364
349
|
:error,
|
365
|
-
"#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production."
|
350
|
+
"#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production."
|
351
|
+
)
|
366
352
|
@ssl_warned_urls.push(base_url)
|
367
353
|
end
|
368
354
|
end
|
@@ -378,13 +364,39 @@ module Aspera
|
|
378
364
|
# Rest.io_http_session(http_session).debug_output = Log.log
|
379
365
|
http_session.verify_mode = SELF_SIGNED_CERT if http_session.use_ssl? && ignore_cert?(http_session.address, http_session.port)
|
380
366
|
http_session.cert_store = @certificate_store if @certificate_store
|
381
|
-
Log.log.debug{"
|
367
|
+
Log.log.debug{"Using cert store #{http_session.cert_store} (#{@certificate_store})"} unless http_session.cert_store.nil?
|
382
368
|
@option_http_options.each do |k, v|
|
383
369
|
method = "#{k}=".to_sym
|
384
370
|
# check if accessor is a method of Net::HTTP
|
385
371
|
# continue_timeout= read_timeout= write_timeout=
|
386
372
|
if http_session.respond_to?(method)
|
387
373
|
http_session.send(method, v)
|
374
|
+
elsif k.eql?('ssl_options')
|
375
|
+
# NOTE: here is a hack that allows setting SSLContext options
|
376
|
+
Aspera.assert_type(v, Array){'ssl_options'}
|
377
|
+
# more dynamic method, but more complex:
|
378
|
+
# Net::HTTP::SSL_ATTRIBUTES.push(:options) unless Net::HTTP::SSL_ATTRIBUTES.include?(:options)
|
379
|
+
# Net::HTTP::SSL_IVNAMES.push(:@options) unless Net::HTTP::SSL_IVNAMES.include?(:@options)
|
380
|
+
# Start with default options
|
381
|
+
ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
382
|
+
v.each do |opt|
|
383
|
+
case opt
|
384
|
+
when Integer
|
385
|
+
ssl_options = opt
|
386
|
+
when String
|
387
|
+
name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
|
388
|
+
raise Cli::BadArgument, "No such ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
|
389
|
+
if opt.start_with?('-')
|
390
|
+
ssl_options &= ~OpenSSL::SSL.const_get(name)
|
391
|
+
else
|
392
|
+
ssl_options |= OpenSSL::SSL.const_get(name)
|
393
|
+
end
|
394
|
+
else
|
395
|
+
Aspera.error_unexpected_value(opt.class.name){'Expected String or Integer in ssl_options'}
|
396
|
+
end
|
397
|
+
end
|
398
|
+
# http_session.instance_variable_set(:@options, ssl_options)
|
399
|
+
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] = ssl_options
|
388
400
|
else
|
389
401
|
Log.log.error{"no such HTTP session attribute: #{k}"}
|
390
402
|
end
|
@@ -423,7 +435,8 @@ module Aspera
|
|
423
435
|
check_date_persist = PersistencyActionOnce.new(
|
424
436
|
manager: persistency,
|
425
437
|
data: last_check_array,
|
426
|
-
id: 'version_last_check'
|
438
|
+
id: 'version_last_check'
|
439
|
+
)
|
427
440
|
# get persisted date or nil
|
428
441
|
current_date = Date.today
|
429
442
|
last_check_days = (current_date - Date.strptime(last_check_array.first, GEM_CHECK_DATE_FMT)) rescue nil
|
@@ -447,7 +460,7 @@ module Aspera
|
|
447
460
|
default_config_name = get_plugin_default_config_name(plugin_name_sym)
|
448
461
|
Log.log.debug{"add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}"}
|
449
462
|
options.add_option_preset(preset_by_name(default_config_name), 'default_plugin', override: false) unless default_config_name.nil?
|
450
|
-
return
|
463
|
+
return
|
451
464
|
end
|
452
465
|
|
453
466
|
# get the default global preset, or set default one
|
@@ -460,6 +473,19 @@ module Aspera
|
|
460
473
|
return result
|
461
474
|
end
|
462
475
|
|
476
|
+
def defaults_set(plugin_name, preset_name, preset_values, option_default, option_override)
|
477
|
+
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
478
|
+
raise Cli::Error, "A default configuration already exists for plugin '#{plugin_name}' (use --override=yes or --default=no)" \
|
479
|
+
if !option_override && option_default && @config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name)
|
480
|
+
raise Cli::Error, "Preset already exists: #{preset_name} (use --override=yes or provide alternate name on command line)" \
|
481
|
+
if !option_override && @config_presets.key?(preset_name)
|
482
|
+
if option_default
|
483
|
+
formatter.display_status("Setting config preset as default for #{plugin_name}")
|
484
|
+
@config_presets[CONF_PRESET_DEFAULTS][plugin_name.to_s] = preset_name
|
485
|
+
end
|
486
|
+
@config_presets[preset_name] = preset_values
|
487
|
+
end
|
488
|
+
|
463
489
|
def set_preset_key(preset, param_name, param_value)
|
464
490
|
Aspera.assert_values(param_name.class, [String, Symbol]){'parameter'}
|
465
491
|
param_name = param_name.to_s
|
@@ -492,15 +518,15 @@ module Aspera
|
|
492
518
|
attr_reader :gem_url
|
493
519
|
attr_accessor :option_config_file
|
494
520
|
|
495
|
-
# @return the hash from name (also expands possible includes)
|
496
521
|
# @param config_name name of the preset in config file
|
497
522
|
# @param include_path used to detect and avoid include loops
|
498
|
-
|
523
|
+
# @return copy of the hash from name (also expands possible includes)
|
524
|
+
def preset_by_name(config_name, include_path = [])
|
499
525
|
raise Cli::Error, 'loop in include' if include_path.include?(config_name)
|
500
526
|
include_path = include_path.clone # avoid messing up if there are multiple branches
|
501
527
|
current = @config_presets
|
502
528
|
config_name.split(PRESET_DIG_SEPARATOR).each do |name|
|
503
|
-
Aspera.assert_type(current, Hash,
|
529
|
+
Aspera.assert_type(current, Hash, type: Cli::Error){"sub key: #{include_path}"}
|
504
530
|
include_path.push(name)
|
505
531
|
current = current[name]
|
506
532
|
raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
|
@@ -537,7 +563,7 @@ module Aspera
|
|
537
563
|
when String
|
538
564
|
options.add_option_preset(preset_by_name(value), 'set_by_name')
|
539
565
|
else
|
540
|
-
raise 'Preset definition must be a String for preset name, or Hash for set of values'
|
566
|
+
raise BadArgument, 'Preset definition must be a String for preset name, or Hash for set of values'
|
541
567
|
end
|
542
568
|
end
|
543
569
|
|
@@ -563,12 +589,12 @@ module Aspera
|
|
563
589
|
@config_checksum_on_disk = config_checksum
|
564
590
|
end
|
565
591
|
files_to_copy = []
|
566
|
-
Log.
|
592
|
+
Log.dump(:available_presets, @config_presets, level: :trace1)
|
567
593
|
Aspera.assert_type(@config_presets, Hash){'config file YAML'}
|
568
594
|
# check there is at least the config section
|
569
595
|
Aspera.assert(@config_presets.key?(CONF_PRESET_CONFIG)){"Cannot find key: #{CONF_PRESET_CONFIG}"}
|
570
596
|
version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
|
571
|
-
raise 'No version found in config section.' if version.nil?
|
597
|
+
raise Error, 'No version found in config section.' if version.nil?
|
572
598
|
Log.log.debug{"conf version: #{version}"}
|
573
599
|
# VVV if there are any conversion needed, those happen here.
|
574
600
|
# fix bug in 4.4 (creating key "true" in "default" preset)
|
@@ -598,46 +624,6 @@ module Aspera
|
|
598
624
|
raise Cli::Error, e.to_s
|
599
625
|
end
|
600
626
|
|
601
|
-
# Find a plugin, and issue the "require"
|
602
|
-
# @return [Hash] plugin info: { product: , url:, version: }
|
603
|
-
def identify_plugins_for_url
|
604
|
-
app_url = options.get_next_argument('url', mandatory: true)
|
605
|
-
check_only = options.get_next_argument('plugin name', mandatory: false)
|
606
|
-
check_only = check_only.to_sym unless check_only.nil?
|
607
|
-
found_apps = []
|
608
|
-
my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
|
609
|
-
PluginFactory.instance.plugin_list.each do |plugin_name_sym|
|
610
|
-
# no detection for internal plugin
|
611
|
-
next if plugin_name_sym.eql?(my_self_plugin_sym)
|
612
|
-
next if check_only && !check_only.eql?(plugin_name_sym)
|
613
|
-
# load plugin class
|
614
|
-
detect_plugin_class = PluginFactory.instance.plugin_class(plugin_name_sym)
|
615
|
-
# requires detection method
|
616
|
-
next unless detect_plugin_class.respond_to?(:detect)
|
617
|
-
detection_info = nil
|
618
|
-
begin
|
619
|
-
Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
|
620
|
-
formatter.long_operation_running("#{plugin_name_sym}\r")
|
621
|
-
detection_info = detect_plugin_class.detect(app_url)
|
622
|
-
rescue OpenSSL::SSL::SSLError => e
|
623
|
-
Log.log.warn(e.message)
|
624
|
-
Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
|
625
|
-
rescue StandardError => e
|
626
|
-
Log.log.debug{"detect error: [#{e.class}] #{e}"}
|
627
|
-
next
|
628
|
-
end
|
629
|
-
next if detection_info.nil?
|
630
|
-
Aspera.assert_type(detection_info, Hash)
|
631
|
-
Aspera.assert_type(detection_info[:url], String) if detection_info.key?(:url)
|
632
|
-
app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
|
633
|
-
# if there is a redirect, then the detector can override the url.
|
634
|
-
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
635
|
-
end
|
636
|
-
raise "No known application found at #{app_url}" if found_apps.empty?
|
637
|
-
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
|
638
|
-
return found_apps
|
639
|
-
end
|
640
|
-
|
641
627
|
def execute_connect_action
|
642
628
|
command = options.get_next_command(%i[list info version])
|
643
629
|
if %i[info version].include?(command)
|
@@ -686,7 +672,7 @@ module Aspera
|
|
686
672
|
set_global_default(:ascp_path, ascp_path)
|
687
673
|
return Main.result_nothing
|
688
674
|
when :show
|
689
|
-
return Main.
|
675
|
+
return Main.result_text(Ascp::Installation.instance.path(:ascp))
|
690
676
|
when :info
|
691
677
|
# collect info from ascp executable
|
692
678
|
data = Ascp::Installation.instance.ascp_info
|
@@ -715,10 +701,8 @@ module Aspera
|
|
715
701
|
n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
|
716
702
|
return Main.result_status("Installed #{n} version #{v}")
|
717
703
|
when :spec
|
718
|
-
|
719
|
-
|
720
|
-
fields: Transfer::SpecDoc::TABLE_COLUMNS.map(&:to_s)
|
721
|
-
)
|
704
|
+
fields, data = Transfer::SpecDoc.man_table(Formatter)
|
705
|
+
return Main.result_object_list(data, fields: fields.map(&:to_s))
|
722
706
|
when :schema
|
723
707
|
schema = Transfer::Spec::SCHEMA.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
|
724
708
|
agent = options.get_next_argument('transfer agent name', mandatory: false)
|
@@ -772,7 +756,7 @@ module Aspera
|
|
772
756
|
raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
|
773
757
|
case action
|
774
758
|
when :list
|
775
|
-
return Main.result_value_list(@config_presets.keys, 'name')
|
759
|
+
return Main.result_value_list(@config_presets.keys, name: 'name')
|
776
760
|
when :overview
|
777
761
|
# display process modifies the value (hide secrets): we do not want to save removed secrets
|
778
762
|
data = self.class.deep_clone(@config_presets)
|
@@ -809,9 +793,7 @@ module Aspera
|
|
809
793
|
return Main.result_nothing
|
810
794
|
when :initialize
|
811
795
|
config_value = options.get_next_argument('extended value', validation: Hash)
|
812
|
-
if @config_presets.key?(name)
|
813
|
-
Log.log.warn{"configuration already exists: #{name}, overwriting"}
|
814
|
-
end
|
796
|
+
Log.log.warn{"configuration already exists: #{name}, overwriting"} if @config_presets.key?(name)
|
815
797
|
@config_presets[name] = config_value
|
816
798
|
return Main.result_status("Modified: #{@option_config_file}")
|
817
799
|
when :update
|
@@ -834,7 +816,7 @@ module Aspera
|
|
834
816
|
url = options.get_option(:url, mandatory: true)
|
835
817
|
user = options.get_option(:username, mandatory: true)
|
836
818
|
result = lookup_preset(url: url, username: user)
|
837
|
-
raise 'no such config found' if result.nil?
|
819
|
+
raise Error, 'no such config found' if result.nil?
|
838
820
|
return Main.result_single_object(result)
|
839
821
|
when :secure
|
840
822
|
identifier = options.get_next_argument('config name', mandatory: false)
|
@@ -901,7 +883,7 @@ module Aspera
|
|
901
883
|
when :preset # newer syntax
|
902
884
|
return execute_preset
|
903
885
|
when :open
|
904
|
-
Environment.open_editor(@option_config_file.to_s)
|
886
|
+
Environment.instance.open_editor(@option_config_file.to_s)
|
905
887
|
return Main.result_nothing
|
906
888
|
when :documentation
|
907
889
|
section = options.get_next_argument('private key file path', mandatory: false)
|
@@ -910,12 +892,12 @@ module Aspera
|
|
910
892
|
return Main.result_nothing
|
911
893
|
when :genkey # generate new rsa key
|
912
894
|
private_key_path = options.get_next_argument('private key file path')
|
913
|
-
private_key_length = options.get_next_argument('size in bits', mandatory: false, validation: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
|
914
|
-
|
895
|
+
private_key_length = options.get_next_argument('size in bits', mandatory: false, validation: Integer, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH)
|
896
|
+
OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
915
897
|
return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
916
898
|
when :pubkey # get pub key
|
917
899
|
private_key_pem = options.get_next_argument('private key PEM value')
|
918
|
-
return Main.
|
900
|
+
return Main.result_text(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
|
919
901
|
when :remote_certificate
|
920
902
|
cert_action = options.get_next_command(%i[chain only name])
|
921
903
|
remote_url = options.get_next_argument('remote URL')
|
@@ -923,20 +905,18 @@ module Aspera
|
|
923
905
|
raise "No certificate found for #{remote_url}" unless remote_chain&.first
|
924
906
|
case cert_action
|
925
907
|
when :chain
|
926
|
-
return Main.
|
908
|
+
return Main.result_text(remote_chain.map(&:to_pem).join("\n"))
|
927
909
|
when :only
|
928
|
-
return Main.
|
910
|
+
return Main.result_text(remote_chain.first.to_pem)
|
929
911
|
when :name
|
930
|
-
return Main.
|
912
|
+
return Main.result_text(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
|
931
913
|
end
|
932
914
|
when :echo # display the content of a value given on command line
|
933
915
|
return Main.result_auto(options.get_next_argument('value', validation: nil))
|
934
916
|
when :download
|
935
917
|
file_url = options.get_next_argument('source URL').chomp
|
936
918
|
file_dest = options.get_next_argument('file path', mandatory: false)
|
937
|
-
if file_dest.nil?
|
938
|
-
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, ''))
|
939
|
-
end
|
919
|
+
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
|
940
920
|
formatter.display_status("Downloading: #{file_url}")
|
941
921
|
Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
|
942
922
|
return Main.result_status("Saved to: #{file_dest}")
|
@@ -990,27 +970,27 @@ module Aspera
|
|
990
970
|
# interactive mode
|
991
971
|
options.ask_missing_mandatory = true
|
992
972
|
# detect plugins by url and optional query
|
993
|
-
apps = identify_plugins_for_url.freeze
|
973
|
+
apps = @wizard.identify_plugins_for_url.freeze
|
994
974
|
return Main.result_object_list(apps) if action.eql?(:detect)
|
995
|
-
return
|
975
|
+
return @wizard.find(apps)
|
996
976
|
when :coffee
|
997
|
-
return Main.result_image(
|
977
|
+
return Main.result_image(COFFEE_IMAGE_URL)
|
998
978
|
when :image
|
999
|
-
return Main.result_image(options.get_next_argument('image
|
979
|
+
return Main.result_image(options.get_next_argument('image URI or blob'))
|
1000
980
|
when :ascp
|
1001
981
|
execute_action_ascp
|
1002
982
|
when :transferd
|
1003
983
|
execute_action_transferd
|
1004
984
|
when :gem
|
1005
985
|
case options.get_next_command(%i[path version name])
|
1006
|
-
when :path then return Main.
|
1007
|
-
when :version then return Main.
|
1008
|
-
when :name then return Main.
|
986
|
+
when :path then return Main.result_text(self.class.gem_src_root)
|
987
|
+
when :version then return Main.result_text(Cli::VERSION)
|
988
|
+
when :name then return Main.result_text(Info::GEM_NAME)
|
1009
989
|
end
|
1010
990
|
when :folder
|
1011
|
-
return Main.
|
991
|
+
return Main.result_text(@main_folder)
|
1012
992
|
when :file
|
1013
|
-
return Main.
|
993
|
+
return Main.result_text(@option_config_file)
|
1014
994
|
when :email_test
|
1015
995
|
send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
1016
996
|
return Main.result_nothing
|
@@ -1020,7 +1000,7 @@ module Aspera
|
|
1020
1000
|
# ensure fpac was provided
|
1021
1001
|
options.get_option(:fpac, mandatory: true)
|
1022
1002
|
server_url = options.get_next_argument('server url')
|
1023
|
-
return Main.
|
1003
|
+
return Main.result_text(@pac_exec.get_proxies(server_url))
|
1024
1004
|
when :check_update
|
1025
1005
|
return Main.result_single_object(check_gem_version)
|
1026
1006
|
when :initdemo
|
@@ -1047,101 +1027,11 @@ module Aspera
|
|
1047
1027
|
when :vault then execute_vault
|
1048
1028
|
when :test then return execute_test
|
1049
1029
|
when :platform
|
1050
|
-
return Main.
|
1030
|
+
return Main.result_text(Environment.instance.architecture)
|
1051
1031
|
else Aspera.error_unreachable_line
|
1052
1032
|
end
|
1053
1033
|
end
|
1054
1034
|
|
1055
|
-
def wizard_find(apps)
|
1056
|
-
identification = if apps.length.eql?(1)
|
1057
|
-
Log.log.debug{"Detected: #{identification}"}
|
1058
|
-
apps.first
|
1059
|
-
else
|
1060
|
-
formatter.display_status('Multiple applications detected, please select from:')
|
1061
|
-
formatter.display_results(type: :object_list, data: apps, fields: %w[product url version])
|
1062
|
-
answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
|
1063
|
-
apps.find{ |a| a[:product].eql?(answer)}
|
1064
|
-
end
|
1065
|
-
wiz_preset_name = options.get_next_argument('preset name', default: '')
|
1066
|
-
Log.log.debug{Log.dump(:identification, identification)}
|
1067
|
-
wiz_url = identification[:url]
|
1068
|
-
formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
|
1069
|
-
# set url for instantiation of plugin
|
1070
|
-
options.add_option_preset({url: wiz_url}, 'wizard')
|
1071
|
-
# instantiate plugin: command line options will be known and wizard can be called
|
1072
|
-
wiz_plugin_class = PluginFactory.instance.plugin_class(identification[:product])
|
1073
|
-
Aspera.assert(wiz_plugin_class.respond_to?(:wizard), exception_class: Cli::BadArgument) do
|
1074
|
-
"Detected: #{identification[:product]}, but this application has no wizard"
|
1075
|
-
end
|
1076
|
-
# instantiate plugin: command line options will be known, e.g. private_key
|
1077
|
-
plugin_instance = wiz_plugin_class.new(**init_params)
|
1078
|
-
wiz_params = {
|
1079
|
-
object: plugin_instance
|
1080
|
-
}
|
1081
|
-
# is private key needed ?
|
1082
|
-
if options.known_options.key?(:private_key) &&
|
1083
|
-
(!wiz_plugin_class.respond_to?(:private_key_required?) || wiz_plugin_class.private_key_required?(wiz_url))
|
1084
|
-
# lets see if path to priv key is provided
|
1085
|
-
private_key_path = options.get_option(:key_path)
|
1086
|
-
# give a chance to provide
|
1087
|
-
if private_key_path.nil?
|
1088
|
-
formatter.display_status('Please provide the path to your private RSA key, or nothing to generate one:')
|
1089
|
-
private_key_path = options.get_option(:key_path, mandatory: true).to_s
|
1090
|
-
end
|
1091
|
-
# else generate path
|
1092
|
-
if private_key_path.empty?
|
1093
|
-
private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
|
1094
|
-
end
|
1095
|
-
if File.exist?(private_key_path)
|
1096
|
-
formatter.display_status('Using existing key:')
|
1097
|
-
else
|
1098
|
-
formatter.display_status("Generating #{DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
|
1099
|
-
self.class.generate_rsa_private_key(path: private_key_path)
|
1100
|
-
formatter.display_status('Created key:')
|
1101
|
-
end
|
1102
|
-
formatter.display_status(private_key_path)
|
1103
|
-
private_key_pem = File.read(private_key_path)
|
1104
|
-
options.set_option(:private_key, private_key_pem)
|
1105
|
-
wiz_params[:private_key_path] = private_key_path
|
1106
|
-
wiz_params[:pub_key_pem] = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
|
1107
|
-
end
|
1108
|
-
Log.log.debug{Log.dump(:wiz_params, wiz_params)}
|
1109
|
-
# finally, call the wizard
|
1110
|
-
wizard_result = wiz_plugin_class.wizard(**wiz_params)
|
1111
|
-
Log.log.debug{"wizard result: #{wizard_result}"}
|
1112
|
-
Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
|
1113
|
-
# get preset name from user or default
|
1114
|
-
if wiz_preset_name.empty?
|
1115
|
-
elements = [
|
1116
|
-
identification[:product],
|
1117
|
-
URI.parse(wiz_url).host
|
1118
|
-
]
|
1119
|
-
elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link) rescue nil
|
1120
|
-
wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
|
1121
|
-
end
|
1122
|
-
# test mode does not change conf file
|
1123
|
-
return Main.result_single_object(wizard_result) if options.get_option(:test_mode)
|
1124
|
-
# Write configuration file
|
1125
|
-
formatter.display_status("Preparing preset: #{wiz_preset_name}")
|
1126
|
-
# init defaults if necessary
|
1127
|
-
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
1128
|
-
option_override = options.get_option(:override, mandatory: true)
|
1129
|
-
raise Cli::Error, "A default configuration already exists for plugin '#{identification[:product]}' (use --override=yes or --default=no)" \
|
1130
|
-
if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULTS].key?(identification[:product])
|
1131
|
-
raise Cli::Error, "Preset already exists: #{wiz_preset_name} (use --override=yes or --id=<name>)" \
|
1132
|
-
if !option_override && @config_presets.key?(wiz_preset_name)
|
1133
|
-
@config_presets[wiz_preset_name] = wizard_result[:preset_value].stringify_keys
|
1134
|
-
test_args = wizard_result[:test_args]
|
1135
|
-
if options.get_option(:default, mandatory: true)
|
1136
|
-
formatter.display_status("Setting config preset as default for #{identification[:product]}")
|
1137
|
-
@config_presets[CONF_PRESET_DEFAULTS][identification[:product].to_s] = wiz_preset_name
|
1138
|
-
else
|
1139
|
-
test_args = "-P#{wiz_preset_name} #{test_args}"
|
1140
|
-
end
|
1141
|
-
# TODO: actually test the command
|
1142
|
-
return Main.result_status("You can test with:\n#{Info::CMD_NAME} #{identification[:product]} #{test_args}")
|
1143
|
-
end
|
1144
|
-
|
1145
1035
|
# @return [Hash] email server setting with defaults if not defined
|
1146
1036
|
def email_settings
|
1147
1037
|
smtp = options.get_option(:smtp, mandatory: true)
|
@@ -1193,7 +1083,7 @@ module Aspera
|
|
1193
1083
|
end
|
1194
1084
|
# execute template
|
1195
1085
|
msg_with_headers = ERB.new(notify_template).result(template_binding)
|
1196
|
-
Log.
|
1086
|
+
Log.dump(:msg_with_headers, msg_with_headers)
|
1197
1087
|
require 'net/smtp'
|
1198
1088
|
smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
|
1199
1089
|
smtp.enable_starttls if mail_conf[:tls]
|
@@ -1207,7 +1097,7 @@ module Aspera
|
|
1207
1097
|
# Save current configuration to config file
|
1208
1098
|
# return true if file was saved
|
1209
1099
|
def save_config_file_if_needed
|
1210
|
-
raise 'no configuration loaded' if @config_presets.nil?
|
1100
|
+
raise Error, 'no configuration loaded' if @config_presets.nil?
|
1211
1101
|
current_checksum = config_checksum
|
1212
1102
|
return false if @config_checksum_on_disk.eql?(current_checksum)
|
1213
1103
|
FileUtils.mkdir_p(@main_folder)
|
@@ -1225,7 +1115,7 @@ module Aspera
|
|
1225
1115
|
Aspera.assert(!@config_presets.nil?){'config_presets shall be defined'}
|
1226
1116
|
if !@use_plugin_defaults
|
1227
1117
|
Log.log.debug('skip default config')
|
1228
|
-
return
|
1118
|
+
return
|
1229
1119
|
end
|
1230
1120
|
if @config_presets.key?(CONF_PRESET_DEFAULTS) &&
|
1231
1121
|
@config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
|
@@ -1241,7 +1131,7 @@ module Aspera
|
|
1241
1131
|
raise Cli::Error, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
|
1242
1132
|
return default_config_name
|
1243
1133
|
end
|
1244
|
-
return
|
1134
|
+
return
|
1245
1135
|
end
|
1246
1136
|
|
1247
1137
|
# @return [Hash] result of execution of vault command
|
@@ -1256,11 +1146,7 @@ module Aspera
|
|
1256
1146
|
when :show
|
1257
1147
|
return Main.result_single_object(vault.get(label: options.get_next_argument('label')))
|
1258
1148
|
when :create
|
1259
|
-
|
1260
|
-
info = options.get_next_argument('info', validation: Hash)
|
1261
|
-
info = info.symbolize_keys
|
1262
|
-
info[:label] = label
|
1263
|
-
vault.set(info)
|
1149
|
+
vault.set(options.get_next_argument('info', validation: Hash).symbolize_keys)
|
1264
1150
|
return Main.result_status('Secret added')
|
1265
1151
|
when :delete
|
1266
1152
|
label_to_delete = options.get_next_argument('label')
|
@@ -1276,7 +1162,7 @@ module Aspera
|
|
1276
1162
|
# @return [String] value from vault matching <name>.<param>
|
1277
1163
|
def vault_value(name)
|
1278
1164
|
m = name.split('.')
|
1279
|
-
raise 'vault name shall match <name>.<param>' unless m.length.eql?(2)
|
1165
|
+
raise BadArgument, 'vault name shall match <name>.<param>' unless m.length.eql?(2)
|
1280
1166
|
# this raise exception if label not found:
|
1281
1167
|
info = vault.get(label: m[0])
|
1282
1168
|
value = info[m[1].to_sym]
|
@@ -1306,16 +1192,16 @@ module Aspera
|
|
1306
1192
|
# options
|
1307
1193
|
exception_class_name = options.get_next_argument('exception class name', mandatory: true)
|
1308
1194
|
exception_text = options.get_next_argument('exception text', mandatory: true)
|
1309
|
-
|
1310
|
-
Aspera.assert(
|
1311
|
-
raise
|
1195
|
+
type = Object.const_get(exception_class_name)
|
1196
|
+
Aspera.assert(type <= Exception){"#{type} is not an exception: #{type.class}"}
|
1197
|
+
raise type, exception_text
|
1312
1198
|
when :web
|
1313
1199
|
end
|
1314
1200
|
end
|
1315
1201
|
|
1316
1202
|
# version of URL without trailing "/" and removing default port
|
1317
1203
|
def canonical_url(url)
|
1318
|
-
url.
|
1204
|
+
url.chomp('/').sub(%r{^(https://[^/]+):443$}, '\1')
|
1319
1205
|
end
|
1320
1206
|
|
1321
1207
|
# Look for a preset that has the corresponding URL and username
|