aspera-cli 4.15.0 → 4.17.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 (108) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +375 -280
  5. data/CONTRIBUTING.md +71 -18
  6. data/README.md +1978 -1656
  7. data/bin/ascli +13 -31
  8. data/bin/asession +32 -22
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/agent/alpha.rb +117 -0
  11. data/lib/aspera/agent/base.rb +61 -0
  12. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  13. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
  14. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
  15. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
  16. data/lib/aspera/agent/trsdk.rb +188 -0
  17. data/lib/aspera/api/aoc.rb +586 -0
  18. data/lib/aspera/api/ats.rb +46 -0
  19. data/lib/aspera/api/cos_node.rb +95 -0
  20. data/lib/aspera/api/node.rb +344 -0
  21. data/lib/aspera/ascmd.rb +47 -14
  22. data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
  23. data/lib/aspera/{fasp → ascp}/management.rb +14 -14
  24. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  25. data/lib/aspera/assert.rb +45 -0
  26. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  27. data/lib/aspera/cli/extended_value.rb +5 -5
  28. data/lib/aspera/cli/formatter.rb +27 -14
  29. data/lib/aspera/cli/hints.rb +7 -6
  30. data/lib/aspera/cli/main.rb +49 -29
  31. data/lib/aspera/cli/manager.rb +46 -36
  32. data/lib/aspera/cli/plugin.rb +34 -20
  33. data/lib/aspera/cli/plugin_factory.rb +61 -0
  34. data/lib/aspera/cli/plugins/alee.rb +7 -7
  35. data/lib/aspera/cli/plugins/aoc.rb +168 -132
  36. data/lib/aspera/cli/plugins/ats.rb +33 -33
  37. data/lib/aspera/cli/plugins/bss.rb +3 -4
  38. data/lib/aspera/cli/plugins/config.rb +250 -272
  39. data/lib/aspera/cli/plugins/console.rb +8 -6
  40. data/lib/aspera/cli/plugins/cos.rb +20 -19
  41. data/lib/aspera/cli/plugins/faspex.rb +71 -60
  42. data/lib/aspera/cli/plugins/faspex5.rb +212 -133
  43. data/lib/aspera/cli/plugins/node.rb +83 -75
  44. data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
  45. data/lib/aspera/cli/plugins/preview.rb +33 -31
  46. data/lib/aspera/cli/plugins/server.rb +33 -32
  47. data/lib/aspera/cli/plugins/shares.rb +39 -33
  48. data/lib/aspera/cli/sync_actions.rb +9 -9
  49. data/lib/aspera/cli/transfer_agent.rb +45 -25
  50. data/lib/aspera/cli/transfer_progress.rb +2 -3
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/colors.rb +5 -0
  53. data/lib/aspera/command_line_builder.rb +16 -14
  54. data/lib/aspera/coverage.rb +21 -0
  55. data/lib/aspera/data_repository.rb +33 -2
  56. data/lib/aspera/environment.rb +5 -4
  57. data/lib/aspera/faspex_gw.rb +13 -11
  58. data/lib/aspera/faspex_postproc.rb +6 -5
  59. data/lib/aspera/id_generator.rb +4 -2
  60. data/lib/aspera/json_rpc.rb +10 -8
  61. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  62. data/lib/aspera/keychain/macos_security.rb +29 -22
  63. data/lib/aspera/log.rb +5 -4
  64. data/lib/aspera/nagios.rb +7 -2
  65. data/lib/aspera/node_simulator.rb +213 -0
  66. data/lib/aspera/oauth/base.rb +143 -0
  67. data/lib/aspera/oauth/factory.rb +124 -0
  68. data/lib/aspera/oauth/generic.rb +34 -0
  69. data/lib/aspera/oauth/jwt.rb +51 -0
  70. data/lib/aspera/oauth/url_json.rb +31 -0
  71. data/lib/aspera/oauth/web.rb +50 -0
  72. data/lib/aspera/oauth.rb +5 -328
  73. data/lib/aspera/open_application.rb +7 -7
  74. data/lib/aspera/persistency_action_once.rb +13 -14
  75. data/lib/aspera/persistency_folder.rb +3 -2
  76. data/lib/aspera/preview/file_types.rb +53 -267
  77. data/lib/aspera/preview/generator.rb +7 -5
  78. data/lib/aspera/preview/terminal.rb +17 -7
  79. data/lib/aspera/preview/utils.rb +8 -7
  80. data/lib/aspera/proxy_auto_config.rb +6 -3
  81. data/lib/aspera/rest.rb +187 -140
  82. data/lib/aspera/rest_error_analyzer.rb +1 -0
  83. data/lib/aspera/rest_errors_aspera.rb +5 -3
  84. data/lib/aspera/resumer.rb +77 -0
  85. data/lib/aspera/secret_hider.rb +5 -2
  86. data/lib/aspera/ssh.rb +15 -8
  87. data/lib/aspera/temp_file_manager.rb +1 -1
  88. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  89. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  90. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  91. data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
  92. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
  93. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  94. data/lib/aspera/transfer/sync.rb +273 -0
  95. data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
  96. data/lib/aspera/web_server_simple.rb +12 -3
  97. data.tar.gz.sig +0 -0
  98. metadata +92 -68
  99. metadata.gz.sig +0 -0
  100. data/lib/aspera/aoc.rb +0 -606
  101. data/lib/aspera/ats_api.rb +0 -47
  102. data/lib/aspera/cos_node.rb +0 -93
  103. data/lib/aspera/fasp/agent_aspera.rb +0 -126
  104. data/lib/aspera/fasp/agent_base.rb +0 -48
  105. data/lib/aspera/fasp/agent_trsdk.rb +0 -146
  106. data/lib/aspera/fasp/resume_policy.rb +0 -77
  107. data/lib/aspera/node.rb +0 -338
  108. data/lib/aspera/sync.rb +0 -219
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aspera
4
- module Fasp
5
- # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
4
+ module Ascp
5
+ # processing of ascp management port events
6
6
  class Management
7
7
  # cspell: disable
8
8
  OPERATIONS = %w[
@@ -185,6 +185,8 @@ module Aspera
185
185
  ExtraCreatePolicy]
186
186
  # Management port start message
187
187
  MGT_HEADER = 'FASPMGR 2'
188
+ # empty line is separator to end event information
189
+ MGT_FRAME_SEPARATOR = ''
188
190
  # fields description for JSON generation
189
191
  # spellchecker: disable
190
192
  INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
@@ -193,28 +195,26 @@ module Aspera
193
195
  ArgScansCompleted PathScansAttempted FileScansCompleted TransfersAttempted TransfersPassed Delay].freeze
194
196
  BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
195
197
  MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
198
+ BOOLEAN_TRUE = 'Yes'
196
199
  # cspell: enable
197
200
 
198
201
  class << self
199
- # translates legacy event into enhanced (JSON) event
202
+ # translates mgt port event into (enhanced) typed event
200
203
  def enhanced_event_format(event)
201
204
  return event.keys.each_with_object({}) do |e, h|
202
- # capital_to_snake_case
203
- new_name = e
204
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
205
- .gsub(/([a-z\d])(usec)$/, '\1_\2')
206
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
207
- .downcase
205
+ new_name = e.capital_to_snake.gsub(/(usec)$/, '_\1').downcase
208
206
  value = event[e]
209
207
  value = value.to_i if INTEGER_FIELDS.include?(e)
210
- value = value.eql?('Yes') if BOOLEAN_FIELDS.include?(e)
208
+ value = value.eql?(BOOLEAN_TRUE) if BOOLEAN_FIELDS.include?(e)
211
209
  h[new_name] = value
212
210
  end
213
211
  end
214
212
  end # class << self
215
213
 
216
214
  def initialize
215
+ # current event being parsed line by line
217
216
  @event_build = nil
217
+ # last fully built event
218
218
  @last_event = nil
219
219
  end
220
220
  attr_reader :last_event
@@ -226,16 +226,16 @@ module Aspera
226
226
  # begin event
227
227
  @event_build = {}
228
228
  when /^([^:]+): (.*)$/
229
+ raise 'mgt port: unexpected line: data without header' if @event_build.nil?
229
230
  # event field
230
231
  @event_build[Regexp.last_match(1)] = Regexp.last_match(2)
231
- when ''
232
- # empty line is separator to end event information
233
- raise 'unexpected empty line' if @event_build.nil?
232
+ when MGT_FRAME_SEPARATOR
233
+ raise 'mgt port: unexpected line: end frame without header' if @event_build.nil?
234
234
  @last_event = @event_build
235
235
  @event_build = nil
236
236
  return @last_event
237
237
  else
238
- raise "unexpected line:[#{line}]"
238
+ raise "mgt port: unexpected line: [#{line}]"
239
239
  end # case
240
240
  return nil
241
241
  end
@@ -4,7 +4,7 @@
4
4
  require 'aspera/environment'
5
5
 
6
6
  module Aspera
7
- module Fasp
7
+ module Ascp
8
8
  # find Aspera standard products installation in standard paths
9
9
  class Products
10
10
  # known product names
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ class InternalError < StandardError
5
+ end
6
+
7
+ class AssertError < StandardError
8
+ end
9
+ class << self
10
+ # the block is executed in the context of the Aspera module
11
+ def assert(assertion, info = nil, level: 2, exception_class: AssertError)
12
+ raise InternalError, 'bad assert: both info and block given' unless info.nil? || !block_given?
13
+ return if assertion
14
+ message = 'assertion failed'
15
+ info = yield if block_given?
16
+ message = "#{message}: #{info}" if info
17
+ message = "#{message}: #{caller(level..level).first}"
18
+ raise exception_class, message
19
+ end
20
+
21
+ # assert that value has the given type
22
+ # @param value [Object] the value to check
23
+ # @param type [Class] the expected type
24
+ def assert_type(value, type, exception_class: AssertError)
25
+ assert(value.is_a?(type), level: 3, exception_class: exception_class){"#{block_given? ? "#{yield}: " : nil}expecting #{type}, but have #{value.inspect}"}
26
+ end
27
+
28
+ # assert that value is one of the given values
29
+ def assert_values(value, values, exception_class: AssertError)
30
+ assert(values.include?(value), level: 3, exception_class: exception_class) do
31
+ "#{block_given? ? "#{yield}: " : nil}expecting one of #{values.inspect}, but have #{value.inspect}"
32
+ end
33
+ end
34
+
35
+ # the line with this shall never be reached
36
+ def error_unreachable_line
37
+ raise InternalError, "unreachable line reached: #{caller(2..2).first}"
38
+ end
39
+
40
+ # the value is not one of the expected values
41
+ def error_unexpected_value(value, exception_class: InternalError)
42
+ raise exception_class, "#{block_given? ? "#{yield}: " : nil}unexpected value: #{value.inspect}"
43
+ end
44
+ end
45
+ end
@@ -6,22 +6,23 @@ require 'aspera/cli/plugin'
6
6
  module Aspera
7
7
  module Cli
8
8
  # base class for applications supporting basic authentication
9
- class BasicAuthPlugin < Aspera::Cli::Plugin
9
+ class BasicAuthPlugin < Cli::Plugin
10
10
  class << self
11
- @@basic_options_declared = false # rubocop:disable Style/ClassVars
12
- def declare_options(options, force: false)
13
- return if @@basic_options_declared && !force
14
- @@basic_options_declared = true # rubocop:disable Style/ClassVars
11
+ #@@basic_options_declared = false # rubocop:disable Style/ClassVars
12
+ def declare_options(options) # , force: false
13
+ #return if @@basic_options_declared && !force
14
+ #@@basic_options_declared = true # rubocop:disable Style/ClassVars
15
15
  options.declare(:url, 'URL of application, e.g. https://faspex.example.com/aspera/faspex')
16
- options.declare(:username, 'Username to log in')
16
+ options.declare(:username, "User's name to log in")
17
17
  options.declare(:password, "User's password")
18
18
  options.parse_options!
19
19
  end
20
20
  end
21
21
 
22
- def initialize(env)
23
- super(env)
24
- BasicAuthPlugin.declare_options(options, force: env[:all_manuals])
22
+ def initialize(basic_options: true, **env)
23
+ super(**env)
24
+ # , force: env[:all_manuals]
25
+ BasicAuthPlugin.declare_options(options) if basic_options
25
26
  end
26
27
 
27
28
  # returns a Rest object with basic auth
@@ -38,7 +39,7 @@ module Aspera
38
39
  end
39
40
 
40
41
  def basic_auth_api(subpath=nil)
41
- return Rest.new(basic_auth_params(subpath))
42
+ return Rest.new(**basic_auth_params(subpath))
42
43
  end
43
44
  end # BasicAuthPlugin
44
45
  end # Cli
@@ -4,6 +4,7 @@
4
4
  require 'aspera/uri_reader'
5
5
  require 'aspera/environment'
6
6
  require 'aspera/log'
7
+ require 'aspera/assert'
7
8
  require 'json'
8
9
  require 'base64'
9
10
  require 'zlib'
@@ -17,6 +18,7 @@ module Aspera
17
18
  include Singleton
18
19
 
19
20
  # special values
21
+ INIT = 'INIT'
20
22
  ALL = 'ALL'
21
23
  DEF = 'DEF'
22
24
 
@@ -30,9 +32,7 @@ module Aspera
30
32
  if col_titles.nil?
31
33
  col_titles = values
32
34
  else
33
- entry = {}
34
- col_titles.each{|title|entry[title] = values.shift}
35
- hash_array.push(entry)
35
+ hash_array.push(col_titles.zip(values).to_h)
36
36
  end
37
37
  end
38
38
  Log.log.warn('Titled CSV file without any line') if hash_array.empty?
@@ -63,7 +63,7 @@ module Aspera
63
63
  path: lambda{|v|File.expand_path(v)},
64
64
  re: lambda{|v|Regexp.new(v)},
65
65
  ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
66
- secret: lambda{|v|ExtendedValue.assert_no_value(v, :secret); $stdin.getpass('secret> ')}, # rubocop:disable Style/Semicolon
66
+ secret: lambda{|v|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
67
67
  stdin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
68
68
  yaml: lambda{|v|YAML.load(v)},
69
69
  zlib: lambda{|v|Zlib::Inflate.inflate(v)},
@@ -78,7 +78,7 @@ module Aspera
78
78
  # add a new handler
79
79
  def set_handler(name, method)
80
80
  Log.log.debug{"setting handler for #{name}"}
81
- raise 'name must be Symbol' unless name.is_a?(Symbol)
81
+ Aspera.assert_type(name, Symbol){'name'}
82
82
  @handlers[name] = method
83
83
  end
84
84
 
@@ -3,6 +3,8 @@
3
3
  # cspell:ignore jsonpp
4
4
  require 'aspera/secret_hider'
5
5
  require 'aspera/environment'
6
+ require 'aspera/log'
7
+ require 'aspera/assert'
6
8
  require 'terminal-table'
7
9
  require 'yaml'
8
10
  require 'pp'
@@ -18,7 +20,7 @@ module Aspera
18
20
 
19
21
  # General method
20
22
  def flatten(something)
21
- raise 'only Hash' unless something.is_a?(Hash)
23
+ Aspera.assert_type(something, Hash)
22
24
  @result = {}
23
25
  flatten_any(something, '')
24
26
  return @result
@@ -94,6 +96,7 @@ module Aspera
94
96
  DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv].freeze
95
97
  # user output levels
96
98
  DISPLAY_LEVELS = %i[info data error].freeze
99
+ RESULT_PARAMS = %i[type data total fields name].freeze
97
100
 
98
101
  private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR
99
102
  # prefix to display error messages in user messages (terminal)
@@ -104,7 +107,7 @@ module Aspera
104
107
  class << self
105
108
  # Highlight special values
106
109
  def special(what, use_colors: $stdout.isatty)
107
- result = "<#{what}>"
110
+ result = $stdout.isatty ? "<#{what}>" : "&lt;#{what}&gt;"
108
111
  if use_colors
109
112
  result = if %w[null empty].any?{|s|what.include?(s)}
110
113
  result.dim
@@ -155,16 +158,26 @@ module Aspera
155
158
  end
156
159
 
157
160
  def option_handler(option_symbol, operation, value=nil)
161
+ Aspera.assert_values(operation, %i[set get])
158
162
  case operation
159
- when :set then @options[option_symbol] = value
163
+ when :set
164
+ @options[option_symbol] = value
165
+ if option_symbol.eql?(:output)
166
+ $stdout = if value.eql?('-')
167
+ STDOUT # rubocop:disable Style/GlobalStdStream
168
+ else
169
+ File.open(value, 'w')
170
+ end
171
+ end
160
172
  when :get then return @options[option_symbol]
161
- else raise "internal error: no such operation: #{operation}"
173
+ else Aspera.error_unreachable_line
162
174
  end
163
175
  nil
164
176
  end
165
177
 
166
178
  def declare_options(options)
167
179
  options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
180
+ options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
168
181
  options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
169
182
  options.declare(
170
183
  :fields, "Comma separated list of: fields, or #{ExtendedValue::ALL}, or #{ExtendedValue::DEF}", handler: {o: self, m: :option_handler},
@@ -186,7 +199,7 @@ module Aspera
186
199
  when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
187
200
  when :info then $stdout.puts(message) if @options[:display].eql?(:info)
188
201
  when :error then $stderr.puts(message)
189
- else raise "wrong message_level:#{message_level}"
202
+ else Aspera.error_unexpected_value(message_level)
190
203
  end
191
204
  end
192
205
 
@@ -219,7 +232,7 @@ module Aspera
219
232
  when Array then @options[:fields]
220
233
  when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
221
234
  when Proc then return all_fields(data).select{|i|@options[:fields].call(i)}
222
- else raise "internal error: option: fields: #{@options[:fields]}"
235
+ else Aspera.error_unexpected_value(@options[:fields])
223
236
  end
224
237
  result = []
225
238
  until request.empty?
@@ -252,7 +265,7 @@ module Aspera
252
265
  # object_array: array of hash
253
266
  # fields: list of column names
254
267
  def display_table(object_array, fields)
255
- raise 'internal error: no field specified' if fields.nil?
268
+ Aspera.assert(!fields.nil?){'missing fields parameter'}
256
269
  case @options[:select]
257
270
  when Proc
258
271
  object_array.select!{|i|@options[:select].call(i)}
@@ -296,12 +309,12 @@ module Aspera
296
309
 
297
310
  # this method displays the results, especially the table format
298
311
  def display_results(results)
299
- raise "INTERNAL ERROR, result unsupported key: #{results.keys - %i[type data fields name]}" unless (results.keys - %i[type data fields name]).empty?
300
- # :type :data :fields :name
301
- raise "INTERNAL ERROR, result must be Hash (got: #{results.class}: #{results})" unless results.is_a?(Hash)
302
- raise "INTERNAL ERROR, result must have type (#{results})" unless results.key?(:type)
303
- raise 'INTERNAL ERROR, result must have data' unless results.key?(:data) || %i[empty nothing].include?(results[:type])
312
+ Aspera.assert_type(results, Hash)
313
+ Aspera.assert((results.keys - RESULT_PARAMS).empty?){"result unsupported key: #{results.keys - RESULT_PARAMS}"}
314
+ Aspera.assert(results.key?(:type)){"result must have type (#{results})"}
315
+ Aspera.assert(results.key?(:data) || %i[empty nothing].include?(results[:type])){'result must have data'}
304
316
  Log.log.debug{"display_results: #{results[:data].class} #{results[:type]}"}
317
+ display_item_count(results[:data].length, results[:total]) if results.key?(:total)
305
318
  SecretHider.deep_remove_secret(results[:data]) unless @options[:show_secrets] || @options[:display].eql?(:data)
306
319
  case @options[:format]
307
320
  when :text
@@ -323,8 +336,8 @@ module Aspera
323
336
  when :object_list, :single_object
324
337
  obj_list = results[:data]
325
338
  obj_list = [obj_list] if results[:type].eql?(:single_object)
326
- raise "internal error: expecting Array: got #{obj_list.class}" unless obj_list.is_a?(Array)
327
- raise 'internal error: expecting Array of Hash' unless obj_list.all?(Hash)
339
+ Aspera.assert_type(obj_list, Array)
340
+ Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
328
341
  # :object_list is an array of hash tables, where key=colum name
329
342
  obj_list = obj_list.map{|obj|Flattener.new.flatten(obj)} if @options[:flat_hash]
330
343
  display_table(obj_list, compute_fields(obj_list, results[:fields]))
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/fasp/error'
3
+ require 'aspera/transfer/error'
4
4
  require 'aspera/rest'
5
+ require 'aspera/log'
6
+ require 'aspera/assert'
5
7
  require 'net/ssh'
6
8
  require 'openssl'
7
9
 
@@ -12,7 +14,7 @@ module Aspera
12
14
  # Well know issues that users may get
13
15
  ERROR_HINTS = [
14
16
  {
15
- exception: Fasp::Error,
17
+ exception: Transfer::Error,
16
18
  match: 'Remote host is not who we expected',
17
19
  remediation: [
18
20
  'For this specific error, refer to:',
@@ -22,7 +24,7 @@ module Aspera
22
24
  ]
23
25
  },
24
26
  {
25
- exception: Aspera::RestCallError,
27
+ exception: RestCallError,
26
28
  match: /Signature has expired/,
27
29
  remediation: [
28
30
  'There is too much time difference between your computer and the server',
@@ -58,14 +60,13 @@ module Aspera
58
60
  matches = hint[:match]
59
61
  matches = [matches] unless matches.is_a?(Array)
60
62
  matches.each do |m|
63
+ Aspera.assert_values(m.class, [String, Regexp])
61
64
  case m
62
65
  when String
63
66
  next unless message.eql?(m)
64
67
  when Regexp
65
68
  next unless message.match?(m)
66
- else
67
- Log.log.warn("Internal error: hint is a #{m.class}")
68
- next
69
+ else Aspera.error_unexpected_value(m)
69
70
  end
70
71
  remediation = hint[:remediation]
71
72
  remediation = [remediation] unless remediation.is_a?(Array)
@@ -4,12 +4,15 @@ require 'aspera/cli/manager'
4
4
  require 'aspera/cli/formatter'
5
5
  require 'aspera/cli/plugins/config'
6
6
  require 'aspera/cli/extended_value'
7
+ require 'aspera/cli/plugin_factory'
7
8
  require 'aspera/cli/transfer_agent'
8
9
  require 'aspera/cli/version'
9
10
  require 'aspera/cli/info'
10
11
  require 'aspera/cli/hints'
12
+ require 'aspera/preview/terminal'
11
13
  require 'aspera/secret_hider'
12
14
  require 'aspera/log'
15
+ require 'aspera/assert'
13
16
 
14
17
  module Aspera
15
18
  module Cli
@@ -17,6 +20,9 @@ module Aspera
17
20
  class Main
18
21
  # Plugins store transfer result using this key and use result_transfer_multiple()
19
22
  STATUS_FIELD = 'status'
23
+ CONF_PLUGIN_SYM = :config
24
+
25
+ private_constant :CONF_PLUGIN_SYM
20
26
 
21
27
  class << self
22
28
  # expect some list, but nothing to display
@@ -53,13 +59,21 @@ module Aspera
53
59
  raise global_status unless global_status.eql?(:success)
54
60
  return {type: :object_list, data: status_table}
55
61
  end
62
+
63
+ def result_picture_in_terminal(options, blob)
64
+ terminal_options = options.get_option(:query, default: {}).symbolize_keys
65
+ allowed_options = Preview::Terminal.method(:build).parameters.select{|i|i[0].eql?(:key)}.map{|i|i[1]}
66
+ unknown_options = terminal_options.keys - allowed_options
67
+ raise "invalid options: #{unknown_options.join(', ')}, use #{allowed_options.join(', ')}" unless unknown_options.empty?
68
+ return Main.result_status(Preview::Terminal.build(blob, **terminal_options))
69
+ end
56
70
  end # self
57
71
 
58
72
  private
59
73
 
60
74
  # shortcuts helpers like in plugins
61
75
  %i[options transfer config formatter persistency].each do |name|
62
- define_method(name){@agents[name]}
76
+ define_method(name){@plug_init[name]}
63
77
  end
64
78
 
65
79
  # =============================================================
@@ -70,7 +84,7 @@ module Aspera
70
84
  def initialize(argv)
71
85
  @argv = argv
72
86
  # environment provided to plugin for various capabilities
73
- @agents = {}
87
+ @plug_init = Plugin::INIT_PARAMS.each_with_object({}) { |key, hash| hash[key] = nil }
74
88
  @option_help = false
75
89
  @option_show_config = false
76
90
  @bash_completion = false
@@ -78,10 +92,12 @@ module Aspera
78
92
 
79
93
  # This can throw exception if there is a problem with the environment, needs to be caught by execute method
80
94
  def init_agents_and_options
81
- # first thing : manage debug level (allows debugging of option parser)
95
+ @plug_init[:only_manual] = false
96
+ # create formatter, in case there is an exception, it is used to display.
97
+ @plug_init[:formatter] = Formatter.new
98
+ # second : manage debug level (allows debugging of option parser)
82
99
  early_debug_setup
83
- @agents[:formatter] = Formatter.new
84
- @agents[:options] = Manager.new(PROGRAM_NAME)
100
+ @plug_init[:options] = Manager.new(PROGRAM_NAME)
85
101
  # give command line arguments to option manager
86
102
  options.parse_command_line(@argv)
87
103
  # formatter adds options
@@ -94,11 +110,14 @@ module Aspera
94
110
  # declare and parse global options
95
111
  declare_global_options
96
112
  # the Config plugin adds the @preset parser, so declare before TransferAgent which may use it
97
- @agents[:config] = Plugins::Config.new(@agents, gem: GEM_NAME, name: PROGRAM_NAME, help: DOC_URL, version: Aspera::Cli::VERSION)
113
+ @plug_init[:config] = Plugins::Config.new(**@plug_init, gem: GEM_NAME, name: PROGRAM_NAME, help: DOC_URL, version: Cli::VERSION)
114
+ @plug_init[:persistency] = @plug_init[:config].persistency
98
115
  # data persistency
99
- raise 'internal error: missing persistency object' unless @agents[:persistency]
116
+ Aspera.assert(@plug_init[:persistency]){'missing persistency object'}
100
117
  # the TransferAgent plugin may use the @preset parser
101
- @agents[:transfer] = TransferAgent.new(options, config)
118
+ @plug_init[:config].transfer = @plug_init[:transfer] = TransferAgent.new(options, config)
119
+ nil_keys = @plug_init.select{|_, value|value.nil?}.keys
120
+ Aspera.assert(nil_keys.empty?){"nil : #{nil_keys}"}
102
121
  Log.log.debug('plugin env created'.red)
103
122
  # set banner when all environment is created so that additional extended value modifiers are known, e.g. @preset
104
123
  options.parser.banner = app_banner
@@ -108,7 +127,7 @@ module Aspera
108
127
  t = ' ' * 8
109
128
  return <<~END_OF_BANNER
110
129
  NAME
111
- #{t}#{PROGRAM_NAME} -- a command line tool for Aspera Applications (v#{Aspera::Cli::VERSION})
130
+ #{t}#{PROGRAM_NAME} -- a command line tool for Aspera Applications (v#{Cli::VERSION})
112
131
 
113
132
  SYNOPSIS
114
133
  #{t}#{PROGRAM_NAME} COMMANDS [OPTIONS] [ARGS]
@@ -144,7 +163,7 @@ module Aspera
144
163
  options.declare(:help, 'Show this message', values: :none, short: 'h') { @option_help = true }
145
164
  options.declare(:bash_comp, 'Generate bash completion for command', values: :none) { @bash_completion = true }
146
165
  options.declare(:show_config, 'Display parameters used for the provided action', values: :none) { @option_show_config = true }
147
- options.declare(:version, 'Display version', values: :none, short: 'v') { formatter.display_message(:data, Aspera::Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon, Layout/LineLength
166
+ options.declare(:version, 'Display version', values: :none, short: 'v') { formatter.display_message(:data, Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon, Layout/LineLength
148
167
  options.declare(:warnings, 'Check for language warnings', values: :none, short: 'w') { $VERBOSE = true }
149
168
  options.declare(
150
169
  :ui, 'Method to start browser',
@@ -166,20 +185,19 @@ module Aspera
166
185
  # also loads the plugin options, and default values from conf file
167
186
  # @param plugin_name_sym : symbol for plugin name
168
187
  def get_plugin_instance_with_options(plugin_name_sym, env=nil)
169
- env ||= @agents
188
+ env ||= @plug_init
170
189
  Log.log.debug{"get_plugin_instance_with_options(#{plugin_name_sym})"}
171
- require config.plugins[plugin_name_sym][:require_stanza]
190
+ require PluginFactory.instance.plugins[plugin_name_sym][:require_stanza]
172
191
  # load default params only if no param already loaded before plugin instantiation
173
192
  env[:config].add_plugin_default_preset(plugin_name_sym)
174
- command_plugin = Plugins::Config.plugin_class(plugin_name_sym).new(env)
193
+ command_plugin = PluginFactory.instance.create(plugin_name_sym, **env)
175
194
  Log.log.debug{"got #{command_plugin.class}"}
176
- # TODO: check that ancestor is Plugin?
177
195
  return command_plugin
178
196
  end
179
197
 
180
198
  def generate_bash_completion
181
199
  if options.get_next_argument('', expected: :multiple, mandatory: false).nil?
182
- config.plugins.each_key{|p|puts p.to_s}
200
+ PluginFactory.instance.plugins.each_key{|p|puts p}
183
201
  else
184
202
  Log.log.warn('only first level completion so far')
185
203
  end
@@ -192,11 +210,11 @@ module Aspera
192
210
  formatter.display_message(:error, options.parser)
193
211
  if all_plugins
194
212
  # list plugins that have a "require" field, i.e. all but main plugin
195
- config.plugins.each_key do |plugin_name_sym|
196
- next if plugin_name_sym.eql?(Plugins::Config::CONF_PLUGIN_SYM)
213
+ PluginFactory.instance.plugins.each_key do |plugin_name_sym|
214
+ next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
197
215
  # override main option parser with a brand new, to avoid having global options
198
- plugin_env = @agents.clone
199
- plugin_env[:all_manuals] = true # force declaration of all options
216
+ plugin_env = @plug_init.clone
217
+ plugin_env[:only_manual] = true # force declaration of all options
200
218
  plugin_env[:options] = Manager.new(PROGRAM_NAME)
201
219
  plugin_env[:options].parser.banner = '' # remove default banner
202
220
  get_plugin_instance_with_options(plugin_name_sym, plugin_env)
@@ -212,13 +230,15 @@ module Aspera
212
230
  # early debug for parser
213
231
  # Note: does not accept shortcuts
214
232
  def early_debug_setup
215
- Aspera::Log.instance.program_name = PROGRAM_NAME
233
+ Log.instance.program_name = PROGRAM_NAME
216
234
  @argv.each do |arg|
217
235
  case arg
218
236
  when '--' then break
219
- when /^--log-level=(.*)/ then Aspera::Log.instance.level = Regexp.last_match(1).to_sym
220
- when /^--logger=(.*)/ then Aspera::Log.instance.logger_type = Regexp.last_match(1).to_sym
237
+ when /^--log-level=(.*)/ then Log.instance.level = Regexp.last_match(1).to_sym
238
+ when /^--logger=(.*)/ then Log.instance.logger_type = Regexp.last_match(1).to_sym
221
239
  end
240
+ rescue => e
241
+ $stderr.puts("Error: #{e}")
222
242
  end
223
243
  end
224
244
 
@@ -234,16 +254,16 @@ module Aspera
234
254
  begin
235
255
  init_agents_and_options
236
256
  # find plugins, shall be after parse! ?
237
- config.add_plugins_from_lookup_folders
257
+ PluginFactory.instance.add_plugins_from_lookup_folders
238
258
  # help requested without command ? (plugins must be known here)
239
259
  exit_with_usage(true) if @option_help && options.command_or_arg_empty?
240
260
  generate_bash_completion if @bash_completion
241
261
  config.periodic_check_newer_gem_version
242
262
  command_sym =
243
263
  if @option_show_config && options.command_or_arg_empty?
244
- Plugins::Config::CONF_PLUGIN_SYM
264
+ CONF_PLUGIN_SYM
245
265
  else
246
- options.get_next_command(config.plugins.keys.dup.unshift(:help))
266
+ options.get_next_command(PluginFactory.instance.plugins.keys.dup.unshift(:help))
247
267
  end
248
268
  # command will not be executed, but we need manual
249
269
  options.fail_on_missing_mandatory = false if @option_help || @option_show_config
@@ -251,7 +271,7 @@ module Aspera
251
271
  case command_sym
252
272
  when :help
253
273
  exit_with_usage(true)
254
- when Plugins::Config::CONF_PLUGIN_SYM
274
+ when CONF_PLUGIN_SYM
255
275
  command_plugin = config
256
276
  else
257
277
  # get plugin, set options, etc
@@ -294,8 +314,8 @@ module Aspera
294
314
  rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
295
315
  rescue Cli::NoSuchIdentifier => e; exception_info = {e: e, t: 'Identifier'}
296
316
  rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
297
- rescue Fasp::Error => e; exception_info = {e: e, t: 'Transfer'}
298
- rescue Aspera::RestCallError => e; exception_info = {e: e, t: 'Rest'}
317
+ rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
318
+ rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
299
319
  rescue SocketError => e; exception_info = {e: e, t: 'Network'}
300
320
  rescue StandardError => e; exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
301
321
  rescue Interrupt => e; exception_info = {e: e, t: 'Interruption', debug: true}
@@ -304,7 +324,7 @@ module Aspera
304
324
  TempFileManager.instance.cleanup
305
325
  # 1- processing of error condition
306
326
  unless exception_info.nil?
307
- Log.log.warn(exception_info[:e].message) if Aspera::Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
327
+ Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
308
328
  formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
309
329
  formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
310
330
  # Is that a known error condition with proposal for remediation ?