aspera-cli 4.14.0 → 4.16.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/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- 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 +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- 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 +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- 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
- data/lib/aspera/sync.rb +0 -213
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# cspell:ignore initdemo genkey pubkey 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/
|
|
21
|
+
require 'aspera/persistency_folder'
|
|
22
|
+
require 'aspera/data_repository'
|
|
23
|
+
require 'aspera/line_logger'
|
|
19
24
|
require 'aspera/rest'
|
|
20
|
-
require '
|
|
21
|
-
require '
|
|
22
|
-
require 'net/smtp'
|
|
25
|
+
require 'aspera/log'
|
|
26
|
+
require 'aspera/assert'
|
|
23
27
|
require 'open3'
|
|
24
28
|
require 'date'
|
|
25
29
|
require 'erb'
|
|
@@ -33,28 +37,28 @@ module Aspera
|
|
|
33
37
|
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
34
38
|
# default config file
|
|
35
39
|
DEFAULT_CONFIG_FILENAME = 'config.yaml'
|
|
40
|
+
DEFAULT_VAULT_FILENAME = 'vault.bin'
|
|
36
41
|
# reserved preset names
|
|
37
42
|
CONF_PRESET_CONFIG = 'config'
|
|
38
43
|
CONF_PRESET_VERSION = 'version'
|
|
39
|
-
|
|
44
|
+
CONF_PRESET_DEFAULTS = 'default'
|
|
40
45
|
CONF_PRESET_GLOBAL = 'global_common_defaults'
|
|
46
|
+
# special name to identify value of default
|
|
47
|
+
GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
|
|
41
48
|
CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
|
|
42
49
|
CONF_GLOBAL_SYM = :config
|
|
43
|
-
# default redirect for AoC web auth
|
|
44
|
-
DEFAULT_REDIRECT = 'http://localhost:12345'
|
|
45
50
|
# folder containing custom plugins in user's config folder
|
|
46
51
|
ASPERA_PLUGINS_FOLDERNAME = 'plugins'
|
|
52
|
+
PERSISTENCY_FOLDER = 'persist_store'
|
|
47
53
|
RUBY_FILE_EXT = '.rb'
|
|
48
54
|
ASPERA = 'aspera'
|
|
49
|
-
AOC_COMMAND = 'aoc'
|
|
50
55
|
SERVER_COMMAND = 'server'
|
|
51
56
|
APP_NAME_SDK = 'sdk'
|
|
52
57
|
CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
|
|
53
|
-
CONNECT_VERSIONS = 'connectversions.js'
|
|
58
|
+
CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
|
|
54
59
|
TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
|
|
55
60
|
DEMO = 'demo'
|
|
56
|
-
DEMO_SERVER_PRESET = 'demoserver'
|
|
57
|
-
AOC_PATH_API_CLIENTS = 'admin/api-clients'
|
|
61
|
+
DEMO_SERVER_PRESET = 'demoserver' # cspell: disable-line
|
|
58
62
|
EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
|
|
59
63
|
From: <%=from_name%> <<%=from_email%>>
|
|
60
64
|
To: <<%=to%>>
|
|
@@ -63,103 +67,200 @@ module Aspera
|
|
|
63
67
|
This email was sent to test #{PROGRAM_NAME}.
|
|
64
68
|
END_OF_TEMPLATE
|
|
65
69
|
# special extended values
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
EXTV_VAULT = :vault
|
|
70
|
+
EXTEND_PRESET = :preset
|
|
71
|
+
EXTEND_VAULT = :vault
|
|
69
72
|
PRESET_DIG_SEPARATOR = '.'
|
|
70
73
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
71
|
-
DEFAULT_PRIV_KEY_FILENAME = '
|
|
72
|
-
|
|
74
|
+
DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
|
|
75
|
+
DEFAULT_PRIV_KEY_LENGTH = 4096
|
|
73
76
|
COFFEE_IMAGE = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
|
77
|
+
WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
|
|
78
|
+
GEM_CHECK_DATE_FMT = '%Y/%m/%d'
|
|
79
|
+
# for testing only
|
|
80
|
+
SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
|
|
74
81
|
private_constant :DEFAULT_CONFIG_FILENAME,
|
|
75
82
|
:CONF_PRESET_CONFIG,
|
|
76
83
|
:CONF_PRESET_VERSION,
|
|
77
|
-
:
|
|
84
|
+
:CONF_PRESET_DEFAULTS,
|
|
78
85
|
:CONF_PRESET_GLOBAL,
|
|
79
|
-
:DEFAULT_REDIRECT,
|
|
80
86
|
:ASPERA_PLUGINS_FOLDERNAME,
|
|
81
87
|
:RUBY_FILE_EXT,
|
|
82
88
|
:ASPERA,
|
|
83
|
-
:AOC_COMMAND,
|
|
84
89
|
:DEMO,
|
|
85
90
|
:TRANSFER_SDK_ARCHIVE_URL,
|
|
86
|
-
:AOC_PATH_API_CLIENTS,
|
|
87
91
|
:DEMO_SERVER_PRESET,
|
|
88
92
|
:EMAIL_TEST_TEMPLATE,
|
|
89
|
-
:
|
|
90
|
-
:
|
|
91
|
-
:EXTV_VAULT,
|
|
93
|
+
:EXTEND_PRESET,
|
|
94
|
+
:EXTEND_VAULT,
|
|
92
95
|
:DEFAULT_CHECK_NEW_VERSION_DAYS,
|
|
93
96
|
:DEFAULT_PRIV_KEY_FILENAME,
|
|
94
97
|
:SERVER_COMMAND,
|
|
95
98
|
:PRESET_DIG_SEPARATOR,
|
|
96
|
-
:COFFEE_IMAGE
|
|
99
|
+
:COFFEE_IMAGE,
|
|
100
|
+
:WIZARD_RESULT_KEYS,
|
|
101
|
+
:SELF_SIGNED_CERT,
|
|
102
|
+
:PERSISTENCY_FOLDER,
|
|
103
|
+
:DEFAULT_PRIV_KEY_LENGTH
|
|
104
|
+
|
|
105
|
+
class << self
|
|
106
|
+
def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
|
|
107
|
+
require 'openssl'
|
|
108
|
+
priv_key = OpenSSL::PKey::RSA.new(length)
|
|
109
|
+
File.write(path, priv_key.to_s)
|
|
110
|
+
File.write("#{path}.pub", priv_key.public_key.to_s)
|
|
111
|
+
Environment.restrict_file_access(path)
|
|
112
|
+
Environment.restrict_file_access("#{path}.pub")
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# folder containing plugins in the gem's main folder
|
|
117
|
+
def gem_plugins_folder
|
|
118
|
+
File.dirname(File.expand_path(__FILE__))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# name of englobing module
|
|
122
|
+
# @return "Aspera::Cli::Plugins"
|
|
123
|
+
def module_full_name
|
|
124
|
+
return Module.nesting[2].to_s
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return main folder where code is, i.e. .../lib
|
|
128
|
+
# go up as many times as englobing modules (not counting class, as it is a file)
|
|
129
|
+
def gem_src_root
|
|
130
|
+
File.expand_path(module_full_name.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# instantiate a plugin
|
|
134
|
+
# plugins must be Capitalized
|
|
135
|
+
def plugin_class(plugin_name_sym)
|
|
136
|
+
# Module.nesting[2] is Aspera::Cli::Plugins
|
|
137
|
+
return Object.const_get("#{module_full_name}::#{plugin_name_sym.to_s.capitalize}")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# deep clone hash so that it does not get modified in case of display and secret hide
|
|
141
|
+
def protect_presets(val)
|
|
142
|
+
return JSON.parse(JSON.generate(val))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# return product family folder (~/.aspera)
|
|
146
|
+
def module_family_folder
|
|
147
|
+
user_home_folder = Dir.home
|
|
148
|
+
assert(Dir.exist?(user_home_folder), exception_class: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
|
|
149
|
+
return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# return product config folder (~/.aspera/<name>)
|
|
153
|
+
def default_app_main_folder(app_name:)
|
|
154
|
+
assert_type(app_name, String)
|
|
155
|
+
assert(!app_name.empty?)
|
|
156
|
+
return File.join(module_family_folder, app_name)
|
|
157
|
+
end
|
|
158
|
+
end # self
|
|
159
|
+
|
|
97
160
|
def initialize(env, params)
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
assert_type(env, Hash)
|
|
162
|
+
assert_type(params, Hash)
|
|
163
|
+
assert(%i[gem help name version].eql?(params.keys.sort)){'missing param'}
|
|
164
|
+
# we need to defer parsing of options until we have the config file, so we can use @extend with @preset
|
|
100
165
|
super(env)
|
|
101
166
|
@info = params
|
|
102
|
-
@main_folder = default_app_main_folder
|
|
103
167
|
@plugins = {}
|
|
104
168
|
@plugin_lookup_folders = []
|
|
105
169
|
@use_plugin_defaults = true
|
|
106
170
|
@config_presets = nil
|
|
171
|
+
@config_checksum_on_disk = nil
|
|
107
172
|
@connect_versions = nil
|
|
108
173
|
@vault = nil
|
|
109
|
-
@conf_file_default = File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
|
110
|
-
@option_config_file = @conf_file_default
|
|
111
174
|
@pac_exec = nil
|
|
112
175
|
@sdk_default_location = false
|
|
176
|
+
@option_insecure = false
|
|
177
|
+
@option_warn_insecure_cert = true
|
|
178
|
+
@option_ignore_cert_host_port = []
|
|
179
|
+
@option_http_options = {}
|
|
180
|
+
@ssl_warned_urls = []
|
|
181
|
+
@option_cache_tokens = true
|
|
182
|
+
@proxy_credentials = nil
|
|
183
|
+
@main_folder = nil
|
|
184
|
+
@option_config_file = nil
|
|
185
|
+
@certificate_store = nil
|
|
186
|
+
@certificate_paths = nil
|
|
187
|
+
@progress_bar = nil
|
|
188
|
+
# option to set main folder
|
|
189
|
+
options.declare(
|
|
190
|
+
:home, 'Home folder for tool',
|
|
191
|
+
handler: {o: self, m: :main_folder},
|
|
192
|
+
types: String,
|
|
193
|
+
default: self.class.default_app_main_folder(app_name: @info[:name]))
|
|
194
|
+
options.parse_options!
|
|
113
195
|
Log.log.debug{"#{@info[:name]} folder: #{@main_folder}"}
|
|
114
|
-
#
|
|
196
|
+
# data persistency manager
|
|
197
|
+
env[:persistency] = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
|
|
198
|
+
# set folders for plugin lookup
|
|
115
199
|
add_plugin_lookup_folder(self.class.gem_plugins_folder)
|
|
116
200
|
add_plugin_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
|
117
|
-
#
|
|
118
|
-
options.declare(
|
|
201
|
+
# option to set config file
|
|
202
|
+
options.declare(
|
|
203
|
+
:config_file, 'Path to YAML file with preset configuration',
|
|
204
|
+
handler: {o: self, m: :option_config_file},
|
|
205
|
+
default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME))
|
|
119
206
|
options.parse_options!
|
|
120
|
-
# read
|
|
207
|
+
# read config file (set @config_presets)
|
|
121
208
|
read_config_file
|
|
122
209
|
# 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)})
|
|
210
|
+
ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{|v|preset_by_name(v)})
|
|
211
|
+
ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{|v|vault_value(v)})
|
|
126
212
|
# load defaults before it can be overridden
|
|
127
213
|
add_plugin_default_preset(CONF_GLOBAL_SYM)
|
|
214
|
+
# vault options
|
|
215
|
+
options.declare(:secret, 'Secret for access keys')
|
|
216
|
+
options.declare(:vault, 'Vault for secrets', types: Hash)
|
|
217
|
+
options.declare(:vault_password, 'Vault password')
|
|
128
218
|
options.parse_options!
|
|
219
|
+
# declare generic plugin options only after handlers are declared
|
|
220
|
+
Plugin.declare_generic_options(options)
|
|
221
|
+
# configuration options
|
|
129
222
|
options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N') { @use_plugin_defaults = false }
|
|
223
|
+
options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
|
|
224
|
+
options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
|
|
225
|
+
options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
|
|
226
|
+
# wizard options
|
|
130
227
|
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
228
|
options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', values: :bool, default: true)
|
|
133
229
|
options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
|
|
134
|
-
options.declare(:
|
|
135
|
-
|
|
230
|
+
options.declare(:key_path, 'Wizard: path to private key for JWT')
|
|
231
|
+
# Transfer SDK options
|
|
136
232
|
options.declare(:ascp_path, 'Path to ascp', handler: {o: Fasp::Installation.instance, m: :ascp_path})
|
|
137
233
|
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
234
|
options.declare(:sdk_url, 'URL to get SDK', default: TRANSFER_SDK_ARCHIVE_URL)
|
|
145
235
|
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(:
|
|
236
|
+
options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
|
|
237
|
+
# email options
|
|
238
|
+
options.declare(:smtp, 'SMTP configuration', types: Hash)
|
|
239
|
+
options.declare(:notify_to, 'Email recipient for notification of transfers')
|
|
240
|
+
options.declare(:notify_template, 'Email ERB template for notification of transfers')
|
|
241
|
+
# HTTP options
|
|
242
|
+
options.declare(:insecure, 'Do not validate any HTTPS certificate', values: :bool, handler: {o: self, m: :option_insecure}, default: :no)
|
|
243
|
+
options.declare(:ignore_certificate, 'Do not validate HTTPS certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
|
|
244
|
+
options.declare(:silent_insecure, 'Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
|
|
245
|
+
options.declare(:cert_stores, 'List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
|
|
246
|
+
options.declare(:http_options, 'Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
|
|
247
|
+
options.declare(:cache_tokens, 'Save and reuse Oauth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
|
|
248
|
+
options.declare(:fpac, 'Proxy auto configuration script')
|
|
249
|
+
options.declare(:proxy_credentials, 'HTTP proxy credentials (user and password)', types: Array)
|
|
150
250
|
options.parse_options!
|
|
251
|
+
@progress_bar = TransferProgress.new if options.get_option(:progress_bar)
|
|
151
252
|
# Check SDK folder is set or not, for compatibility, we check in two places
|
|
152
253
|
sdk_folder = Fasp::Installation.instance.sdk_folder rescue nil
|
|
153
254
|
if sdk_folder.nil?
|
|
154
255
|
@sdk_default_location = true
|
|
155
256
|
Log.log.debug('SDK folder is not set, checking default')
|
|
156
257
|
# new location
|
|
157
|
-
sdk_folder = default_app_main_folder(app_name: APP_NAME_SDK)
|
|
258
|
+
sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK)
|
|
158
259
|
Log.log.debug{"checking: #{sdk_folder}"}
|
|
159
260
|
if !Dir.exist?(sdk_folder)
|
|
160
261
|
Log.log.debug{"not exists: #{sdk_folder}"}
|
|
161
262
|
# former location
|
|
162
|
-
former_sdk_folder = File.join(default_app_main_folder, APP_NAME_SDK)
|
|
263
|
+
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: @info[:name]), APP_NAME_SDK)
|
|
163
264
|
Log.log.debug{"checking: #{former_sdk_folder}"}
|
|
164
265
|
sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
|
165
266
|
end
|
|
@@ -169,36 +270,115 @@ module Aspera
|
|
|
169
270
|
pac_script = options.get_option(:fpac)
|
|
170
271
|
# create PAC executor
|
|
171
272
|
@pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
|
|
172
|
-
|
|
173
|
-
if !
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
@pac_exec.proxy_user =
|
|
177
|
-
@pac_exec.proxy_pass =
|
|
273
|
+
proxy_user_pass = options.get_option(:proxy_credentials)
|
|
274
|
+
if !proxy_user_pass.nil?
|
|
275
|
+
assert(proxy_user_pass.length.eql?(2), exception_class: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
|
|
276
|
+
@proxy_credentials = {user: proxy_user_pass[0], pass: proxy_user_pass[1]}
|
|
277
|
+
@pac_exec.proxy_user = @proxy_credentials[:user]
|
|
278
|
+
@pac_exec.proxy_pass = @proxy_credentials[:pass]
|
|
279
|
+
end
|
|
280
|
+
Rest.set_parameters(
|
|
281
|
+
user_agent: PROGRAM_NAME,
|
|
282
|
+
session_cb: lambda{|http_session|update_http_session(http_session)},
|
|
283
|
+
progress_bar: @progress_bar)
|
|
284
|
+
Oauth.persist_mgr = persistency if @option_cache_tokens
|
|
285
|
+
Fasp::Parameters.file_list_folder = File.join(@main_folder, 'filelists') # cspell: disable-line
|
|
286
|
+
Aspera::RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
|
|
287
|
+
# register aspera REST call error handlers
|
|
288
|
+
Aspera::RestErrorsAspera.register_handlers
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
|
|
292
|
+
attr_reader :option_ignore_cert_host_port, :progress_bar
|
|
293
|
+
|
|
294
|
+
def trusted_cert_locations=(path_list)
|
|
295
|
+
path_list = [path_list] unless path_list.is_a?(Array)
|
|
296
|
+
if @certificate_store.nil?
|
|
297
|
+
Log.log.debug('Creating SSL Cert store')
|
|
298
|
+
@certificate_store = OpenSSL::X509::Store.new
|
|
299
|
+
@certificate_store.set_default_paths
|
|
300
|
+
@certificate_paths = []
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
path_list.each do |path|
|
|
304
|
+
assert_type(path, String){'Expecting a String for cert location'}
|
|
305
|
+
Log.log.debug("Adding cert location: #{path}")
|
|
306
|
+
if path.eql?(ExtendedValue::DEF)
|
|
307
|
+
path = OpenSSL::X509::DEFAULT_CERT_DIR
|
|
308
|
+
@certificate_store.add_path(path)
|
|
309
|
+
@certificate_paths.push(path)
|
|
310
|
+
path = OpenSSL::X509::DEFAULT_CERT_FILE
|
|
311
|
+
@certificate_store.add_file(path)
|
|
312
|
+
elsif File.file?(path)
|
|
313
|
+
@certificate_store.add_file(path)
|
|
314
|
+
elsif File.directory?(path)
|
|
315
|
+
@certificate_store.add_path(path)
|
|
316
|
+
else
|
|
317
|
+
raise "No such file or folder: #{path}"
|
|
318
|
+
end
|
|
319
|
+
@certificate_paths.push(path)
|
|
178
320
|
end
|
|
179
321
|
end
|
|
180
322
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
323
|
+
def trusted_cert_locations(files_only: false)
|
|
324
|
+
locations = if @certificate_paths.nil?
|
|
325
|
+
[OpenSSL::X509::DEFAULT_CERT_DIR, OpenSSL::X509::DEFAULT_CERT_FILE]
|
|
326
|
+
else
|
|
327
|
+
@certificate_paths
|
|
328
|
+
end
|
|
329
|
+
locations = locations.select{|f|File.file?(f)} if files_only
|
|
330
|
+
return locations
|
|
185
331
|
end
|
|
186
332
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
333
|
+
def option_ignore_cert_host_port=(url_list)
|
|
334
|
+
url_list.each do |url|
|
|
335
|
+
uri = URI.parse(url)
|
|
336
|
+
raise "Expecting https scheme: #{url}" unless uri.scheme.eql?('https')
|
|
337
|
+
@option_ignore_cert_host_port.push([uri.host, uri.port].freeze)
|
|
338
|
+
end
|
|
192
339
|
end
|
|
193
340
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
341
|
+
def ignore_cert?(address, port)
|
|
342
|
+
endpoint = [address, port].freeze
|
|
343
|
+
ignore_cert = false
|
|
344
|
+
if @option_insecure || @option_ignore_cert_host_port.any?(endpoint)
|
|
345
|
+
ignore_cert = true
|
|
346
|
+
if @option_warn_insecure_cert
|
|
347
|
+
base_url = "https://#{address}:#{port}"
|
|
348
|
+
if !@ssl_warned_urls.include?(base_url)
|
|
349
|
+
formatter.display_message(
|
|
350
|
+
:error,
|
|
351
|
+
"#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production.")
|
|
352
|
+
@ssl_warned_urls.push(base_url)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
Log.log.debug{"ignore cert? #{endpoint} -> #{ignore_cert}"}
|
|
357
|
+
return ignore_cert
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# called every time a new REST HTTP session is opened to set user-provided options
|
|
361
|
+
# @param http_session [Net::HTTP] the newly created HTTP/S session object
|
|
362
|
+
def update_http_session(http_session)
|
|
363
|
+
http_session.set_debug_output(LineLogger.new(:trace2)) if Log.instance.logger.trace2?
|
|
364
|
+
# Rest.io_http_session(http_session).debug_output = Log.log
|
|
365
|
+
http_session.verify_mode = SELF_SIGNED_CERT if http_session.use_ssl? && ignore_cert?(http_session.address, http_session.port)
|
|
366
|
+
http_session.cert_store = @certificate_store if @certificate_store
|
|
367
|
+
Log.log.debug{"using cert store #{http_session.cert_store} (#{@certificate_store})"} unless http_session.cert_store.nil?
|
|
368
|
+
if @proxy_credentials
|
|
369
|
+
http_session.proxy_user = @proxy_credentials[:user]
|
|
370
|
+
http_session.proxy_pass = @proxy_credentials[:pass]
|
|
371
|
+
end
|
|
372
|
+
@option_http_options.each do |k, v|
|
|
373
|
+
method = "#{k}=".to_sym
|
|
374
|
+
# check if accessor is a method of Net::HTTP
|
|
375
|
+
# continue_timeout= read_timeout= write_timeout=
|
|
376
|
+
if http_session.respond_to?(method)
|
|
377
|
+
http_session.send(method, v)
|
|
378
|
+
else
|
|
379
|
+
Log.log.error{"no such HTTP session attribute: #{k}"}
|
|
380
|
+
end
|
|
381
|
+
end
|
|
202
382
|
end
|
|
203
383
|
|
|
204
384
|
def check_gem_version
|
|
@@ -211,7 +391,7 @@ module Aspera
|
|
|
211
391
|
end
|
|
212
392
|
if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(RUBY_FUTURE_MINIMUM_VERSION)
|
|
213
393
|
Log.log.warn do
|
|
214
|
-
"Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, "\
|
|
394
|
+
"Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, " \
|
|
215
395
|
"you are using #{Environment.ruby_version}"
|
|
216
396
|
end
|
|
217
397
|
end
|
|
@@ -226,7 +406,6 @@ module Aspera
|
|
|
226
406
|
def periodic_check_newer_gem_version
|
|
227
407
|
# get verification period
|
|
228
408
|
delay_days = options.get_option(:version_check_days, mandatory: true)
|
|
229
|
-
Log.log.info{"check days: #{delay_days}"}
|
|
230
409
|
# check only if not zero day
|
|
231
410
|
return if delay_days.eql?(0)
|
|
232
411
|
# get last date from persistency
|
|
@@ -237,17 +416,11 @@ module Aspera
|
|
|
237
416
|
id: 'version_last_check')
|
|
238
417
|
# get persisted date or nil
|
|
239
418
|
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
|
|
419
|
+
last_check_days = (current_date - Date.strptime(last_check_array.first, GEM_CHECK_DATE_FMT)) rescue nil
|
|
420
|
+
Log.log.debug{"gem check new version: #{delay_days}, #{last_check_days}, #{current_date}, #{last_check_array}"}
|
|
421
|
+
return if !last_check_days.nil? && last_check_days < delay_days
|
|
249
422
|
# generate timestamp
|
|
250
|
-
last_check_array[0] = current_date.strftime(
|
|
423
|
+
last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
|
|
251
424
|
check_date_persist.save
|
|
252
425
|
# compare this version and the one on internet
|
|
253
426
|
check_data = check_gem_version
|
|
@@ -266,7 +439,7 @@ module Aspera
|
|
|
266
439
|
Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
|
|
267
440
|
# get javascript object only
|
|
268
441
|
found = connect_versions_javascript.match(/^.*? = (.*);/)
|
|
269
|
-
raise
|
|
442
|
+
raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
|
|
270
443
|
all_data = JSON.parse(found[1])
|
|
271
444
|
@connect_versions = all_data['entries']
|
|
272
445
|
end
|
|
@@ -284,66 +457,45 @@ module Aspera
|
|
|
284
457
|
return nil
|
|
285
458
|
end
|
|
286
459
|
|
|
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
|
|
460
|
+
# get the default global preset, or set default one
|
|
461
|
+
def global_default_preset
|
|
462
|
+
result = get_plugin_default_config_name(CONF_GLOBAL_SYM)
|
|
463
|
+
if result.nil?
|
|
464
|
+
result = CONF_PRESET_GLOBAL
|
|
465
|
+
set_preset_key(CONF_PRESET_DEFAULTS, CONF_GLOBAL_SYM, result)
|
|
309
466
|
end
|
|
467
|
+
return result
|
|
468
|
+
end
|
|
310
469
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
470
|
+
def set_preset_key(preset, param_name, param_value)
|
|
471
|
+
assert_values(param_name.class, [String, Symbol]){'parameter'}
|
|
472
|
+
param_name = param_name.to_s
|
|
473
|
+
selected_preset = @config_presets[preset]
|
|
474
|
+
if selected_preset.nil?
|
|
475
|
+
Log.log.debug{"No such preset name: #{preset}, initializing"}
|
|
476
|
+
selected_preset = @config_presets[preset] = {}
|
|
315
477
|
end
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
478
|
+
assert_type(selected_preset, Hash){"#{preset}.#{param_name}"}
|
|
479
|
+
if selected_preset.key?(param_name)
|
|
480
|
+
if selected_preset[param_name].eql?(param_value)
|
|
481
|
+
Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
|
|
482
|
+
return
|
|
483
|
+
end
|
|
484
|
+
Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
|
|
322
485
|
end
|
|
486
|
+
selected_preset[param_name] = param_value
|
|
487
|
+
formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
488
|
+
nil
|
|
323
489
|
end
|
|
324
490
|
|
|
325
491
|
# set parameter and value in global config
|
|
326
492
|
# creates one if none already created
|
|
327
493
|
# @return preset name that contains global default
|
|
328
494
|
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
|
|
495
|
+
set_preset_key(global_default_preset, key, value)
|
|
341
496
|
end
|
|
342
497
|
|
|
343
|
-
public
|
|
344
|
-
|
|
345
498
|
# $HOME/.aspera/`program_name`
|
|
346
|
-
attr_reader :main_folder
|
|
347
499
|
attr_reader :gem_url, :plugins
|
|
348
500
|
attr_accessor :option_config_file
|
|
349
501
|
|
|
@@ -351,40 +503,17 @@ module Aspera
|
|
|
351
503
|
# @param config_name name of the preset in config file
|
|
352
504
|
# @param include_path used to detect and avoid include loops
|
|
353
505
|
def preset_by_name(config_name, include_path=[])
|
|
354
|
-
raise
|
|
506
|
+
raise Cli::Error, 'loop in include' if include_path.include?(config_name)
|
|
355
507
|
include_path = include_path.clone # avoid messing up if there are multiple branches
|
|
356
508
|
current = @config_presets
|
|
357
509
|
config_name.split(PRESET_DIG_SEPARATOR).each do |name|
|
|
358
|
-
|
|
510
|
+
assert_type(current, Hash, exception_class: Cli::Error){"sub key: #{include_path}"}
|
|
359
511
|
include_path.push(name)
|
|
360
512
|
current = current[name]
|
|
361
|
-
raise
|
|
362
|
-
end
|
|
363
|
-
case current
|
|
364
|
-
when Hash then return expanded_with_preset_includes(current, include_path)
|
|
365
|
-
when String then return ExtendedValue.instance.evaluate(current)
|
|
366
|
-
else return current
|
|
513
|
+
raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
|
|
367
514
|
end
|
|
368
|
-
|
|
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
|
|
515
|
+
current = self.class.protect_presets(current) unless current.is_a?(String)
|
|
516
|
+
return ExtendedValue.instance.evaluate(current)
|
|
388
517
|
end
|
|
389
518
|
|
|
390
519
|
def option_use_product=(value)
|
|
@@ -396,11 +525,10 @@ module Aspera
|
|
|
396
525
|
end
|
|
397
526
|
|
|
398
527
|
def option_plugin_folder=(value)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
end
|
|
528
|
+
assert_values(value.class, [String, Array]){'plugin folder'}
|
|
529
|
+
value = [value] if value.is_a?(String)
|
|
530
|
+
assert(value.all?(String)){'plugin folder'}
|
|
531
|
+
value.each{|f|add_plugin_lookup_folder(f)}
|
|
404
532
|
end
|
|
405
533
|
|
|
406
534
|
def option_plugin_folder
|
|
@@ -420,6 +548,10 @@ module Aspera
|
|
|
420
548
|
end
|
|
421
549
|
end
|
|
422
550
|
|
|
551
|
+
def config_checksum
|
|
552
|
+
JSON.generate(@config_presets).hash
|
|
553
|
+
end
|
|
554
|
+
|
|
423
555
|
# read config file and validate format
|
|
424
556
|
def read_config_file
|
|
425
557
|
Log.log.debug{"config file is: #{@option_config_file}".red}
|
|
@@ -427,38 +559,35 @@ module Aspera
|
|
|
427
559
|
search_files = [@option_config_file]
|
|
428
560
|
# find first existing file (or nil)
|
|
429
561
|
conf_file_to_load = search_files.find{|f| File.exist?(f)}
|
|
430
|
-
# require save if old version of file
|
|
431
|
-
save_required = false
|
|
432
562
|
# if no file found, create default config
|
|
433
563
|
if conf_file_to_load.nil?
|
|
434
|
-
Log.log.warn{"No config file found.
|
|
435
|
-
@config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION =>
|
|
564
|
+
Log.log.warn{"No config file found. New configuration file: #{@option_config_file}"}
|
|
565
|
+
@config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => 'new file'}}
|
|
566
|
+
# @config_checksum_on_disk is nil
|
|
436
567
|
else
|
|
437
568
|
Log.log.debug{"loading #{@option_config_file}"}
|
|
438
569
|
@config_presets = YAML.load_file(conf_file_to_load)
|
|
570
|
+
@config_checksum_on_disk = config_checksum
|
|
439
571
|
end
|
|
440
572
|
files_to_copy = []
|
|
441
|
-
Log.log.debug{
|
|
442
|
-
|
|
573
|
+
Log.log.debug{Log.dump('Available_presets', @config_presets)}
|
|
574
|
+
assert_type(@config_presets, Hash){'config file YAML'}
|
|
443
575
|
# check there is at least the config section
|
|
444
|
-
|
|
445
|
-
raise "Cannot find key: #{CONF_PRESET_CONFIG}"
|
|
446
|
-
end
|
|
576
|
+
assert(@config_presets.key?(CONF_PRESET_CONFIG)){"Cannot find key: #{CONF_PRESET_CONFIG}"}
|
|
447
577
|
version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
|
|
448
|
-
if version.nil?
|
|
449
|
-
raise 'No version found in config section.'
|
|
450
|
-
end
|
|
578
|
+
raise 'No version found in config section.' if version.nil?
|
|
451
579
|
Log.log.debug{"conf version: #{version}"}
|
|
452
|
-
# if there are any conversion needed, those happen here.
|
|
453
|
-
#
|
|
454
|
-
if
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
580
|
+
# VVV if there are any conversion needed, those happen here.
|
|
581
|
+
# fix bug in 4.4 (creating key "true" in "default" preset)
|
|
582
|
+
@config_presets[CONF_PRESET_DEFAULTS].delete(true) if @config_presets[CONF_PRESET_DEFAULTS].is_a?(Hash)
|
|
583
|
+
# ^^^ Place new compatibility code before this line
|
|
584
|
+
# set version to current
|
|
585
|
+
@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
|
|
586
|
+
unless files_to_copy.empty?
|
|
458
587
|
Log.log.warn('Copying referenced files')
|
|
459
588
|
files_to_copy.each do |file|
|
|
460
589
|
FileUtils.cp(file, @main_folder)
|
|
461
|
-
Log.log.warn{"
|
|
590
|
+
Log.log.warn{"#{file} -> #{@main_folder}"}
|
|
462
591
|
end
|
|
463
592
|
end
|
|
464
593
|
rescue Psych::SyntaxError => e
|
|
@@ -473,7 +602,7 @@ module Aspera
|
|
|
473
602
|
Log.log.warn{"Renamed config file to #{new_name}."}
|
|
474
603
|
Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
|
|
475
604
|
end
|
|
476
|
-
raise
|
|
605
|
+
raise Cli::Error, e.to_s
|
|
477
606
|
end
|
|
478
607
|
|
|
479
608
|
# find plugins in defined paths
|
|
@@ -505,45 +634,40 @@ module Aspera
|
|
|
505
634
|
|
|
506
635
|
# Find a plugin, and issue the "require"
|
|
507
636
|
# @return [Hash] plugin info: { product: , url:, version: }
|
|
508
|
-
def
|
|
637
|
+
def identify_plugins_for_url
|
|
638
|
+
app_url = options.get_next_argument('url', mandatory: true)
|
|
639
|
+
check_only = options.get_next_argument('plugin name', mandatory: false)
|
|
509
640
|
check_only = check_only.to_sym unless check_only.nil?
|
|
641
|
+
found_apps = []
|
|
510
642
|
plugins.each do |plugin_name_sym, plugin_info|
|
|
511
643
|
# no detection for internal plugin
|
|
512
644
|
next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
|
|
513
645
|
next if check_only && !check_only.eql?(plugin_name_sym)
|
|
514
646
|
# load plugin class
|
|
515
647
|
require plugin_info[:require_stanza]
|
|
516
|
-
|
|
648
|
+
detect_plugin_class = self.class.plugin_class(plugin_name_sym)
|
|
517
649
|
# requires detection method
|
|
518
|
-
next unless
|
|
519
|
-
current_url = url
|
|
650
|
+
next unless detect_plugin_class.respond_to?(:detect)
|
|
520
651
|
detection_info = nil
|
|
521
|
-
# first try : direct
|
|
522
652
|
begin
|
|
523
|
-
detection_info =
|
|
653
|
+
detection_info = detect_plugin_class.detect(app_url)
|
|
524
654
|
rescue OpenSSL::SSL::SSLError => e
|
|
525
655
|
Log.log.warn(e.message)
|
|
526
656
|
Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
|
|
527
657
|
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
|
|
658
|
+
Log.log.debug{"detect error: #{e}"}
|
|
659
|
+
next
|
|
542
660
|
end
|
|
661
|
+
next if detection_info.nil?
|
|
662
|
+
assert_type(detection_info, Hash)
|
|
663
|
+
assert_type(detection_info[:url], String) if detection_info.key?(:url)
|
|
664
|
+
app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
|
|
543
665
|
# if there is a redirect, then the detector can override the url.
|
|
544
|
-
|
|
666
|
+
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
545
667
|
end # loop
|
|
546
|
-
raise "No known application found at #{
|
|
668
|
+
raise "No known application found at #{app_url}" if found_apps.empty?
|
|
669
|
+
assert(found_apps.all?{|a|a.keys.all?(Symbol)})
|
|
670
|
+
return found_apps
|
|
547
671
|
end
|
|
548
672
|
|
|
549
673
|
def execute_connect_action
|
|
@@ -551,15 +675,15 @@ module Aspera
|
|
|
551
675
|
if %i[info version].include?(command)
|
|
552
676
|
connect_id = options.get_next_argument('id or title')
|
|
553
677
|
one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
|
|
554
|
-
raise
|
|
678
|
+
raise Cli::NoSuchIdentifier.new(:connect, connect_id) if one_res.nil?
|
|
555
679
|
end
|
|
556
680
|
case command
|
|
557
681
|
when :list
|
|
558
682
|
return {type: :object_list, data: connect_versions, fields: %w[id title version]}
|
|
559
|
-
when :info
|
|
683
|
+
when :info
|
|
560
684
|
one_res.delete('links')
|
|
561
685
|
return {type: :single_object, data: one_res}
|
|
562
|
-
when :version
|
|
686
|
+
when :version
|
|
563
687
|
all_links = one_res['links']
|
|
564
688
|
command = options.get_next_command(%i[list download open])
|
|
565
689
|
if %i[download open].include?(command)
|
|
@@ -568,7 +692,7 @@ module Aspera
|
|
|
568
692
|
raise 'no such value' if one_link.nil?
|
|
569
693
|
end
|
|
570
694
|
case command
|
|
571
|
-
when :list
|
|
695
|
+
when :list
|
|
572
696
|
return {type: :object_list, data: all_links}
|
|
573
697
|
when :download
|
|
574
698
|
folder_dest = transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE)
|
|
@@ -594,63 +718,34 @@ module Aspera
|
|
|
594
718
|
ascp_path = options.get_next_argument('path to ascp')
|
|
595
719
|
ascp_version = Fasp::Installation.instance.get_ascp_version(ascp_path)
|
|
596
720
|
formatter.display_status("ascp version: #{ascp_version}")
|
|
597
|
-
|
|
598
|
-
return Main.
|
|
599
|
-
when :show
|
|
721
|
+
set_global_default(:ascp_path, ascp_path)
|
|
722
|
+
return Main.result_nothing
|
|
723
|
+
when :show
|
|
600
724
|
return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
|
|
601
|
-
when :info
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
Fasp::Installation.instance.path(v)
|
|
606
|
-
rescue => e
|
|
607
|
-
e.message
|
|
608
|
-
end
|
|
609
|
-
end
|
|
610
|
-
# read PATHs from ascp directly, and pvcl modules as well
|
|
611
|
-
Open3.popen3(Fasp::Installation.instance.path(:ascp), '-DDL-') do |_stdin, _stdout, stderr, thread|
|
|
612
|
-
last_line = ''
|
|
613
|
-
while (line = stderr.gets)
|
|
614
|
-
line.chomp!
|
|
615
|
-
last_line = line
|
|
616
|
-
case line
|
|
617
|
-
when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
|
|
618
|
-
data[Regexp.last_match(1)] = Regexp.last_match(3)
|
|
619
|
-
when /^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$/
|
|
620
|
-
data[Regexp.last_match(2)] = "#{Regexp.last_match(4)} #{Regexp.last_match(1)} v#{Regexp.last_match(3)}"
|
|
621
|
-
when %r{^DBG License result \(/license/(\S+)\): (.+)$}
|
|
622
|
-
data[Regexp.last_match(1)] = Regexp.last_match(2)
|
|
623
|
-
when /^LOG (.+) version ([0-9.]+)$/
|
|
624
|
-
data['product_name'] = Regexp.last_match(1)
|
|
625
|
-
data['product_version'] = Regexp.last_match(2)
|
|
626
|
-
when /^LOG Initializing FASP version ([^,]+),/
|
|
627
|
-
data['ascp_version'] = Regexp.last_match(1)
|
|
628
|
-
end
|
|
629
|
-
end
|
|
630
|
-
if !thread.value.exitstatus.eql?(1) && !data.key?('root')
|
|
631
|
-
raise last_line
|
|
632
|
-
end
|
|
633
|
-
end
|
|
634
|
-
data['keypass'] = Fasp::Installation.instance.bypass_pass
|
|
635
|
-
# log is "-" no need to display
|
|
636
|
-
data.delete('log')
|
|
637
|
-
# show command line transfer spec
|
|
725
|
+
when :info
|
|
726
|
+
# collect info from ascp executable
|
|
727
|
+
data = Fasp::Installation.instance.ascp_info
|
|
728
|
+
# add command line transfer spec
|
|
638
729
|
data['ts'] = transfer.updated_ts
|
|
730
|
+
# add keys
|
|
731
|
+
DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
|
|
732
|
+
# declare those as secrets
|
|
733
|
+
SecretHider::ADDITIONAL_KEYS_TO_HIDE.push(*DataRepository::ELEMENTS.map(&:to_s))
|
|
639
734
|
return {type: :single_object, data: data}
|
|
640
735
|
when :products
|
|
641
736
|
command = options.get_next_command(%i[list use])
|
|
642
737
|
case command
|
|
643
738
|
when :list
|
|
644
|
-
return {type: :object_list, data: Fasp::
|
|
739
|
+
return {type: :object_list, data: Fasp::Products.installed_products, fields: %w[name app_root]}
|
|
645
740
|
when :use
|
|
646
741
|
default_product = options.get_next_argument('product name')
|
|
647
742
|
Fasp::Installation.instance.use_ascp_from_product(default_product)
|
|
648
|
-
|
|
649
|
-
return Main.
|
|
743
|
+
set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
|
|
744
|
+
return Main.result_nothing
|
|
650
745
|
end
|
|
651
746
|
when :install
|
|
652
747
|
# 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
|
|
748
|
+
Fasp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
|
|
654
749
|
v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url, mandatory: true))
|
|
655
750
|
return Main.result_status("Installed version #{v}")
|
|
656
751
|
when :spec
|
|
@@ -680,24 +775,23 @@ module Aspera
|
|
|
680
775
|
def execute_preset(action: nil, name: nil)
|
|
681
776
|
action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
|
|
682
777
|
name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
|
|
778
|
+
name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
683
779
|
# those operations require existing option
|
|
684
780
|
raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
|
|
685
|
-
selected_preset = @config_presets[name]
|
|
686
781
|
case action
|
|
687
782
|
when :list
|
|
688
783
|
return {type: :value_list, data: @config_presets.keys, name: 'name'}
|
|
689
784
|
when :overview
|
|
690
|
-
|
|
785
|
+
# display process modifies the value (hide secrets): we do not want to save removed secrets
|
|
786
|
+
return {type: :config_over, data: self.class.protect_presets(@config_presets)}
|
|
691
787
|
when :show
|
|
692
|
-
|
|
693
|
-
return {type: :single_object, data: selected_preset}
|
|
788
|
+
return {type: :single_object, data: self.class.protect_presets(@config_presets[name])}
|
|
694
789
|
when :delete
|
|
695
790
|
@config_presets.delete(name)
|
|
696
|
-
save_presets_to_config_file
|
|
697
791
|
return Main.result_status("Deleted: #{name}")
|
|
698
792
|
when :get
|
|
699
793
|
param_name = options.get_next_argument('parameter name')
|
|
700
|
-
value =
|
|
794
|
+
value = @config_presets[name][param_name]
|
|
701
795
|
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
|
702
796
|
case value
|
|
703
797
|
when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
|
|
@@ -705,30 +799,20 @@ module Aspera
|
|
|
705
799
|
return {type: :single_object, data: value}
|
|
706
800
|
when :unset
|
|
707
801
|
param_name = options.get_next_argument('parameter name')
|
|
708
|
-
|
|
709
|
-
save_presets_to_config_file
|
|
802
|
+
@config_presets[name].delete(param_name)
|
|
710
803
|
return Main.result_status("Removed: #{name}: #{param_name}")
|
|
711
804
|
when :set
|
|
712
805
|
param_name = options.get_next_argument('parameter name')
|
|
713
|
-
param_value = options.get_next_argument('parameter value')
|
|
714
806
|
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}")
|
|
807
|
+
param_value = options.get_next_argument('parameter value')
|
|
808
|
+
set_preset_key(name, param_name, param_value)
|
|
809
|
+
return Main.result_nothing
|
|
725
810
|
when :initialize
|
|
726
811
|
config_value = options.get_next_argument('extended value', type: Hash)
|
|
727
812
|
if @config_presets.key?(name)
|
|
728
813
|
Log.log.warn{"configuration already exists: #{name}, overwriting"}
|
|
729
814
|
end
|
|
730
815
|
@config_presets[name] = config_value
|
|
731
|
-
save_presets_to_config_file
|
|
732
816
|
return Main.result_status("Modified: #{@option_config_file}")
|
|
733
817
|
when :update
|
|
734
818
|
# get unprocessed options
|
|
@@ -736,9 +820,6 @@ module Aspera
|
|
|
736
820
|
Log.log.debug{"opts=#{unprocessed_options}"}
|
|
737
821
|
@config_presets[name] ||= {}
|
|
738
822
|
@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
823
|
return Main.result_status("Updated: #{name}")
|
|
743
824
|
when :ask
|
|
744
825
|
options.ask_missing_mandatory = :yes
|
|
@@ -747,10 +828,9 @@ module Aspera
|
|
|
747
828
|
option_value = options.get_interactive(:option, option_name)
|
|
748
829
|
@config_presets[name][option_name] = option_value
|
|
749
830
|
end
|
|
750
|
-
save_presets_to_config_file
|
|
751
831
|
return Main.result_status("Updated: #{name}")
|
|
752
832
|
when :lookup
|
|
753
|
-
BasicAuthPlugin.
|
|
833
|
+
BasicAuthPlugin.declare_options(options)
|
|
754
834
|
url = options.get_option(:url, mandatory: true)
|
|
755
835
|
user = options.get_option(:username, mandatory: true)
|
|
756
836
|
result = lookup_preset(url: url, username: user)
|
|
@@ -789,8 +869,10 @@ module Aspera
|
|
|
789
869
|
open
|
|
790
870
|
documentation
|
|
791
871
|
genkey
|
|
872
|
+
pubkey
|
|
873
|
+
remote_certificate
|
|
792
874
|
gem
|
|
793
|
-
|
|
875
|
+
plugins
|
|
794
876
|
flush_tokens
|
|
795
877
|
echo
|
|
796
878
|
wizard
|
|
@@ -804,9 +886,10 @@ module Aspera
|
|
|
804
886
|
file
|
|
805
887
|
check_update
|
|
806
888
|
initdemo
|
|
807
|
-
vault
|
|
889
|
+
vault
|
|
890
|
+
throw].freeze
|
|
808
891
|
|
|
809
|
-
#
|
|
892
|
+
# Main action procedure for plugin
|
|
810
893
|
def execute_action
|
|
811
894
|
action = options.get_next_command(ACTIONS)
|
|
812
895
|
case action
|
|
@@ -817,31 +900,48 @@ module Aspera
|
|
|
817
900
|
return Main.result_nothing
|
|
818
901
|
when :documentation
|
|
819
902
|
section = options.get_next_argument('private key file path', mandatory: false)
|
|
820
|
-
section =
|
|
903
|
+
section = "##{section}" unless section.nil?
|
|
821
904
|
OpenApplication.instance.uri("#{@info[:help]}#{section}")
|
|
822
905
|
return Main.result_nothing
|
|
823
906
|
when :genkey # generate new rsa key
|
|
824
907
|
private_key_path = options.get_next_argument('private key file path')
|
|
825
|
-
private_key_length = options.get_next_argument('size in bits', mandatory: false
|
|
908
|
+
private_key_length = options.get_next_argument('size in bits', mandatory: false, type: Integer, default: DEFAULT_PRIV_KEY_LENGTH)
|
|
826
909
|
self.class.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
|
827
|
-
return Main.result_status(
|
|
910
|
+
return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
|
911
|
+
when :pubkey # get pub key
|
|
912
|
+
private_key_pem = options.get_next_argument('private key PEM value')
|
|
913
|
+
return Main.result_status(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
|
|
914
|
+
when :remote_certificate
|
|
915
|
+
remote_url = options.get_next_argument('remote URL')
|
|
916
|
+
@option_insecure = true
|
|
917
|
+
remote_certificate = Rest.start_http_session(remote_url).peer_cert
|
|
918
|
+
remote_certificate.subject.to_a.find { |name, _, _| name == 'CN' }[1]
|
|
919
|
+
formatter.display_status("CN=#{remote_certificate.subject.to_a.find { |name, _, _| name == 'CN' }[1] rescue ''}")
|
|
920
|
+
return Main.result_status(remote_certificate.to_pem)
|
|
828
921
|
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
|
|
922
|
+
return Formatter.auto_type(options.get_next_argument('value'))
|
|
834
923
|
when :flush_tokens
|
|
835
924
|
deleted_files = Oauth.flush_tokens
|
|
836
925
|
return {type: :value_list, data: deleted_files, name: 'file'}
|
|
837
|
-
when :
|
|
926
|
+
when :plugins
|
|
838
927
|
case options.get_next_command(%i[list create])
|
|
839
928
|
when :list
|
|
840
|
-
|
|
929
|
+
result = []
|
|
930
|
+
@plugins.each do |name, info|
|
|
931
|
+
require info[:require_stanza]
|
|
932
|
+
plugin_class = self.class.plugin_class(name)
|
|
933
|
+
result.push({
|
|
934
|
+
plugin: name,
|
|
935
|
+
detect: Formatter.tick(plugin_class.respond_to?(:detect)),
|
|
936
|
+
wizard: Formatter.tick(plugin_class.respond_to?(:wizard)),
|
|
937
|
+
path: info[:source]
|
|
938
|
+
})
|
|
939
|
+
end
|
|
940
|
+
return {type: :object_list, data: result, fields: %w[plugin detect wizard path]}
|
|
841
941
|
when :create
|
|
842
942
|
plugin_name = options.get_next_argument('name', expected: :single).downcase
|
|
843
|
-
|
|
844
|
-
plugin_file = File.join(
|
|
943
|
+
destination_folder = options.get_next_argument('folder', expected: :single, mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
|
|
944
|
+
plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
|
|
845
945
|
content = <<~END_OF_PLUGIN_CODE
|
|
846
946
|
require 'aspera/cli/plugin'
|
|
847
947
|
module Aspera
|
|
@@ -858,94 +958,19 @@ module Aspera
|
|
|
858
958
|
File.write(plugin_file, content)
|
|
859
959
|
return Main.result_status("Created #{plugin_file}")
|
|
860
960
|
end
|
|
861
|
-
when :wizard
|
|
961
|
+
when :detect, :wizard
|
|
862
962
|
# interactive mode
|
|
863
963
|
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))}
|
|
964
|
+
# detect plugins by url and optional query
|
|
965
|
+
apps = identify_plugins_for_url.freeze
|
|
966
|
+
return {
|
|
967
|
+
type: :object_list,
|
|
968
|
+
data: apps
|
|
969
|
+
} if action.eql?(:detect)
|
|
970
|
+
return wizard_find(apps)
|
|
945
971
|
when :coffee
|
|
946
972
|
if OpenApplication.instance.url_method.eql?(:text)
|
|
947
|
-
|
|
948
|
-
return Main.result_status(Preview::Terminal.build(Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body, reserved_lines: 3))
|
|
973
|
+
return Main.result_picture_in_terminal(options, Rest.new(base_url: COFFEE_IMAGE).read('')[:http].body)
|
|
949
974
|
end
|
|
950
975
|
OpenApplication.instance.uri(COFFEE_IMAGE)
|
|
951
976
|
return Main.result_nothing
|
|
@@ -979,30 +1004,126 @@ module Aspera
|
|
|
979
1004
|
else
|
|
980
1005
|
Log.log.info{"Creating Demo server preset: #{DEMO_SERVER_PRESET}"}
|
|
981
1006
|
@config_presets[DEMO_SERVER_PRESET] = {
|
|
982
|
-
'url' =>
|
|
1007
|
+
'url' => "ssh://#{DEMO}.asperasoft.com:33001",
|
|
983
1008
|
'username' => ASPERA,
|
|
984
1009
|
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO + ASPERA # cspell:disable-line
|
|
985
1010
|
}
|
|
986
1011
|
end
|
|
987
|
-
@config_presets[
|
|
988
|
-
if @config_presets[
|
|
989
|
-
Log.log.warn{"Server default preset already set to: #{@config_presets[
|
|
1012
|
+
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
|
1013
|
+
if @config_presets[CONF_PRESET_DEFAULTS].key?(SERVER_COMMAND)
|
|
1014
|
+
Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND]}"}
|
|
990
1015
|
Log.log.warn{"Use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}"} unless
|
|
991
|
-
DEMO_SERVER_PRESET.eql?(@config_presets[
|
|
1016
|
+
DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND])
|
|
992
1017
|
else
|
|
993
|
-
@config_presets[
|
|
1018
|
+
@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND] = DEMO_SERVER_PRESET
|
|
994
1019
|
Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
|
|
995
1020
|
end
|
|
996
|
-
save_presets_to_config_file
|
|
997
1021
|
return Main.result_status('Done')
|
|
998
1022
|
when :vault then execute_vault
|
|
999
|
-
|
|
1023
|
+
when :throw
|
|
1024
|
+
# :type [String]
|
|
1025
|
+
options
|
|
1026
|
+
exception_class_name = options.get_next_argument('exception class name', mandatory: true)
|
|
1027
|
+
exception_text = options.get_next_argument('exception text', mandatory: true)
|
|
1028
|
+
exception_class = Object.const_get(exception_class_name)
|
|
1029
|
+
assert(exception_class <= Exception){"#{exception_class} is not an exception: #{exception_class.class}"}
|
|
1030
|
+
raise exception_class, exception_text
|
|
1031
|
+
else error_unreachable_line
|
|
1000
1032
|
end
|
|
1001
1033
|
end
|
|
1002
1034
|
|
|
1003
|
-
|
|
1035
|
+
def wizard_find(apps)
|
|
1036
|
+
identification = if apps.length.eql?(1)
|
|
1037
|
+
Log.log.debug{"Detected: #{identification}"}
|
|
1038
|
+
apps.first
|
|
1039
|
+
else
|
|
1040
|
+
formatter.display_status('Multiple applications detected, please select from:')
|
|
1041
|
+
formatter.display_results({type: :object_list, data: apps, fields: %w[product url version]})
|
|
1042
|
+
answer = options.prompt_user_input_in_list('product', apps.map{|a|a[:product]})
|
|
1043
|
+
apps.find{|a|a[:product].eql?(answer)}
|
|
1044
|
+
end
|
|
1045
|
+
wiz_url = identification[:url]
|
|
1046
|
+
Log.log.debug{Log.dump(:identification, identification, :ruby)}
|
|
1047
|
+
formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
|
|
1048
|
+
# set url for instantiation of plugin
|
|
1049
|
+
options.add_option_preset({url: wiz_url})
|
|
1050
|
+
# instantiate plugin: command line options will be known and wizard can be called
|
|
1051
|
+
wiz_plugin_class = self.class.plugin_class(identification[:product])
|
|
1052
|
+
assert(wiz_plugin_class.respond_to?(:wizard), exception_class: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
|
|
1053
|
+
# instantiate plugin: command line options will be known, e.g. private_key
|
|
1054
|
+
plugin_instance = wiz_plugin_class.new(@agents)
|
|
1055
|
+
wiz_params = {
|
|
1056
|
+
object: plugin_instance
|
|
1057
|
+
}
|
|
1058
|
+
# is private key needed ?
|
|
1059
|
+
if options.known_options.key?(:private_key) &&
|
|
1060
|
+
(!wiz_plugin_class.respond_to?(:private_key_required?) || wiz_plugin_class.private_key_required?(wiz_url))
|
|
1061
|
+
# lets see if path to priv key is provided
|
|
1062
|
+
private_key_path = options.get_option(:key_path)
|
|
1063
|
+
# give a chance to provide
|
|
1064
|
+
if private_key_path.nil?
|
|
1065
|
+
formatter.display_status('Please provide the path to your private RSA key, or nothing to generate one:')
|
|
1066
|
+
private_key_path = options.get_option(:key_path, mandatory: true).to_s
|
|
1067
|
+
# private_key_path = File.expand_path(private_key_path)
|
|
1068
|
+
end
|
|
1069
|
+
# else generate path
|
|
1070
|
+
if private_key_path.empty?
|
|
1071
|
+
private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
|
|
1072
|
+
end
|
|
1073
|
+
if File.exist?(private_key_path)
|
|
1074
|
+
formatter.display_status('Using existing key:')
|
|
1075
|
+
else
|
|
1076
|
+
formatter.display_status("Generating #{DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
|
|
1077
|
+
self.class.generate_rsa_private_key(path: private_key_path)
|
|
1078
|
+
formatter.display_status('Created key:')
|
|
1079
|
+
end
|
|
1080
|
+
formatter.display_status(private_key_path)
|
|
1081
|
+
private_key_pem = File.read(private_key_path)
|
|
1082
|
+
options.set_option(:private_key, private_key_pem)
|
|
1083
|
+
wiz_params[:private_key_path] = private_key_path
|
|
1084
|
+
wiz_params[:pub_key_pem] = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
|
|
1085
|
+
end
|
|
1086
|
+
Log.log.debug{Log.dump(:wiz_params, wiz_params)}
|
|
1087
|
+
# finally, call the wizard
|
|
1088
|
+
wizard_result = wiz_plugin_class.wizard(**wiz_params)
|
|
1089
|
+
Log.log.debug{"wizard result: #{wizard_result}"}
|
|
1090
|
+
assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
|
|
1091
|
+
# get preset name from user or default
|
|
1092
|
+
wiz_preset_name = options.get_option(:id)
|
|
1093
|
+
if wiz_preset_name.nil?
|
|
1094
|
+
elements = [
|
|
1095
|
+
identification[:product],
|
|
1096
|
+
URI.parse(wiz_url).host
|
|
1097
|
+
]
|
|
1098
|
+
elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link)
|
|
1099
|
+
wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
|
|
1100
|
+
end
|
|
1101
|
+
# test mode does not change conf file
|
|
1102
|
+
return {type: :single_object, data: wizard_result} if options.get_option(:test_mode)
|
|
1103
|
+
# Write configuration file
|
|
1104
|
+
formatter.display_status("Preparing preset: #{wiz_preset_name}")
|
|
1105
|
+
# init defaults if necessary
|
|
1106
|
+
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
|
1107
|
+
option_override = options.get_option(:override, mandatory: true)
|
|
1108
|
+
raise Cli::Error, "A default configuration already exists for plugin '#{identification[:product]}' (use --override=yes or --default=no)" \
|
|
1109
|
+
if !option_override && options.get_option(:default, mandatory: true) && @config_presets[CONF_PRESET_DEFAULTS].key?(identification[:product])
|
|
1110
|
+
raise Cli::Error, "Preset already exists: #{wiz_preset_name} (use --override=yes or --id=<name>)" \
|
|
1111
|
+
if !option_override && @config_presets.key?(wiz_preset_name)
|
|
1112
|
+
@config_presets[wiz_preset_name] = wizard_result[:preset_value].stringify_keys
|
|
1113
|
+
test_args = wizard_result[:test_args]
|
|
1114
|
+
if options.get_option(:default, mandatory: true)
|
|
1115
|
+
formatter.display_status("Setting config preset as default for #{identification[:product]}")
|
|
1116
|
+
@config_presets[CONF_PRESET_DEFAULTS][identification[:product].to_s] = wiz_preset_name
|
|
1117
|
+
else
|
|
1118
|
+
test_args = "-P#{wiz_preset_name} #{test_args}"
|
|
1119
|
+
end
|
|
1120
|
+
# TODO: actually test the command
|
|
1121
|
+
return Main.result_status("You can test with:\n#{@info[:name]} #{identification[:product]} #{test_args}")
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
# @return [Hash] email server setting with defaults if not defined
|
|
1004
1125
|
def email_settings
|
|
1005
|
-
smtp = options.get_option(:smtp, mandatory: true
|
|
1126
|
+
smtp = options.get_option(:smtp, mandatory: true)
|
|
1006
1127
|
# change string keys into symbol keys
|
|
1007
1128
|
smtp = smtp.symbolize_keys
|
|
1008
1129
|
# defaults
|
|
@@ -1019,91 +1140,97 @@ module Aspera
|
|
|
1019
1140
|
smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/, '') if smtp.key?(:from_email)
|
|
1020
1141
|
# check minimum required
|
|
1021
1142
|
%i[server port domain].each do |n|
|
|
1022
|
-
|
|
1143
|
+
assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
|
|
1023
1144
|
end
|
|
1024
1145
|
Log.log.debug{"smtp=#{smtp}"}
|
|
1025
1146
|
return smtp
|
|
1026
1147
|
end
|
|
1027
1148
|
|
|
1028
|
-
#
|
|
1029
|
-
def empty_binding
|
|
1030
|
-
Kernel.binding
|
|
1031
|
-
end
|
|
1032
|
-
|
|
1149
|
+
# send email using ERB template
|
|
1033
1150
|
def send_email_template(email_template_default: nil, values: {})
|
|
1034
|
-
values[:to] ||= options.get_option(:
|
|
1035
|
-
|
|
1151
|
+
values[:to] ||= options.get_option(:notify_to, mandatory: true)
|
|
1152
|
+
notify_template = options.get_option(:notify_template, mandatory: email_template_default.nil?) || email_template_default
|
|
1036
1153
|
mail_conf = email_settings
|
|
1037
1154
|
values[:from_name] ||= mail_conf[:from_name]
|
|
1038
1155
|
values[:from_email] ||= mail_conf[:from_email]
|
|
1039
1156
|
%i[from_name from_email].each do |n|
|
|
1040
|
-
|
|
1157
|
+
assert(values.key?(n)){"Missing email parameter: #{n}"}
|
|
1041
1158
|
end
|
|
1042
1159
|
start_options = [mail_conf[:domain]]
|
|
1043
1160
|
start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
|
|
1044
1161
|
# create a binding with only variables defined in values
|
|
1045
|
-
template_binding = empty_binding
|
|
1162
|
+
template_binding = Environment.empty_binding
|
|
1046
1163
|
# add variables to binding
|
|
1047
1164
|
values.each do |k, v|
|
|
1048
|
-
|
|
1165
|
+
assert_type(k, Symbol)
|
|
1049
1166
|
template_binding.local_variable_set(k, v)
|
|
1050
1167
|
end
|
|
1051
1168
|
# execute template
|
|
1052
|
-
msg_with_headers = ERB.new(
|
|
1053
|
-
Log.dump(:msg_with_headers, msg_with_headers)
|
|
1169
|
+
msg_with_headers = ERB.new(notify_template).result(template_binding)
|
|
1170
|
+
Log.log.debug{Log.dump(:msg_with_headers, msg_with_headers)}
|
|
1171
|
+
require 'net/smtp'
|
|
1054
1172
|
smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
|
|
1055
1173
|
smtp.enable_starttls if mail_conf[:tls]
|
|
1056
1174
|
smtp.enable_tls if mail_conf[:ssl]
|
|
1057
1175
|
smtp.start(*start_options) do |smtp_session|
|
|
1058
1176
|
smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
|
|
1059
1177
|
end
|
|
1178
|
+
nil
|
|
1060
1179
|
end
|
|
1061
1180
|
|
|
1062
|
-
|
|
1181
|
+
# Save current configuration to config file
|
|
1182
|
+
# return true if file was saved
|
|
1183
|
+
def save_config_file_if_needed
|
|
1063
1184
|
raise 'no configuration loaded' if @config_presets.nil?
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1185
|
+
current_checksum = config_checksum
|
|
1186
|
+
return false if @config_checksum_on_disk.eql?(current_checksum)
|
|
1187
|
+
FileUtils.mkdir_p(@main_folder)
|
|
1067
1188
|
Environment.restrict_file_access(@main_folder)
|
|
1068
|
-
|
|
1189
|
+
Log.log.info{"Writing #{@option_config_file}"}
|
|
1190
|
+
formatter.display_status('Saving config file.')
|
|
1191
|
+
Environment.write_file_restricted(@option_config_file, force: true) {@config_presets.to_yaml}
|
|
1192
|
+
@config_checksum_on_disk = current_checksum
|
|
1193
|
+
return true
|
|
1069
1194
|
end
|
|
1070
1195
|
|
|
1071
1196
|
# returns [String] name if config_presets has default
|
|
1072
1197
|
# returns nil if there is no config or bypass default params
|
|
1073
1198
|
def get_plugin_default_config_name(plugin_name_sym)
|
|
1074
|
-
|
|
1199
|
+
assert(!@config_presets.nil?){'config_presets shall be defined'}
|
|
1075
1200
|
if !@use_plugin_defaults
|
|
1076
1201
|
Log.log.debug('skip default config')
|
|
1077
1202
|
return nil
|
|
1078
1203
|
end
|
|
1079
|
-
if @config_presets.key?(
|
|
1080
|
-
@config_presets[
|
|
1081
|
-
default_config_name = @config_presets[
|
|
1204
|
+
if @config_presets.key?(CONF_PRESET_DEFAULTS) &&
|
|
1205
|
+
@config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
|
|
1206
|
+
default_config_name = @config_presets[CONF_PRESET_DEFAULTS][plugin_name_sym.to_s]
|
|
1082
1207
|
if !@config_presets.key?(default_config_name)
|
|
1083
1208
|
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: '\
|
|
1209
|
+
"Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
|
|
1210
|
+
'Please fix the issue: either create preset with one parameter: ' \
|
|
1086
1211
|
"(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_name_sym})."
|
|
1087
1212
|
end
|
|
1088
1213
|
end
|
|
1089
|
-
raise
|
|
1214
|
+
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
1215
|
return default_config_name
|
|
1091
1216
|
end
|
|
1092
1217
|
return nil
|
|
1093
1218
|
end # get_plugin_default_config_name
|
|
1094
1219
|
|
|
1095
|
-
ALLOWED_KEYS = %i[password username description].freeze
|
|
1220
|
+
# TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
|
|
1221
|
+
# @return [Hash] result of execution of vault command
|
|
1096
1222
|
def execute_vault
|
|
1097
|
-
command = options.get_next_command(%i[list show create delete password])
|
|
1223
|
+
command = options.get_next_command(%i[info list show create delete password])
|
|
1098
1224
|
case command
|
|
1225
|
+
when :info
|
|
1226
|
+
return {type: :single_object, data: vault_info}
|
|
1099
1227
|
when :list
|
|
1100
1228
|
return {type: :object_list, data: vault.list}
|
|
1101
1229
|
when :show
|
|
1102
1230
|
return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
|
|
1103
1231
|
when :create
|
|
1104
|
-
label = options.get_next_argument('label')
|
|
1105
|
-
info = options.get_next_argument('info Hash
|
|
1106
|
-
raise 'info must be Hash' unless info.is_a?(Hash)
|
|
1232
|
+
label = options.get_next_argument('label', type: String)
|
|
1233
|
+
info = options.get_next_argument('info', type: Hash)
|
|
1107
1234
|
info = info.symbolize_keys
|
|
1108
1235
|
info[:label] = label
|
|
1109
1236
|
vault.set(info)
|
|
@@ -1112,7 +1239,7 @@ module Aspera
|
|
|
1112
1239
|
vault.delete(label: options.get_next_argument('label'))
|
|
1113
1240
|
return Main.result_status('Password deleted')
|
|
1114
1241
|
when :password
|
|
1115
|
-
|
|
1242
|
+
assert(vault.respond_to?(:password=)){'Vault does not support password change'}
|
|
1116
1243
|
new_password = options.get_next_argument('new_password')
|
|
1117
1244
|
vault.password = new_password
|
|
1118
1245
|
vault.save
|
|
@@ -1120,38 +1247,47 @@ module Aspera
|
|
|
1120
1247
|
end
|
|
1121
1248
|
end
|
|
1122
1249
|
|
|
1250
|
+
# @return [String] value from vault matching <name>.<param>
|
|
1123
1251
|
def vault_value(name)
|
|
1124
1252
|
m = name.match(/^(.+)\.(.+)$/)
|
|
1125
1253
|
raise 'vault name shall match <name>.<param>' if m.nil?
|
|
1254
|
+
# this raise exception if label not found:
|
|
1126
1255
|
info = vault.get(label: m[1])
|
|
1127
|
-
# raise "no such vault entry: #{m[1]}" if info.nil?
|
|
1128
1256
|
value = info[m[2].to_sym]
|
|
1129
1257
|
raise "no such entry value: #{m[2]}" if value.nil?
|
|
1130
1258
|
return value
|
|
1131
1259
|
end
|
|
1132
1260
|
|
|
1261
|
+
def vault_info
|
|
1262
|
+
info = options.get_option(:vault) || {}
|
|
1263
|
+
info = info.symbolize_keys
|
|
1264
|
+
info[:type] ||= 'file'
|
|
1265
|
+
info[:name] ||= (info[:type].eql?('file') ? DEFAULT_VAULT_FILENAME : PROGRAM_NAME)
|
|
1266
|
+
assert(info.keys.sort == %i[name type]) {"vault info shall have exactly keys 'type' and 'name'"}
|
|
1267
|
+
assert(info.values.all?(String)){'vault info shall have only string values'}
|
|
1268
|
+
info[:password] = options.get_option(:vault_password, mandatory: true)
|
|
1269
|
+
return info
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
# @return [Object] vault, from options or cache
|
|
1133
1273
|
def vault
|
|
1134
1274
|
if @vault.nil?
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
raise 'vault must be Hash' unless vault_info.is_a?(Hash)
|
|
1138
|
-
vault_type = vault_info['type'] || 'file'
|
|
1139
|
-
vault_name = vault_info['name'] || (vault_type.eql?('file') ? 'vault.bin' : PROGRAM_NAME)
|
|
1140
|
-
case vault_type
|
|
1275
|
+
info = vault_info
|
|
1276
|
+
case info[:type]
|
|
1141
1277
|
when 'file'
|
|
1142
1278
|
# absolute_path? introduced in ruby 2.7
|
|
1143
|
-
|
|
1144
|
-
|
|
1279
|
+
@vault = Keychain::EncryptedHash.new(
|
|
1280
|
+
info[:name].eql?(File.absolute_path(info[:name])) ? info[:name] : File.join(@main_folder, info[:name]),
|
|
1281
|
+
info[:password])
|
|
1145
1282
|
when 'system'
|
|
1146
1283
|
case Environment.os
|
|
1147
1284
|
when Environment::OS_X
|
|
1148
|
-
@vault = Keychain::MacosSystem.new(
|
|
1149
|
-
|
|
1150
|
-
raise 'not implemented'
|
|
1151
|
-
else raise 'Error, OS not supported'
|
|
1285
|
+
@vault = Keychain::MacosSystem.new(info[:name], info[:password])
|
|
1286
|
+
else
|
|
1287
|
+
raise 'not implemented for this OS'
|
|
1152
1288
|
end
|
|
1153
1289
|
else
|
|
1154
|
-
raise
|
|
1290
|
+
raise Cli::BadArgument, "Unknown vault type: #{info[:type]}"
|
|
1155
1291
|
end
|
|
1156
1292
|
end
|
|
1157
1293
|
raise 'No vault defined' if @vault.nil?
|
|
@@ -1170,7 +1306,7 @@ module Aspera
|
|
|
1170
1306
|
@config_presets.each do |_k, v|
|
|
1171
1307
|
next unless v.is_a?(Hash)
|
|
1172
1308
|
conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
|
|
1173
|
-
return v if conf_url.eql?(url) && v['username'].eql?(username)
|
|
1309
|
+
return self.class.protect_presets(v) if conf_url.eql?(url) && v['username'].eql?(username)
|
|
1174
1310
|
end
|
|
1175
1311
|
nil
|
|
1176
1312
|
end
|