aspera-cli 4.0.0 → 4.2.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +843 -304
  3. data/bin/dascli +13 -0
  4. data/docs/Makefile +4 -4
  5. data/docs/README.erb.md +805 -172
  6. data/docs/test_env.conf +22 -3
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +89 -0
  9. data/lib/aspera/aoc.rb +87 -108
  10. data/lib/aspera/cli/formater.rb +2 -0
  11. data/lib/aspera/cli/main.rb +89 -49
  12. data/lib/aspera/cli/plugin.rb +9 -4
  13. data/lib/aspera/cli/plugins/alee.rb +1 -1
  14. data/lib/aspera/cli/plugins/aoc.rb +188 -173
  15. data/lib/aspera/cli/plugins/ats.rb +2 -2
  16. data/lib/aspera/cli/plugins/config.rb +218 -145
  17. data/lib/aspera/cli/plugins/console.rb +2 -2
  18. data/lib/aspera/cli/plugins/faspex.rb +114 -61
  19. data/lib/aspera/cli/plugins/faspex5.rb +85 -43
  20. data/lib/aspera/cli/plugins/node.rb +3 -3
  21. data/lib/aspera/cli/plugins/preview.rb +59 -45
  22. data/lib/aspera/cli/plugins/server.rb +23 -8
  23. data/lib/aspera/cli/transfer_agent.rb +77 -49
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/command_line_builder.rb +49 -31
  26. data/lib/aspera/cos_node.rb +33 -28
  27. data/lib/aspera/environment.rb +2 -2
  28. data/lib/aspera/fasp/connect.rb +28 -21
  29. data/lib/aspera/fasp/http_gw.rb +140 -28
  30. data/lib/aspera/fasp/installation.rb +93 -46
  31. data/lib/aspera/fasp/local.rb +88 -45
  32. data/lib/aspera/fasp/manager.rb +15 -0
  33. data/lib/aspera/fasp/node.rb +4 -4
  34. data/lib/aspera/fasp/parameters.rb +59 -101
  35. data/lib/aspera/fasp/parameters.yaml +531 -0
  36. data/lib/aspera/fasp/resume_policy.rb +13 -12
  37. data/lib/aspera/fasp/uri.rb +1 -1
  38. data/lib/aspera/log.rb +1 -1
  39. data/lib/aspera/node.rb +61 -1
  40. data/lib/aspera/oauth.rb +49 -46
  41. data/lib/aspera/persistency_folder.rb +9 -4
  42. data/lib/aspera/preview/file_types.rb +53 -21
  43. data/lib/aspera/preview/generator.rb +3 -3
  44. data/lib/aspera/rest.rb +29 -18
  45. data/lib/aspera/secrets.rb +20 -0
  46. data/lib/aspera/sync.rb +40 -35
  47. data/lib/aspera/temp_file_manager.rb +19 -0
  48. data/lib/aspera/web_auth.rb +105 -0
  49. metadata +54 -20
  50. data/docs/transfer_spec.html +0 -99
@@ -10,6 +10,7 @@ require 'aspera/persistency_folder'
10
10
  require 'aspera/log'
11
11
  require 'aspera/rest'
12
12
  require 'aspera/nagios'
13
+ require 'aspera/secrets'
13
14
 
14
15
  module Aspera
15
16
  module Cli
@@ -20,15 +21,17 @@ module Aspera
20
21
  private
21
22
  # name of application, also foldername where config is stored
22
23
  PROGRAM_NAME = 'ascli'
24
+ # name of the containing gem, same as in <gem name>.gemspec
23
25
  GEM_NAME = 'aspera-cli'
24
- VERBOSE_LEVELS=[:normal,:minimal,:quiet]
25
-
26
- private_constant :PROGRAM_NAME,:GEM_NAME,:VERBOSE_LEVELS
26
+ HELP_URL = "http://www.rubydoc.info/gems/#{GEM_NAME}"
27
+ GEM_URL = "https://rubygems.org/gems/#{GEM_NAME}"
28
+ SRC_URL = "https://github.com/IBM/aspera-cli"
29
+ STATUS_FIELD = 'status'
27
30
 
31
+ private_constant :PROGRAM_NAME,:GEM_NAME,:HELP_URL,:GEM_URL
28
32
  # =============================================================
29
33
  # Parameter handlers
30
34
  #
31
-
32
35
  def option_insecure; Rest.insecure ; end
33
36
 
34
37
  def option_insecure=(value); Rest.insecure = value; end
@@ -39,32 +42,41 @@ module Aspera
39
42
 
40
43
  # minimum initialization
41
44
  def initialize(argv)
42
- # first thing : manage debug level (allows debugging or option parser)
45
+ # first thing : manage debug level (allows debugging of option parser)
43
46
  early_debug_setup(argv)
47
+ # compare $0 with expected name
44
48
  current_prog_name=File.basename($PROGRAM_NAME)
45
49
  unless current_prog_name.eql?(PROGRAM_NAME)
46
50
  @plugin_env[:formater].display_message(:error,"#{"WARNING".bg_red.blink.gray} Please use '#{PROGRAM_NAME}' instead of '#{current_prog_name}', '#{current_prog_name}' will be removed in a future version")
47
51
  end
48
- # overriding parameters on transfer spec
49
52
  @option_help=false
50
53
  @bash_completion=false
51
54
  @option_show_config=false
55
+ # environment provided to plugin for various capabilities
52
56
  @plugin_env={}
53
- @help_url='http://www.rubydoc.info/gems/'+GEM_NAME
54
- @gem_url='https://rubygems.org/gems/'+GEM_NAME
57
+ # find out application main folder
58
+ app_main_folder=ENV[conf_dir_env_var]
59
+ # if env var undefined or empty
60
+ if app_main_folder.nil? or app_main_folder.empty?
61
+ user_home_folder=Dir.home
62
+ raise CliError,"Home folder does not exist: #{user_home_folder}. Check your user environment or use #{conf_dir_env_var}." unless Dir.exist?(user_home_folder)
63
+ app_main_folder=File.join(user_home_folder,Plugins::Config::ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME)
64
+ end
55
65
  # give command line arguments to option manager (no parsing)
56
- @plugin_env[:options]=@opt_mgr=Manager.new(self.program_name,argv,app_banner())
66
+ @plugin_env[:options]=@opt_mgr=Manager.new(PROGRAM_NAME,argv,app_banner())
57
67
  @plugin_env[:formater]=Formater.new(@plugin_env[:options])
58
- Rest.user_agent=self.program_name
59
- # must override help methods before parser called (in other constructors)
68
+ Rest.user_agent=PROGRAM_NAME
69
+ # declare and parse global options
60
70
  init_global_options()
71
+ # secret manager
72
+ @plugin_env[:secret]=Aspera::Secrets.new
61
73
  # the Config plugin adds the @preset parser
62
- @plugin_env[:config]=Plugins::Config.new(@plugin_env,self.program_name,@help_url,Aspera::Cli::VERSION)
74
+ @plugin_env[:config]=Plugins::Config.new(@plugin_env,PROGRAM_NAME,HELP_URL,Aspera::Cli::VERSION,app_main_folder)
63
75
  # the TransferAgent plugin may use the @preset parser
64
76
  @plugin_env[:transfer]=TransferAgent.new(@plugin_env)
65
- Log.log.debug('created plugin env'.red)
66
77
  # set application folder for modules
67
78
  @plugin_env[:persistency]=PersistencyFolder.new(File.join(@plugin_env[:config].main_folder,'persist_store'))
79
+ Log.log.debug('plugin env created'.red)
68
80
  Oauth.persist_mgr=@plugin_env[:persistency]
69
81
  Fasp::Parameters.file_list_folder=File.join(@plugin_env[:config].main_folder,'filelists')
70
82
  Aspera::RestErrorAnalyzer.instance.log_file=File.join(@plugin_env[:config].main_folder,'rest_exceptions.log')
@@ -73,21 +85,21 @@ module Aspera
73
85
  end
74
86
 
75
87
  def app_banner
76
- banner = "NAME\n\t#{self.program_name} -- a command line tool for Aspera Applications (v#{Aspera::Cli::VERSION})\n\n"
88
+ banner = "NAME\n\t#{PROGRAM_NAME} -- a command line tool for Aspera Applications (v#{Aspera::Cli::VERSION})\n\n"
77
89
  banner << "SYNOPSIS\n"
78
- banner << "\t#{self.program_name} COMMANDS [OPTIONS] [ARGS]\n"
79
- banner << "\n"
80
- banner << "DESCRIPTION\n"
90
+ banner << "\t#{PROGRAM_NAME} COMMANDS [OPTIONS] [ARGS]\n"
91
+ banner << "\nDESCRIPTION\n"
81
92
  banner << "\tUse Aspera application to perform operations on command line.\n"
82
- banner << "\tDocumentation and examples: #{@gem_url}\n"
83
- banner << "\texecute: #{self.program_name} conf doc\n"
84
- banner << "\tor visit: #{@help_url}\n"
85
- banner << "\n"
86
- banner << "COMMANDS\n"
87
- banner << "\tTo list first level commands, execute: #{self.program_name}\n"
93
+ banner << "\tDocumentation and examples: #{GEM_URL}\n"
94
+ banner << "\texecute: #{PROGRAM_NAME} conf doc\n"
95
+ banner << "\tor visit: #{HELP_URL}\n"
96
+ banner << "\nENVIRONMENT VARIABLES\n"
97
+ banner << "\t#{conf_dir_env_var} config folder, default: $HOME/#{Plugins::Config::ASPERA_HOME_FOLDER_NAME}/#{PROGRAM_NAME}\n"
98
+ banner << "\t#any option can be set as an environment variable, refer to the manual\n"
99
+ banner << "\nCOMMANDS\n"
100
+ banner << "\tTo list first level commands, execute: #{PROGRAM_NAME}\n"
88
101
  banner << "\tNote that commands can be written shortened (provided it is unique).\n"
89
- banner << "\n"
90
- banner << "OPTIONS\n"
102
+ banner << "\nOPTIONS\n"
91
103
  banner << "\tOptions begin with a '-' (minus), and value is provided on command line.\n"
92
104
  banner << "\tSpecial values are supported beginning with special prefix, like: #{ExtendedValue.instance.modifiers.map{|m|"@#{m}:"}.join(' ')}.\n"
93
105
  banner << "\tDates format is 'DD-MM-YY HH:MM:SS', or 'now' or '-<num>h'\n\n"
@@ -167,7 +179,7 @@ module Aspera
167
179
  # override main option parser with a brand new, to avoid having global options
168
180
  plugin_env=@plugin_env.clone
169
181
  plugin_env[:man_only]=true
170
- plugin_env[:options]=Manager.new(self.program_name,[],'')
182
+ plugin_env[:options]=Manager.new(PROGRAM_NAME,[],'')
171
183
  get_plugin_instance_with_options(plugin_name_sym,plugin_env)
172
184
  # display generated help for plugin options
173
185
  @plugin_env[:formater].display_message(:error,plugin_env[:options].parser.to_s)
@@ -178,10 +190,16 @@ module Aspera
178
190
 
179
191
  protected
180
192
 
193
+ # env var name to override the app's main folder
194
+ # default main folder is $HOME/<vendor main app folder>/<program name>
195
+ def conf_dir_env_var
196
+ return "#{PROGRAM_NAME}_home".upcase
197
+ end
198
+
181
199
  # early debug for parser
182
200
  # Note: does not accept shortcuts
183
201
  def early_debug_setup(argv)
184
- Log.instance.program_name=self.program_name
202
+ Log.instance.program_name=PROGRAM_NAME
185
203
  argv.each do |arg|
186
204
  case arg
187
205
  when '--'
@@ -205,14 +223,28 @@ module Aspera
205
223
  return Main.result_nothing
206
224
  end
207
225
 
208
- def options;@opt_mgr;end
209
-
210
- def program_name;PROGRAM_NAME;end
226
+ # used when one command executes several transfer jobs (each job being possibly multi session)
227
+ # @param status_table [Array] [{STATUS_FIELD=>[status array],...},...]
228
+ # each element has a key STATUS_FIELD which contains the result of possibly mulmtiple sessions
229
+ def self.result_transfer_multiple(status_table)
230
+ global_status=:success
231
+ # transform status into string and find if there was problem
232
+ status_table.each do |item|
233
+ worst=TransferAgent.session_status(item[STATUS_FIELD])
234
+ global_status=worst unless worst.eql?(:success)
235
+ item[STATUS_FIELD]=item[STATUS_FIELD].map{|i|i.to_s}.join(',')
236
+ end
237
+ raise global_status unless global_status.eql?(:success)
238
+ return {:type=>:object_list,:data=>status_table}
239
+ end
211
240
 
212
241
  # this is the main function called by initial script just after constructor
213
242
  def process_command_line
214
243
  Log.log.debug('process_command_line')
244
+ # catch exception information , if any
215
245
  exception_info=nil
246
+ # false if command shall not be executed ("once_only")
247
+ execute_command=true
216
248
  begin
217
249
  # find plugins, shall be after parse! ?
218
250
  @plugin_env[:config].add_plugins_from_lookup_folders
@@ -222,16 +254,7 @@ module Aspera
222
254
  # load global default options and process
223
255
  @plugin_env[:config].add_plugin_default_preset(Plugins::Config::CONF_GLOBAL_SYM)
224
256
  @opt_mgr.parse_options!
225
- # dual execution locking
226
- lock_port=@opt_mgr.get_option(:lock_port,:optional)
227
- if !lock_port.nil?
228
- begin
229
- # no need to close later, will be freed on process exit. must save in member else it is garbage collected
230
- @tcp_server=TCPServer.new('127.0.0.1',lock_port.to_i)
231
- rescue => e
232
- raise CliError,"Another instance is already running (lock port=#{lock_port})."
233
- end
234
- end
257
+ @plugin_env[:config].periodic_check_newer_gem_version
235
258
  if @option_show_config and @opt_mgr.command_or_arg_empty?
236
259
  command_sym=Plugins::Config::CONF_PLUGIN_SYM
237
260
  else
@@ -255,18 +278,30 @@ module Aspera
255
278
  @plugin_env[:formater].display_results({:type=>:single_object,:data=>@opt_mgr.declared_options(false)})
256
279
  Process.exit(0)
257
280
  end
258
- # execute and display
259
- @plugin_env[:formater].display_results(command_plugin.execute_action)
281
+ # locking for single execution (only after "per plugin" option, in case lock port is there)
282
+ lock_port=@opt_mgr.get_option(:lock_port,:optional)
283
+ if !lock_port.nil?
284
+ begin
285
+ # no need to close later, will be freed on process exit. must save in member else it is garbage collected
286
+ Log.log.debug("Opening lock port #{lock_port.to_i}")
287
+ @tcp_server=TCPServer.new('127.0.0.1',lock_port.to_i)
288
+ rescue => e
289
+ execute_command=false
290
+ Log.log.warn("Another instance is already running (#{e.message}).")
291
+ end
292
+ end
293
+ # execute and display (if not exclusive execution)
294
+ @plugin_env[:formater].display_results(command_plugin.execute_action) if execute_command
260
295
  # finish
261
296
  @plugin_env[:transfer].shutdown
262
297
  rescue CliBadArgument => e; exception_info=[e,'Argument',:usage]
263
298
  rescue CliNoSuchId => e; exception_info=[e,'Identifier']
264
299
  rescue CliError => e; exception_info=[e,'Tool',:usage]
265
- rescue Fasp::Error => e; exception_info=[e,"FASP(ascp]"]
266
- rescue Aspera::RestCallError => e; exception_info=[e,"Rest"]
267
- rescue SocketError => e; exception_info=[e,"Network"]
268
- rescue StandardError => e; exception_info=[e,"Other",:debug]
269
- rescue Interrupt => e; exception_info=[e,"Interruption",:debug]
300
+ rescue Fasp::Error => e; exception_info=[e,'FASP(ascp)']
301
+ rescue Aspera::RestCallError => e; exception_info=[e,'Rest']
302
+ rescue SocketError => e; exception_info=[e,'Network']
303
+ rescue StandardError => e; exception_info=[e,'Other',:debug]
304
+ rescue Interrupt => e; exception_info=[e,'Interruption',:debug]
270
305
  end
271
306
  # cleanup file list files
272
307
  TempFileManager.instance.cleanup
@@ -274,10 +309,15 @@ module Aspera
274
309
  unless exception_info.nil?
275
310
  @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" "+exception_info[1]+": "+exception_info[0].message)
276
311
  @plugin_env[:formater].display_message(:error,"Use '-h' option to get help.") if exception_info[2].eql?(:usage)
312
+ if exception_info.first.is_a?(Fasp::Error) and exception_info.first.message.eql?('Remote host is not who we expected')
313
+ @plugin_env[:formater].display_message(:error,"For this specific error, refer to:\n#{SRC_URL}#error-remote-host-is-not-who-we-expected\nAdd this to arguments:\n--ts=@json:'{\"sshfp\":null}'")
314
+ end
277
315
  end
278
316
  # 2- processing of command not processed (due to exception or bad command line)
279
- @opt_mgr.final_errors.each do |msg|
280
- @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" Argument: "+msg)
317
+ if execute_command
318
+ @opt_mgr.final_errors.each do |msg|
319
+ @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" Argument: "+msg)
320
+ end
281
321
  end
282
322
  # 3- in case of error, fail the process status
283
323
  unless exception_info.nil?
@@ -67,7 +67,10 @@ module Aspera
67
67
  when :delete
68
68
  rest_api.delete(one_res_path)
69
69
  return Main.result_status("deleted")
70
+ else
71
+ raise "unknown action: #{command}"
70
72
  end
73
+ raise "internal error should not reach here"
71
74
  end
72
75
 
73
76
  # implement generic rest operations on given resource path
@@ -77,13 +80,15 @@ module Aspera
77
80
  return entity_command(command,rest_api,res_class_path,display_fields,id_symb,id_default,subkey)
78
81
  end
79
82
 
80
- def options;@agents[:options];end
83
+ def options; return @agents[:options];end
81
84
 
82
- def transfer;@agents[:transfer];end
85
+ def transfer; return @agents[:transfer];end
83
86
 
84
- def config;return @agents[:config];end
87
+ def config; return @agents[:config];end
85
88
 
86
- def format;return @agents[:formater];end
89
+ def format; return @agents[:formater];end
90
+
91
+ def persistency; return @agents[:persistency];end
87
92
 
88
93
  end # Plugin
89
94
  end # Cli
@@ -12,7 +12,7 @@ module Aspera
12
12
  command=self.options.get_next_command(ACTIONS)
13
13
  case command
14
14
  when :entitlement
15
- entitlement_id = self.options.get_option(:username,:mandatory),
15
+ entitlement_id = self.options.get_option(:username,:mandatory)
16
16
  customer_id = self.options.get_option(:password,:mandatory)
17
17
  api_metering=AoC.metering_api(entitlement_id,customer_id)
18
18
  return {:type=>:single_object, :data=>api_metering.read('entitlement')[:data]}