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
@@ -5,6 +5,7 @@ require 'aspera/cli/error'
5
5
  require 'aspera/colors'
6
6
  require 'aspera/secret_hider'
7
7
  require 'aspera/log'
8
+ require 'aspera/assert'
8
9
  require 'io/console'
9
10
  require 'optparse'
10
11
 
@@ -20,7 +21,7 @@ module Aspera
20
21
  @option_name = option_name
21
22
  @has_writer = @object.respond_to?(writer_method)
22
23
  Log.log.debug{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
23
- raise "internal error: #{object} does not respond to #{method_name}" unless @object.respond_to?(@method)
24
+ Aspera.assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
24
25
  end
25
26
 
26
27
  def value
@@ -54,12 +55,13 @@ module Aspera
54
55
  # option name separator in code (symbol)
55
56
  OPTION_SEP_SYMBOL = '_'
56
57
  SOURCE_USER = 'cmdline' # cspell:disable-line
58
+ TYPE_INTEGER = [Integer].freeze
57
59
 
58
- private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER
60
+ private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER, :TYPE_INTEGER
59
61
 
60
62
  class << self
61
63
  def enum_to_bool(enum)
62
- raise "Value not valid for boolean: [#{enum}]/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
64
+ Aspera.assert_values(enum, BOOLEAN_VALUES){'boolean'}
63
65
  return TRUE_VALUES.include?(enum)
64
66
  end
65
67
 
@@ -73,14 +75,17 @@ module Aspera
73
75
  matching_exact = allowed_values.select{|i| i.to_s.eql?(short_value)}
74
76
  return matching_exact.first if matching_exact.length == 1
75
77
  matching = allowed_values.select{|i| i.to_s.start_with?(short_value)}
76
- raise Cli::BadArgument, bad_arg_message_multi("unknown value for #{descr}: #{short_value}", allowed_values) if matching.empty?
77
- raise Cli::BadArgument, bad_arg_message_multi("ambiguous shortcut for #{descr}: #{short_value}", matching) unless matching.length.eql?(1)
78
+ multi_choice_assert(!matching.empty?, "unknown value for #{descr}: #{short_value}", allowed_values)
79
+ multi_choice_assert(matching.length.eql?(1), "ambiguous shortcut for #{descr}: #{short_value}", matching)
78
80
  return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
79
81
  return matching.first
80
82
  end
81
83
 
82
- def bad_arg_message_multi(error_msg, choices)
83
- return [error_msg, 'Use:'].concat(choices.map{|c|"- #{c}"}.sort).join("\n")
84
+ # Generates error message with list of allowed values
85
+ # @param error_msg [String] error message
86
+ # @param choices [Array] list of allowed values
87
+ def multi_choice_assert(assertion, error_msg, choices)
88
+ raise Cli::BadArgument, [error_msg, 'Use:'].concat(choices.map{|c|"- #{c}"}.sort).join("\n") unless assertion
84
89
  end
85
90
 
86
91
  # change option name with dash to name with underscore
@@ -92,9 +97,13 @@ module Aspera
92
97
  return "--#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}"
93
98
  end
94
99
 
100
+ # @param what [Symbol] :option or :argument
101
+ # @param descr [String] description for help
102
+ # @param value [Object] value to check
103
+ # @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
95
104
  def validate_type(what, descr, value, type_list)
96
105
  return nil if type_list.nil?
97
- raise 'internal error: types must be a Class Array' unless type_list.is_a?(Array) && type_list.all?(Class)
106
+ Aspera.assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
98
107
  raise Cli::BadArgument,
99
108
  "#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of ' : ''}#{type_list.map(&:name).join(',')}" unless \
100
109
  type_list.any?{|t|value.is_a?(t)}
@@ -172,10 +181,15 @@ module Aspera
172
181
  # @param default [Object] default value
173
182
  # @return value, list or nil
174
183
  def get_next_argument(descr, expected: :single, mandatory: true, type: nil, aliases: nil, default: nil)
175
- unless type.nil?
176
- type = [type] unless type.is_a?(Array)
177
- raise "INTERNAL ERROR: type must be Array of Class: #{type}" unless type.all?(Class)
178
- descr = "#{descr} (#{type})"
184
+ Aspera.assert(%i[single multiple].include?(expected) || (expected.is_a?(Array) && expected.all?(Symbol))) do
185
+ 'expected must be single, multiple, or array of symbol'
186
+ end
187
+ Aspera.assert(type.nil? || type.is_a?(Class) || (type.is_a?(Array) && type.all?(Class))){'type must be Class or Array of Class'}
188
+ Aspera.assert(aliases.nil? || (aliases.is_a?(Hash) && aliases.keys.all?(Symbol) && aliases.values.all?(Symbol))){'aliases must be Hash'}
189
+ allowed_types = type
190
+ unless allowed_types.nil?
191
+ allowed_types = [allowed_types] unless allowed_types.is_a?(Array)
192
+ descr = "#{descr} (#{allowed_types.join(', ')})"
179
193
  end
180
194
  result =
181
195
  if !@unprocessed_cmd_line_arguments.empty?
@@ -193,23 +207,21 @@ module Aspera
193
207
  when Array
194
208
  allowed_values = [].concat(expected)
195
209
  allowed_values.concat(aliases.keys) unless aliases.nil?
196
- raise "internal error: only symbols allowed: #{allowed_values}" unless allowed_values.all?(Symbol)
197
210
  self.class.get_from_list(@unprocessed_cmd_line_arguments.shift, descr, allowed_values)
198
- else
199
- raise 'Internal error: expected: must be single, multiple, or value array'
211
+ else Aspera.error_unexpected_value(expected)
200
212
  end
201
213
  elsif !default.nil? then default
202
214
  # no value provided, either get value interactively, or exception
203
215
  elsif mandatory then get_interactive(:argument, descr, expected: expected)
204
216
  end
205
- if result.is_a?(String) && type.eql?([Integer])
206
- str_result = result
207
- result = Integer(str_result, exception: false)
208
- raise Cli::BadArgument, "Invalid integer: #{str_result}" if result.nil?
217
+ if result.is_a?(String) && allowed_types.eql?(TYPE_INTEGER)
218
+ int_result = Integer(result, exception: false)
219
+ raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
220
+ result = int_result
209
221
  end
210
222
  Log.log.debug{"#{descr}=#{result}"}
211
- result = aliases[result] if !aliases.nil? && aliases.key?(result)
212
- self.class.validate_type(:argument, descr, result, type) unless result.nil? && !mandatory
223
+ result = aliases[result] if aliases&.key?(result)
224
+ self.class.validate_type(:argument, descr, result, allowed_types) unless result.nil? && !mandatory
213
225
  return result
214
226
  end
215
227
 
@@ -221,7 +233,7 @@ module Aspera
221
233
  # @param mandatory [Boolean] if true, raise error if option not set
222
234
  def get_option(option_symbol, mandatory: false, default: nil)
223
235
  attributes = @declared_options[option_symbol]
224
- raise "INTERNAL ERROR: option not declared: #{option_symbol}" unless attributes
236
+ Aspera.assert(attributes){"option not declared: #{option_symbol}"}
225
237
  result = nil
226
238
  case attributes[:read_write]
227
239
  when :accessor
@@ -284,12 +296,10 @@ module Aspera
284
296
  # @param types [Class, Array] accepted value type(s)
285
297
  # @param block [Proc] block to execute when option is found
286
298
  def declare(option_symbol, description, handler: nil, default: nil, values: nil, short: nil, coerce: nil, types: nil, deprecation: nil, &block)
287
- raise "INTERNAL ERROR: #{option_symbol} already declared" if @declared_options.key?(option_symbol)
288
- # raise "INTERNAL ERROR: #{option_symbol} clash with another option" if
289
- # @declared_options.keys.map(&:to_s).any?{|k|k.start_with?(option_symbol.to_s) || option_symbol.to_s.start_with?(k)}
290
- raise "INTERNAL ERROR: #{option_symbol} ends with dot" unless description[-1] != '.'
291
- raise "INTERNAL ERROR: #{option_symbol} description does not start with capital" unless description[0] == description[0].upcase
292
- raise "INTERNAL ERROR: #{option_symbol} shall use :types" if ['hash', 'extended value'].any?{|s|description.downcase.include?(s) }
299
+ Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
300
+ Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
301
+ Aspera.assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with capital"}
302
+ Aspera.assert(!['hash', 'extended value'].any?{|s|description.downcase.include?(s) }){"#{option_symbol} shall use :types"}
293
303
  opt = @declared_options[option_symbol] = {
294
304
  read_write: handler.nil? ? :value : :accessor,
295
305
  # by default passwords and secrets are sensitive, else specify when declaring the option
@@ -297,7 +307,7 @@ module Aspera
297
307
  }
298
308
  if !types.nil?
299
309
  types = [types] unless types.is_a?(Array)
300
- raise "INTERNAL ERROR: types must be Array of Class: #{types}" unless types.all?(Class)
310
+ Aspera.assert(types.all?(Class)){"types must be Array of Class: #{types}"}
301
311
  opt[:types] = types
302
312
  description = "#{description} (#{types.map(&:name).join(', ')})"
303
313
  end
@@ -307,8 +317,8 @@ module Aspera
307
317
  end
308
318
  Log.log.debug{"declare: #{option_symbol}: #{opt[:read_write]}".green}
309
319
  if opt[:read_write].eql?(:accessor)
310
- raise 'internal error' unless handler.is_a?(Hash)
311
- raise 'internal error' unless handler.keys.sort.eql?(%i[m o])
320
+ Aspera.assert_type(handler, Hash)
321
+ Aspera.assert(handler.keys.sort.eql?(%i[m o]))
312
322
  Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
313
323
  opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
314
324
  end
@@ -347,11 +357,11 @@ module Aspera
347
357
  set_option(option_symbol, time_string, SOURCE_USER)
348
358
  end
349
359
  when :none
350
- raise "internal error: missing block for #{option_symbol}" if block.nil?
360
+ Aspera.assert(!block.nil?){"missing block for #{option_symbol}"}
351
361
  on_args.push(symbol_to_option(option_symbol, nil))
352
362
  on_args.push("-#{short}") if short.is_a?(String)
353
363
  @parser.on(*on_args, &block)
354
- else raise "internal error: Unknown type for values: #{values} / #{values.class}"
364
+ else Aspera.error_unexpected_value(values)
355
365
  end
356
366
  Log.log.debug{"on_args=#{on_args}"}
357
367
  end
@@ -359,8 +369,8 @@ module Aspera
359
369
  # Adds each of the keys of specified hash as an option
360
370
  # @param preset_hash [Hash] hash of options to add
361
371
  def add_option_preset(preset_hash, op: :push)
372
+ Aspera.assert_type(preset_hash, Hash)
362
373
  Log.log.debug{"add_option_preset=#{preset_hash}"}
363
- raise "internal error: default expects Hash: #{preset_hash.class}" unless preset_hash.is_a?(Hash)
364
374
  # incremental override
365
375
  preset_hash.each{|k, v|@unprocessed_defaults.send(op, [k.to_sym, v])}
366
376
  end
@@ -460,8 +470,8 @@ module Aspera
460
470
 
461
471
  def get_interactive(type, descr, expected: :single)
462
472
  if !@ask_missing_mandatory
463
- raise Cli::BadArgument, self.class.bad_arg_message_multi("missing: #{descr}", expected) if expected.is_a?(Array)
464
- raise Cli::BadArgument, "missing argument (#{expected}): #{descr}"
473
+ raise Cli::BadArgument, "missing argument (#{expected}): #{descr}" unless expected.is_a?(Array)
474
+ self.class.multi_choice_assert(false, "missing: #{descr}", expected)
465
475
  end
466
476
  result = nil
467
477
  sensitive = type.eql?(:option) && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/cli/extended_value'
4
+ require 'aspera/assert'
4
5
 
5
6
  module Aspera
6
7
  module Cli
@@ -18,6 +19,7 @@ module Aspera
18
19
  MAX_PAGES = 'pmax'
19
20
  # special identifier format: look for this name to find where supported
20
21
  REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/.freeze
22
+ INIT_PARAMS = %i[options transfer config formatter persistency only_manual].freeze
21
23
 
22
24
  class << self
23
25
  def declare_generic_options(options)
@@ -32,12 +34,18 @@ module Aspera
32
34
  end
33
35
  end
34
36
 
35
- def initialize(env)
36
- raise 'env must be Hash' unless env.is_a?(Hash)
37
- @agents = env
37
+ attr_accessor(*INIT_PARAMS)
38
+
39
+ def initialize(options:, transfer:, config:, formatter:, persistency:, only_manual:)
40
+ @options = options
41
+ @transfer = transfer
42
+ @config = config
43
+ @formatter = formatter
44
+ @persistency = persistency
45
+ @only_manual = only_manual
38
46
  # check presence in descendant of mandatory method and constant
39
- raise StandardError, "Missing method 'execute_action' in #{self.class}" unless respond_to?(:execute_action)
40
- raise StandardError, 'ACTIONS shall be redefined by subclass' unless self.class.constants.include?(:ACTIONS)
47
+ Aspera.assert(respond_to?(:execute_action)){"Missing method 'execute_action' in #{self.class}"}
48
+ Aspera.assert(self.class.constants.include?(:ACTIONS)){'ACTIONS shall be redefined by subclass'}
41
49
  # manual header for all plugins
42
50
  options.parser.separator('')
43
51
  options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
@@ -45,6 +53,11 @@ module Aspera
45
53
  options.parser.separator('OPTIONS:')
46
54
  end
47
55
 
56
+ def init_params
57
+ # return a hash of instance variables
58
+ INIT_PARAMS.map{|p| [p, instance_variable_get("@#{p}".to_sym)]}.to_h
59
+ end
60
+
48
61
  # must be called AFTER the instance action, ... folder browse <call instance_identifier>
49
62
  # @param description [String] description of the identifier
50
63
  # @param as_option [Symbol] option name to use if identifier is an option
@@ -75,6 +88,7 @@ module Aspera
75
88
  # @param id_result [String] key in result hash to use as identifier
76
89
  # @param fields [Array] fields to display
77
90
  def do_bulk_operation(command:, descr:, values: Hash, id_result: 'id', fields: :default)
91
+ Aspera.assert(block_given?){'missing block'}
78
92
  is_bulk = options.get_option(:bulk)
79
93
  case values
80
94
  when :identifier
@@ -82,7 +96,6 @@ module Aspera
82
96
  when Class
83
97
  values = value_create_modify(command: command, type: values, bulk: is_bulk)
84
98
  end
85
- raise 'Internal error: missing block' unless block_given?
86
99
  # if not bulk, there is a single value
87
100
  params = is_bulk ? values : [values]
88
101
  Log.log.warn('Empty list given for bulk operation') if params.empty?
@@ -147,6 +160,7 @@ module Aspera
147
160
  return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
148
161
  when :list
149
162
  resp = rest_api.read(res_class_path, old_query_read_delete)
163
+ return Main.result_empty if resp[:http].code == '204'
150
164
  data = resp[:data]
151
165
  # TODO: not generic : which application is this for ?
152
166
  if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
@@ -180,10 +194,10 @@ module Aspera
180
194
  end
181
195
 
182
196
  # implement generic rest operations on given resource path
183
- def entity_action(rest_api, res_class_path, **opts)
197
+ def entity_action(rest_api, res_class_path, **opts, &block)
184
198
  # res_name=res_class_path.gsub(%r{^.*/},'').gsub(%r{s$},'').gsub('_',' ')
185
199
  command = options.get_next_command(ALL_OPS)
186
- return entity_command(command, rest_api, res_class_path, **opts)
200
+ return entity_command(command, rest_api, res_class_path, **opts, &block)
187
201
  end
188
202
 
189
203
  # query parameters in URL suitable for REST list/GET and delete/DELETE
@@ -223,31 +237,31 @@ module Aspera
223
237
 
224
238
  # Retrieves an extended value from command line, used for creation or modification of entities
225
239
  # @param command [Symbol] command name for error message
226
- # @param type [Class] expected type of value, either a Class, an Array of Class, or :bulk_hash
240
+ # @param type [Class] expected type of value, either a Class, an Array of Class
241
+ # @param bulk [Boolean] if true, value must be an Array of <type>
227
242
  # @param default [Object] default value if not provided
228
243
  # TODO: when deprecation of `value` is completed: remove line with :value
229
- def value_create_modify(command:, type: Hash, bulk: false, default: nil)
244
+ def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
230
245
  value = options.get_option(:value)
231
246
  Log.log.warn("option `value` is deprecated. Use positional parameter for #{command}") unless value.nil?
232
- value = options.get_next_argument("parameters for #{command}", mandatory: default.nil?) if value.nil?
247
+ value = options.get_next_argument(
248
+ "parameters for #{command}#{description.nil? ? '' : " (#{description})"}", mandatory: default.nil?,
249
+ type: bulk ? Array : type) if value.nil?
233
250
  value = default if value.nil?
234
251
  unless type.nil?
235
252
  type = [type] unless type.is_a?(Array)
236
- raise "Internal error, check types must be a Class, not #{type.map(&:class).join(',')}" unless type.all?(Class)
253
+ Aspera.assert(type.all?(Class)){"check types must be a Class, not #{type.map(&:class).join(',')}"}
237
254
  if bulk
238
- raise Cli::BadArgument, "Value must be an Array of #{type.join(',')}" unless value.is_a?(Array)
239
- raise Cli::BadArgument, "Value must be a #{type.join(',')}, not #{value.map{|i| i.class.name}.uniq.join(',')}" unless value.all?{|v|type.include?(v.class)}
255
+ Aspera.assert_type(value, Array, exception_class: Cli::BadArgument)
256
+ value.each do |v|
257
+ Aspera.assert_values(v.class, type, exception_class: Cli::BadArgument)
258
+ end
240
259
  else
241
- raise Cli::BadArgument, "Value must be a #{type.join(',')}, not #{value.class.name}" unless type.include?(value.class)
260
+ Aspera.assert_values(value.class, type, exception_class: Cli::BadArgument)
242
261
  end
243
262
  end
244
263
  return value
245
264
  end
246
-
247
- # shortcuts helpers for plugin environment
248
- %i[options transfer config formatter persistency].each do |name|
249
- define_method(name){@agents[name]}
250
- end
251
265
  end # Plugin
252
266
  end # Cli
253
267
  end # Aspera
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ module Aspera
5
+ module Cli
6
+ # option is retrieved from another object using accessor
7
+ class PluginFactory
8
+ include Singleton
9
+ attr_reader :lookup_folders, :plugins
10
+
11
+ RUBY_FILE_EXT = '.rb'
12
+ PLUGINS_MODULE = 'Plugins'
13
+ private_constant :RUBY_FILE_EXT
14
+ class << self
15
+ # instantiate a plugin
16
+ # plugins must be Capitalized
17
+ def plugin_class(plugin_name_sym)
18
+ # Module.nesting[2] is Cli::Plugins
19
+ return Object.const_get("#{Module.nesting[2]}::#{PLUGINS_MODULE}::#{plugin_name_sym.to_s.capitalize}")
20
+ end
21
+ end
22
+
23
+ def initialize
24
+ @lookup_folders = []
25
+ @plugins = {}
26
+ end
27
+
28
+ def add_plugin_info(path)
29
+ raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
30
+ plugin_symbol = File.basename(path, RUBY_FILE_EXT).to_sym
31
+ req = path.sub(/#{RUBY_FILE_EXT}$/o, '')
32
+ if @plugins.key?(plugin_symbol)
33
+ Log.log.warn{"skipping plugin already registered: #{plugin_symbol}"}
34
+ return
35
+ end
36
+ @plugins[plugin_symbol] = {source: path, require_stanza: req}
37
+ end
38
+
39
+ def add_lookup_folder(folder)
40
+ @lookup_folders.unshift(folder)
41
+ end
42
+
43
+ # find plugins in defined paths
44
+ def add_plugins_from_lookup_folders
45
+ @lookup_folders.each do |folder|
46
+ next unless File.directory?(folder)
47
+ # TODO: add gem root to load path ? and require short folder ?
48
+ # $LOAD_PATH.push(folder) if i[:add_path]
49
+ Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
50
+ add_plugin_info(File.join(folder, source))
51
+ end
52
+ end
53
+ end
54
+
55
+ def create(plugin_name_sym, **args)
56
+ # TODO: check that ancestor is Plugin?
57
+ self.class.plugin_class(plugin_name_sym).new(**args)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/aoc'
3
+ require 'aspera/api/aoc'
4
4
 
5
5
  module Aspera
6
6
  module Cli
7
7
  module Plugins
8
- class Alee < Aspera::Cli::BasicAuthPlugin
8
+ class Alee < Cli::BasicAuthPlugin
9
9
  ACTIONS = %i[entitlement].freeze
10
10
 
11
11
  def execute_action
@@ -14,11 +14,11 @@ module Aspera
14
14
  when :entitlement
15
15
  entitlement_id = options.get_option(:username, mandatory: true)
16
16
  customer_id = options.get_option(:password, mandatory: true)
17
- api_metering = AoC.metering_api(entitlement_id, customer_id)
17
+ api_metering = Api::AoC.metering_api(entitlement_id, customer_id)
18
18
  return {type: :single_object, data: api_metering.read('entitlement')[:data]}
19
19
  end
20
20
  end
21
- end # Aspera
22
- end # Plugins
23
- end # Cli
24
- end # Aspera
21
+ end
22
+ end
23
+ end
24
+ end