aspera-cli 4.7.0 → 4.9.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 (96) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1267 -999
  4. data/bin/ascli +20 -1
  5. data/bin/asession +37 -34
  6. data/docs/test_env.conf +7 -3
  7. data/examples/aoc.rb +13 -12
  8. data/examples/dascli +23 -0
  9. data/examples/faspex4.rb +34 -29
  10. data/examples/{transfer.rb → node.rb} +31 -59
  11. data/examples/server.rb +93 -0
  12. data/lib/aspera/aoc.rb +153 -143
  13. data/lib/aspera/ascmd.rb +56 -45
  14. data/lib/aspera/ats_api.rb +9 -6
  15. data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
  16. data/lib/aspera/cli/extended_value.rb +33 -30
  17. data/lib/aspera/cli/formater.rb +105 -111
  18. data/lib/aspera/cli/info.rb +3 -2
  19. data/lib/aspera/cli/listener/line_dump.rb +1 -0
  20. data/lib/aspera/cli/listener/logger.rb +1 -0
  21. data/lib/aspera/cli/listener/progress.rb +13 -12
  22. data/lib/aspera/cli/listener/progress_multi.rb +21 -20
  23. data/lib/aspera/cli/main.rb +110 -90
  24. data/lib/aspera/cli/manager.rb +99 -88
  25. data/lib/aspera/cli/plugin.rb +98 -39
  26. data/lib/aspera/cli/plugins/alee.rb +6 -5
  27. data/lib/aspera/cli/plugins/aoc.rb +581 -450
  28. data/lib/aspera/cli/plugins/ats.rb +84 -83
  29. data/lib/aspera/cli/plugins/bss.rb +30 -27
  30. data/lib/aspera/cli/plugins/config.rb +488 -397
  31. data/lib/aspera/cli/plugins/console.rb +17 -15
  32. data/lib/aspera/cli/plugins/cos.rb +26 -35
  33. data/lib/aspera/cli/plugins/faspex.rb +206 -172
  34. data/lib/aspera/cli/plugins/faspex5.rb +109 -74
  35. data/lib/aspera/cli/plugins/node.rb +379 -189
  36. data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
  37. data/lib/aspera/cli/plugins/preview.rb +131 -122
  38. data/lib/aspera/cli/plugins/server.rb +50 -150
  39. data/lib/aspera/cli/plugins/shares.rb +61 -27
  40. data/lib/aspera/cli/plugins/sync.rb +15 -14
  41. data/lib/aspera/cli/transfer_agent.rb +75 -64
  42. data/lib/aspera/cli/version.rb +2 -1
  43. data/lib/aspera/colors.rb +29 -28
  44. data/lib/aspera/command_line_builder.rb +50 -43
  45. data/lib/aspera/cos_node.rb +64 -38
  46. data/lib/aspera/data_repository.rb +1 -0
  47. data/lib/aspera/environment.rb +33 -10
  48. data/lib/aspera/fasp/agent_base.rb +35 -30
  49. data/lib/aspera/fasp/agent_connect.rb +35 -30
  50. data/lib/aspera/fasp/agent_direct.rb +68 -60
  51. data/lib/aspera/fasp/agent_httpgw.rb +71 -64
  52. data/lib/aspera/fasp/agent_node.rb +24 -23
  53. data/lib/aspera/fasp/agent_trsdk.rb +19 -20
  54. data/lib/aspera/fasp/error.rb +2 -1
  55. data/lib/aspera/fasp/error_info.rb +79 -68
  56. data/lib/aspera/fasp/installation.rb +130 -126
  57. data/lib/aspera/fasp/listener.rb +1 -0
  58. data/lib/aspera/fasp/parameters.rb +71 -60
  59. data/lib/aspera/fasp/parameters.yaml +69 -17
  60. data/lib/aspera/fasp/resume_policy.rb +14 -11
  61. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  62. data/lib/aspera/fasp/uri.rb +25 -24
  63. data/lib/aspera/faspex_gw.rb +83 -72
  64. data/lib/aspera/hash_ext.rb +23 -13
  65. data/lib/aspera/id_generator.rb +16 -13
  66. data/lib/aspera/keychain/encrypted_hash.rb +61 -46
  67. data/lib/aspera/keychain/macos_security.rb +26 -24
  68. data/lib/aspera/log.rb +35 -39
  69. data/lib/aspera/nagios.rb +36 -28
  70. data/lib/aspera/node.rb +19 -19
  71. data/lib/aspera/oauth.rb +120 -100
  72. data/lib/aspera/open_application.rb +25 -22
  73. data/lib/aspera/persistency_action_once.rb +9 -8
  74. data/lib/aspera/persistency_folder.rb +13 -9
  75. data/lib/aspera/preview/file_types.rb +261 -266
  76. data/lib/aspera/preview/generator.rb +74 -73
  77. data/lib/aspera/preview/image_error.png +0 -0
  78. data/lib/aspera/preview/options.rb +7 -6
  79. data/lib/aspera/preview/utils.rb +30 -33
  80. data/lib/aspera/preview/video_error.png +0 -0
  81. data/lib/aspera/proxy_auto_config.rb +27 -23
  82. data/lib/aspera/rest.rb +73 -74
  83. data/lib/aspera/rest_call_error.rb +1 -0
  84. data/lib/aspera/rest_error_analyzer.rb +23 -19
  85. data/lib/aspera/rest_errors_aspera.rb +43 -40
  86. data/lib/aspera/secret_hider.rb +74 -0
  87. data/lib/aspera/ssh.rb +13 -10
  88. data/lib/aspera/sync.rb +49 -47
  89. data/lib/aspera/temp_file_manager.rb +7 -5
  90. data/lib/aspera/timer_limiter.rb +9 -8
  91. data/lib/aspera/uri_reader.rb +17 -18
  92. data/lib/aspera/web_auth.rb +17 -15
  93. data.tar.gz.sig +5 -0
  94. metadata +119 -35
  95. metadata.gz.sig +0 -0
  96. data/bin/dascli +0 -13
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/colors'
3
4
  require 'aspera/log'
4
5
  require 'aspera/cli/extended_value'
@@ -15,7 +16,7 @@ module Aspera
15
16
 
16
17
  class CliNoSuchId < CliError
17
18
  def initialize(res_type,res_id)
18
- msg="No such #{res_type} identifier: #{res_id}"
19
+ msg = "No such #{res_type} identifier: #{res_id}"
19
20
  super(msg)
20
21
  end
21
22
  end
@@ -25,8 +26,8 @@ module Aspera
25
26
  #attr_accessor :object
26
27
  #attr_accessor :attr_symb
27
28
  def initialize(object,attr_symb)
28
- @object=object
29
- @attr_symb=attr_symb
29
+ @object = object
30
+ @attr_symb = attr_symb
30
31
  end
31
32
 
32
33
  def value
@@ -43,19 +44,19 @@ module Aspera
43
44
  # resolves on extended value syntax
44
45
  class Manager
45
46
  # boolean options are set to true/false from the following values
46
- TRUE_VALUES=[:yes,true].freeze
47
- BOOLEAN_VALUES=[TRUE_VALUES,:no,false].flatten.freeze
48
- BOOLEAN_SIMPLE=[:yes,:no].freeze
47
+ TRUE_VALUES = [:yes,true].freeze
48
+ BOOLEAN_VALUES = [TRUE_VALUES,:no,false].flatten.freeze
49
+ BOOLEAN_SIMPLE = %i[yes no].freeze
49
50
  # option name separator on command line
50
- OPTION_SEP_LINE='-'
51
+ OPTION_SEP_LINE = '-'
51
52
  # option name separator in code (symbol)
52
- OPTION_SEP_NAME='_'
53
+ OPTION_SEP_NAME = '_'
53
54
 
54
- private_constant :TRUE_VALUES,:BOOLEAN_VALUES,:BOOLEAN_SIMPLE,:OPTION_SEP_LINE,:OPTION_SEP_NAME
55
+ private_constant :TRUE_VALUES,:BOOLEAN_VALUES,:OPTION_SEP_LINE,:OPTION_SEP_NAME
55
56
 
56
57
  class << self
57
58
  def enum_to_bool(enum)
58
- raise "Value not valid for boolean: #{enum}/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
59
+ raise "Value not valid for boolean: [#{enum}]/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
59
60
  return TRUE_VALUES.include?(enum)
60
61
  end
61
62
 
@@ -66,9 +67,9 @@ module Aspera
66
67
  # find shortened string value in allowed symbol list
67
68
  def get_from_list(shortval,descr,allowed_values)
68
69
  # we accept shortcuts
69
- matching_exact=allowed_values.select{|i| i.to_s.eql?(shortval)}
70
+ matching_exact = allowed_values.select{|i| i.to_s.eql?(shortval)}
70
71
  return matching_exact.first if matching_exact.length == 1
71
- matching=allowed_values.select{|i| i.to_s.start_with?(shortval)}
72
+ matching = allowed_values.select{|i| i.to_s.start_with?(shortval)}
72
73
  raise CliBadArgument,bad_arg_message_multi("unknown value for #{descr}: #{shortval}",allowed_values) if matching.empty?
73
74
  raise CliBadArgument,bad_arg_message_multi("ambigous shortcut for #{descr}: #{shortval}",matching) unless matching.length.eql?(1)
74
75
  return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
@@ -84,36 +85,36 @@ module Aspera
84
85
  attr_accessor :ask_missing_mandatory, :ask_missing_optional
85
86
  attr_writer :fail_on_missing_mandatory
86
87
 
87
- def initialize(program_name,argv=nil)
88
+ def initialize(program_name,argv: nil)
88
89
  # command line values not starting with '-'
89
- @unprocessed_cmd_line_arguments=[]
90
+ @unprocessed_cmd_line_arguments = []
90
91
  # command line values starting with '-'
91
- @unprocessed_cmd_line_options=[]
92
+ @unprocessed_cmd_line_options = []
92
93
  # a copy of all initial options
93
- @initial_cli_options=[]
94
+ @initial_cli_options = []
94
95
  # option description: key = option symbol, value=hash, :type, :accessor, :value, :accepted
95
- @declared_options={}
96
+ @declared_options = {}
96
97
  # do we ask missing options and arguments to user ?
97
- @ask_missing_mandatory=false # STDIN.isatty
98
+ @ask_missing_mandatory = false # STDIN.isatty
98
99
  # ask optional options if not provided and in interactive
99
- @ask_missing_optional=false
100
- @fail_on_missing_mandatory=true
100
+ @ask_missing_optional = false
101
+ @fail_on_missing_mandatory = true
101
102
  # those must be set before parse, parse consumes those defined only
102
- @unprocessed_defaults=[]
103
- @unprocessed_env=[]
103
+ @unprocessed_defaults = []
104
+ @unprocessed_env = []
104
105
  # Note: was initially inherited but it is prefered to have specific methods
105
- @parser=OptionParser.new
106
- @parser.program_name=program_name
106
+ @parser = OptionParser.new
107
+ @parser.program_name = program_name
107
108
  # options can also be provided by env vars : --param-name -> ASLMCLI_PARAM_NAME
108
- env_prefix=program_name.upcase+OPTION_SEP_NAME
109
+ env_prefix = program_name.upcase + OPTION_SEP_NAME
109
110
  ENV.each do |k,v|
110
111
  if k.start_with?(env_prefix)
111
112
  @unprocessed_env.push([k[env_prefix.length..-1].downcase.to_sym,v])
112
113
  end
113
114
  end
114
115
  Log.log.debug("env=#{@unprocessed_env}".red)
115
- @unprocessed_cmd_line_options=[]
116
- @unprocessed_cmd_line_arguments=[]
116
+ @unprocessed_cmd_line_options = []
117
+ @unprocessed_cmd_line_arguments = []
117
118
  # argv is nil when help is generated for every plugin
118
119
  unless argv.nil?
119
120
  @parser.separator('')
@@ -123,12 +124,12 @@ module Aspera
123
124
  add_opt_boolean(:interactive,'use interactive input of missing params')
124
125
  add_opt_boolean(:ask_options,'ask even optional options')
125
126
  parse_options!
126
- process_options=true
127
+ process_options = true
127
128
  while !argv.empty?
128
- value=argv.shift
129
+ value = argv.shift
129
130
  if process_options && value.start_with?('-')
130
131
  if value.eql?('--')
131
- process_options=false
132
+ process_options = false
132
133
  else
133
134
  @unprocessed_cmd_line_options.push(value)
134
135
  end
@@ -137,39 +138,45 @@ module Aspera
137
138
  end
138
139
  end
139
140
  end
140
- @initial_cli_options=@unprocessed_cmd_line_options.dup
141
+ @initial_cli_options = @unprocessed_cmd_line_options.dup
141
142
  Log.log.debug("add_cmd_line_options:commands/args=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red)
142
143
  end
143
144
 
144
- def get_next_command(command_list); return get_next_argument('command',command_list); end
145
+ def get_next_command(command_list); return get_next_argument('command',expected: command_list); end
145
146
 
146
147
  # @param expected is
147
148
  # - Array of allowed value (single value)
148
149
  # - :multiple for remaining values
149
150
  # - :single for a single unconstrained value
150
- # @param is_type : :mandatory or :optional
151
+ # @param mandatory true/false
152
+ # @param type expected class for result
151
153
  # @return value, list or nil
152
- def get_next_argument(descr,expected=:single,is_type=:mandatory)
153
- result=nil
154
+ def get_next_argument(descr,expected: :single,mandatory: true, type: nil)
155
+ unless type.nil?
156
+ raise 'internal: type must be a Class' unless type.is_a?(Class)
157
+ descr="#{descr} (#{type})"
158
+ end
159
+ result = nil
154
160
  if !@unprocessed_cmd_line_arguments.empty?
155
161
  # there are values
156
162
  case expected
157
163
  when :single
158
- result=ExtendedValue.instance.evaluate(@unprocessed_cmd_line_arguments.shift)
164
+ result = ExtendedValue.instance.evaluate(@unprocessed_cmd_line_arguments.shift)
159
165
  when :multiple
160
166
  result = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length).map{|v|ExtendedValue.instance.evaluate(v)}
161
167
  # if expecting list and only one arg of type array : it is the list
162
168
  if result.length.eql?(1) && result.first.is_a?(Array)
163
- result=result.first
169
+ result = result.first
164
170
  end
165
171
  else
166
- result=self.class.get_from_list(@unprocessed_cmd_line_arguments.shift,descr,expected)
172
+ result = self.class.get_from_list(@unprocessed_cmd_line_arguments.shift,descr,expected)
167
173
  end
168
- elsif is_type.eql?(:mandatory)
174
+ elsif mandatory
169
175
  # no value provided
170
- result=get_interactive(:argument,descr,expected)
176
+ result = get_interactive(:argument,descr,expected: expected)
171
177
  end
172
178
  Log.log.debug("#{descr}=#{result}")
179
+ raise "argument shall be #{type.name}" unless type.nil? || result.is_a?(type)
173
180
  return result
174
181
  end
175
182
 
@@ -177,19 +184,20 @@ module Aspera
177
184
  def declare_option(option_symbol,type)
178
185
  Log.log.debug("declare_option: #{option_symbol}: #{type}: skip=#{@declared_options.has_key?(option_symbol)}".green)
179
186
  if @declared_options.has_key?(option_symbol)
180
- raise "INTERNAL ERROR: option #{option_symbol} already declared. only accessor can be redeclared and ignored" unless @declared_options[option_symbol][:type].eql?(:accessor)
187
+ raise "INTERNAL ERROR: option #{option_symbol} already declared. only accessor can be redeclared and ignored" \
188
+ unless @declared_options[option_symbol][:type].eql?(:accessor)
181
189
  return
182
190
  end
183
- @declared_options[option_symbol]={type: type}
191
+ @declared_options[option_symbol] = {type: type}
184
192
  # by default passwords and secrets are sensitive, else specify when declaring the option
185
- @declared_options[option_symbol][:sensitive]=true if !%w[password secret key].select{|i| option_symbol.to_s.end_with?(i)}.empty?
193
+ @declared_options[option_symbol][:sensitive] = true if !%w[password secret key].select{|i| option_symbol.to_s.end_with?(i)}.empty?
186
194
  end
187
195
 
188
196
  # define option with handler
189
197
  def set_obj_attr(option_symbol,object,attr_symb,default_value=nil)
190
198
  Log.log.debug("set attr obj #{option_symbol} (#{object},#{attr_symb})")
191
199
  declare_option(option_symbol,:accessor)
192
- @declared_options[option_symbol][:accessor]=AttrAccessor.new(object,attr_symb)
200
+ @declared_options[option_symbol][:accessor] = AttrAccessor.new(object,attr_symb)
193
201
  set_option(option_symbol,default_value,'default obj attr') if !default_value.nil?
194
202
  end
195
203
 
@@ -200,14 +208,14 @@ module Aspera
200
208
  raise 'ERROR: cannot set undeclared option'
201
209
  #declare_option(option_symbol)
202
210
  end
203
- value=ExtendedValue.instance.evaluate(value)
204
- value=Manager.enum_to_bool(value) if @declared_options[option_symbol][:values].eql?(BOOLEAN_VALUES)
205
- Log.log.debug("set #{option_symbol}=#{value} (#{@declared_options[option_symbol][:type]}) : #{where}")
211
+ value = ExtendedValue.instance.evaluate(value)
212
+ value = Manager.enum_to_bool(value) if @declared_options[option_symbol][:values].eql?(BOOLEAN_VALUES)
213
+ Log.log.debug("(#{@declared_options[option_symbol][:type]}/#{where}) set #{option_symbol}=#{value}")
206
214
  case @declared_options[option_symbol][:type]
207
215
  when :accessor
208
- @declared_options[option_symbol][:accessor].value=value
216
+ @declared_options[option_symbol][:accessor].value = value
209
217
  when :value
210
- @declared_options[option_symbol][:value]=value
218
+ @declared_options[option_symbol][:value] = value
211
219
  else # nil or other
212
220
  raise 'error'
213
221
  end
@@ -216,33 +224,33 @@ module Aspera
216
224
  # get an option value by name
217
225
  # either return value or call handler, can return nil
218
226
  # ask interactively if requested/required
219
- def get_option(option_symbol,is_type=:optional)
220
- result=nil
227
+ def get_option(option_symbol,is_type: :optional)
228
+ result = nil
221
229
  if @declared_options.has_key?(option_symbol)
222
230
  case @declared_options[option_symbol][:type]
223
231
  when :accessor
224
- result=@declared_options[option_symbol][:accessor].value
232
+ result = @declared_options[option_symbol][:accessor].value
225
233
  when :value
226
- result=@declared_options[option_symbol][:value]
234
+ result = @declared_options[option_symbol][:value]
227
235
  else
228
236
  raise 'unknown type'
229
237
  end
230
- Log.log.debug("get #{option_symbol} (#{@declared_options[option_symbol][:type]}) : #{result}")
238
+ Log.log.debug("(#{@declared_options[option_symbol][:type]}) get #{option_symbol}=#{result}")
231
239
  end
232
240
  # do not fail for manual generation if option mandatory but not set
233
- result='' if result.nil? && !@fail_on_missing_mandatory
241
+ result = '' if result.nil? && is_type.eql?(:mandatory) && !@fail_on_missing_mandatory
234
242
  #Log.log.debug("interactive=#{@ask_missing_mandatory}")
235
243
  if result.nil?
236
244
  if !@ask_missing_mandatory
237
245
  raise CliBadArgument,"Missing mandatory option: #{option_symbol}" if is_type.eql?(:mandatory)
238
246
  elsif @ask_missing_optional || is_type.eql?(:mandatory)
239
247
  # ask_missing_mandatory
240
- expected=:single
248
+ expected = :single
241
249
  #print "please enter: #{option_symbol.to_s}"
242
250
  if @declared_options.has_key?(option_symbol) && @declared_options[option_symbol].has_key?(:values)
243
- expected=@declared_options[option_symbol][:values]
251
+ expected = @declared_options[option_symbol][:values]
244
252
  end
245
- result=get_interactive(:option,option_symbol.to_s,expected)
253
+ result = get_interactive(:option,option_symbol.to_s,expected: expected)
246
254
  set_option(option_symbol,result,'interactive')
247
255
  end
248
256
  end
@@ -250,7 +258,7 @@ module Aspera
250
258
  end
251
259
 
252
260
  # param must be hash
253
- def add_option_preset(preset_hash,op=:push)
261
+ def add_option_preset(preset_hash,op: :push)
254
262
  Log.log.debug("add_option_preset=#{preset_hash}")
255
263
  raise "internal error: setting default with no hash: #{preset_hash.class}" if !preset_hash.is_a?(Hash)
256
264
  # incremental override
@@ -263,11 +271,11 @@ module Aspera
263
271
  Log.log.debug("add_opt_list #{option_symbol}")
264
272
  on_args.unshift(symbol_to_option(option_symbol,'ENUM'))
265
273
  # this option value must be a symbol
266
- @declared_options[option_symbol][:values]=values
267
- value=get_option(option_symbol)
268
- help_values=values.map{|i|i.eql?(value)?highlight_current(i):i}.join(', ')
274
+ @declared_options[option_symbol][:values] = values
275
+ value = get_option(option_symbol)
276
+ help_values = values.map{|i|i.eql?(value) ? highlight_current(i) : i}.join(', ')
269
277
  if values.eql?(BOOLEAN_VALUES)
270
- help_values=BOOLEAN_SIMPLE.map{|i|(i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
278
+ help_values = BOOLEAN_SIMPLE.map{|i|(i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
271
279
  end
272
280
  on_args.push(values)
273
281
  on_args.push("#{help}: #{help_values}")
@@ -277,6 +285,9 @@ module Aspera
277
285
 
278
286
  def add_opt_boolean(option_symbol,help,*on_args)
279
287
  add_opt_list(option_symbol,BOOLEAN_VALUES,help,*on_args)
288
+ # if default was defined for obj, it may still be enum (yes/no) instead of boolean
289
+ default_value=get_option(option_symbol)
290
+ set_option(option_symbol,default_value,'opt boolean') unless default_value.nil?
280
291
  end
281
292
 
282
293
  # define an option with open values
@@ -297,7 +308,7 @@ module Aspera
297
308
  @parser.on(*on_args) do |v|
298
309
  case v
299
310
  when 'now' then set_option(option_symbol,Manager.time_to_string(Time.now),'cmdline')
300
- when /^-([0-9]+)h/ then set_option(option_symbol,Manager.time_to_string(Time.now-(3600*Regexp.last_match(1).to_i)),'cmdline')
311
+ when /^-([0-9]+)h/ then set_option(option_symbol,Manager.time_to_string(Time.now - (3600 * Regexp.last_match(1).to_i)),'cmdline')
301
312
  else set_option(option_symbol,v,'cmdline')
302
313
  end
303
314
  end
@@ -318,7 +329,7 @@ module Aspera
318
329
 
319
330
  # unprocessed options or arguments ?
320
331
  def final_errors
321
- result=[]
332
+ result = []
322
333
  result.push("unprocessed options: #{@unprocessed_cmd_line_options}") unless @unprocessed_cmd_line_options.empty?
323
334
  result.push("unprocessed values: #{@unprocessed_cmd_line_arguments}") unless @unprocessed_cmd_line_arguments.empty?
324
335
  return result
@@ -326,18 +337,18 @@ module Aspera
326
337
 
327
338
  # get all original options on command line used to generate a config in config file
328
339
  def get_options_table(remove_from_remaining: true)
329
- result={}
340
+ result = {}
330
341
  @initial_cli_options.each do |optionval|
331
342
  case optionval
332
343
  when /^--([^=]+)$/
333
344
  # ignore
334
345
  when /^--([^=]+)=(.*)$/
335
- name=Regexp.last_match(1)
336
- value=Regexp.last_match(2)
346
+ name = Regexp.last_match(1)
347
+ value = Regexp.last_match(2)
337
348
  name.gsub!(OPTION_SEP_LINE,OPTION_SEP_NAME)
338
- value=ExtendedValue.instance.evaluate(value)
349
+ value = ExtendedValue.instance.evaluate(value)
339
350
  Log.log.debug("option #{name}=#{value}")
340
- result[name]=value
351
+ result[name] = value
341
352
  @unprocessed_cmd_line_options.delete(optionval) if remove_from_remaining
342
353
  else
343
354
  raise CliBadArgument,"wrong option format: #{optionval}"
@@ -348,10 +359,10 @@ module Aspera
348
359
 
349
360
  # return options as taken from config file and command line just before command execution
350
361
  def declared_options(only_defined: false)
351
- result={}
362
+ result = {}
352
363
  @declared_options.keys.each do |option_symb|
353
- v=get_option(option_symb)
354
- result[option_symb.to_s]=v unless only_defined && v.nil?
364
+ v = get_option(option_symb)
365
+ result[option_symb.to_s] = v unless only_defined && v.nil?
355
366
  end
356
367
  return result
357
368
  end
@@ -363,7 +374,7 @@ module Aspera
363
374
  apply_options_preset(@unprocessed_defaults,'file')
364
375
  apply_options_preset(@unprocessed_env,'env')
365
376
  # command line override
366
- unknown_options=[]
377
+ unknown_options = []
367
378
  begin
368
379
  # remove known options one by one, exception if unknown
369
380
  Log.log.debug('before parse'.red)
@@ -377,47 +388,47 @@ module Aspera
377
388
  end
378
389
  Log.log.debug("remains: #{unknown_options}")
379
390
  # set unprocessed options for next time
380
- @unprocessed_cmd_line_options=unknown_options
391
+ @unprocessed_cmd_line_options = unknown_options
381
392
  end
382
393
 
383
394
  private
384
395
 
385
396
  def prompt_user_input(prompt,sensitive)
386
397
  return $stdin.getpass("#{prompt}> ") if sensitive
387
- print "#{prompt}> "
398
+ print("#{prompt}> ")
388
399
  return $stdin.gets.chomp
389
400
  end
390
401
 
391
- def get_interactive(type,descr,expected=:single)
402
+ def get_interactive(type,descr,expected: :single)
392
403
  if !@ask_missing_mandatory
393
404
  raise CliBadArgument,self.class.bad_arg_message_multi("missing: #{descr}",expected) if expected.is_a?(Array)
394
405
  raise CliBadArgument,"missing argument (#{expected}): #{descr}"
395
406
  end
396
- result=nil
407
+ result = nil
397
408
  sensitive = type.eql?(:option) && @declared_options[descr.to_sym][:sensitive]
398
- default_prompt="#{type}: #{descr}"
409
+ default_prompt = "#{type}: #{descr}"
399
410
  # ask interactively
400
411
  case expected
401
412
  when :multiple
402
- result=[]
413
+ result = []
403
414
  puts(' (one per line, end with empty line)')
404
415
  loop do
405
- entry=prompt_user_input(default_prompt,sensitive)
416
+ entry = prompt_user_input(default_prompt,sensitive)
406
417
  break if entry.empty?
407
418
  result.push(ExtendedValue.instance.evaluate(entry))
408
419
  end
409
420
  when :single
410
- result=ExtendedValue.instance.evaluate(prompt_user_input(default_prompt,sensitive))
421
+ result = ExtendedValue.instance.evaluate(prompt_user_input(default_prompt,sensitive))
411
422
  else # one fixed
412
- result=self.class.get_from_list(prompt_user_input("#{expected.join(' ')}\n#{default_prompt}",sensitive),descr,expected)
423
+ result = self.class.get_from_list(prompt_user_input("#{expected.join(' ')}\n#{default_prompt}",sensitive),descr,expected)
413
424
  end
414
425
  return result
415
426
  end
416
427
 
417
428
  # generate command line option from option symbol
418
429
  def symbol_to_option(symbol,opt_val)
419
- result='--'+symbol.to_s.gsub(OPTION_SEP_NAME,OPTION_SEP_LINE)
420
- result=result+'='+opt_val unless opt_val.nil?
430
+ result = '--' + symbol.to_s.gsub(OPTION_SEP_NAME,OPTION_SEP_LINE)
431
+ result = result + '=' + opt_val unless opt_val.nil?
421
432
  return result
422
433
  end
423
434
 
@@ -426,13 +437,13 @@ module Aspera
426
437
  end
427
438
 
428
439
  def apply_options_preset(preset,where)
429
- unprocessed=[]
440
+ unprocessed = []
430
441
  preset.each do |pair|
431
- k,v=*pair
442
+ k,v = *pair
432
443
  if @declared_options.has_key?(k)
433
444
  # constrained parameters as string are revert to symbol
434
445
  if @declared_options[k].has_key?(:values) && v.is_a?(String)
435
- v=self.class.get_from_list(v,k.to_s+" in #{where}",@declared_options[k][:values])
446
+ v = self.class.get_from_list(v,k.to_s + " in #{where}",@declared_options[k][:values])
436
447
  end
437
448
  set_option(k,v,where)
438
449
  else
@@ -1,92 +1,151 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aspera
3
4
  module Cli
4
5
  # base class for plugins modules
5
6
  class Plugin
6
- # operation without id
7
- GLOBAL_OPS=[:create,:list].freeze
8
- # operation on specific instance
9
- INSTANCE_OPS=[:modify,:delete,:show].freeze
10
- ALL_OPS=[GLOBAL_OPS,INSTANCE_OPS].flatten.freeze
7
+ # operations without id
8
+ GLOBAL_OPS = %i[create list].freeze
9
+ # operations with id
10
+ INSTANCE_OPS = %i[modify delete show].freeze
11
+ ALL_OPS = [GLOBAL_OPS,INSTANCE_OPS].flatten.freeze
11
12
  # max number of items for list command
12
- MAX_ITEMS='max'
13
+ MAX_ITEMS = 'max'
13
14
  # max number of pages for list command
14
- MAX_PAGES='pmax'
15
+ MAX_PAGES = 'pmax'
16
+
17
+ #AGENTS=%i[options transfer config formater persistency].freeze
15
18
 
16
19
  # global for inherited classes
17
- @@options_created=false # rubocop:disable Style/ClassVars
20
+ @@options_created = false # rubocop:disable Style/ClassVars
18
21
 
19
22
  def initialize(env)
20
- @agents=env
21
- raise StandardError,"execute_action shall be redefined by subclass #{self.class}" unless respond_to?(:execute_action)
23
+ raise 'must be Hash' unless env.is_a?(Hash)
24
+ #env.each_key {|k| raise "wrong agent key #{k}" unless AGENTS.include?(k)}
25
+ @agents = env
26
+ # check presence in descendant of mandatory method and constant
27
+ raise StandardError,"missing method 'execute_action' in #{self.class}" unless respond_to?(:execute_action)
22
28
  raise StandardError,'ACTIONS shall be redefined by subclass' unless self.class.constants.include?(:ACTIONS)
23
- unless env[:skip_option_header]
24
- options.parser.separator ''
25
- options.parser.separator "COMMAND: #{self.class.name.split('::').last.downcase}"
26
- options.parser.separator "SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).join(' ')}"
27
- options.parser.separator 'OPTIONS:'
28
- end
29
+ options.parser.separator('')
30
+ options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
31
+ options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).join(' ')}")
32
+ options.parser.separator('OPTIONS:')
29
33
  return if @@options_created
30
34
  options.add_opt_simple(:value,'extended value for create, update, list filter')
31
35
  options.add_opt_simple(:property,'name of property to set')
32
36
  options.add_opt_simple(:id,"resource identifier (#{INSTANCE_OPS.join(',')})")
37
+ options.add_opt_boolean(:bulk,'Bulk operation (only some)')
38
+ options.set_option(:bulk,:no)
33
39
  options.parse_options!
34
- @@options_created=true # rubocop:disable Style/ClassVars
40
+ @@options_created = true # rubocop:disable Style/ClassVars
35
41
  end
36
42
 
37
43
  # must be called AFTER the instance action
38
44
  def instance_identifier
39
- res_id=options.get_option(:id)
40
- res_id=options.get_next_argument('identifier') if res_id.nil?
45
+ res_id = options.get_option(:id)
46
+ res_id = options.get_next_argument('identifier') if res_id.nil?
41
47
  return res_id
42
48
  end
43
49
 
50
+ # TODO
51
+ #def get_next_id_command(instance_ops: INSTANCE_OPS,global_ops: GLOBAL_OPS)
52
+ # return get_next_argument('command',expected: command_list)
53
+ #end
54
+
55
+ # For create and delete operations: execute one actin or multiple if bulk is yes
56
+ # @param params either single id or hash, or array for bulk
57
+ # @param success_msg deleted or created
58
+ def do_bulk_operation(single_or_array,success_msg,id_result: 'id',fields: :default)
59
+ raise 'programming error: missing block' unless block_given?
60
+ params = options.get_option(:bulk) ? single_or_array : [single_or_array]
61
+ raise 'expecting Array for bulk operation' unless params.is_a?(Array)
62
+ Log.log.warn('Empty list given for bulk operation') if params.empty?
63
+ Log.dump(:bulk_create,params)
64
+ result_list = []
65
+ params.each do |param|
66
+ # init for delete
67
+ result = {id_result => param}
68
+ begin
69
+ # execute custom code
70
+ res = yield(param)
71
+ # if block returns a hash, let's use this (create)
72
+ result = res if param.is_a?(Hash)
73
+ result['status'] = success_msg
74
+ rescue StandardError => e
75
+ result['status'] = e.to_s
76
+ end
77
+ result_list.push(result)
78
+ end
79
+ display_fields = [id_result,'status']
80
+ if options.get_option(:bulk)
81
+ return {type: :object_list,data: result_list,fields: display_fields}
82
+ else
83
+ display_fields = fields unless fields.eql?(:default)
84
+ return {type: :single_object,data: result_list.first,fields: display_fields}
85
+ end
86
+ end
87
+
44
88
  # @param command [Symbol] command to execute: create show list modify delete
45
89
  # @param rest_api [Rest] api to use
46
90
  # @param res_class_path [String] sub path in URL to resource relative to base url
47
91
  # @param display_fields [Array] fields to display by default
48
92
  # @param id_default [String] default identifier to use for existing entity commands (show, modify)
49
- # @param use_subkey [bool] true if the result is in a subkey of the json
50
- def entity_command(command,rest_api,res_class_path,display_fields: nil,id_default: nil,use_subkey: false)
93
+ # @param item_list_key [String] result is in a subkey of the json
94
+ # @return result suitable for CLI result
95
+ def entity_command(command,rest_api,res_class_path,display_fields: nil,id_default: nil,item_list_key: false)
51
96
  if INSTANCE_OPS.include?(command)
52
97
  begin
53
- one_res_id=instance_identifier()
98
+ one_res_id = instance_identifier
54
99
  rescue StandardError => e
55
100
  raise e if id_default.nil?
56
- one_res_id=id_default
101
+ one_res_id = id_default
57
102
  end
58
- one_res_path="#{res_class_path}/#{one_res_id}"
103
+ one_res_path = "#{res_class_path}/#{one_res_id}"
59
104
  end
60
105
  # parameters mandatory for create/modify
61
- if [:create,:modify].include?(command)
62
- parameters=options.get_option(:value,:mandatory)
106
+ if %i[create modify].include?(command)
107
+ parameters = options.get_option(:value,is_type: :mandatory)
63
108
  end
64
109
  # parameters optional for list
65
110
  if [:list].include?(command)
66
- parameters=options.get_option(:value,:optional)
111
+ parameters = options.get_option(:value)
67
112
  end
68
113
  case command
69
114
  when :create
70
- return {type: :single_object, data: rest_api.create(res_class_path,parameters)[:data], fields: display_fields}
115
+ return do_bulk_operation(parameters,'created',fields: display_fields) do |params|
116
+ raise 'expecting Hash' unless params.is_a?(Hash)
117
+ rest_api.create(res_class_path,params)[:data]
118
+ end
119
+ when :delete
120
+ return do_bulk_operation(one_res_id,'deleted') do |one_id|
121
+ rest_api.delete("#{res_class_path}/#{one_id}")
122
+ {'id' => one_id}
123
+ end
71
124
  when :show
72
125
  return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
73
126
  when :list
74
- resp=rest_api.read(res_class_path,parameters)
75
- data=resp[:data]
127
+ resp = rest_api.read(res_class_path,parameters)
128
+ data = resp[:data]
76
129
  # TODO: not generic : which application is this for ?
77
130
  if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
78
- data=data[res_class_path]
131
+ data = data[res_class_path]
132
+ end
133
+ if item_list_key
134
+ item_list=data[item_list_key]
135
+ total_count=data['total_count']
136
+ if !total_count.nil?
137
+ count_msg = "Items: #{item_list.length}/#{total_count}"
138
+ count_msg = count_msg.bg_red unless item_list.length.eql?(total_count.to_i)
139
+ self.format.display_status(count_msg)
140
+ end
141
+ data = item_list
79
142
  end
80
- data=data[res_class_path] if use_subkey
81
143
  return {type: :object_list, data: data, fields: display_fields}
82
144
  when :modify
83
- property=options.get_option(:property,:optional)
84
- parameters={property => parameters} unless property.nil?
145
+ property = options.get_option(:property)
146
+ parameters = {property => parameters} unless property.nil?
85
147
  rest_api.update(one_res_path,parameters)
86
148
  return Main.result_status('modified')
87
- when :delete
88
- rest_api.delete(one_res_path)
89
- return Main.result_status('deleted')
90
149
  else
91
150
  raise "unknown action: #{command}"
92
151
  end
@@ -95,11 +154,11 @@ module Aspera
95
154
  # implement generic rest operations on given resource path
96
155
  def entity_action(rest_api,res_class_path,**opts)
97
156
  #res_name=res_class_path.gsub(%r{^.*/},'').gsub(%r{s$},'').gsub('_',' ')
98
- command=options.get_next_command(ALL_OPS)
157
+ command = options.get_next_command(ALL_OPS)
99
158
  return entity_command(command,rest_api,res_class_path,**opts)
100
159
  end
101
160
 
102
- # shortcuts for plugin environment
161
+ # shortcuts helpers for plugin environment
103
162
  def options; return @agents[:options];end
104
163
 
105
164
  def transfer; return @agents[:transfer];end