aspera-cli 4.14.0 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +54 -3
- data/CONTRIBUTING.md +7 -7
- data/README.md +1457 -880
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +198 -127
- data/lib/aspera/ascmd.rb +24 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -171
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +101 -147
- data/lib/aspera/cli/manager.rb +160 -124
- data/lib/aspera/cli/plugin.rb +70 -59
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +239 -273
- data/lib/aspera/cli/plugins/ats.rb +8 -5
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +516 -375
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +99 -84
- data/lib/aspera/cli/plugins/faspex5.rb +179 -148
- data/lib/aspera/cli/plugins/node.rb +219 -153
- data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
- data/lib/aspera/cli/plugins/preview.rb +46 -32
- data/lib/aspera/cli/plugins/server.rb +57 -17
- data/lib/aspera/cli/plugins/shares.rb +34 -12
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +45 -55
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/environment.rb +17 -6
- data/lib/aspera/fasp/agent_aspera.rb +126 -0
- data/lib/aspera/fasp/agent_base.rb +31 -77
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +88 -102
- data/lib/aspera/fasp/agent_httpgw.rb +196 -192
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -34
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +43 -184
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +59 -26
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/transfer_spec.rb +1 -1
- data/lib/aspera/fasp/uri.rb +4 -4
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +57 -16
- data/lib/aspera/node.rb +97 -14
- data/lib/aspera/oauth.rb +36 -18
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -2
- data/lib/aspera/preview/generator.rb +22 -35
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +24 -13
- data/lib/aspera/preview/utils.rb +19 -26
- data/lib/aspera/rest.rb +103 -72
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +15 -14
- data/lib/aspera/rest_errors_aspera.rb +37 -34
- data/lib/aspera/secret_hider.rb +14 -16
- data/lib/aspera/ssh.rb +4 -1
- data/lib/aspera/sync.rb +128 -122
- data/lib/aspera/temp_file_manager.rb +10 -3
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +9 -4
- data.tar.gz.sig +0 -0
- metadata +33 -15
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- 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/
|
17
|
-
require 'aspera/
|
18
|
-
require 'aspera/aoc'
|
21
|
+
require 'aspera/persistency_folder'
|
22
|
+
require 'aspera/line_logger'
|
19
23
|
require 'aspera/rest'
|
20
|
-
require '
|
21
|
-
require 'base64'
|
22
|
-
require 'net/smtp'
|
24
|
+
require 'aspera/log'
|
23
25
|
require 'open3'
|
24
26
|
require 'date'
|
25
27
|
require 'erb'
|
@@ -38,23 +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
|
-
# default redirect for AoC web auth
|
44
|
-
DEFAULT_REDIRECT = 'http://localhost:12345'
|
45
46
|
# folder containing custom plugins in user's config folder
|
46
47
|
ASPERA_PLUGINS_FOLDERNAME = 'plugins'
|
48
|
+
PERSISTENCY_FOLDER = 'persist_store'
|
47
49
|
RUBY_FILE_EXT = '.rb'
|
48
50
|
ASPERA = 'aspera'
|
49
|
-
AOC_COMMAND = 'aoc'
|
50
51
|
SERVER_COMMAND = 'server'
|
51
52
|
APP_NAME_SDK = 'sdk'
|
52
53
|
CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
|
53
|
-
CONNECT_VERSIONS = 'connectversions.js'
|
54
|
+
CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
|
54
55
|
TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
|
55
56
|
DEMO = 'demo'
|
56
|
-
DEMO_SERVER_PRESET = 'demoserver'
|
57
|
-
AOC_PATH_API_CLIENTS = 'admin/api-clients'
|
57
|
+
DEMO_SERVER_PRESET = 'demoserver' # cspell: disable-line
|
58
58
|
EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
|
59
59
|
From: <%=from_name%> <<%=from_email%>>
|
60
60
|
To: <<%=to%>>
|
@@ -63,103 +63,196 @@ module Aspera
|
|
63
63
|
This email was sent to test #{PROGRAM_NAME}.
|
64
64
|
END_OF_TEMPLATE
|
65
65
|
# special extended values
|
66
|
-
|
67
|
-
|
68
|
-
EXTV_VAULT = :vault
|
66
|
+
EXTEND_PRESET = :preset
|
67
|
+
EXTEND_VAULT = :vault
|
69
68
|
PRESET_DIG_SEPARATOR = '.'
|
70
69
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
71
|
-
DEFAULT_PRIV_KEY_FILENAME = '
|
72
|
-
|
70
|
+
DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
|
71
|
+
DEFAULT_PRIV_KEY_LENGTH = 4096
|
73
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
|
74
77
|
private_constant :DEFAULT_CONFIG_FILENAME,
|
75
78
|
:CONF_PRESET_CONFIG,
|
76
79
|
:CONF_PRESET_VERSION,
|
77
80
|
:CONF_PRESET_DEFAULT,
|
78
81
|
:CONF_PRESET_GLOBAL,
|
79
|
-
:DEFAULT_REDIRECT,
|
80
82
|
:ASPERA_PLUGINS_FOLDERNAME,
|
81
83
|
:RUBY_FILE_EXT,
|
82
84
|
:ASPERA,
|
83
|
-
:AOC_COMMAND,
|
84
85
|
:DEMO,
|
85
86
|
:TRANSFER_SDK_ARCHIVE_URL,
|
86
|
-
:AOC_PATH_API_CLIENTS,
|
87
87
|
:DEMO_SERVER_PRESET,
|
88
88
|
:EMAIL_TEST_TEMPLATE,
|
89
|
-
:
|
90
|
-
:
|
91
|
-
:EXTV_VAULT,
|
89
|
+
:EXTEND_PRESET,
|
90
|
+
:EXTEND_VAULT,
|
92
91
|
:DEFAULT_CHECK_NEW_VERSION_DAYS,
|
93
92
|
:DEFAULT_PRIV_KEY_FILENAME,
|
94
93
|
:SERVER_COMMAND,
|
95
94
|
:PRESET_DIG_SEPARATOR,
|
96
|
-
: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
|
+
|
97
155
|
def initialize(env, params)
|
98
|
-
raise 'env and params must be Hash' unless env.is_a?(Hash) && params.is_a?(Hash)
|
99
|
-
raise 'missing param' unless %i[
|
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
|
100
159
|
super(env)
|
101
160
|
@info = params
|
102
|
-
@main_folder = default_app_main_folder
|
103
161
|
@plugins = {}
|
104
162
|
@plugin_lookup_folders = []
|
105
163
|
@use_plugin_defaults = true
|
106
164
|
@config_presets = nil
|
165
|
+
@config_checksum_on_disk = nil
|
107
166
|
@connect_versions = nil
|
108
167
|
@vault = nil
|
109
|
-
@conf_file_default = File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
110
|
-
@option_config_file = @conf_file_default
|
111
168
|
@pac_exec = nil
|
112
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!
|
113
188
|
Log.log.debug{"#{@info[:name]} folder: #{@main_folder}"}
|
114
|
-
#
|
189
|
+
# data persistency manager
|
190
|
+
env[:persistency] = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
|
191
|
+
# set folders for plugin lookup
|
115
192
|
add_plugin_lookup_folder(self.class.gem_plugins_folder)
|
116
193
|
add_plugin_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
117
|
-
#
|
118
|
-
options.declare(
|
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))
|
119
199
|
options.parse_options!
|
120
|
-
# read
|
200
|
+
# read config file (set @config_presets)
|
121
201
|
read_config_file
|
122
202
|
# add preset handler (needed for smtp)
|
123
|
-
ExtendedValue.instance.set_handler(
|
124
|
-
ExtendedValue.instance.set_handler(
|
125
|
-
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)})
|
126
205
|
# load defaults before it can be overridden
|
127
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')
|
128
211
|
options.parse_options!
|
212
|
+
# declare generic plugin options only after handlers are declared
|
213
|
+
Plugin.declare_generic_options(options)
|
214
|
+
# configuration options
|
129
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
|
130
220
|
options.declare(:override, 'Wizard: override existing value', values: :bool, default: :no)
|
131
|
-
options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: true)
|
132
221
|
options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', values: :bool, default: true)
|
133
222
|
options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
|
134
|
-
options.declare(:
|
135
|
-
|
223
|
+
options.declare(:key_path, 'Wizard: path to private key for JWT')
|
224
|
+
# Transfer SDK options
|
136
225
|
options.declare(:ascp_path, 'Path to ascp', handler: {o: Fasp::Installation.instance, m: :ascp_path})
|
137
226
|
options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
|
138
|
-
options.declare(:smtp, 'SMTP configuration', types: Hash)
|
139
|
-
options.declare(:fpac, 'Proxy auto configuration script')
|
140
|
-
options.declare(:proxy_credentials, 'HTTP proxy credentials (Array with user and password)')
|
141
|
-
options.declare(:secret, 'Secret for access keys')
|
142
|
-
options.declare(:vault, 'Vault for secrets')
|
143
|
-
options.declare(:vault_password, 'Vault password')
|
144
227
|
options.declare(:sdk_url, 'URL to get SDK', default: TRANSFER_SDK_ARCHIVE_URL)
|
145
228
|
options.declare(:sdk_folder, 'SDK folder path', handler: {o: Fasp::Installation.instance, m: :sdk_folder})
|
146
|
-
options.declare(:
|
147
|
-
|
148
|
-
options.declare(:
|
149
|
-
options.declare(:
|
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)
|
150
242
|
options.parse_options!
|
243
|
+
@progress_bar = TransferProgress.new if options.get_option(:progress_bar)
|
151
244
|
# Check SDK folder is set or not, for compatibility, we check in two places
|
152
245
|
sdk_folder = Fasp::Installation.instance.sdk_folder rescue nil
|
153
246
|
if sdk_folder.nil?
|
154
247
|
@sdk_default_location = true
|
155
248
|
Log.log.debug('SDK folder is not set, checking default')
|
156
249
|
# new location
|
157
|
-
sdk_folder = default_app_main_folder(app_name: APP_NAME_SDK)
|
250
|
+
sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK)
|
158
251
|
Log.log.debug{"checking: #{sdk_folder}"}
|
159
252
|
if !Dir.exist?(sdk_folder)
|
160
253
|
Log.log.debug{"not exists: #{sdk_folder}"}
|
161
254
|
# former location
|
162
|
-
former_sdk_folder = File.join(default_app_main_folder, APP_NAME_SDK)
|
255
|
+
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: @info[:name]), APP_NAME_SDK)
|
163
256
|
Log.log.debug{"checking: #{former_sdk_folder}"}
|
164
257
|
sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
165
258
|
end
|
@@ -169,36 +262,109 @@ module Aspera
|
|
169
262
|
pac_script = options.get_option(:fpac)
|
170
263
|
# create PAC executor
|
171
264
|
@pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
|
172
|
-
|
173
|
-
if !
|
174
|
-
raise
|
175
|
-
|
176
|
-
@pac_exec.proxy_user =
|
177
|
-
@pac_exec.proxy_pass =
|
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]
|
178
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
|
179
281
|
end
|
180
282
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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 = []
|
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
|
185
313
|
end
|
186
314
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
323
|
+
end
|
324
|
+
|
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
|
192
330
|
end
|
193
331
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
367
|
+
end
|
202
368
|
end
|
203
369
|
|
204
370
|
def check_gem_version
|
@@ -211,7 +377,7 @@ module Aspera
|
|
211
377
|
end
|
212
378
|
if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(RUBY_FUTURE_MINIMUM_VERSION)
|
213
379
|
Log.log.warn do
|
214
|
-
"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, " \
|
215
381
|
"you are using #{Environment.ruby_version}"
|
216
382
|
end
|
217
383
|
end
|
@@ -226,7 +392,6 @@ module Aspera
|
|
226
392
|
def periodic_check_newer_gem_version
|
227
393
|
# get verification period
|
228
394
|
delay_days = options.get_option(:version_check_days, mandatory: true)
|
229
|
-
Log.log.info{"check days: #{delay_days}"}
|
230
395
|
# check only if not zero day
|
231
396
|
return if delay_days.eql?(0)
|
232
397
|
# get last date from persistency
|
@@ -237,17 +402,11 @@ module Aspera
|
|
237
402
|
id: 'version_last_check')
|
238
403
|
# get persisted date or nil
|
239
404
|
current_date = Date.today
|
240
|
-
last_check_days =
|
241
|
-
|
242
|
-
|
243
|
-
rescue StandardError
|
244
|
-
# negative value will force check
|
245
|
-
-1
|
246
|
-
end
|
247
|
-
Log.log.debug{"days elapsed: #{last_check_days}"}
|
248
|
-
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
|
249
408
|
# generate timestamp
|
250
|
-
last_check_array[0] = current_date.strftime(
|
409
|
+
last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
|
251
410
|
check_date_persist.save
|
252
411
|
# compare this version and the one on internet
|
253
412
|
check_data = check_gem_version
|
@@ -266,7 +425,7 @@ module Aspera
|
|
266
425
|
Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
|
267
426
|
# get javascript object only
|
268
427
|
found = connect_versions_javascript.match(/^.*? = (.*);/)
|
269
|
-
raise
|
428
|
+
raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
|
270
429
|
all_data = JSON.parse(found[1])
|
271
430
|
@connect_versions = all_data['entries']
|
272
431
|
end
|
@@ -284,66 +443,45 @@ module Aspera
|
|
284
443
|
return nil
|
285
444
|
end
|
286
445
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
File.write(path, priv_key.to_s)
|
294
|
-
File.write(path + '.pub', priv_key.public_key.to_s)
|
295
|
-
Environment.restrict_file_access(path)
|
296
|
-
Environment.restrict_file_access(path + '.pub')
|
297
|
-
nil
|
298
|
-
end
|
299
|
-
|
300
|
-
# folder containing plugins in the gem's main folder
|
301
|
-
def gem_plugins_folder
|
302
|
-
File.dirname(File.expand_path(__FILE__))
|
303
|
-
end
|
304
|
-
|
305
|
-
# name of englobing module
|
306
|
-
# @return "Aspera::Cli::Plugins"
|
307
|
-
def module_full_name
|
308
|
-
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)
|
309
452
|
end
|
453
|
+
return global_default_preset_name
|
454
|
+
end
|
310
455
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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] = {}
|
315
463
|
end
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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]}"}
|
322
471
|
end
|
472
|
+
selected_preset[param_name] = param_value
|
473
|
+
formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
|
474
|
+
nil
|
323
475
|
end
|
324
476
|
|
325
477
|
# set parameter and value in global config
|
326
478
|
# creates one if none already created
|
327
479
|
# @return preset name that contains global default
|
328
480
|
def set_global_default(key, value)
|
329
|
-
|
330
|
-
global_default_preset_name = get_plugin_default_config_name(CONF_GLOBAL_SYM)
|
331
|
-
if global_default_preset_name.nil?
|
332
|
-
global_default_preset_name = CONF_PRESET_GLOBAL
|
333
|
-
@config_presets[CONF_PRESET_DEFAULT] ||= {}
|
334
|
-
@config_presets[CONF_PRESET_DEFAULT][CONF_GLOBAL_SYM.to_s] = global_default_preset_name
|
335
|
-
end
|
336
|
-
@config_presets[global_default_preset_name] ||= {}
|
337
|
-
@config_presets[global_default_preset_name][key.to_s] = value
|
338
|
-
formatter.display_status("Updated: #{global_default_preset_name}: #{key} <- #{value}")
|
339
|
-
save_presets_to_config_file
|
340
|
-
return global_default_preset_name
|
481
|
+
set_preset_key(global_default_preset, key, value)
|
341
482
|
end
|
342
483
|
|
343
|
-
public
|
344
|
-
|
345
484
|
# $HOME/.aspera/`program_name`
|
346
|
-
attr_reader :main_folder
|
347
485
|
attr_reader :gem_url, :plugins
|
348
486
|
attr_accessor :option_config_file
|
349
487
|
|
@@ -351,40 +489,17 @@ module Aspera
|
|
351
489
|
# @param config_name name of the preset in config file
|
352
490
|
# @param include_path used to detect and avoid include loops
|
353
491
|
def preset_by_name(config_name, include_path=[])
|
354
|
-
raise
|
492
|
+
raise Cli::Error, 'loop in include' if include_path.include?(config_name)
|
355
493
|
include_path = include_path.clone # avoid messing up if there are multiple branches
|
356
494
|
current = @config_presets
|
357
495
|
config_name.split(PRESET_DIG_SEPARATOR).each do |name|
|
358
|
-
raise
|
496
|
+
raise Cli::Error, "Expecting Hash for sub key: #{include_path} (#{current.class})" unless current.is_a?(Hash)
|
359
497
|
include_path.push(name)
|
360
498
|
current = current[name]
|
361
|
-
raise
|
499
|
+
raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
|
362
500
|
end
|
363
|
-
|
364
|
-
|
365
|
-
when String then return ExtendedValue.instance.evaluate(current)
|
366
|
-
else return current
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
# @return the hash value with 'incps' keys expanded to include other presets
|
371
|
-
# @param hash_val
|
372
|
-
# @param include_path to avoid inclusion loop
|
373
|
-
def expanded_with_preset_includes(hash_val, include_path=[])
|
374
|
-
raise CliError, "#{EXTV_INCLUDE_PRESETS} requires a Hash, have #{hash_val.class}" unless hash_val.is_a?(Hash)
|
375
|
-
if hash_val.key?(EXTV_INCLUDE_PRESETS)
|
376
|
-
memory = hash_val.clone
|
377
|
-
includes = memory[EXTV_INCLUDE_PRESETS]
|
378
|
-
memory.delete(EXTV_INCLUDE_PRESETS)
|
379
|
-
hash_val = {}
|
380
|
-
raise "#{EXTV_INCLUDE_PRESETS} must be an Array" unless includes.is_a?(Array)
|
381
|
-
raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map(&:class).uniq.eql?([String])
|
382
|
-
includes.each do |preset_name|
|
383
|
-
hash_val.merge!(preset_by_name(preset_name, include_path))
|
384
|
-
end
|
385
|
-
hash_val.merge!(memory)
|
386
|
-
end
|
387
|
-
return hash_val
|
501
|
+
current = self.class.protect_presets(current) unless current.is_a?(String)
|
502
|
+
return ExtendedValue.instance.evaluate(current)
|
388
503
|
end
|
389
504
|
|
390
505
|
def option_use_product=(value)
|
@@ -420,6 +535,10 @@ module Aspera
|
|
420
535
|
end
|
421
536
|
end
|
422
537
|
|
538
|
+
def config_checksum
|
539
|
+
JSON.generate(@config_presets).hash
|
540
|
+
end
|
541
|
+
|
423
542
|
# read config file and validate format
|
424
543
|
def read_config_file
|
425
544
|
Log.log.debug{"config file is: #{@option_config_file}".red}
|
@@ -427,38 +546,35 @@ module Aspera
|
|
427
546
|
search_files = [@option_config_file]
|
428
547
|
# find first existing file (or nil)
|
429
548
|
conf_file_to_load = search_files.find{|f| File.exist?(f)}
|
430
|
-
# require save if old version of file
|
431
|
-
save_required = false
|
432
549
|
# if no file found, create default config
|
433
550
|
if conf_file_to_load.nil?
|
434
|
-
Log.log.warn{"No config file found.
|
435
|
-
@config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_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
|
436
554
|
else
|
437
555
|
Log.log.debug{"loading #{@option_config_file}"}
|
438
556
|
@config_presets = YAML.load_file(conf_file_to_load)
|
557
|
+
@config_checksum_on_disk = config_checksum
|
439
558
|
end
|
440
559
|
files_to_copy = []
|
441
|
-
Log.log.debug{
|
560
|
+
Log.log.debug{Log.dump('Available_presets', @config_presets)}
|
442
561
|
raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
|
443
562
|
# check there is at least the config section
|
444
|
-
|
445
|
-
raise "Cannot find key: #{CONF_PRESET_CONFIG}"
|
446
|
-
end
|
563
|
+
raise "Cannot find key: #{CONF_PRESET_CONFIG}" unless @config_presets.key?(CONF_PRESET_CONFIG)
|
447
564
|
version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
|
448
|
-
if version.nil?
|
449
|
-
raise 'No version found in config section.'
|
450
|
-
end
|
565
|
+
raise 'No version found in config section.' if version.nil?
|
451
566
|
Log.log.debug{"conf version: #{version}"}
|
452
|
-
# if there are any conversion needed, those happen here.
|
453
|
-
#
|
454
|
-
if
|
455
|
-
|
456
|
-
|
457
|
-
|
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?
|
458
574
|
Log.log.warn('Copying referenced files')
|
459
575
|
files_to_copy.each do |file|
|
460
576
|
FileUtils.cp(file, @main_folder)
|
461
|
-
Log.log.warn{"
|
577
|
+
Log.log.warn{"#{file} -> #{@main_folder}"}
|
462
578
|
end
|
463
579
|
end
|
464
580
|
rescue Psych::SyntaxError => e
|
@@ -473,7 +589,7 @@ module Aspera
|
|
473
589
|
Log.log.warn{"Renamed config file to #{new_name}."}
|
474
590
|
Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
|
475
591
|
end
|
476
|
-
raise
|
592
|
+
raise Cli::Error, e.to_s
|
477
593
|
end
|
478
594
|
|
479
595
|
# find plugins in defined paths
|
@@ -505,45 +621,39 @@ module Aspera
|
|
505
621
|
|
506
622
|
# Find a plugin, and issue the "require"
|
507
623
|
# @return [Hash] plugin info: { product: , url:, version: }
|
508
|
-
def
|
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)
|
509
627
|
check_only = check_only.to_sym unless check_only.nil?
|
628
|
+
found_apps = []
|
510
629
|
plugins.each do |plugin_name_sym, plugin_info|
|
511
630
|
# no detection for internal plugin
|
512
631
|
next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
|
513
632
|
next if check_only && !check_only.eql?(plugin_name_sym)
|
514
633
|
# load plugin class
|
515
634
|
require plugin_info[:require_stanza]
|
516
|
-
|
635
|
+
detect_plugin_class = self.class.plugin_class(plugin_name_sym)
|
517
636
|
# requires detection method
|
518
|
-
next unless
|
519
|
-
current_url = url
|
637
|
+
next unless detect_plugin_class.respond_to?(:detect)
|
520
638
|
detection_info = nil
|
521
|
-
# first try : direct
|
522
639
|
begin
|
523
|
-
detection_info =
|
640
|
+
detection_info = detect_plugin_class.detect(app_url)
|
524
641
|
rescue OpenSSL::SSL::SSLError => e
|
525
642
|
Log.log.warn(e.message)
|
526
643
|
Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
|
527
644
|
rescue StandardError => e
|
528
|
-
Log.log.debug{"
|
529
|
-
|
530
|
-
# second try : is there a redirect ?
|
531
|
-
if detection_info.nil?
|
532
|
-
begin
|
533
|
-
# TODO: check if redirect ?
|
534
|
-
new_url = Rest.new(base_url: url).call(operation: 'GET', subpath: '', redirect_max: 1)[:http].uri.to_s
|
535
|
-
unless url.eql?(new_url)
|
536
|
-
detection_info = c.detect(new_url)
|
537
|
-
current_url = new_url
|
538
|
-
end
|
539
|
-
rescue StandardError => e
|
540
|
-
Log.log.debug{"Cannot detect #{plugin_name_sym} : #{e.message}"}
|
541
|
-
end
|
645
|
+
Log.log.debug{"detect error: #{e}"}
|
646
|
+
next
|
542
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
|
543
651
|
# if there is a redirect, then the detector can override the url.
|
544
|
-
|
652
|
+
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
545
653
|
end # loop
|
546
|
-
raise "No known application found at #{
|
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
|
547
657
|
end
|
548
658
|
|
549
659
|
def execute_connect_action
|
@@ -551,7 +661,7 @@ module Aspera
|
|
551
661
|
if %i[info version].include?(command)
|
552
662
|
connect_id = options.get_next_argument('id or title')
|
553
663
|
one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
|
554
|
-
raise
|
664
|
+
raise Cli::NoSuchIdentifier.new(:connect, connect_id) if one_res.nil?
|
555
665
|
end
|
556
666
|
case command
|
557
667
|
when :list
|
@@ -594,21 +704,14 @@ module Aspera
|
|
594
704
|
ascp_path = options.get_next_argument('path to ascp')
|
595
705
|
ascp_version = Fasp::Installation.instance.get_ascp_version(ascp_path)
|
596
706
|
formatter.display_status("ascp version: #{ascp_version}")
|
597
|
-
|
598
|
-
return Main.
|
707
|
+
set_global_default(:ascp_path, ascp_path)
|
708
|
+
return Main.result_nothing
|
599
709
|
when :show # shows files used
|
600
710
|
return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
|
601
711
|
when :info # shows files used
|
602
|
-
data = Fasp::Installation
|
603
|
-
m[v.to_s] =
|
604
|
-
begin
|
605
|
-
Fasp::Installation.instance.path(v)
|
606
|
-
rescue => e
|
607
|
-
e.message
|
608
|
-
end
|
609
|
-
end
|
712
|
+
data = Fasp::Installation.instance.file_paths
|
610
713
|
# read PATHs from ascp directly, and pvcl modules as well
|
611
|
-
Open3.popen3(
|
714
|
+
Open3.popen3(data['ascp'], '-DDL-') do |_stdin, _stdout, stderr, thread|
|
612
715
|
last_line = ''
|
613
716
|
while (line = stderr.gets)
|
614
717
|
line.chomp!
|
@@ -616,8 +719,11 @@ module Aspera
|
|
616
719
|
case line
|
617
720
|
when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
|
618
721
|
data[Regexp.last_match(1)] = Regexp.last_match(3)
|
619
|
-
when /^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$/
|
620
|
-
|
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]}")
|
621
727
|
when %r{^DBG License result \(/license/(\S+)\): (.+)$}
|
622
728
|
data[Regexp.last_match(1)] = Regexp.last_match(2)
|
623
729
|
when /^LOG (.+) version ([0-9.]+)$/
|
@@ -631,7 +737,14 @@ module Aspera
|
|
631
737
|
raise last_line
|
632
738
|
end
|
633
739
|
end
|
634
|
-
|
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
|
635
748
|
# log is "-" no need to display
|
636
749
|
data.delete('log')
|
637
750
|
# show command line transfer spec
|
@@ -641,16 +754,16 @@ module Aspera
|
|
641
754
|
command = options.get_next_command(%i[list use])
|
642
755
|
case command
|
643
756
|
when :list
|
644
|
-
return {type: :object_list, data: Fasp::
|
757
|
+
return {type: :object_list, data: Fasp::Products.installed_products, fields: %w[name app_root]}
|
645
758
|
when :use
|
646
759
|
default_product = options.get_next_argument('product name')
|
647
760
|
Fasp::Installation.instance.use_ascp_from_product(default_product)
|
648
|
-
|
649
|
-
return Main.
|
761
|
+
set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
|
762
|
+
return Main.result_nothing
|
650
763
|
end
|
651
764
|
when :install
|
652
765
|
# reset to default location, if older default was used
|
653
|
-
Fasp::Installation.instance.sdk_folder = default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
|
766
|
+
Fasp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
|
654
767
|
v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url, mandatory: true))
|
655
768
|
return Main.result_status("Installed version #{v}")
|
656
769
|
when :spec
|
@@ -680,24 +793,23 @@ module Aspera
|
|
680
793
|
def execute_preset(action: nil, name: nil)
|
681
794
|
action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
|
682
795
|
name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
|
796
|
+
name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
683
797
|
# those operations require existing option
|
684
798
|
raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
|
685
|
-
selected_preset = @config_presets[name]
|
686
799
|
case action
|
687
800
|
when :list
|
688
801
|
return {type: :value_list, data: @config_presets.keys, name: 'name'}
|
689
802
|
when :overview
|
690
|
-
|
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)}
|
691
805
|
when :show
|
692
|
-
|
693
|
-
return {type: :single_object, data: selected_preset}
|
806
|
+
return {type: :single_object, data: self.class.protect_presets(@config_presets[name])}
|
694
807
|
when :delete
|
695
808
|
@config_presets.delete(name)
|
696
|
-
save_presets_to_config_file
|
697
809
|
return Main.result_status("Deleted: #{name}")
|
698
810
|
when :get
|
699
811
|
param_name = options.get_next_argument('parameter name')
|
700
|
-
value =
|
812
|
+
value = @config_presets[name][param_name]
|
701
813
|
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
702
814
|
case value
|
703
815
|
when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
|
@@ -705,30 +817,20 @@ module Aspera
|
|
705
817
|
return {type: :single_object, data: value}
|
706
818
|
when :unset
|
707
819
|
param_name = options.get_next_argument('parameter name')
|
708
|
-
|
709
|
-
save_presets_to_config_file
|
820
|
+
@config_presets[name].delete(param_name)
|
710
821
|
return Main.result_status("Removed: #{name}: #{param_name}")
|
711
822
|
when :set
|
712
823
|
param_name = options.get_next_argument('parameter name')
|
713
|
-
param_value = options.get_next_argument('parameter value')
|
714
824
|
param_name = Manager.option_line_to_name(param_name)
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
end
|
719
|
-
if selected_preset.key?(param_name)
|
720
|
-
Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
|
721
|
-
end
|
722
|
-
selected_preset[param_name] = param_value
|
723
|
-
save_presets_to_config_file
|
724
|
-
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
|
725
828
|
when :initialize
|
726
829
|
config_value = options.get_next_argument('extended value', type: Hash)
|
727
830
|
if @config_presets.key?(name)
|
728
831
|
Log.log.warn{"configuration already exists: #{name}, overwriting"}
|
729
832
|
end
|
730
833
|
@config_presets[name] = config_value
|
731
|
-
save_presets_to_config_file
|
732
834
|
return Main.result_status("Modified: #{@option_config_file}")
|
733
835
|
when :update
|
734
836
|
# get unprocessed options
|
@@ -736,9 +838,6 @@ module Aspera
|
|
736
838
|
Log.log.debug{"opts=#{unprocessed_options}"}
|
737
839
|
@config_presets[name] ||= {}
|
738
840
|
@config_presets[name].merge!(unprocessed_options)
|
739
|
-
# fix bug in 4.4 (creating key "true" in "default" preset)
|
740
|
-
@config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
|
741
|
-
save_presets_to_config_file
|
742
841
|
return Main.result_status("Updated: #{name}")
|
743
842
|
when :ask
|
744
843
|
options.ask_missing_mandatory = :yes
|
@@ -747,10 +846,9 @@ module Aspera
|
|
747
846
|
option_value = options.get_interactive(:option, option_name)
|
748
847
|
@config_presets[name][option_name] = option_value
|
749
848
|
end
|
750
|
-
save_presets_to_config_file
|
751
849
|
return Main.result_status("Updated: #{name}")
|
752
850
|
when :lookup
|
753
|
-
BasicAuthPlugin.
|
851
|
+
BasicAuthPlugin.declare_options(options)
|
754
852
|
url = options.get_option(:url, mandatory: true)
|
755
853
|
user = options.get_option(:username, mandatory: true)
|
756
854
|
result = lookup_preset(url: url, username: user)
|
@@ -789,8 +887,9 @@ module Aspera
|
|
789
887
|
open
|
790
888
|
documentation
|
791
889
|
genkey
|
890
|
+
remote_certificate
|
792
891
|
gem
|
793
|
-
|
892
|
+
plugins
|
794
893
|
flush_tokens
|
795
894
|
echo
|
796
895
|
wizard
|
@@ -804,9 +903,10 @@ module Aspera
|
|
804
903
|
file
|
805
904
|
check_update
|
806
905
|
initdemo
|
807
|
-
vault
|
906
|
+
vault
|
907
|
+
throw].freeze
|
808
908
|
|
809
|
-
#
|
909
|
+
# Main action procedure for plugin
|
810
910
|
def execute_action
|
811
911
|
action = options.get_next_command(ACTIONS)
|
812
912
|
case action
|
@@ -817,31 +917,45 @@ module Aspera
|
|
817
917
|
return Main.result_nothing
|
818
918
|
when :documentation
|
819
919
|
section = options.get_next_argument('private key file path', mandatory: false)
|
820
|
-
section =
|
920
|
+
section = "##{section}" unless section.nil?
|
821
921
|
OpenApplication.instance.uri("#{@info[:help]}#{section}")
|
822
922
|
return Main.result_nothing
|
823
923
|
when :genkey # generate new rsa key
|
824
924
|
private_key_path = options.get_next_argument('private key file path')
|
825
|
-
private_key_length = options.get_next_argument('size in bits', mandatory: false
|
925
|
+
private_key_length = options.get_next_argument('size in bits', mandatory: false, type: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
|
826
926
|
self.class.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
827
|
-
return Main.result_status(
|
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)
|
828
935
|
when :echo # display the content of a value given on command line
|
829
|
-
|
830
|
-
# special for csv
|
831
|
-
result[:type] = :object_list if result[:data].is_a?(Array) && result[:data].first.is_a?(Hash)
|
832
|
-
result[:type] = :single_object if result[:data].is_a?(Hash)
|
833
|
-
return result
|
936
|
+
return Formatter.auto_type(options.get_next_argument('value'))
|
834
937
|
when :flush_tokens
|
835
938
|
deleted_files = Oauth.flush_tokens
|
836
939
|
return {type: :value_list, data: deleted_files, name: 'file'}
|
837
|
-
when :
|
940
|
+
when :plugins
|
838
941
|
case options.get_next_command(%i[list create])
|
839
942
|
when :list
|
840
|
-
|
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]}
|
841
955
|
when :create
|
842
956
|
plugin_name = options.get_next_argument('name', expected: :single).downcase
|
843
|
-
|
844
|
-
plugin_file = File.join(
|
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")
|
845
959
|
content = <<~END_OF_PLUGIN_CODE
|
846
960
|
require 'aspera/cli/plugin'
|
847
961
|
module Aspera
|
@@ -858,94 +972,20 @@ module Aspera
|
|
858
972
|
File.write(plugin_file, content)
|
859
973
|
return Main.result_status("Created #{plugin_file}")
|
860
974
|
end
|
861
|
-
when :wizard
|
975
|
+
when :detect, :wizard
|
862
976
|
# interactive mode
|
863
977
|
options.ask_missing_mandatory = true
|
864
|
-
#
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
new_url = "https://#{params[:instance_url]}"
|
872
|
-
Log.log.warn("URL #{params[:instance_url]} does not start with a scheme, using #{new_url}")
|
873
|
-
params[:instance_url] = new_url
|
874
|
-
end
|
875
|
-
# allow user to tell the preset name
|
876
|
-
params[:preset_name] = options.get_option(:id)
|
877
|
-
# allow user to specify type of application (symbol)
|
878
|
-
identification = identify_plugin_for_url(params[:instance_url], check_only: value_or_query(allowed_types: String))
|
879
|
-
Log.log.debug{"Detected: #{identification}"}
|
880
|
-
formatter.display_status("Detected: #{identification[:name]} at #{identification[:url]}".bold)
|
881
|
-
# we detected application (not set by user)
|
882
|
-
params[:plugin_sym] = identification[:product]
|
883
|
-
# update the url option
|
884
|
-
params[:instance_url] = identification[:url]
|
885
|
-
options.set_option(:url, params[:instance_url])
|
886
|
-
# instantiate plugin: command line options are known and wizard can be called
|
887
|
-
plugin_instance = self.class.plugin_class(params[:plugin_sym]).new(@agents.merge({skip_basic_auth_options: true}))
|
888
|
-
raise CliBadArgument, "Detected: #{params[:plugin_sym]}, no wizard available for this application" unless plugin_instance.respond_to?(:wizard)
|
889
|
-
# get default preset name if not set by user
|
890
|
-
params[:prepare] = true
|
891
|
-
plugin_instance.send(:wizard, params)
|
892
|
-
params[:prepare] = false
|
893
|
-
|
894
|
-
if params[:need_private_key]
|
895
|
-
# lets see if path to priv key is provided
|
896
|
-
private_key_path = options.get_option(:pkeypath)
|
897
|
-
# give a chance to provide
|
898
|
-
if private_key_path.nil?
|
899
|
-
formatter.display_status('Please provide path to your private RSA key, or empty to generate one:')
|
900
|
-
private_key_path = options.get_option(:pkeypath, mandatory: true).to_s
|
901
|
-
# private_key_path = File.expand_path(private_key_path)
|
902
|
-
end
|
903
|
-
# else generate path
|
904
|
-
if private_key_path.empty?
|
905
|
-
private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
|
906
|
-
end
|
907
|
-
if File.exist?(private_key_path)
|
908
|
-
formatter.display_status('Using existing key:')
|
909
|
-
else
|
910
|
-
formatter.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
|
911
|
-
Config.generate_rsa_private_key(path: private_key_path)
|
912
|
-
formatter.display_status('Created key:')
|
913
|
-
end
|
914
|
-
formatter.display_status(private_key_path)
|
915
|
-
params[:pub_key_pem] = OpenSSL::PKey::RSA.new(File.read(private_key_path)).public_key.to_s
|
916
|
-
params[:private_key_path] = private_key_path
|
917
|
-
end
|
918
|
-
|
919
|
-
formatter.display_status("Preparing preset: #{params[:preset_name]}")
|
920
|
-
# init defaults if necessary
|
921
|
-
@config_presets[CONF_PRESET_DEFAULT] ||= {}
|
922
|
-
option_override = options.get_option(:override, mandatory: true)
|
923
|
-
raise CliError, "A default configuration already exists for plugin '#{params[:plugin_sym]}' (use --override=yes or --default=no)" \
|
924
|
-
if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULT].key?(params[:plugin_sym])
|
925
|
-
raise CliError, "Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
|
926
|
-
if !option_override && @config_presets.key?(params[:preset_name])
|
927
|
-
wizard_result = plugin_instance.send(:wizard, params)
|
928
|
-
Log.log.debug{"wizard result: #{wizard_result}"}
|
929
|
-
raise "Internal error: missing keys in wizard result: #{wizard_result.keys}" unless %i[preset_value test_args].eql?(wizard_result.keys.sort)
|
930
|
-
@config_presets[params[:preset_name]] = wizard_result[:preset_value].stringify_keys
|
931
|
-
params[:test_args] = wizard_result[:test_args]
|
932
|
-
if options.get_option(:default, mandatory: true)
|
933
|
-
formatter.display_status("Setting config preset as default for #{params[:plugin_sym]}")
|
934
|
-
@config_presets[CONF_PRESET_DEFAULT][params[:plugin_sym].to_s] = params[:preset_name]
|
935
|
-
else
|
936
|
-
params[:test_args] = "-P#{params[:preset_name]} #{params[:test_args]}"
|
937
|
-
end
|
938
|
-
formatter.display_status('Saving config file.')
|
939
|
-
save_presets_to_config_file
|
940
|
-
return Main.result_status("Done.\nYou can test with:\n#{@info[:name]} #{params[:test_args]}")
|
941
|
-
when :detect
|
942
|
-
# need url / username
|
943
|
-
BasicAuthPlugin.register_options(@agents)
|
944
|
-
return {type: :single_object, data: identify_plugin_for_url(options.get_option(:url, mandatory: true))}
|
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)
|
945
985
|
when :coffee
|
946
986
|
if OpenApplication.instance.url_method.eql?(:text)
|
947
987
|
require 'aspera/preview/terminal'
|
948
|
-
return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body
|
988
|
+
return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body))
|
949
989
|
end
|
950
990
|
OpenApplication.instance.uri(COFFEE_IMAGE)
|
951
991
|
return Main.result_nothing
|
@@ -979,7 +1019,7 @@ module Aspera
|
|
979
1019
|
else
|
980
1020
|
Log.log.info{"Creating Demo server preset: #{DEMO_SERVER_PRESET}"}
|
981
1021
|
@config_presets[DEMO_SERVER_PRESET] = {
|
982
|
-
'url' =>
|
1022
|
+
'url' => "ssh://#{DEMO}.asperasoft.com:33001",
|
983
1023
|
'username' => ASPERA,
|
984
1024
|
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + ASPERA # cspell:disable-line
|
985
1025
|
}
|
@@ -993,16 +1033,112 @@ module Aspera
|
|
993
1033
|
@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND] = DEMO_SERVER_PRESET
|
994
1034
|
Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
|
995
1035
|
end
|
996
|
-
save_presets_to_config_file
|
997
1036
|
return Main.result_status('Done')
|
998
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
|
999
1046
|
else raise 'INTERNAL ERROR: wrong case'
|
1000
1047
|
end
|
1001
1048
|
end
|
1002
1049
|
|
1003
|
-
|
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
|
1004
1140
|
def email_settings
|
1005
|
-
smtp = options.get_option(:smtp, mandatory: true
|
1141
|
+
smtp = options.get_option(:smtp, mandatory: true)
|
1006
1142
|
# change string keys into symbol keys
|
1007
1143
|
smtp = smtp.symbolize_keys
|
1008
1144
|
# defaults
|
@@ -1025,14 +1161,10 @@ module Aspera
|
|
1025
1161
|
return smtp
|
1026
1162
|
end
|
1027
1163
|
|
1028
|
-
#
|
1029
|
-
def empty_binding
|
1030
|
-
Kernel.binding
|
1031
|
-
end
|
1032
|
-
|
1164
|
+
# send email using ERB template
|
1033
1165
|
def send_email_template(email_template_default: nil, values: {})
|
1034
|
-
values[:to] ||= options.get_option(:
|
1035
|
-
|
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
|
1036
1168
|
mail_conf = email_settings
|
1037
1169
|
values[:from_name] ||= mail_conf[:from_name]
|
1038
1170
|
values[:from_email] ||= mail_conf[:from_email]
|
@@ -1042,30 +1174,38 @@ module Aspera
|
|
1042
1174
|
start_options = [mail_conf[:domain]]
|
1043
1175
|
start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
|
1044
1176
|
# create a binding with only variables defined in values
|
1045
|
-
template_binding = empty_binding
|
1177
|
+
template_binding = Environment.empty_binding
|
1046
1178
|
# add variables to binding
|
1047
1179
|
values.each do |k, v|
|
1048
1180
|
raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
|
1049
1181
|
template_binding.local_variable_set(k, v)
|
1050
1182
|
end
|
1051
1183
|
# execute template
|
1052
|
-
msg_with_headers = ERB.new(
|
1053
|
-
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'
|
1054
1187
|
smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
|
1055
1188
|
smtp.enable_starttls if mail_conf[:tls]
|
1056
1189
|
smtp.enable_tls if mail_conf[:ssl]
|
1057
1190
|
smtp.start(*start_options) do |smtp_session|
|
1058
1191
|
smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
|
1059
1192
|
end
|
1193
|
+
nil
|
1060
1194
|
end
|
1061
1195
|
|
1062
|
-
|
1196
|
+
# Save current configuration to config file
|
1197
|
+
# return true if file was saved
|
1198
|
+
def save_config_file_if_needed
|
1063
1199
|
raise 'no configuration loaded' if @config_presets.nil?
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1200
|
+
current_checksum = config_checksum
|
1201
|
+
return false if @config_checksum_on_disk.eql?(current_checksum)
|
1202
|
+
FileUtils.mkdir_p(@main_folder)
|
1067
1203
|
Environment.restrict_file_access(@main_folder)
|
1068
|
-
|
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
|
1069
1209
|
end
|
1070
1210
|
|
1071
1211
|
# returns [String] name if config_presets has default
|
@@ -1081,18 +1221,19 @@ module Aspera
|
|
1081
1221
|
default_config_name = @config_presets[CONF_PRESET_DEFAULT][plugin_name_sym.to_s]
|
1082
1222
|
if !@config_presets.key?(default_config_name)
|
1083
1223
|
Log.log.error do
|
1084
|
-
"Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n"\
|
1085
|
-
'Please fix the issue: either create preset with one parameter: '\
|
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: ' \
|
1086
1226
|
"(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_name_sym})."
|
1087
1227
|
end
|
1088
1228
|
end
|
1089
|
-
raise
|
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)
|
1090
1230
|
return default_config_name
|
1091
1231
|
end
|
1092
1232
|
return nil
|
1093
1233
|
end # get_plugin_default_config_name
|
1094
1234
|
|
1095
|
-
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
|
1096
1237
|
def execute_vault
|
1097
1238
|
command = options.get_next_command(%i[list show create delete password])
|
1098
1239
|
case command
|
@@ -1120,21 +1261,22 @@ module Aspera
|
|
1120
1261
|
end
|
1121
1262
|
end
|
1122
1263
|
|
1264
|
+
# @return [String] value from vault matching <name>.<param>
|
1123
1265
|
def vault_value(name)
|
1124
1266
|
m = name.match(/^(.+)\.(.+)$/)
|
1125
1267
|
raise 'vault name shall match <name>.<param>' if m.nil?
|
1268
|
+
# this raise exception if label not found:
|
1126
1269
|
info = vault.get(label: m[1])
|
1127
|
-
# raise "no such vault entry: #{m[1]}" if info.nil?
|
1128
1270
|
value = info[m[2].to_sym]
|
1129
1271
|
raise "no such entry value: #{m[2]}" if value.nil?
|
1130
1272
|
return value
|
1131
1273
|
end
|
1132
1274
|
|
1275
|
+
# @return [Object] vault, from options or cache
|
1133
1276
|
def vault
|
1134
1277
|
if @vault.nil?
|
1135
1278
|
vault_info = options.get_option(:vault) || {'type' => 'file', 'name' => 'vault.bin'}
|
1136
1279
|
vault_password = options.get_option(:vault_password, mandatory: true)
|
1137
|
-
raise 'vault must be Hash' unless vault_info.is_a?(Hash)
|
1138
1280
|
vault_type = vault_info['type'] || 'file'
|
1139
1281
|
vault_name = vault_info['name'] || (vault_type.eql?('file') ? 'vault.bin' : PROGRAM_NAME)
|
1140
1282
|
case vault_type
|
@@ -1146,12 +1288,11 @@ module Aspera
|
|
1146
1288
|
case Environment.os
|
1147
1289
|
when Environment::OS_X
|
1148
1290
|
@vault = Keychain::MacosSystem.new(vault_name, vault_password)
|
1149
|
-
|
1150
|
-
raise 'not implemented'
|
1151
|
-
else raise 'Error, OS not supported'
|
1291
|
+
else
|
1292
|
+
raise 'not implemented for this OS'
|
1152
1293
|
end
|
1153
1294
|
else
|
1154
|
-
raise
|
1295
|
+
raise Cli::BadArgument, "Unknown vault type: #{vault_type}"
|
1155
1296
|
end
|
1156
1297
|
end
|
1157
1298
|
raise 'No vault defined' if @vault.nil?
|
@@ -1170,7 +1311,7 @@ module Aspera
|
|
1170
1311
|
@config_presets.each do |_k, v|
|
1171
1312
|
next unless v.is_a?(Hash)
|
1172
1313
|
conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
|
1173
|
-
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)
|
1174
1315
|
end
|
1175
1316
|
nil
|
1176
1317
|
end
|