aspera-cli 4.24.1 → 4.25.0.pre

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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1064 -745
  4. data/CONTRIBUTING.md +43 -100
  5. data/README.md +1281 -720
  6. data/bin/ascli +20 -1
  7. data/bin/asession +23 -27
  8. data/lib/aspera/agent/base.rb +10 -21
  9. data/lib/aspera/agent/connect.rb +2 -3
  10. data/lib/aspera/agent/desktop.rb +2 -2
  11. data/lib/aspera/agent/direct.rb +49 -32
  12. data/lib/aspera/agent/factory.rb +31 -0
  13. data/lib/aspera/api/aoc.rb +134 -76
  14. data/lib/aspera/api/cos_node.rb +3 -2
  15. data/lib/aspera/api/faspex.rb +213 -0
  16. data/lib/aspera/api/node.rb +107 -94
  17. data/lib/aspera/ascmd.rb +1 -2
  18. data/lib/aspera/ascp/installation.rb +73 -58
  19. data/lib/aspera/ascp/management.rb +119 -23
  20. data/lib/aspera/assert.rb +39 -11
  21. data/lib/aspera/cli/error.rb +4 -2
  22. data/lib/aspera/cli/extended_value.rb +91 -67
  23. data/lib/aspera/cli/formatter.rb +62 -27
  24. data/lib/aspera/cli/hints.rb +8 -0
  25. data/lib/aspera/cli/info.rb +4 -4
  26. data/lib/aspera/cli/main.rb +76 -84
  27. data/lib/aspera/cli/manager.rb +352 -248
  28. data/lib/aspera/cli/plugins/alee.rb +5 -4
  29. data/lib/aspera/cli/plugins/aoc.rb +175 -195
  30. data/lib/aspera/cli/plugins/ats.rb +4 -4
  31. data/lib/aspera/cli/plugins/base.rb +343 -0
  32. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  33. data/lib/aspera/cli/plugins/config.rb +283 -269
  34. data/lib/aspera/cli/plugins/console.rb +27 -22
  35. data/lib/aspera/cli/plugins/cos.rb +3 -3
  36. data/lib/aspera/cli/plugins/factory.rb +78 -0
  37. data/lib/aspera/cli/plugins/faspex.rb +49 -46
  38. data/lib/aspera/cli/plugins/faspex5.rb +113 -225
  39. data/lib/aspera/cli/plugins/faspio.rb +19 -18
  40. data/lib/aspera/cli/plugins/httpgw.rb +14 -13
  41. data/lib/aspera/cli/plugins/node.rb +162 -149
  42. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  43. data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
  44. data/lib/aspera/cli/plugins/preview.rb +30 -50
  45. data/lib/aspera/cli/plugins/server.rb +21 -21
  46. data/lib/aspera/cli/plugins/shares.rb +45 -47
  47. data/lib/aspera/cli/sync_actions.rb +50 -39
  48. data/lib/aspera/cli/transfer_agent.rb +35 -49
  49. data/lib/aspera/cli/transfer_progress.rb +6 -6
  50. data/lib/aspera/cli/version.rb +3 -3
  51. data/lib/aspera/cli/wizard.rb +70 -55
  52. data/lib/aspera/colors.rb +6 -0
  53. data/lib/aspera/command_line_builder.rb +59 -61
  54. data/lib/aspera/command_line_converter.rb +2 -1
  55. data/lib/aspera/coverage.rb +2 -2
  56. data/lib/aspera/data_repository.rb +1 -1
  57. data/lib/aspera/environment.rb +51 -41
  58. data/lib/aspera/faspex_gw.rb +7 -5
  59. data/lib/aspera/faspex_postproc.rb +1 -1
  60. data/lib/aspera/keychain/factory.rb +1 -2
  61. data/lib/aspera/keychain/macos_security.rb +1 -1
  62. data/lib/aspera/log.rb +37 -9
  63. data/lib/aspera/markdown.rb +31 -0
  64. data/lib/aspera/nagios.rb +7 -6
  65. data/lib/aspera/oauth/base.rb +25 -28
  66. data/lib/aspera/oauth/factory.rb +9 -9
  67. data/lib/aspera/oauth/url_json.rb +2 -1
  68. data/lib/aspera/oauth/web.rb +2 -2
  69. data/lib/aspera/preview/file_types.rb +23 -37
  70. data/lib/aspera/products/connect.rb +7 -6
  71. data/lib/aspera/products/desktop.rb +1 -4
  72. data/lib/aspera/products/other.rb +9 -1
  73. data/lib/aspera/products/transferd.rb +0 -1
  74. data/lib/aspera/rest.rb +168 -113
  75. data/lib/aspera/rest_error_analyzer.rb +4 -4
  76. data/lib/aspera/ssh.rb +7 -4
  77. data/lib/aspera/ssl.rb +41 -0
  78. data/lib/aspera/sync/args.schema.yaml +46 -3
  79. data/lib/aspera/sync/conf.schema.yaml +307 -123
  80. data/lib/aspera/sync/database.rb +2 -1
  81. data/lib/aspera/sync/operations.rb +135 -79
  82. data/lib/aspera/temp_file_manager.rb +17 -5
  83. data/lib/aspera/transfer/error.rb +16 -7
  84. data/lib/aspera/transfer/parameters.rb +35 -22
  85. data/lib/aspera/transfer/resumer.rb +74 -0
  86. data/lib/aspera/transfer/spec.rb +5 -5
  87. data/lib/aspera/transfer/spec.schema.yaml +170 -59
  88. data/lib/aspera/transfer/spec_doc.rb +49 -43
  89. data/lib/aspera/uri_reader.rb +2 -2
  90. data/lib/aspera/web_auth.rb +6 -6
  91. data/lib/transferd_pb.rb +2 -2
  92. data.tar.gz.sig +0 -0
  93. metadata +26 -11
  94. metadata.gz.sig +0 -0
  95. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  96. data/lib/aspera/cli/plugin.rb +0 -333
  97. data/lib/aspera/cli/plugin_factory.rb +0 -81
  98. data/lib/aspera/resumer.rb +0 -77
  99. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # cspell:ignore initdemo genkey pubkey asperasoft filelists
4
- require 'aspera/cli/basic_auth_plugin'
4
+ require 'aspera/cli/plugins/basic_auth'
5
+ require 'aspera/cli/plugins/factory'
5
6
  require 'aspera/cli/extended_value'
6
7
  require 'aspera/cli/special_values'
7
8
  require 'aspera/cli/version'
@@ -9,9 +10,10 @@ require 'aspera/cli/formatter'
9
10
  require 'aspera/cli/info'
10
11
  require 'aspera/cli/transfer_progress'
11
12
  require 'aspera/cli/wizard'
13
+ require 'aspera/cli/sync_actions'
12
14
  require 'aspera/ascp/installation'
15
+ require 'aspera/sync/operations'
13
16
  require 'aspera/products/transferd'
14
- require 'aspera/transfer/error_info'
15
17
  require 'aspera/transfer/parameters'
16
18
  require 'aspera/transfer/spec'
17
19
  require 'aspera/transfer/spec_doc'
@@ -28,6 +30,7 @@ require 'aspera/oauth/jwt'
28
30
  require 'aspera/log'
29
31
  require 'aspera/assert'
30
32
  require 'aspera/oauth'
33
+ require 'aspera/ssl'
31
34
  require 'openssl'
32
35
  require 'open3'
33
36
  require 'date'
@@ -36,93 +39,36 @@ require 'erb'
36
39
  module Aspera
37
40
  module Cli
38
41
  module Plugins
39
- # manage the CLI config file
40
- class Config < Cli::Plugin
41
- # folder in $HOME for application files (config, cache)
42
- ASPERA_HOME_FOLDER_NAME = '.aspera'
43
- # default config file
44
- DEFAULT_CONFIG_FILENAME = 'config.yaml'
45
- # reserved preset names
46
- CONF_PRESET_CONFIG = 'config'
47
- CONF_PRESET_VERSION = 'version'
48
- CONF_PRESET_DEFAULTS = 'default'
49
- CONF_PRESET_GLOBAL = 'global_common_defaults'
50
- # special name to identify value of default
51
- GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
52
- CONF_GLOBAL_SYM = :config
53
- # folder containing custom plugins in user's config folder
54
- ASPERA_PLUGINS_FOLDERNAME = 'plugins'
55
- PERSISTENCY_FOLDER = 'persist_store'
56
- ASPERA = 'aspera'
57
- SERVER_COMMAND = 'server'
58
- DIR_SDK = 'sdk'
59
- DEMO_SERVER = 'demo'
60
- DEMO_PRESET = 'demoserver' # cspell: disable-line
61
- EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
62
- From: <%=from_name%> <<%=from_email%>>
63
- To: <<%=to%>>
64
- Subject: #{Info::GEM_NAME} email test
65
-
66
- This email was sent to test #{Info::CMD_NAME}.
67
- END_OF_TEMPLATE
68
- # special extended values
69
- EXTEND_PRESET = :preset
70
- EXTEND_VAULT = :vault
71
- PRESET_DIG_SEPARATOR = '.'
72
- DEFAULT_CHECK_NEW_VERSION_DAYS = 7
73
- COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
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
77
- CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
78
- SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
79
- private_constant :DEFAULT_CONFIG_FILENAME,
80
- :CONF_PRESET_CONFIG,
81
- :CONF_PRESET_VERSION,
82
- :CONF_PRESET_DEFAULTS,
83
- :CONF_PRESET_GLOBAL,
84
- :ASPERA_PLUGINS_FOLDERNAME,
85
- :ASPERA,
86
- :DEMO_SERVER,
87
- :DEMO_PRESET,
88
- :EMAIL_TEST_TEMPLATE,
89
- :EXTEND_PRESET,
90
- :EXTEND_VAULT,
91
- :DEFAULT_CHECK_NEW_VERSION_DAYS,
92
- :SERVER_COMMAND,
93
- :PRESET_DIG_SEPARATOR,
94
- :COFFEE_IMAGE_URL,
95
- :SELF_SIGNED_CERT,
96
- :PERSISTENCY_FOLDER,
97
- :CONF_OVERVIEW_KEYS,
98
- :SMTP_CONF_PARAMS
42
+ # Manage the CLI config file
43
+ class Config < Base
44
+ include SyncActions
99
45
 
100
46
  class << self
101
- # folder containing plugins in the gem's main folder
47
+ # Folder containing plugins in the gem's main folder
102
48
  def gem_plugins_folder
103
49
  File.dirname(File.expand_path(__FILE__))
104
50
  end
105
51
 
106
52
  # @return main folder where code is, i.e. .../lib
107
- # go up as many times as englobing modules (not counting class, as it is a file)
53
+ # Go up as many times as englobing modules (not counting class, as it is a file)
108
54
  def gem_src_root
109
55
  # Module.nesting[2] is Cli::Plugins
110
56
  File.expand_path(Module.nesting[2].to_s.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
111
57
  end
112
58
 
113
- # deep clone hash so that it does not get modified in case of display and secret hide
59
+ # Deep clone hash so that it does not get modified in case of display and secret hide
114
60
  def deep_clone(val)
115
61
  return Marshal.load(Marshal.dump(val))
116
62
  end
117
63
 
118
- # return product family folder (~/.aspera)
64
+ # @return product family folder (~/.aspera)
119
65
  def module_family_folder
120
66
  user_home_folder = Dir.home
121
67
  Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
122
68
  return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
123
69
  end
124
70
 
125
- # return product config folder (~/.aspera/<name>)
71
+ # @return [String] Product config folder (~/.aspera/<name>)
126
72
  def default_app_main_folder(app_name:)
127
73
  Aspera.assert_type(app_name, String)
128
74
  Aspera.assert(!app_name.empty?)
@@ -131,7 +77,7 @@ module Aspera
131
77
  end
132
78
 
133
79
  def initialize(**_)
134
- # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
80
+ # We need to defer parsing of options until we have the config file, so we can use @extend with @preset
135
81
  super
136
82
  @use_plugin_defaults = true
137
83
  @config_presets = nil
@@ -147,96 +93,79 @@ module Aspera
147
93
  @option_cache_tokens = true
148
94
  @main_folder = nil
149
95
  @option_config_file = nil
150
- # store is used for ruby https
96
+ # Store is used for ruby https (OpenSSL::X509::Store)
151
97
  @certificate_store = nil
152
- # paths are used for ascp
98
+ # Paths are used for ascp
153
99
  @certificate_paths = nil
154
100
  @progress_bar = nil
155
- # option to set main folder
101
+ # Option to set main folder
156
102
  options.declare(
157
103
  :home, 'Home folder for tool',
158
104
  handler: {o: self, m: :main_folder},
159
- types: String,
160
105
  default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
161
106
  )
162
107
  options.parse_options!
163
108
  Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
164
- # data persistency manager, created by config plugin, set for global object
109
+ # Data persistency manager, created by config plugin, set for global object
165
110
  context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
166
- # set folders for plugin lookup
167
- PluginFactory.instance.add_lookup_folder(self.class.gem_plugins_folder)
168
- PluginFactory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
169
- # option to set config file
111
+ # Set folders for plugin lookup
112
+ Plugins::Factory.instance.add_lookup_folder(self.class.gem_plugins_folder)
113
+ Plugins::Factory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
114
+ # Option to set config file
170
115
  options.declare(
171
116
  :config_file, 'Path to YAML file with preset configuration',
172
117
  handler: {o: self, m: :option_config_file},
173
118
  default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
174
119
  )
175
120
  options.parse_options!
176
- # read config file (set @config_presets)
121
+ # Read config file (set @config_presets)
177
122
  read_config_file
178
- # add preset handler (needed for smtp)
179
- ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{ |v| preset_by_name(v)})
180
- ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{ |v| vault_value(v)})
181
- # load defaults before it can be overridden
123
+ # Add preset handler (needed for smtp)
124
+ ExtendedValue.instance.on(EXTEND_PRESET){ |v| preset_by_name(v)}
125
+ ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
126
+ ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
127
+ # Load defaults before it can be overridden
182
128
  add_plugin_default_preset(CONF_GLOBAL_SYM)
183
- # vault options
129
+ # Vault options
184
130
  options.declare(:secret, 'Secret for access keys')
185
- options.declare(:vault, 'Vault for secrets', types: Hash, default: {})
131
+ options.declare(:vault, 'Vault for secrets', allowed: Hash)
186
132
  options.declare(:vault_password, 'Vault password')
187
133
  options.parse_options!
188
- # declare generic plugin options only after handlers are declared
189
- Plugin.declare_generic_options(options)
190
- # configuration options
191
- options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N'){@use_plugin_defaults = false}
134
+ # Declare generic plugin options only after handlers are declared
135
+ Base.declare_options(options)
136
+ # Configuration options
137
+ options.declare(:no_default, 'Do not load default configuration for plugin', allowed: Allowed::TYPES_NONE, short: 'N'){@use_plugin_defaults = false}
192
138
  options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
193
- options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
139
+ options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', allowed: Allowed::TYPES_INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
194
140
  options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
195
- # declare wizard options
141
+ # Declare wizard options
196
142
  @wizard = Wizard.new(self, @main_folder)
197
143
  # Transfer SDK options
198
- options.declare(:ascp_path, 'Ascp: Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
199
- options.declare(:use_product, 'Ascp: Use ascp from specified product', handler: {o: self, m: :option_use_product})
200
144
  options.declare(:sdk_url, 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF)
201
- options.declare(:locations_url, 'Ascp: URL to get locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
202
- options.declare(:sdk_folder, 'Ascp: SDK folder path', handler: {o: Products::Transferd, m: :sdk_directory})
203
- options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
204
- # email options
205
- options.declare(:smtp, 'Email: SMTP configuration', types: Hash)
145
+ options.parse_options!
146
+ set_sdk_dir
147
+ options.declare(:ascp_path, 'Ascp: Path to ascp (or product with "product:")', handler: {o: Ascp::Installation.instance, m: :ascp_path}, default: "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{Ascp::Installation::FIRST_FOUND}")
148
+ options.declare(:locations_url, 'Ascp: URL to get download locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
149
+ options.declare(:sdk_folder, 'Ascp: SDK installation folder path', handler: {o: Products::Transferd, m: :sdk_directory})
150
+ options.declare(:progress_bar, 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
151
+ # Email options
152
+ options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
206
153
  options.declare(:notify_to, 'Email: Recipient for notification of transfers')
207
154
  options.declare(:notify_template, 'Email: ERB template for notification of transfers')
208
155
  # HTTP options
209
- options.declare(:insecure, 'HTTP/S: Do not validate any certificate', values: :bool, handler: {o: self, m: :option_insecure}, default: :no)
210
- options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
211
- options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
212
- options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
213
- options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
214
- options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', types: String, handler: {o: self, m: :option_http_proxy})
215
- options.declare(:cache_tokens, 'Save and reuse OAuth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
156
+ options.declare(:insecure, 'HTTP/S: Do not validate any certificate', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_insecure}, default: false)
157
+ options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', allowed: [Array, NilClass], handler: {o: self, m: :option_ignore_cert_host_port})
158
+ options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_warn_insecure_cert}, default: true)
159
+ options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', allowed: Allowed::TYPES_STRING_ARRAY, handler: {o: self, m: :trusted_cert_locations})
160
+ options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', allowed: Hash, handler: {o: self, m: :option_http_options}, default: {})
161
+ options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', handler: {o: self, m: :option_http_proxy})
162
+ options.declare(:cache_tokens, 'Save and reuse OAuth tokens', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_cache_tokens})
216
163
  options.declare(:fpac, 'Proxy auto configuration script')
217
- options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', types: Array)
164
+ options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
218
165
  options.parse_options!
219
166
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
220
- # Check SDK folder is set or not, for compatibility, we check in two places
221
- sdk_dir = Products::Transferd.sdk_directory rescue nil
222
- if sdk_dir.nil?
223
- @sdk_default_location = true
224
- Log.log.debug('SDK folder is not set, checking default')
225
- # new location
226
- sdk_dir = self.class.default_app_main_folder(app_name: DIR_SDK)
227
- Log.log.debug{"checking: #{sdk_dir}"}
228
- if !Dir.exist?(sdk_dir)
229
- Log.log.debug{"not exists: #{sdk_dir}"}
230
- # former location
231
- former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), DIR_SDK)
232
- Log.log.debug{"checking: #{former_sdk_folder}"}
233
- sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
234
- end
235
- Log.log.debug{"using: #{sdk_dir}"}
236
- Products::Transferd.sdk_directory = sdk_dir
237
- end
238
167
  pac_script = options.get_option(:fpac)
239
- # create PAC executor
168
+ # Create PAC executor
240
169
  if !pac_script.nil?
241
170
  @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
242
171
  proxy_user_pass = options.get_option(:proxy_credentials)
@@ -249,27 +178,58 @@ module Aspera
249
178
  RestParameters.instance.user_agent = Info::CMD_NAME
250
179
  RestParameters.instance.progress_bar = @progress_bar
251
180
  RestParameters.instance.session_cb = lambda{ |http_session| update_http_session(http_session)}
252
- @option_http_options.keys.select{ |i| RestParameters.instance.respond_to?(i)}.each do |k|
181
+ # Check http options that are global
182
+ keys_to_delete = []
183
+ @option_http_options.each do |k, v|
253
184
  method = "#{k}=".to_sym
254
- RestParameters.instance.send(method, @option_http_options[k])
255
- @option_http_options.delete(k)
185
+ if RestParameters.instance.respond_to?(method)
186
+ keys_to_delete.push(k)
187
+ RestParameters.instance.send(method, v)
188
+ elsif k.eql?('ssl_options')
189
+ keys_to_delete.push(k)
190
+ Aspera::SSL.option_list = v
191
+ elsif OAuth::Factory.instance.parameters.key?(k.to_sym)
192
+ keys_to_delete.push(k)
193
+ OAuth::Factory.instance.parameters[k.to_sym] = v
194
+ end
256
195
  end
196
+ keys_to_delete.each{ |k| @option_http_options.delete(k)}
257
197
  OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
258
- OAuth::Web.additionnal_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
198
+ OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
259
199
  Transfer::Parameters.file_list_folder = File.join(@main_folder, 'filelists')
260
200
  RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
261
- # register aspera REST call error handlers
201
+ # Register aspera REST call error handlers
262
202
  RestErrorsAspera.register_handlers
263
203
  end
264
204
 
265
205
  attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
266
206
  attr_reader :option_ignore_cert_host_port, :progress_bar
267
207
 
268
- # add files, folders or default locations to the certificate store
269
- # @param path_list [Array<String>] list of paths to add
208
+ def set_sdk_dir
209
+ # Check SDK folder is set or not, for compatibility, we check in two places
210
+ sdk_dir = Products::Transferd.sdk_directory rescue nil
211
+ if sdk_dir.nil?
212
+ @sdk_default_location = true
213
+ Log.log.debug('SDK folder is not set, checking default')
214
+ # New location
215
+ sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
216
+ Log.log.debug{"Checking: #{sdk_dir}"}
217
+ if !Dir.exist?(sdk_dir)
218
+ Log.log.debug{"No such folder: #{sdk_dir}"}
219
+ # Former location
220
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
221
+ Log.log.debug{"Checking: #{former_sdk_folder}"}
222
+ sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
223
+ end
224
+ Log.log.debug{"Using: #{sdk_dir}"}
225
+ Products::Transferd.sdk_directory = sdk_dir
226
+ end
227
+ end
228
+
229
+ # Add files, folders or default locations to the certificate store
230
+ # @param path_list [Array<String>] List of paths to add
270
231
  # @return the list of paths
271
232
  def trusted_cert_locations=(path_list)
272
- path_list = [path_list] unless path_list.is_a?(Array)
273
233
  Aspera.assert_type(path_list, Array){'cert locations'}
274
234
  if @certificate_store.nil?
275
235
  Log.log.debug('Creating SSL Cert store')
@@ -300,6 +260,7 @@ module Aspera
300
260
  pp = Dir.entries(p)
301
261
  .map{ |e| File.realpath(File.join(p, e))}
302
262
  .select{ |entry| File.file?(entry)}
263
+ .select{ |entry| CERT_EXT.any?{ |ext| entry.end_with?(ext)}}
303
264
  end
304
265
  @certificate_paths.concat(pp)
305
266
  end
@@ -307,14 +268,14 @@ module Aspera
307
268
  @certificate_paths.uniq!
308
269
  end
309
270
 
310
- # returns only files
271
+ # @return only files
311
272
  def trusted_cert_locations
312
273
  locations = @certificate_paths
313
274
  if locations.nil?
314
- # compute default locations
315
- self.trusted_cert_locations = SpecialValues::DEF
275
+ # Compute default locations
276
+ self.trusted_cert_locations = [SpecialValues::DEF]
316
277
  locations = @certificate_paths
317
- # restore defaults
278
+ # Restore defaults
318
279
  @certificate_paths = @certificate_store = nil
319
280
  end
320
281
  return locations
@@ -357,7 +318,7 @@ module Aspera
357
318
  return ignore_cert
358
319
  end
359
320
 
360
- # called every time a new REST HTTP session is opened to set user-provided options
321
+ # Called every time a new REST HTTP session is opened to set user-provided options
361
322
  # @param http_session [Net::HTTP] the newly created HTTP/S session object
362
323
  def update_http_session(http_session)
363
324
  http_session.set_debug_output(LineLogger.new(:trace2)) if Log.instance.logger.trace2?
@@ -367,38 +328,12 @@ module Aspera
367
328
  Log.log.debug{"Using cert store #{http_session.cert_store} (#{@certificate_store})"} unless http_session.cert_store.nil?
368
329
  @option_http_options.each do |k, v|
369
330
  method = "#{k}=".to_sym
370
- # check if accessor is a method of Net::HTTP
331
+ # Check if accessor is a method of Net::HTTP
371
332
  # continue_timeout= read_timeout= write_timeout=
372
333
  if http_session.respond_to?(method)
373
334
  http_session.send(method, v)
374
- elsif k.eql?('ssl_options')
375
- # NOTE: here is a hack that allows setting SSLContext options
376
- Aspera.assert_type(v, Array){'ssl_options'}
377
- # more dynamic method, but more complex:
378
- # Net::HTTP::SSL_ATTRIBUTES.push(:options) unless Net::HTTP::SSL_ATTRIBUTES.include?(:options)
379
- # Net::HTTP::SSL_IVNAMES.push(:@options) unless Net::HTTP::SSL_IVNAMES.include?(:@options)
380
- # Start with default options
381
- ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
382
- v.each do |opt|
383
- case opt
384
- when Integer
385
- ssl_options = opt
386
- when String
387
- name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
388
- raise Cli::BadArgument, "No such ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
389
- if opt.start_with?('-')
390
- ssl_options &= ~OpenSSL::SSL.const_get(name)
391
- else
392
- ssl_options |= OpenSSL::SSL.const_get(name)
393
- end
394
- else
395
- Aspera.error_unexpected_value(opt.class.name){'Expected String or Integer in ssl_options'}
396
- end
397
- end
398
- # http_session.instance_variable_set(:@options, ssl_options)
399
- OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] = ssl_options
400
335
  else
401
- Log.log.error{"no such HTTP session attribute: #{k}"}
336
+ Log.log.error{"Unknown HTTP session attribute: #{k}"}
402
337
  end
403
338
  end
404
339
  end
@@ -426,35 +361,35 @@ module Aspera
426
361
  end
427
362
 
428
363
  def periodic_check_newer_gem_version
429
- # get verification period
364
+ # Get verification period
430
365
  delay_days = options.get_option(:version_check_days, mandatory: true).to_i
431
- # check only if not zero day
366
+ # Check only if not zero day
432
367
  return if delay_days.eql?(0)
433
- # get last date from persistency
368
+ # Get last date from persistency
434
369
  last_check_array = []
435
370
  check_date_persist = PersistencyActionOnce.new(
436
371
  manager: persistency,
437
372
  data: last_check_array,
438
373
  id: 'version_last_check'
439
374
  )
440
- # get persisted date or nil
375
+ # Get persisted date or nil
441
376
  current_date = Date.today
442
377
  last_check_days = (current_date - Date.strptime(last_check_array.first, GEM_CHECK_DATE_FMT)) rescue nil
443
378
  Log.log.debug{"gem check new version: #{delay_days}, #{last_check_days}, #{current_date}, #{last_check_array}"}
444
379
  return if !last_check_days.nil? && last_check_days < delay_days
445
- # generate timestamp
380
+ # Generate timestamp
446
381
  last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
447
382
  check_date_persist.save
448
- # compare this version and the one on internet
383
+ # Compare this version and the one on internet
449
384
  check_data = check_gem_version
450
385
  Log.log.warn do
451
386
  "A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}"
452
387
  end if check_data[:need_update]
453
388
  end
454
389
 
455
- # loads default parameters of plugin if no -P parameter
390
+ # Loads default parameters of plugin if no -P parameter
456
391
  # and if there is a section defined for the plugin in the "default" section
457
- # try to find: conf[conf["default"][plugin_str]]
392
+ # Try to find: conf[conf["default"][plugin_str]]
458
393
  # @param plugin_name_sym : symbol for plugin name
459
394
  def add_plugin_default_preset(plugin_name_sym)
460
395
  default_config_name = get_plugin_default_config_name(plugin_name_sym)
@@ -463,7 +398,7 @@ module Aspera
463
398
  return
464
399
  end
465
400
 
466
- # get the default global preset, or set default one
401
+ # Get the default global preset, or set default one
467
402
  def global_default_preset
468
403
  result = get_plugin_default_config_name(CONF_GLOBAL_SYM)
469
404
  if result.nil?
@@ -491,7 +426,7 @@ module Aspera
491
426
  param_name = param_name.to_s
492
427
  selected_preset = @config_presets[preset]
493
428
  if selected_preset.nil?
494
- Log.log.debug{"No such preset name: #{preset}, initializing"}
429
+ Log.log.debug{"Unknown preset name: #{preset}, initializing"}
495
430
  selected_preset = @config_presets[preset] = {}
496
431
  end
497
432
  Aspera.assert_type(selected_preset, Hash){"#{preset}.#{param_name}"}
@@ -500,15 +435,15 @@ module Aspera
500
435
  Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
501
436
  return
502
437
  end
503
- Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
438
+ Log.log.warn{"overwriting value for #{param_name}: #{selected_preset[param_name]}"}
504
439
  end
505
440
  selected_preset[param_name] = param_value
506
441
  formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
507
442
  nil
508
443
  end
509
444
 
510
- # set parameter and value in global config
511
- # creates one if none already created
445
+ # Set parameter and value in global config
446
+ # Creates one if none already created
512
447
  # @return preset name that contains global default
513
448
  def set_global_default(key, value)
514
449
  set_preset_key(global_default_preset, key, value)
@@ -523,35 +458,26 @@ module Aspera
523
458
  # @return copy of the hash from name (also expands possible includes)
524
459
  def preset_by_name(config_name, include_path = [])
525
460
  raise Cli::Error, 'loop in include' if include_path.include?(config_name)
526
- include_path = include_path.clone # avoid messing up if there are multiple branches
461
+ include_path = include_path.clone # Avoid messing up if there are multiple branches
527
462
  current = @config_presets
528
463
  config_name.split(PRESET_DIG_SEPARATOR).each do |name|
529
464
  Aspera.assert_type(current, Hash, type: Cli::Error){"sub key: #{include_path}"}
530
465
  include_path.push(name)
531
466
  current = current[name]
532
- raise Cli::Error, "No such config preset: #{include_path}" if current.nil?
467
+ raise Cli::Error, "Unknown config preset: #{include_path}" if current.nil?
533
468
  end
534
469
  current = self.class.deep_clone(current) unless current.is_a?(String)
535
- return ExtendedValue.instance.evaluate(current)
536
- end
537
-
538
- def option_use_product=(value)
539
- Ascp::Installation.instance.use_ascp_from_product(value)
540
- end
541
-
542
- def option_use_product
543
- 'write-only option, see value of ascp_path'
470
+ return ExtendedValue.instance.evaluate(current, context: 'preset')
544
471
  end
545
472
 
546
473
  def option_plugin_folder=(value)
547
- Aspera.assert_values(value.class, [String, Array]){'plugin folder'}
548
- value = [value] if value.is_a?(String)
549
- Aspera.assert(value.all?(String)){'plugin folder'}
550
- value.each{ |f| PluginFactory.instance.add_lookup_folder(f)}
474
+ value = [value] unless value.is_a?(Array)
475
+ Aspera.assert_array_all(value, String){'plugin folder(s)'}
476
+ value.each{ |f| Plugins::Factory.instance.add_lookup_folder(f)}
551
477
  end
552
478
 
553
479
  def option_plugin_folder
554
- return PluginFactory.instance.lookup_folders
480
+ return Plugins::Factory.instance.lookup_folders
555
481
  end
556
482
 
557
483
  def option_preset; 'write-only option'; end
@@ -571,14 +497,14 @@ module Aspera
571
497
  JSON.generate(@config_presets).hash
572
498
  end
573
499
 
574
- # read config file and validate format
500
+ # Read config file and validate format
575
501
  def read_config_file
576
502
  Log.log.debug{"config file is: #{@option_config_file}".red}
577
- # files search for configuration, by default the one given by user
503
+ # Files search for configuration, by default the one given by user
578
504
  search_files = [@option_config_file]
579
- # find first existing file (or nil)
505
+ # Find first existing file (or nil)
580
506
  conf_file_to_load = search_files.find{ |f| File.exist?(f)}
581
- # if no file found, create default config
507
+ # If no file found, create default config
582
508
  if conf_file_to_load.nil?
583
509
  Log.log.warn{"No config file found. New configuration file: #{@option_config_file}"}
584
510
  @config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => 'new file'}}
@@ -591,16 +517,16 @@ module Aspera
591
517
  files_to_copy = []
592
518
  Log.dump(:available_presets, @config_presets, level: :trace1)
593
519
  Aspera.assert_type(@config_presets, Hash){'config file YAML'}
594
- # check there is at least the config section
520
+ # Check there is at least the config section
595
521
  Aspera.assert(@config_presets.key?(CONF_PRESET_CONFIG)){"Cannot find key: #{CONF_PRESET_CONFIG}"}
596
522
  version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
597
523
  raise Error, 'No version found in config section.' if version.nil?
598
524
  Log.log.debug{"conf version: #{version}"}
599
525
  # VVV if there are any conversion needed, those happen here.
600
- # fix bug in 4.4 (creating key "true" in "default" preset)
526
+ # Fix bug in 4.4 (creating key "true" in "default" preset)
601
527
  @config_presets[CONF_PRESET_DEFAULTS].delete(true) if @config_presets[CONF_PRESET_DEFAULTS].is_a?(Hash)
602
528
  # ^^^ Place new compatibility code before this line
603
- # set version to current
529
+ # Set version to current
604
530
  @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = Cli::VERSION
605
531
  unless files_to_copy.empty?
606
532
  Log.log.warn('Copying referenced files')
@@ -615,7 +541,7 @@ module Aspera
615
541
  rescue StandardError => e
616
542
  Log.log.debug{"-> #{e.class.name} : #{e}"}
617
543
  if File.exist?(@option_config_file)
618
- # then there is a problem with that file.
544
+ # Then there is a problem with that file.
619
545
  new_name = "#{@option_config_file}.pre#{Cli::VERSION}.manual_conversion_needed"
620
546
  File.rename(@option_config_file, new_name)
621
547
  Log.log.warn{"Renamed config file to #{new_name}."}
@@ -674,13 +600,13 @@ module Aspera
674
600
  when :show
675
601
  return Main.result_text(Ascp::Installation.instance.path(:ascp))
676
602
  when :info
677
- # collect info from ascp executable
603
+ # Collect info from ascp executable
678
604
  data = Ascp::Installation.instance.ascp_info
679
- # add command line transfer spec
680
- data['ts'] = transfer.option_transfer_spec
681
- # add keys
605
+ # Add command line transfer spec
606
+ data['ts'] = transfer.user_transfer_spec
607
+ # Add keys
682
608
  DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
683
- # declare those as secrets
609
+ # Declare those as secrets
684
610
  SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
685
611
  return Main.result_single_object(data)
686
612
  when :products
@@ -691,17 +617,17 @@ module Aspera
691
617
  when :use
692
618
  default_product = options.get_next_argument('product name')
693
619
  Ascp::Installation.instance.use_ascp_from_product(default_product)
694
- set_global_default(:ascp_path, Ascp::Installation.instance.path(:ascp))
620
+ set_global_default(:ascp_path, "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{default_product}")
695
621
  return Main.result_nothing
696
622
  end
697
623
  when :install
698
- # reset to default location, if older default was used
699
- Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: DIR_SDK) if @sdk_default_location
624
+ # Reset to default location, if older default was used
625
+ Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
700
626
  version = options.get_next_argument('transferd version', mandatory: false)
701
627
  n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
702
628
  return Main.result_status("Installed #{n} version #{v}")
703
629
  when :spec
704
- fields, data = Transfer::SpecDoc.man_table(Formatter)
630
+ fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true)
705
631
  return Main.result_object_list(data, fields: fields.map(&:to_s))
706
632
  when :schema
707
633
  schema = Transfer::Spec::SCHEMA.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
@@ -711,7 +637,7 @@ module Aspera
711
637
  return Main.result_single_object(schema)
712
638
  when :errors
713
639
  error_data = []
714
- Transfer::ERROR_INFO.each_pair do |code, prop|
640
+ Ascp::Management::ERRORS.each_pair do |code, prop|
715
641
  error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
716
642
  end
717
643
  return Main.result_object_list(error_data)
@@ -724,8 +650,8 @@ module Aspera
724
650
  command = options.get_next_command(%i[list install])
725
651
  case command
726
652
  when :install
727
- # reset to default location, if older default was used
728
- Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: DIR_SDK) if @sdk_default_location
653
+ # Reset to default location, if older default was used
654
+ Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
729
655
  version = options.get_next_argument('transferd version', mandatory: false)
730
656
  n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
731
657
  return Main.result_status("Installed #{n} version #{v}")
@@ -740,9 +666,9 @@ module Aspera
740
666
  Aspera.error_unreachable_line
741
667
  end
742
668
 
743
- # legacy actions available globally
669
+ # Legacy actions available globally
744
670
  PRESET_GBL_ACTIONS = %i[list overview lookup secure].freeze
745
- # operations requiring that preset exists
671
+ # Operations requiring that preset exists
746
672
  PRESET_EXIST_ACTIONS = %i[show delete get unset].freeze
747
673
  # require id
748
674
  PRESET_INSTANCE_ACTIONS = %i[initialize update ask set].concat(PRESET_EXIST_ACTIONS).freeze
@@ -752,13 +678,13 @@ module Aspera
752
678
  action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
753
679
  name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
754
680
  name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
755
- # those operations require existing option
681
+ # Those operations require existing option
756
682
  raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
757
683
  case action
758
684
  when :list
759
685
  return Main.result_value_list(@config_presets.keys, name: 'name')
760
686
  when :overview
761
- # display process modifies the value (hide secrets): we do not want to save removed secrets
687
+ # Display process modifies the value (hide secrets): we do not want to save removed secrets
762
688
  data = self.class.deep_clone(@config_presets)
763
689
  formatter.hide_secrets(data)
764
690
  result = []
@@ -778,7 +704,7 @@ module Aspera
778
704
  value = @config_presets[name][param_name]
779
705
  raise "no such option in preset #{name} : #{param_name}" if value.nil?
780
706
  case value
781
- when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s))
707
+ when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
782
708
  end
783
709
  return Main.result_single_object(value)
784
710
  when :unset
@@ -807,12 +733,12 @@ module Aspera
807
733
  options.ask_missing_mandatory = true
808
734
  @config_presets[name] ||= {}
809
735
  options.get_next_argument('option names', multiple: true).each do |option_name|
810
- option_value = options.get_interactive(option_name, option: true)
736
+ option_value = options.get_interactive(option_name, check_option: true)
811
737
  @config_presets[name][option_name] = option_value
812
738
  end
813
739
  return Main.result_status("Updated: #{name}")
814
740
  when :lookup
815
- BasicAuthPlugin.declare_options(options)
741
+ BasicAuth.declare_options(options)
816
742
  url = options.get_option(:url, mandatory: true)
817
743
  user = options.get_option(:username, mandatory: true)
818
744
  result = lookup_preset(url: url, username: user)
@@ -863,6 +789,7 @@ module Aspera
863
789
  coffee
864
790
  image
865
791
  ascp
792
+ sync
866
793
  transferd
867
794
  email_test
868
795
  smtp_settings
@@ -880,7 +807,7 @@ module Aspera
880
807
  def execute_action
881
808
  action = options.get_next_command(ACTIONS)
882
809
  case action
883
- when :preset # newer syntax
810
+ when :preset # Newer syntax
884
811
  return execute_preset
885
812
  when :open
886
813
  Environment.instance.open_editor(@option_config_file.to_s)
@@ -890,12 +817,12 @@ module Aspera
890
817
  section = "##{section}" unless section.nil?
891
818
  Environment.instance.open_uri("#{Info::DOC_URL}#{section}")
892
819
  return Main.result_nothing
893
- when :genkey # generate new rsa key
820
+ when :genkey # Generate new rsa key
894
821
  private_key_path = options.get_next_argument('private key file path')
895
822
  private_key_length = options.get_next_argument('size in bits', mandatory: false, validation: Integer, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH)
896
823
  OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
897
824
  return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
898
- when :pubkey # get pub key
825
+ when :pubkey # Get pub key
899
826
  private_key_pem = options.get_next_argument('private key PEM value')
900
827
  return Main.result_text(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
901
828
  when :remote_certificate
@@ -911,7 +838,7 @@ module Aspera
911
838
  when :name
912
839
  return Main.result_text(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
913
840
  end
914
- when :echo # display the content of a value given on command line
841
+ when :echo # Display the content of a value given on command line
915
842
  return Main.result_auto(options.get_next_argument('value', validation: nil))
916
843
  when :download
917
844
  file_url = options.get_next_argument('source URL').chomp
@@ -929,20 +856,20 @@ module Aspera
929
856
  return Main.result_object_list(OAuth::Factory.instance.persisted_tokens)
930
857
  when :show
931
858
  data = OAuth::Factory.instance.get_token_info(instance_identifier)
932
- raise Cli::Error, 'No such identifier' if data.nil?
859
+ raise Cli::Error, 'Unknown identifier' if data.nil?
933
860
  return Main.result_single_object(data)
934
861
  end
935
862
  when :plugins
936
863
  case options.get_next_command(%i[list create])
937
864
  when :list
938
865
  result = []
939
- PluginFactory.instance.plugin_list.each do |name|
940
- plugin_class = PluginFactory.instance.plugin_class(name)
866
+ Plugins::Factory.instance.plugin_list.each do |name|
867
+ plugin_class = Plugins::Factory.instance.plugin_class(name)
941
868
  result.push({
942
869
  plugin: name,
943
870
  detect: Formatter.tick(plugin_class.respond_to?(:detect)),
944
- wizard: Formatter.tick(plugin_class.respond_to?(:wizard)),
945
- path: PluginFactory.instance.plugin_source(name)
871
+ wizard: Formatter.tick(plugin_class.method_defined?(:wizard)),
872
+ path: Plugins::Factory.instance.plugin_source(name)
946
873
  })
947
874
  end
948
875
  return Main.result_object_list(result, fields: %w[plugin detect wizard path])
@@ -951,25 +878,27 @@ module Aspera
951
878
  destination_folder = options.get_next_argument('folder', mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
952
879
  plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
953
880
  content = <<~END_OF_PLUGIN_CODE
954
- require 'aspera/cli/plugin'
881
+ require 'aspera/cli/plugins/base'
955
882
  module Aspera
956
883
  module Cli
957
884
  module Plugins
958
- class #{plugin_name.capitalize} < Plugin
885
+ class #{plugin_name.snake_to_capital} < Base
959
886
  ACTIONS=[]
960
- def execute_action; return Main.result_status('You called plugin #{plugin_name}'); end
961
- end # #{plugin_name.capitalize}
962
- end # Plugins
963
- end # Cli
964
- end # Aspera
887
+ def execute_action
888
+ return Main.result_status('You called plugin #{plugin_name}')
889
+ end
890
+ end
891
+ end
892
+ end
893
+ end
965
894
  END_OF_PLUGIN_CODE
966
895
  File.write(plugin_file, content)
967
896
  return Main.result_status("Created #{plugin_file}")
968
897
  end
969
898
  when :detect, :wizard
970
- # interactive mode
899
+ # Interactive mode
971
900
  options.ask_missing_mandatory = true
972
- # detect plugins by url and optional query
901
+ # Detect plugins by url and optional query
973
902
  apps = @wizard.identify_plugins_for_url.freeze
974
903
  return Main.result_object_list(apps) if action.eql?(:detect)
975
904
  return @wizard.find(apps)
@@ -979,6 +908,17 @@ module Aspera
979
908
  return Main.result_image(options.get_next_argument('image URI or blob'))
980
909
  when :ascp
981
910
  execute_action_ascp
911
+ when :sync
912
+ case options.get_next_command(%i[spec admin translate])
913
+ when :spec
914
+ fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true, agent_columns: false, schema: Sync::Operations::CONF_SCHEMA)
915
+ return Main.result_object_list(data, fields: fields.map(&:to_s))
916
+ when :admin
917
+ return execute_sync_admin
918
+ when :translate
919
+ return Main.result_single_object(Sync::Operations.args_to_conf(options.get_next_argument('async arguments', multiple: true)))
920
+ else Aspera.error_unreachable_line
921
+ end
982
922
  when :transferd
983
923
  execute_action_transferd
984
924
  when :gem
@@ -986,6 +926,7 @@ module Aspera
986
926
  when :path then return Main.result_text(self.class.gem_src_root)
987
927
  when :version then return Main.result_text(Cli::VERSION)
988
928
  when :name then return Main.result_text(Info::GEM_NAME)
929
+ else Aspera.error_unreachable_line
989
930
  end
990
931
  when :folder
991
932
  return Main.result_text(@main_folder)
@@ -997,7 +938,7 @@ module Aspera
997
938
  when :smtp_settings
998
939
  return Main.result_single_object(email_settings)
999
940
  when :proxy_check
1000
- # ensure fpac was provided
941
+ # Ensure fpac was provided
1001
942
  options.get_option(:fpac, mandatory: true)
1002
943
  server_url = options.get_next_argument('server url')
1003
944
  return Main.result_text(@pac_exec.get_proxies(server_url))
@@ -1035,11 +976,11 @@ module Aspera
1035
976
  # @return [Hash] email server setting with defaults if not defined
1036
977
  def email_settings
1037
978
  smtp = options.get_option(:smtp, mandatory: true)
1038
- # change keys from string into symbol
979
+ # Change keys from string into symbol
1039
980
  smtp = smtp.symbolize_keys
1040
981
  unsupported = smtp.keys - SMTP_CONF_PARAMS
1041
982
  raise Cli::Error, "Unsupported SMTP parameter: #{unsupported.join(', ')}, use: #{SMTP_CONF_PARAMS.join(', ')}" unless unsupported.empty?
1042
- # defaults
983
+ # Defaults
1043
984
  # smtp[:ssl] = nil (false)
1044
985
  smtp[:tls] = !smtp[:ssl] unless smtp.key?(:tls)
1045
986
  smtp[:port] ||= if smtp[:tls]
@@ -1052,7 +993,7 @@ module Aspera
1052
993
  smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
1053
994
  smtp[:from_name] ||= smtp[:from_email].sub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
1054
995
  smtp[:domain] ||= smtp[:from_email].sub(/^.*@/, '') if smtp.key?(:from_email)
1055
- # check minimum required
996
+ # Check minimum required
1056
997
  %i[server port domain].each do |n|
1057
998
  Aspera.assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
1058
999
  end
@@ -1060,8 +1001,8 @@ module Aspera
1060
1001
  return smtp
1061
1002
  end
1062
1003
 
1063
- # send email using ERB template
1064
- # @param email_template_default [String] default template, can be overriden by option
1004
+ # Send email using ERB template
1005
+ # @param email_template_default [String] default template, can be overridden by option
1065
1006
  # @param values [Hash] values to be used in template, keys with default: to, from_name, from_email
1066
1007
  def send_email_template(email_template_default: nil, values: {})
1067
1008
  values[:to] ||= options.get_option(:notify_to, mandatory: true)
@@ -1074,14 +1015,14 @@ module Aspera
1074
1015
  end
1075
1016
  start_options = [mail_conf[:domain]]
1076
1017
  start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
1077
- # create a binding with only variables defined in values
1018
+ # Create a binding with only variables defined in values
1078
1019
  template_binding = Environment.empty_binding
1079
- # add variables to binding
1020
+ # Add variables to binding
1080
1021
  values.each do |k, v|
1081
1022
  Aspera.assert_type(k, Symbol)
1082
1023
  template_binding.local_variable_set(k, v)
1083
1024
  end
1084
- # execute template
1025
+ # Execute template
1085
1026
  msg_with_headers = ERB.new(notify_template).result(template_binding)
1086
1027
  Log.dump(:msg_with_headers, msg_with_headers)
1087
1028
  require 'net/smtp'
@@ -1095,7 +1036,7 @@ module Aspera
1095
1036
  end
1096
1037
 
1097
1038
  # Save current configuration to config file
1098
- # return true if file was saved
1039
+ # @return true if file was saved
1099
1040
  def save_config_file_if_needed
1100
1041
  raise Error, 'no configuration loaded' if @config_presets.nil?
1101
1042
  current_checksum = config_checksum
@@ -1109,29 +1050,35 @@ module Aspera
1109
1050
  return true
1110
1051
  end
1111
1052
 
1112
- # returns [String] name if config_presets has default
1113
- # returns nil if there is no config or bypass default params
1053
+ # @return [String] name if config_presets has default
1054
+ # @return nil if there is no config or bypass default params
1114
1055
  def get_plugin_default_config_name(plugin_name_sym)
1115
1056
  Aspera.assert(!@config_presets.nil?){'config_presets shall be defined'}
1116
1057
  if !@use_plugin_defaults
1117
1058
  Log.log.debug('skip default config')
1118
1059
  return
1119
1060
  end
1120
- if @config_presets.key?(CONF_PRESET_DEFAULTS) &&
1121
- @config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
1122
- default_config_name = @config_presets[CONF_PRESET_DEFAULTS][plugin_name_sym.to_s]
1123
- if !@config_presets.key?(default_config_name)
1124
- Log.log.error do
1125
- "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
1126
- 'Please fix the issue: either create preset with one parameter: ' \
1127
- "(#{Info::CMD_NAME} config id #{default_config_name} init @json:'{}') " \
1128
- "or remove default (#{Info::CMD_NAME} config id default remove #{plugin_name_sym})."
1129
- end
1061
+ if !@config_presets.key?(CONF_PRESET_DEFAULTS)
1062
+ Log.log.debug('No default section')
1063
+ return
1064
+ end
1065
+ Aspera.assert_type(@config_presets[CONF_PRESET_DEFAULTS], Hash){'default section'}
1066
+ if !@config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
1067
+ Log.log.debug("No default for #{plugin_name_sym}")
1068
+ return
1069
+ end
1070
+ default_config_name = @config_presets[CONF_PRESET_DEFAULTS][plugin_name_sym.to_s]
1071
+ if !@config_presets.key?(default_config_name)
1072
+ Log.log.error do
1073
+ "Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
1074
+ "Please fix the issue: either create preset with one parameter:\n" \
1075
+ "#{Info::CMD_NAME} config id #{default_config_name} init @json:'{}'\n" \
1076
+ "or remove default:\n#{Info::CMD_NAME} config id default remove #{plugin_name_sym}"
1130
1077
  end
1131
- raise Cli::Error, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
1132
- return default_config_name
1078
+ raise Cli::Error, "No such preset: #{default_config_name}"
1133
1079
  end
1134
- return
1080
+ Aspera.assert_type(@config_presets[default_config_name], Hash, type: Cli::Error){'preset type'}
1081
+ return default_config_name
1135
1082
  end
1136
1083
 
1137
1084
  # @return [Hash] result of execution of vault command
@@ -1163,7 +1110,7 @@ module Aspera
1163
1110
  def vault_value(name)
1164
1111
  m = name.split('.')
1165
1112
  raise BadArgument, 'vault name shall match <name>.<param>' unless m.length.eql?(2)
1166
- # this raise exception if label not found:
1113
+ # This raise exception if label not found:
1167
1114
  info = vault.get(label: m[0])
1168
1115
  value = info[m[1].to_sym]
1169
1116
  raise "no such entry value: #{m[1]}" if value.nil?
@@ -1184,12 +1131,12 @@ module Aspera
1184
1131
  )
1185
1132
  end
1186
1133
 
1187
- # Artifically raise an exception for tests
1134
+ # Artificially raise an exception for tests
1188
1135
  def execute_test
1189
1136
  case options.get_next_command(%i[throw web])
1190
1137
  when :throw
1191
1138
  # :type [String]
1192
- # options
1139
+ # Options
1193
1140
  exception_class_name = options.get_next_argument('exception class name', mandatory: true)
1194
1141
  exception_text = options.get_next_argument('exception text', mandatory: true)
1195
1142
  type = Object.const_get(exception_class_name)
@@ -1199,7 +1146,7 @@ module Aspera
1199
1146
  end
1200
1147
  end
1201
1148
 
1202
- # version of URL without trailing "/" and removing default port
1149
+ # Version of URL without trailing "/" and removing default port
1203
1150
  def canonical_url(url)
1204
1151
  url.chomp('/').sub(%r{^(https://[^/]+):443$}, '\1')
1205
1152
  end
@@ -1207,7 +1154,7 @@ module Aspera
1207
1154
  # Look for a preset that has the corresponding URL and username
1208
1155
  # @return the first one matching
1209
1156
  def lookup_preset(url:, username:)
1210
- # remove extra info to maximize match
1157
+ # Remove extra info to maximize match
1211
1158
  url = canonical_url(url)
1212
1159
  Log.log.debug{"Lookup preset for #{username}@#{url}"}
1213
1160
  @config_presets.each_value do |v|
@@ -1232,6 +1179,73 @@ module Aspera
1232
1179
  end
1233
1180
  return secret
1234
1181
  end
1182
+ # Private
1183
+ # Folder in $HOME for application files (config, cache)
1184
+ ASPERA_HOME_FOLDER_NAME = '.aspera'
1185
+ # Default config file
1186
+ DEFAULT_CONFIG_FILENAME = 'config.yaml'
1187
+ # Reserved preset names
1188
+ CONF_PRESET_CONFIG = 'config'
1189
+ CONF_PRESET_VERSION = 'version'
1190
+ CONF_PRESET_DEFAULTS = 'default'
1191
+ CONF_PRESET_GLOBAL = 'global_common_defaults'
1192
+ # Special name to identify value of default
1193
+ GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
1194
+ CONF_GLOBAL_SYM = :config
1195
+ # Folder containing custom plugins in user's config folder
1196
+ ASPERA_PLUGINS_FOLDERNAME = 'plugins'
1197
+ PERSISTENCY_FOLDER = 'persist_store'
1198
+ ASPERA = 'aspera'
1199
+ SERVER_COMMAND = 'server'
1200
+ TRANSFERD_APP_NAME = 'sdk'
1201
+ DEMO_SERVER = 'demo'
1202
+ DEMO_PRESET = 'demoserver' # cspell: disable-line
1203
+ EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
1204
+ From: <%=from_name%> <<%=from_email%>>
1205
+ To: <<%=to%>>
1206
+ Subject: #{Info::GEM_NAME} email test
1207
+
1208
+ This email was sent to test #{Info::CMD_NAME}.
1209
+ END_OF_TEMPLATE
1210
+ # Special extended values
1211
+ EXTEND_PRESET = :preset
1212
+ EXTEND_VAULT = :vault
1213
+ EXTEND_ARGS = :''
1214
+ PRESET_DIG_SEPARATOR = '.'
1215
+ DEFAULT_CHECK_NEW_VERSION_DAYS = 7
1216
+ COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
1217
+ GEM_CHECK_DATE_FMT = '%Y/%m/%d'
1218
+ # For testing only
1219
+ SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
1220
+ CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
1221
+ SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
1222
+ CERT_EXT = %w[crt cer pem der].freeze
1223
+ private_constant :ASPERA_HOME_FOLDER_NAME,
1224
+ :DEFAULT_CONFIG_FILENAME,
1225
+ :CONF_PRESET_CONFIG,
1226
+ :CONF_PRESET_VERSION,
1227
+ :CONF_PRESET_DEFAULTS,
1228
+ :CONF_PRESET_GLOBAL,
1229
+ :ASPERA_PLUGINS_FOLDERNAME,
1230
+ :ASPERA,
1231
+ :DEMO_SERVER,
1232
+ :DEMO_PRESET,
1233
+ :EMAIL_TEST_TEMPLATE,
1234
+ :EXTEND_PRESET,
1235
+ :EXTEND_VAULT,
1236
+ :DEFAULT_CHECK_NEW_VERSION_DAYS,
1237
+ :SERVER_COMMAND,
1238
+ :PRESET_DIG_SEPARATOR,
1239
+ :COFFEE_IMAGE_URL,
1240
+ :SELF_SIGNED_CERT,
1241
+ :PERSISTENCY_FOLDER,
1242
+ :CONF_OVERVIEW_KEYS,
1243
+ :SMTP_CONF_PARAMS,
1244
+ :TRANSFERD_APP_NAME,
1245
+ :GLOBAL_DEFAULT_KEYWORD,
1246
+ :CONF_GLOBAL_SYM,
1247
+ :GEM_CHECK_DATE_FMT,
1248
+ :CERT_EXT
1235
1249
  end
1236
1250
  end
1237
1251
  end