aspera-cli 4.21.2 → 4.22.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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +34 -16
  5. data/CONTRIBUTING.md +6 -10
  6. data/README.md +805 -574
  7. data/examples/get_proto_file.rb +1 -1
  8. data/lib/aspera/agent/base.rb +9 -5
  9. data/lib/aspera/agent/connect.rb +30 -28
  10. data/lib/aspera/agent/desktop.rb +29 -25
  11. data/lib/aspera/agent/direct.rb +137 -125
  12. data/lib/aspera/agent/httpgw.rb +22 -26
  13. data/lib/aspera/agent/node.rb +14 -11
  14. data/lib/aspera/agent/transferd.rb +6 -2
  15. data/lib/aspera/api/aoc.rb +6 -6
  16. data/lib/aspera/api/cos_node.rb +1 -1
  17. data/lib/aspera/api/httpgw.rb +7 -3
  18. data/lib/aspera/api/node.rb +6 -4
  19. data/lib/aspera/ascmd.rb +3 -3
  20. data/lib/aspera/ascp/installation.rb +15 -16
  21. data/lib/aspera/ascp/management.rb +1 -1
  22. data/lib/aspera/assert.rb +11 -2
  23. data/lib/aspera/cli/error.rb +2 -2
  24. data/lib/aspera/cli/extended_value.rb +38 -19
  25. data/lib/aspera/cli/formatter.rb +48 -48
  26. data/lib/aspera/cli/hints.rb +1 -1
  27. data/lib/aspera/cli/main.rb +190 -168
  28. data/lib/aspera/cli/manager.rb +15 -15
  29. data/lib/aspera/cli/plugin.rb +23 -20
  30. data/lib/aspera/cli/plugin_factory.rb +1 -1
  31. data/lib/aspera/cli/plugins/alee.rb +1 -1
  32. data/lib/aspera/cli/plugins/aoc.rb +144 -107
  33. data/lib/aspera/cli/plugins/ats.rb +19 -17
  34. data/lib/aspera/cli/plugins/config.rb +67 -83
  35. data/lib/aspera/cli/plugins/console.rb +5 -3
  36. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  37. data/lib/aspera/cli/plugins/faspex5.rb +104 -80
  38. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  39. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  40. data/lib/aspera/cli/plugins/node.rb +306 -179
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  42. data/lib/aspera/cli/plugins/preview.rb +3 -3
  43. data/lib/aspera/cli/plugins/server.rb +6 -6
  44. data/lib/aspera/cli/plugins/shares.rb +5 -5
  45. data/lib/aspera/cli/sync_actions.rb +19 -18
  46. data/lib/aspera/cli/transfer_agent.rb +5 -5
  47. data/lib/aspera/cli/transfer_progress.rb +2 -2
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/command_line_builder.rb +116 -95
  50. data/lib/aspera/coverage.rb +4 -3
  51. data/lib/aspera/environment.rb +6 -6
  52. data/lib/aspera/faspex_gw.rb +14 -14
  53. data/lib/aspera/faspex_postproc.rb +7 -6
  54. data/lib/aspera/hash_ext.rb +2 -2
  55. data/lib/aspera/json_rpc.rb +1 -1
  56. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  57. data/lib/aspera/keychain/factory.rb +41 -0
  58. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  59. data/lib/aspera/keychain/macos_security.rb +19 -11
  60. data/lib/aspera/log.rb +28 -34
  61. data/lib/aspera/nagios.rb +6 -6
  62. data/lib/aspera/node_simulator.rb +8 -8
  63. data/lib/aspera/oauth/base.rb +8 -6
  64. data/lib/aspera/oauth/factory.rb +5 -6
  65. data/lib/aspera/oauth/url_json.rb +6 -6
  66. data/lib/aspera/persistency_action_once.rb +6 -4
  67. data/lib/aspera/persistency_folder.rb +2 -2
  68. data/lib/aspera/preview/generator.rb +1 -1
  69. data/lib/aspera/preview/options.rb +16 -16
  70. data/lib/aspera/preview/terminal.rb +3 -3
  71. data/lib/aspera/preview/utils.rb +11 -13
  72. data/lib/aspera/products/connect.rb +1 -1
  73. data/lib/aspera/products/desktop.rb +1 -1
  74. data/lib/aspera/products/transferd.rb +1 -1
  75. data/lib/aspera/proxy_auto_config.rb +2 -2
  76. data/lib/aspera/rest.rb +52 -43
  77. data/lib/aspera/rest_errors_aspera.rb +1 -1
  78. data/lib/aspera/secret_hider.rb +5 -5
  79. data/lib/aspera/ssh.rb +4 -4
  80. data/lib/aspera/transfer/convert.rb +29 -0
  81. data/lib/aspera/transfer/error_info.rb +66 -66
  82. data/lib/aspera/transfer/parameters.rb +13 -68
  83. data/lib/aspera/transfer/spec.rb +5 -6
  84. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  85. data/lib/aspera/transfer/spec_doc.rb +62 -0
  86. data/lib/aspera/transfer/sync.rb +23 -72
  87. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  88. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  89. data/lib/aspera/transfer/uri.rb +6 -6
  90. data/lib/aspera/uri_reader.rb +1 -1
  91. data/lib/aspera/web_auth.rb +1 -1
  92. data/lib/aspera/web_server_simple.rb +53 -44
  93. data.tar.gz.sig +1 -2
  94. metadata +37 -4
  95. metadata.gz.sig +0 -0
  96. data/examples/build_package.sh +0 -28
  97. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -15,14 +15,36 @@ require 'aspera/assert'
15
15
 
16
16
  module Aspera
17
17
  module Cli
18
+ # Global objects shared with plugins
19
+ class Objects
20
+ MEMBERS = %i[options transfer config formatter persistency].freeze
21
+ attr_accessor(*MEMBERS)
22
+
23
+ def validate
24
+ MEMBERS.each do |i|
25
+ Aspera.assert(instance_variable_defined?(:"@#{i}"))
26
+ Aspera.assert(!instance_variable_get(:"@#{i}").nil?)
27
+ end
28
+ end
29
+
30
+ def only_manual?
31
+ transfer.eql?(:only_manual)
32
+ end
33
+
34
+ def only_manual
35
+ @transfer = :only_manual
36
+ end
37
+ end
38
+
18
39
  # The main CLI class
19
40
  class Main
20
41
  # Plugins store transfer result using this key and use result_transfer_multiple()
21
42
  STATUS_FIELD = 'status'
22
43
  COMMAND_CONFIG = :config
23
44
  COMMAND_HELP = :help
45
+ SCALAR_TYPES = [String, Integer, Symbol].freeze
24
46
 
25
- private_constant :COMMAND_CONFIG, :COMMAND_HELP
47
+ private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES
26
48
 
27
49
  class << self
28
50
  # expect some list, but nothing to display
@@ -33,6 +55,8 @@ module Aspera
33
55
 
34
56
  def result_status(status); return {type: :status, data: status}; end
35
57
 
58
+ def result_text(data); return {type: :text, data: data}; end
59
+
36
60
  def result_success; return result_status('complete'); end
37
61
 
38
62
  # Process statuses of finished transfer sessions
@@ -57,7 +81,7 @@ module Aspera
57
81
  item[STATUS_FIELD] = item[STATUS_FIELD].map(&:to_s).join(',')
58
82
  end
59
83
  raise global_status unless global_status.eql?(:success)
60
- return {type: :object_list, data: status_table}
84
+ return result_object_list(status_table)
61
85
  end
62
86
 
63
87
  def result_image(blob, formatter:)
@@ -69,68 +93,177 @@ module Aspera
69
93
  end
70
94
 
71
95
  def result_object_list(data, fields: nil, total: nil)
72
- return {type: :object_list, data: data, fields: fields, total: nil}
96
+ return {type: :object_list, data: data, fields: fields, total: total}
73
97
  end
74
98
 
75
99
  def result_value_list(data, name)
76
100
  return {type: :value_list, data: data, name: name}
77
101
  end
78
- end
79
102
 
80
- private
81
-
82
- # shortcuts helpers like in plugins
83
- %i[options transfer config formatter persistency].each do |name|
84
- define_method(name){@plug_init[name]}
103
+ # Determines type of result based on data
104
+ def result_auto(data)
105
+ case data
106
+ when Hash
107
+ return result_single_object(data)
108
+ when Array
109
+ all_types = data.map(&:class).uniq
110
+ return result_object_list(data) if all_types.eql?([Hash])
111
+ unsupported_types = all_types - SCALAR_TYPES
112
+ return result_value_list(data, 'list') if unsupported_types.empty?
113
+ Aspera.error_unexpected_value(unsupported_types){'list item types'}
114
+ when *SCALAR_TYPES
115
+ return result_text(data)
116
+ else Aspera.error_unexpected_value(data.class.name){'result type'}
117
+ end
118
+ end
85
119
  end
86
120
 
87
- # =============================================================
88
- # Parameter handlers
89
- #
90
-
91
121
  # minimum initialization, no exception raised
92
122
  def initialize(argv)
93
123
  @argv = argv
94
- # environment provided to plugin for various capabilities
95
- @plug_init = Plugin::INIT_PARAMS.each_with_object({}) { |key, hash| hash[key] = nil }
124
+ early_debug_setup
125
+ Log.log.trace2{Log.dump(:argv, @argv)}
96
126
  @option_help = false
97
127
  @option_show_config = false
98
128
  @bash_completion = false
99
- early_debug_setup
100
- Log.log.trace2{Log.dump(:argv, @argv)}
129
+ @env = Objects.new
101
130
  end
102
131
 
132
+ # this is the main function called by initial script just after constructor
133
+ def process_command_line
134
+ # catch exception information , if any
135
+ exception_info = nil
136
+ # false if command shall not be executed (e.g. --show-config)
137
+ execute_command = true
138
+ # catch exceptions
139
+ begin
140
+ init_agents_and_options
141
+ # find plugins, shall be after parse! ?
142
+ PluginFactory.instance.add_plugins_from_lookup_folders
143
+ # help requested without command ? (plugins must be known here)
144
+ exit_with_usage(true) if @option_help && @env.options.command_or_arg_empty?
145
+ generate_bash_completion if @bash_completion
146
+ @env.config.periodic_check_newer_gem_version
147
+ command_sym =
148
+ if @option_show_config && @env.options.command_or_arg_empty?
149
+ COMMAND_CONFIG
150
+ else
151
+ @env.options.get_next_command(PluginFactory.instance.plugin_list.unshift(COMMAND_HELP))
152
+ end
153
+ # command will not be executed, but we need manual
154
+ @env.options.fail_on_missing_mandatory = false if @option_help || @option_show_config
155
+ # main plugin is not dynamically instantiated
156
+ case command_sym
157
+ when COMMAND_HELP
158
+ exit_with_usage(true)
159
+ when COMMAND_CONFIG
160
+ command_plugin = @env.config
161
+ else
162
+ # get plugin, set options, etc
163
+ command_plugin = get_plugin_instance_with_options(command_sym)
164
+ # parse plugin specific options
165
+ @env.options.parse_options!
166
+ end
167
+ # help requested for current plugin
168
+ exit_with_usage(false) if @option_help
169
+ if @option_show_config
170
+ @env.formatter.display_results(type: :single_object, data: @env.options.known_options(only_defined: true).stringify_keys)
171
+ execute_command = false
172
+ end
173
+ # locking for single execution (only after "per plugin" option, in case lock port is there)
174
+ lock_port = @env.options.get_option(:lock_port)
175
+ if !lock_port.nil?
176
+ begin
177
+ # no need to close later, will be freed on process exit. must save in member else it is garbage collected
178
+ Log.log.debug{"Opening lock port #{lock_port}"}
179
+ # loopback address, could also be 'localhost'
180
+ @tcp_server = TCPServer.new('127.0.0.1', lock_port)
181
+ rescue StandardError => e
182
+ execute_command = false
183
+ Log.log.warn{"Another instance is already running (#{e.message})."}
184
+ end
185
+ end
186
+ pid_file = @env.options.get_option(:pid_file)
187
+ if !pid_file.nil?
188
+ File.write(pid_file, Process.pid)
189
+ Log.log.debug{"Wrote pid #{Process.pid} to #{pid_file}"}
190
+ at_exit{File.delete(pid_file)}
191
+ end
192
+ # execute and display (if not exclusive execution)
193
+ @env.formatter.display_results(**command_plugin.execute_action) if execute_command
194
+ # save config file if command modified it
195
+ @env.config.save_config_file_if_needed
196
+ # finish
197
+ @env.transfer.shutdown
198
+ rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
199
+ rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
200
+ rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
201
+ rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
202
+ rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
203
+ rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
204
+ rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
205
+ rescue SocketError => e; exception_info = {e: e, t: 'Network'}
206
+ rescue StandardError => e; exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
207
+ rescue Interrupt => e; exception_info = {e: e, t: 'Interruption', debug: true}
208
+ end
209
+ # cleanup file list files
210
+ TempFileManager.instance.cleanup
211
+ # 1- processing of error condition
212
+ unless exception_info.nil?
213
+ Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
214
+ @env.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
215
+ @env.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
216
+ # Is that a known error condition with proposal for remediation ?
217
+ Hints.hint_for(exception_info[:e], @env.formatter)
218
+ end
219
+ # 2- processing of command not processed (due to exception or bad command line)
220
+ if execute_command || @option_show_config
221
+ @env.options.final_errors.each do |msg|
222
+ @env.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} Argument: #{msg}")
223
+ # add code as exception if there is not already an error
224
+ exception_info = {e: Exception.new(msg), t: 'UnusedArg'} if exception_info.nil?
225
+ end
226
+ end
227
+ # 3- in case of error, fail the process status
228
+ unless exception_info.nil?
229
+ # show stack trace in debug mode
230
+ raise exception_info[:e] if Log.log.debug?
231
+ # else give hint and exit
232
+ @env.formatter.display_message(:error, 'Use --log-level=debug to get more details.') if exception_info[:debug]
233
+ Process.exit(1)
234
+ end
235
+ return nil
236
+ end
237
+
238
+ private
239
+
103
240
  # This can throw exception if there is a problem with the environment, needs to be caught by execute method
104
241
  def init_agents_and_options
105
- @plug_init[:only_manual] = false
106
242
  # create formatter, in case there is an exception, it is used to display.
107
- @plug_init[:formatter] = Formatter.new
243
+ @env.formatter = Formatter.new
108
244
  # create command line manager with arguments
109
- @plug_init[:options] = Manager.new(Info::CMD_NAME, @argv)
245
+ @env.options = Manager.new(Info::CMD_NAME, @argv)
110
246
  # formatter adds options
111
- @plug_init[:formatter].declare_options(options)
112
- ExtendedValue.instance.default_decoder = options.get_option(:struct_parser)
247
+ @env.formatter.declare_options(@env.options)
248
+ ExtendedValue.instance.default_decoder = @env.options.get_option(:struct_parser)
113
249
  # compare $0 with expected name
114
250
  current_prog_name = File.basename($PROGRAM_NAME)
115
- formatter.display_message(
251
+ @env.formatter.display_message(
116
252
  :error,
117
253
  "#{Formatter::WARNING_FLASH} Please use '#{Info::CMD_NAME}' instead of '#{current_prog_name}'") unless current_prog_name.eql?(Info::CMD_NAME)
118
254
  # declare and parse global options
119
255
  declare_global_options
120
256
  # the Config plugin adds the @preset parser, so declare before TransferAgent which may use it
121
- @plug_init[:config] = Plugins::Config.new(**@plug_init, man_header: false)
122
- @plug_init[:persistency] = @plug_init[:config].persistency
123
- # data persistency
124
- Aspera.assert(@plug_init[:persistency]){'missing persistency object'}
257
+ @env.config = Plugins::Config.new(broker: @env, man_header: false)
258
+ # data persistency is set in config
259
+ Aspera.assert(@env.persistency){'missing persistency object'}
125
260
  # the TransferAgent plugin may use the @preset parser
126
- @plug_init[:config].transfer = @plug_init[:transfer] = TransferAgent.new(options, config)
261
+ @env.transfer = TransferAgent.new(@env.options, @env.config)
127
262
  # add commands for config plugin after all options have been added
128
- @plug_init[:config].add_manual_header(false)
129
- nil_keys = @plug_init.select{|_, value|value.nil?}.keys
130
- Aspera.assert(nil_keys.empty?){"nil : #{nil_keys}"}
131
- Log.log.debug('plugin env created'.red)
263
+ @env.config.add_manual_header(false)
264
+ @env.validate
132
265
  # set banner when all environment is created so that additional extended value modifiers are known, e.g. @preset
133
- options.parser.banner = app_banner
266
+ @env.options.parser.banner = app_banner
134
267
  end
135
268
 
136
269
  def app_banner
@@ -170,41 +303,40 @@ module Aspera
170
303
  # define header for manual
171
304
  def declare_global_options
172
305
  Log.log.debug('declare_global_options')
173
- options.declare(:help, 'Show this message', values: :none, short: 'h') { @option_help = true }
174
- options.declare(:bash_comp, 'Generate bash completion for command', values: :none) { @bash_completion = true }
175
- options.declare(:show_config, 'Display parameters used for the provided action', values: :none) { @option_show_config = true }
176
- options.declare(:version, 'Display version', values: :none, short: 'v') { formatter.display_message(:data, Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon, Layout/LineLength
177
- options.declare(
306
+ @env.options.declare(:help, 'Show this message', values: :none, short: 'h'){@option_help = true}
307
+ @env.options.declare(:bash_comp, 'Generate bash completion for command', values: :none){@bash_completion = true}
308
+ @env.options.declare(:show_config, 'Display parameters used for the provided action', values: :none){@option_show_config = true}
309
+ @env.options.declare(:version, 'Display version', values: :none, short: 'v'){@env.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon, Layout/LineLength
310
+ @env.options.declare(
178
311
  :ui, 'Method to start browser',
179
312
  values: Environment::USER_INTERFACES,
180
313
  handler: {o: Environment.instance, m: :url_method},
181
314
  default: Environment.default_gui_mode)
182
- options.declare(:log_level, 'Log level', values: Log.levels, handler: {o: Log.instance, m: :level})
183
- options.declare(:logger, 'Logging method', values: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
184
- options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', coerce: Integer, types: Integer)
185
- options.declare(:once_only, 'Process only new items (some commands)', values: :bool, default: false)
186
- options.declare(:log_secrets, 'Show passwords in logs', values: :bool, handler: {o: SecretHider, m: :log_secrets})
187
- options.declare(:clean_temp, 'Cleanup temporary files on exit', values: :bool, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
188
- options.declare(:pid_file, 'Write process identifier to file, delete on exit', types: String)
315
+ @env.options.declare(:log_level, 'Log level', values: Log.levels, handler: {o: Log.instance, m: :level})
316
+ @env.options.declare(:logger, 'Logging method', values: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
317
+ @env.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', coerce: Integer, types: Integer)
318
+ @env.options.declare(:once_only, 'Process only new items (some commands)', values: :bool, default: false)
319
+ @env.options.declare(:log_secrets, 'Show passwords in logs', values: :bool, handler: {o: SecretHider, m: :log_secrets})
320
+ @env.options.declare(:clean_temp, 'Cleanup temporary files on exit', values: :bool, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
321
+ @env.options.declare(:pid_file, 'Write process identifier to file, delete on exit', types: String)
189
322
  # parse declared options
190
- options.parse_options!
323
+ @env.options.parse_options!
191
324
  end
192
325
 
193
326
  # @return the plugin instance, based on name
194
327
  # also loads the plugin options, and default values from conf file
195
328
  # @param plugin_name_sym : symbol for plugin name
196
- def get_plugin_instance_with_options(plugin_name_sym, env=nil)
197
- env ||= @plug_init
329
+ def get_plugin_instance_with_options(plugin_name_sym)
198
330
  Log.log.debug{"get_plugin_instance_with_options(#{plugin_name_sym})"}
199
331
  # load default params only if no param already loaded before plugin instantiation
200
- env[:config].add_plugin_default_preset(plugin_name_sym)
201
- command_plugin = PluginFactory.instance.create(plugin_name_sym, **env)
332
+ @env.config.add_plugin_default_preset(plugin_name_sym)
333
+ command_plugin = PluginFactory.instance.create(plugin_name_sym, broker: @env)
202
334
  return command_plugin
203
335
  end
204
336
 
205
337
  def generate_bash_completion
206
- if options.get_next_argument('', multiple: true, mandatory: false).nil?
207
- PluginFactory.instance.plugin_list.each{|p|puts p}
338
+ if @env.options.get_next_argument('', multiple: true, mandatory: false).nil?
339
+ PluginFactory.instance.plugin_list.each{ |p| puts p}
208
340
  else
209
341
  Log.log.warn('only first level completion so far')
210
342
  end
@@ -214,27 +346,24 @@ module Aspera
214
346
  def exit_with_usage(include_all_plugins)
215
347
  Log.log.debug{"exit_with_usage(#{include_all_plugins})".bg_red}
216
348
  # display main plugin options (+config)
217
- formatter.display_message(:error, options.parser)
349
+ @env.formatter.display_message(:error, @env.options.parser)
218
350
  if include_all_plugins
351
+ @env.only_manual
219
352
  # list plugins that have a "require" field, i.e. all but main plugin
220
353
  PluginFactory.instance.plugin_list.each do |plugin_name_sym|
221
354
  # config was already included in the global options
222
355
  next if plugin_name_sym.eql?(COMMAND_CONFIG)
223
356
  # override main option parser with a brand new, to avoid having global options
224
- plugin_env = @plug_init.clone
225
- plugin_env[:only_manual] = true # force declaration of all options
226
- plugin_env[:options] = Manager.new(Info::CMD_NAME)
227
- plugin_env[:options].parser.banner = '' # remove default banner
228
- get_plugin_instance_with_options(plugin_name_sym, plugin_env)
357
+ @env.options = Manager.new(Info::CMD_NAME)
358
+ @env.options.parser.banner = '' # remove default banner
359
+ get_plugin_instance_with_options(plugin_name_sym)
229
360
  # display generated help for plugin options
230
- formatter.display_message(:error, plugin_env[:options].parser.help)
361
+ @env.formatter.display_message(:error, @env.options.parser.help)
231
362
  end
232
363
  end
233
364
  Process.exit(0)
234
365
  end
235
366
 
236
- protected
237
-
238
367
  # early debug for parser
239
368
  # Note: does not accept shortcuts
240
369
  def early_debug_setup
@@ -249,113 +378,6 @@ module Aspera
249
378
  $stderr.puts("Error: #{e}")
250
379
  end
251
380
  end
252
-
253
- public
254
-
255
- # this is the main function called by initial script just after constructor
256
- def process_command_line
257
- # catch exception information , if any
258
- exception_info = nil
259
- # false if command shall not be executed (e.g. --show-config)
260
- execute_command = true
261
- # catch exceptions
262
- begin
263
- init_agents_and_options
264
- # find plugins, shall be after parse! ?
265
- PluginFactory.instance.add_plugins_from_lookup_folders
266
- # help requested without command ? (plugins must be known here)
267
- exit_with_usage(true) if @option_help && options.command_or_arg_empty?
268
- generate_bash_completion if @bash_completion
269
- config.periodic_check_newer_gem_version
270
- command_sym =
271
- if @option_show_config && options.command_or_arg_empty?
272
- COMMAND_CONFIG
273
- else
274
- options.get_next_command(PluginFactory.instance.plugin_list.unshift(COMMAND_HELP))
275
- end
276
- # command will not be executed, but we need manual
277
- options.fail_on_missing_mandatory = false if @option_help || @option_show_config
278
- # main plugin is not dynamically instantiated
279
- case command_sym
280
- when COMMAND_HELP
281
- exit_with_usage(true)
282
- when COMMAND_CONFIG
283
- command_plugin = config
284
- else
285
- # get plugin, set options, etc
286
- command_plugin = get_plugin_instance_with_options(command_sym)
287
- # parse plugin specific options
288
- options.parse_options!
289
- end
290
- # help requested for current plugin
291
- exit_with_usage(false) if @option_help
292
- if @option_show_config
293
- formatter.display_results(type: :single_object, data: options.known_options(only_defined: true).stringify_keys)
294
- execute_command = false
295
- end
296
- # locking for single execution (only after "per plugin" option, in case lock port is there)
297
- lock_port = options.get_option(:lock_port)
298
- if !lock_port.nil?
299
- begin
300
- # no need to close later, will be freed on process exit. must save in member else it is garbage collected
301
- Log.log.debug{"Opening lock port #{lock_port}"}
302
- @tcp_server = TCPServer.new('127.0.0.1', lock_port)
303
- rescue StandardError => e
304
- execute_command = false
305
- Log.log.warn{"Another instance is already running (#{e.message})."}
306
- end
307
- end
308
- pid_file = options.get_option(:pid_file)
309
- if !pid_file.nil?
310
- File.write(pid_file, Process.pid)
311
- Log.log.debug{"Wrote pid #{Process.pid} to #{pid_file}"}
312
- at_exit{File.delete(pid_file)}
313
- end
314
- # execute and display (if not exclusive execution)
315
- formatter.display_results(**command_plugin.execute_action) if execute_command
316
- # save config file if command modified it
317
- config.save_config_file_if_needed
318
- # finish
319
- transfer.shutdown
320
- rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
321
- rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
322
- rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
323
- rescue Cli::NoSuchIdentifier => e; exception_info = {e: e, t: 'Identifier'}
324
- rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
325
- rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
326
- rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
327
- rescue SocketError => e; exception_info = {e: e, t: 'Network'}
328
- rescue StandardError => e; exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
329
- rescue Interrupt => e; exception_info = {e: e, t: 'Interruption', debug: true}
330
- end
331
- # cleanup file list files
332
- TempFileManager.instance.cleanup
333
- # 1- processing of error condition
334
- unless exception_info.nil?
335
- Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
336
- formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
337
- formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
338
- # Is that a known error condition with proposal for remediation ?
339
- Hints.hint_for(exception_info[:e], formatter)
340
- end
341
- # 2- processing of command not processed (due to exception or bad command line)
342
- if execute_command || @option_show_config
343
- options.final_errors.each do |msg|
344
- formatter.display_message(:error, "#{Formatter::ERROR_FLASH} Argument: #{msg}")
345
- # add code as exception if there is not already an error
346
- exception_info = {e: Exception.new(msg), t: 'UnusedArg'} if exception_info.nil?
347
- end
348
- end
349
- # 3- in case of error, fail the process status
350
- unless exception_info.nil?
351
- # show stack trace in debug mode
352
- raise exception_info[:e] if Log.log.debug?
353
- # else give hint and exit
354
- formatter.display_message(:error, 'Use --log-level=debug to get more details.') if exception_info[:debug]
355
- Process.exit(1)
356
- end
357
- return nil
358
- end
359
381
  end
360
382
  end
361
383
  end
@@ -21,7 +21,7 @@ module Aspera
21
21
  @option_name = option_name
22
22
  @has_writer = @object.respond_to?(writer_method)
23
23
  Log.log.trace1{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
24
- Aspera.assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
24
+ Aspera.assert(@object.respond_to?(@method)){"#{object} does not respond to #{method_name}"}
25
25
  end
26
26
 
27
27
  def value
@@ -48,7 +48,7 @@ module Aspera
48
48
  BOOLEAN_SIMPLE = %i[no yes].freeze
49
49
  FALSE_VALUES = [BOOLEAN_SIMPLE.first, false].freeze
50
50
  TRUE_VALUES = [BOOLEAN_SIMPLE.last, true].freeze
51
- BOOLEAN_VALUES = [TRUE_VALUES, FALSE_VALUES].flatten.freeze
51
+ BOOLEAN_VALUES = (TRUE_VALUES + FALSE_VALUES).freeze
52
52
 
53
53
  # option name separator on command line
54
54
  OPTION_SEP_LINE = '-'
@@ -76,9 +76,9 @@ module Aspera
76
76
  # find shortened string value in allowed symbol list
77
77
  def get_from_list(short_value, descr, allowed_values)
78
78
  # we accept shortcuts
79
- matching_exact = allowed_values.select{|i| i.to_s.eql?(short_value)}
79
+ matching_exact = allowed_values.select{ |i| i.to_s.eql?(short_value)}
80
80
  return matching_exact.first if matching_exact.length == 1
81
- matching = allowed_values.select{|i| i.to_s.start_with?(short_value)}
81
+ matching = allowed_values.select{ |i| i.to_s.start_with?(short_value)}
82
82
  multi_choice_assert(!matching.empty?, "unknown value for #{descr}: #{short_value}", allowed_values)
83
83
  multi_choice_assert(matching.length.eql?(1), "ambiguous shortcut for #{descr}: #{short_value}", matching)
84
84
  return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
@@ -89,7 +89,7 @@ module Aspera
89
89
  # @param error_msg [String] error message
90
90
  # @param accept_list [Array] list of allowed values
91
91
  def multi_choice_assert(assertion, error_msg, accept_list)
92
- raise Cli::BadArgument, [error_msg, 'Use:'].concat(accept_list.map{|c|"- #{c}"}.sort).join("\n") unless assertion
92
+ raise Cli::BadArgument, [error_msg, 'Use:'].concat(accept_list.map{ |c| "- #{c}"}.sort).join("\n") unless assertion
93
93
  end
94
94
 
95
95
  # change option name with dash to name with underscore
@@ -113,7 +113,7 @@ module Aspera
113
113
  value_list.each do |value|
114
114
  raise Cli::BadArgument,
115
115
  "#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of: ' : ''}#{type_list.map(&:name).join(', ')}" unless
116
- type_list.any?{|t|value.is_a?(t)}
116
+ type_list.any?{ |t| value.is_a?(t)}
117
117
  end
118
118
  end
119
119
  end
@@ -202,13 +202,13 @@ module Aspera
202
202
  if !@unprocessed_cmd_line_arguments.empty?
203
203
  how_many = multiple ? @unprocessed_cmd_line_arguments.length : 1
204
204
  values = @unprocessed_cmd_line_arguments.shift(how_many)
205
- values = values.map{|v|evaluate_extended_value(v, allowed_types)}
205
+ values = values.map{ |v| evaluate_extended_value(v, allowed_types)}
206
206
  # if expecting list and only one arg of type array : it is the list
207
207
  values = values.first if multiple && values.length.eql?(1) && values.first.is_a?(Array)
208
208
  if accept_list
209
209
  allowed_values = [].concat(accept_list)
210
210
  allowed_values.concat(aliases.keys) unless aliases.nil?
211
- values = values.map{|v|self.class.get_from_list(v, descr, allowed_values)}
211
+ values = values.map{ |v| self.class.get_from_list(v, descr, allowed_values)}
212
212
  end
213
213
  multiple ? values : values.first
214
214
  elsif !default.nil? then default
@@ -309,7 +309,7 @@ module Aspera
309
309
  Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
310
310
  Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
311
311
  Aspera.assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with capital"}
312
- Aspera.assert(!['hash', 'extended value'].any?{|s|description.downcase.include?(s) }){"#{option_symbol} shall use :types"}
312
+ Aspera.assert(!['hash', 'extended value'].any?{ |s| description.downcase.include?(s)}){"#{option_symbol} shall use :types"}
313
313
  opt = @declared_options[option_symbol] = {
314
314
  read_write: handler.nil? ? :value : :accessor,
315
315
  # by default passwords and secrets are sensitive, else specify when declaring the option
@@ -339,7 +339,7 @@ module Aspera
339
339
  on_args.push(symbol_to_option(option_symbol, 'VALUE'))
340
340
  on_args.push("-#{short}VALUE") unless short.nil?
341
341
  on_args.push(coerce) unless coerce.nil?
342
- @parser.on(*on_args) { |v| set_option(option_symbol, v, where: SOURCE_USER) }
342
+ @parser.on(*on_args){ |v| set_option(option_symbol, v, where: SOURCE_USER)}
343
343
  when Array, :bool
344
344
  if values.eql?(:bool)
345
345
  values = BOOLEAN_VALUES
@@ -348,14 +348,14 @@ module Aspera
348
348
  # this option value must be a symbol
349
349
  opt[:values] = values
350
350
  value = get_option(option_symbol)
351
- help_values = values.map{|i|i.eql?(value) ? highlight_current(i) : i}.join(', ')
351
+ help_values = values.map{ |i| i.eql?(value) ? highlight_current(i) : i}.join(', ')
352
352
  if values.eql?(BOOLEAN_VALUES)
353
- help_values = BOOLEAN_SIMPLE.map{|i|(i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
353
+ help_values = BOOLEAN_SIMPLE.map{ |i| (i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
354
354
  end
355
355
  on_args[0] = "#{description}: #{help_values}"
356
356
  on_args.push(symbol_to_option(option_symbol, 'ENUM'))
357
357
  on_args.push(values)
358
- @parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), where: SOURCE_USER)}
358
+ @parser.on(*on_args){ |v| set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), where: SOURCE_USER)}
359
359
  when :date
360
360
  on_args.push(symbol_to_option(option_symbol, 'DATE'))
361
361
  @parser.on(*on_args) do |v|
@@ -478,7 +478,7 @@ module Aspera
478
478
  def prompt_user_input_in_list(prompt, sym_list)
479
479
  loop do
480
480
  input = prompt_user_input(prompt).to_sym
481
- if sym_list.any?{|a|a.eql?(input)}
481
+ if sym_list.any?{ |a| a.eql?(input)}
482
482
  return input
483
483
  else
484
484
  $stderr.puts("No such #{prompt}: #{input}, select one of: #{sym_list.join(', ')}")
@@ -522,7 +522,7 @@ module Aspera
522
522
  private
523
523
 
524
524
  def evaluate_extended_value(value, types)
525
- if DEFAULT_PARSER_TYPES.include?(types) || (types.is_a?(Array) && types.all?{|t|DEFAULT_PARSER_TYPES.include?(t)})
525
+ if DEFAULT_PARSER_TYPES.include?(types) || (types.is_a?(Array) && types.all?{ |t| DEFAULT_PARSER_TYPES.include?(t)})
526
526
  return ExtendedValue.instance.evaluate_with_default(value)
527
527
  end
528
528
  return ExtendedValue.instance.evaluate(value)