aspera-cli 4.4.0 → 4.7.0

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