aspera-cli 4.2.0 → 4.4.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +749 -353
  3. data/docs/Makefile +4 -4
  4. data/docs/README.erb.md +743 -283
  5. data/docs/doc_tools.rb +58 -0
  6. data/docs/test_env.conf +9 -1
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +89 -0
  9. data/lib/aspera/aoc.rb +24 -22
  10. data/lib/aspera/cli/main.rb +48 -20
  11. data/lib/aspera/cli/plugin.rb +13 -6
  12. data/lib/aspera/cli/plugins/aoc.rb +117 -78
  13. data/lib/aspera/cli/plugins/config.rb +127 -80
  14. data/lib/aspera/cli/plugins/faspex.rb +112 -63
  15. data/lib/aspera/cli/plugins/faspex5.rb +29 -25
  16. data/lib/aspera/cli/plugins/node.rb +54 -25
  17. data/lib/aspera/cli/plugins/preview.rb +94 -68
  18. data/lib/aspera/cli/plugins/server.rb +16 -5
  19. data/lib/aspera/cli/transfer_agent.rb +92 -72
  20. data/lib/aspera/cli/version.rb +1 -1
  21. data/lib/aspera/command_line_builder.rb +48 -31
  22. data/lib/aspera/cos_node.rb +4 -3
  23. data/lib/aspera/fasp/http_gw.rb +47 -26
  24. data/lib/aspera/fasp/local.rb +31 -24
  25. data/lib/aspera/fasp/manager.rb +3 -0
  26. data/lib/aspera/fasp/node.rb +23 -1
  27. data/lib/aspera/fasp/parameters.rb +72 -89
  28. data/lib/aspera/fasp/parameters.yaml +531 -0
  29. data/lib/aspera/fasp/uri.rb +1 -1
  30. data/lib/aspera/faspex_gw.rb +10 -9
  31. data/lib/aspera/id_generator.rb +22 -0
  32. data/lib/aspera/node.rb +11 -3
  33. data/lib/aspera/oauth.rb +131 -135
  34. data/lib/aspera/persistency_action_once.rb +11 -7
  35. data/lib/aspera/persistency_folder.rb +6 -26
  36. data/lib/aspera/rest.rb +1 -1
  37. data/lib/aspera/sync.rb +40 -35
  38. data/lib/aspera/timer_limiter.rb +22 -0
  39. data/lib/aspera/web_auth.rb +105 -0
  40. metadata +22 -4
  41. data/docs/transfer_spec.html +0 -99
  42. data/lib/aspera/fasp/aoc.rb +0 -24
data/docs/doc_tools.rb ADDED
@@ -0,0 +1,58 @@
1
+ # get transfer spec parameter description
2
+ $LOAD_PATH.unshift(ENV["INCL_DIR_GEM"])
3
+ require 'aspera/fasp/parameters'
4
+
5
+ # check that required env vars exist, and files
6
+ %w{EXENAME GEMSPEC INCL_USAGE INCL_COMMANDS INCL_ASESSION INCL_DIR_GEM}.each do |e|
7
+ raise "missing env var #{e}" unless ENV.has_key?(e)
8
+ raise "missing file #{ENV[e]}" unless File.exist?(ENV[e]) or !e.start_with?('INCL_')
9
+ end
10
+
11
+ # set values used in ERB
12
+ # just command name
13
+ def cmd;ENV['EXENAME'];end
14
+
15
+ # used in text with formatting of command
16
+ def tool;'`'+cmd+'`';end
17
+
18
+ # prefix for env vars
19
+ def evp;cmd.upcase+'_';end
20
+
21
+ # just the name for "option preset"
22
+ def opprst;'option preset';end
23
+
24
+ # name with link
25
+ def prst;'['+opprst+'](#lprt)';end
26
+
27
+ # name with link (plural)
28
+ def prsts;'['+opprst+'s](#lprt)';end
29
+
30
+ # in title
31
+ def prstt;opprst.capitalize;end
32
+
33
+ def gemspec;Gem::Specification::load(ENV['GEMSPEC']) or raise "error loading #{ENV["GEMSPEC"]}";end
34
+
35
+ def geminstadd;gemspec.version.to_s.match(/\.[^0-9]/)?' --pre':'';end
36
+
37
+ # transfer spec description generation
38
+ def spec_table
39
+ r='<table><tr><th>Field</th><th>Type</th>'
40
+ Aspera::Fasp::Parameters::SUPPORTED_AGENTS_SHORT.each do |c|
41
+ r << '<th>'<<c.to_s.upcase<<'</th>'
42
+ end
43
+ r << '<th>Description</th></tr>'
44
+ Aspera::Fasp::Parameters.man_table.each do |p|
45
+ p[:description] << (p[:description].empty? ? '' : "\n") << "(" << p[:cli] << ")" unless p[:cli].to_s.empty?
46
+ p.delete(:cli)
47
+ p.keys.each{|c|p[c]='&nbsp;' if p[c].to_s.empty?}
48
+ p[:description].gsub!("\n",'<br/>')
49
+ p[:type].gsub!(',','<br/>')
50
+ r << '<tr><td>'<<p[:name]<<'</td><td>'<<p[:type]<<'</td>'
51
+ Aspera::Fasp::Parameters::SUPPORTED_AGENTS_SHORT.each do |c|
52
+ r << '<td>'<<p[c]<<'</td>'
53
+ end
54
+ r << '<td>'<<p[:description]<<'</td></tr>'
55
+ end
56
+ r << '</table>'
57
+ return r
58
+ end
data/docs/test_env.conf CHANGED
@@ -5,7 +5,7 @@ default:
5
5
  config: cli_default
6
6
  aoc: tst_aoc1
7
7
  faspex: tst_faspex
8
- faspex5: tst_faspex5_boot
8
+ faspex5: tst_faspex5
9
9
  shares: tst_shares_1
10
10
  shares2: tst_shares2
11
11
  node: tst_node
@@ -52,6 +52,12 @@ tst_faspex5_boot:
52
52
  auth: your value here
53
53
  username: your value here
54
54
  password: your value here
55
+ tst_faspex5_web:
56
+ url: your value here
57
+ auth: your value here
58
+ redirect_uri: your value here
59
+ client_id: your value here
60
+ client_secret: your value here
55
61
  tst_faspex5:
56
62
  url: your value here
57
63
  auth: your value here
@@ -59,6 +65,7 @@ tst_faspex5:
59
65
  client_secret: your value here
60
66
  private_key: your value here
61
67
  username: your value here
68
+ password: your value here
62
69
  tst_shares:
63
70
  url: your value here
64
71
  username: your value here
@@ -149,4 +156,5 @@ misc:
149
156
  email_external: your value here
150
157
  aoc_org: your value here
151
158
  aoc_user_email: your value here
159
+ aoc_workspace2: your value here
152
160
  http_gw_fqdn_port: your value here
data/examples/aoc.rb CHANGED
@@ -4,11 +4,22 @@ require 'aspera/log'
4
4
 
5
5
  Aspera::Log.instance.level=:debug
6
6
 
7
+ if ! ARGV.length.eql?(3)
8
+ Aspera::Log.log.error("wrong number of args: #{ARGV.length}")
9
+ Aspera::Log.log.error("Usage: #{$0} <aoc URL> <aoc username> <aoc private key content>")
10
+ Aspera::Log.log.error("Example: #{$0} https://myorg.ibmaspera.com john@example.com $(cat /home/john/my_key.pem)")
11
+ Process.exit(1)
12
+ end
13
+
14
+ aoc_url=ARGV[0]
15
+ aoc_user=ARGV[1]
16
+ aoc_key_value=ARGV[2]
17
+
7
18
  aocapi=Aspera::AoC.new(
8
- url: 'https://myorg.ibmaspera.com',
19
+ url: aoc_url,
9
20
  auth: :jwt,
10
- private_key: File.read('path/to_your_private_key.pem'),
11
- username: 'my.email@example.com',
21
+ private_key: aoc_key_value,
22
+ username: aoc_user,
12
23
  scope: 'user:all',
13
24
  subpath: 'api/v1')
14
25
 
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # find Faspex API here: https://developer.ibm.com/apis/catalog/?search=faspex
3
+ # this example makes use of class Aspera::Rest for REST calls, alternatively class RestClient of gem rest-client could be used
4
+ # this example makes use of class Aspera::Fasp::Local for transfers, alternatively the official "Transfer SDK" could be used
5
+ # Aspera SDK can be downloaded with: `ascli conf ascp install` , it installs in $HOME/.aspera/ascli/sdk
6
+ require 'aspera/rest'
7
+ require 'aspera/log'
8
+ require 'aspera/fasp/local'
9
+
10
+ tmpdir=ENV['tmp']||Dir.tmpdir || '.'
11
+
12
+ # Set high log level for the example, decrease to :warn usually
13
+ Aspera::Log.instance.level=:debug
14
+
15
+ # Set folder where SDK is installed (mandatory)
16
+ # (if ascp is not there, the lib will try to find in usual locations)
17
+ # (if data files are not there, they will be created)
18
+ Aspera::Fasp::Installation.instance.folder = tmpdir
19
+
20
+ if ! ARGV.length.eql?(3)
21
+ Aspera::Log.log.error("Wrong number of args: #{ARGV.length}")
22
+ Aspera::Log.log.error("Usage: #{$0} <faspex URL> <faspex username> <faspex password>")
23
+ Aspera::Log.log.error("Example: #{$0} https://faspex.com/aspera/faspex john p@sSw0rd")
24
+ Process.exit(1)
25
+ end
26
+
27
+ faspex_url=ARGV[0] # typically: https://faspex.example.com/aspera/faspex
28
+ faspex_user=ARGV[1]
29
+ faspex_pass=ARGV[2]
30
+
31
+ # comment out this if certificate is valid, keep line to ignore certificate
32
+ Aspera::Rest.insecure=true
33
+
34
+ # 1: Faspex 4 API v3
35
+ #---------------
36
+
37
+ # create REST API object
38
+ api_v3=Aspera::Rest.new({
39
+ :base_url => faspex_url,
40
+ :auth => {
41
+ :type => :basic,
42
+ :username => faspex_user,
43
+ :password => faspex_pass
44
+ }})
45
+
46
+ # very simple api call
47
+ api_v3.read('me')
48
+
49
+ # 2: send a package
50
+ #---------------
51
+
52
+ # create a sample file to send
53
+ file_to_send=File.join(tmpdir,'myfile.bin')
54
+ File.open(file_to_send, "w") {|f| f.write("sample data") }
55
+ # package creation parameters
56
+ package_create_params={'delivery'=>{'title'=>'test package','recipients'=>['aspera.user1@gmail.com'],'sources'=>[{'paths'=>[file_to_send]}]}}
57
+ pkg_created=api_v3.create('send',package_create_params)[:data]
58
+ # get transfer specification (normally: only one)
59
+ transfer_spec=pkg_created['xfer_sessions'].first
60
+ # set paths of files to send
61
+ transfer_spec['paths']=[{'source'=>file_to_send}]
62
+ # get the local agent (i.e. ascp)
63
+ transfer_client=Aspera::Fasp::Local.new
64
+ # disable ascp output on stdout (optional)
65
+ transfer_client.quiet=true
66
+ # start transfer (asynchronous)
67
+ job_id=transfer_client.start_transfer(transfer_spec)
68
+ # wait for all transfer completion (for the example)
69
+ result=transfer_client.wait_for_transfers_completion
70
+ # notify of any transfer error
71
+ result.select{|i|!i.eql?(:success)}.each do |e|
72
+ Aspera::Log.log.error("A transfer error occured: #{e.message}")
73
+ end
74
+
75
+ # 3: Faspex 4 API v4
76
+ #---------------
77
+ api_v4=Aspera::Rest.new({
78
+ :base_url => faspex_url+'/api',
79
+ :auth => {
80
+ :type => :oauth2,
81
+ :base_url => faspex_url+'/auth/oauth2',
82
+ :grant => :header_userpass,
83
+ :user_name => faspex_user,
84
+ :user_pass => faspex_pass,
85
+ :scope => 'admin'
86
+ }})
87
+
88
+ # Use it. Note that Faspex 4 API v4 is totally different from Faspex 4 v3 APIs, see ref on line 2
89
+ Aspera::Log.dump('users',api_v4.read('users')[:data])
data/lib/aspera/aoc.rb CHANGED
@@ -2,6 +2,7 @@ require 'aspera/log'
2
2
  require 'aspera/rest'
3
3
  require 'aspera/hash_ext'
4
4
  require 'aspera/data_repository'
5
+ require 'aspera/node'
5
6
  require 'base64'
6
7
 
7
8
  module Aspera
@@ -24,9 +25,9 @@ module Aspera
24
25
  JWT_AUDIENCE='https://api.asperafiles.com/api/v1/oauth2/token'
25
26
  OAUTH_API_SUBPATH='api/v1/oauth2'
26
27
  DEFAULT_TSPEC_INFO={
27
- 'remote_user' => 'xfer',
28
- 'ssh_port' => 33001,
29
- 'fasp_port' => 33001
28
+ 'remote_user' => Node::ACCESS_KEY_TRANSFER_USER,
29
+ 'ssh_port' => Node::SSH_PORT_DEFAULT,
30
+ 'fasp_port' => Node::UDP_PORT_DEFAULT
30
31
  }
31
32
 
32
33
  private_constant :PRODUCT_NAME,:PROD_DOMAIN,:MAX_REDIRECT,:CLIENT_APPS,:PATHS_PUBLIC_LINK,:JWT_AUDIENCE,:OAUTH_API_SUBPATH,:DEFAULT_TSPEC_INFO
@@ -273,7 +274,7 @@ module Aspera
273
274
  transfer_spec['remote_host']=node_file[:node_info]['host']
274
275
  else
275
276
  # retrieve values from API
276
- std_t_spec=get_node_api(node_file[:node_info],SCOPE_NODE_USER).create('files/download_setup',{:transfer_requests => [ { :transfer_request => {:paths => [ {"source"=>'/'} ] } } ] } )[:data]['transfer_specs'].first['transfer_spec']
277
+ std_t_spec=get_node_api(node_file[:node_info],scope: SCOPE_NODE_USER).create('files/download_setup',{:transfer_requests => [ { :transfer_request => {:paths => [ {"source"=>'/'} ] } } ] } )[:data]['transfer_specs'].first['transfer_spec']
277
278
  ['remote_host','remote_user','ssh_port','fasp_port'].each {|i| transfer_spec[i]=std_t_spec[i]}
278
279
  end
279
280
  # add caller provided transfer spec
@@ -290,26 +291,27 @@ module Aspera
290
291
  # @param scope e.g. SCOPE_NODE_USER
291
292
  # no scope: requires secret
292
293
  # if secret provided beforehand: use it
293
- def get_node_api(node_info,node_scope=nil)
294
- # X-Aspera-AccessKey required for bearer token only
295
- node_rest_params={
296
- :base_url => node_info['url'],
297
- :headers => {'X-Aspera-AccessKey'=>node_info['access_key']},
298
- }
299
- ak_secret=@key_chain.get_secret(node_info['access_key'],false)
300
- if ak_secret.nil? and node_scope.nil?
294
+ def get_node_api(node_info,options={})
295
+ raise "INTERNAL ERROR: method parameters: options must ne hash" unless options.is_a?(Hash)
296
+ options.keys.each {|k| raise "INTERNAL ERROR: not valid option: #{k}" unless [:scope,:use_secret].include?(k)}
297
+ # get optional secret unless :use_secret is false (default is true)
298
+ ak_secret=@key_chain.get_secret(node_info['access_key'],false) if !options.has_key?(:use_secret) or options[:use_secret]
299
+ if ak_secret.nil? and !options.has_key?(:scope)
301
300
  raise "There must be at least one of: 'secret' or 'scope' for access key #{node_info['access_key']}"
302
301
  end
303
- # if secret provided on command line or if there is no scope
304
- if !ak_secret.nil? or node_scope.nil?
302
+ node_rest_params={base_url: node_info['url']}
303
+ # if secret is available
304
+ if !ak_secret.nil?
305
305
  node_rest_params[:auth]={
306
- :type => :basic,
307
- :username => node_info['access_key'],
308
- :password => ak_secret
306
+ type: :basic,
307
+ username: node_info['access_key'],
308
+ password: ak_secret
309
309
  }
310
310
  else
311
+ # X-Aspera-AccessKey required for bearer token only
312
+ node_rest_params[:headers]= {'X-Aspera-AccessKey'=>node_info['access_key']}
311
313
  node_rest_params[:auth]=self.params[:auth].clone
312
- node_rest_params[:auth][:scope]=self.class.node_scope(node_info['access_key'],node_scope)
314
+ node_rest_params[:auth][:scope]=self.class.node_scope(node_info['access_key'],options[:scope])
313
315
  end
314
316
  return Node.new(node_rest_params)
315
317
  end
@@ -336,7 +338,7 @@ module Aspera
336
338
  if entry[:type].eql?('link')
337
339
  sub_node_info=self.read("nodes/#{entry['target_node_id']}")[:data]
338
340
  sub_opt={method: process_find_files, top_file_id: entry['target_id'], top_file_path: path}
339
- get_node_api(sub_node_info,SCOPE_NODE_USER).crawl(self,sub_opt)
341
+ get_node_api(sub_node_info,scope: SCOPE_NODE_USER).crawl(self,sub_opt)
340
342
  end
341
343
  rescue => e
342
344
  Log.log.error("#{path}: #{e.message}")
@@ -349,7 +351,7 @@ module Aspera
349
351
  top_node_info,top_file_id=check_get_node_file(top_node_file)
350
352
  Log.log.debug("find_files: node_info=#{top_node_info}, fileid=#{top_file_id}")
351
353
  @find_state={found: [], test_block: test_block}
352
- get_node_api(top_node_info,SCOPE_NODE_USER).crawl(self,{method: :process_find_files, top_file_id: top_file_id})
354
+ get_node_api(top_node_info,scope: SCOPE_NODE_USER).crawl(self,{method: :process_find_files, top_file_id: top_file_id})
353
355
  result=@find_state[:found]
354
356
  @find_state=nil
355
357
  return result
@@ -370,7 +372,7 @@ module Aspera
370
372
  if @resolve_state[:path].empty?
371
373
  @resolve_state[:result][:file_id]=entry['target_id']
372
374
  else
373
- get_node_api(@resolve_state[:result][:node_info],SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: entry['target_id']})
375
+ get_node_api(@resolve_state[:result][:node_info],scope: SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: entry['target_id']})
374
376
  end
375
377
  when 'folder'
376
378
  if @resolve_state[:path].empty?
@@ -397,7 +399,7 @@ module Aspera
397
399
  result[:file_id]=top_file_id
398
400
  else
399
401
  @resolve_state={path: path_elements, result: result}
400
- get_node_api(top_node_info,SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: top_file_id})
402
+ get_node_api(top_node_info,scope: SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: top_file_id})
401
403
  not_found=@resolve_state[:path]
402
404
  @resolve_state=nil
403
405
  raise "entry not found: #{not_found}" if result[:file_id].nil?
@@ -21,15 +21,17 @@ module Aspera
21
21
  private
22
22
  # name of application, also foldername where config is stored
23
23
  PROGRAM_NAME = 'ascli'
24
+ # name of the containing gem, same as in <gem name>.gemspec
24
25
  GEM_NAME = 'aspera-cli'
25
- VERBOSE_LEVELS=[:normal,:minimal,:quiet]
26
-
27
- 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'
28
30
 
31
+ private_constant :PROGRAM_NAME,:GEM_NAME,:HELP_URL,:GEM_URL
29
32
  # =============================================================
30
33
  # Parameter handlers
31
34
  #
32
-
33
35
  def option_insecure; Rest.insecure ; end
34
36
 
35
37
  def option_insecure=(value); Rest.insecure = value; end
@@ -40,20 +42,19 @@ module Aspera
40
42
 
41
43
  # minimum initialization
42
44
  def initialize(argv)
43
- # first thing : manage debug level (allows debugging or option parser)
45
+ # first thing : manage debug level (allows debugging of option parser)
44
46
  early_debug_setup(argv)
47
+ # compare $0 with expected name
45
48
  current_prog_name=File.basename($PROGRAM_NAME)
46
49
  unless current_prog_name.eql?(PROGRAM_NAME)
47
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")
48
51
  end
49
- # overriding parameters on transfer spec
50
52
  @option_help=false
51
53
  @bash_completion=false
52
54
  @option_show_config=false
55
+ # environment provided to plugin for various capabilities
53
56
  @plugin_env={}
54
- @help_url='http://www.rubydoc.info/gems/'+GEM_NAME
55
- @gem_url='https://rubygems.org/gems/'+GEM_NAME
56
- # give command line arguments to option manager (no parsing)
57
+ # find out application main folder
57
58
  app_main_folder=ENV[conf_dir_env_var]
58
59
  # if env var undefined or empty
59
60
  if app_main_folder.nil? or app_main_folder.empty?
@@ -61,20 +62,21 @@ module Aspera
61
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)
62
63
  app_main_folder=File.join(user_home_folder,Plugins::Config::ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME)
63
64
  end
65
+ # give command line arguments to option manager (no parsing)
64
66
  @plugin_env[:options]=@opt_mgr=Manager.new(PROGRAM_NAME,argv,app_banner())
65
67
  @plugin_env[:formater]=Formater.new(@plugin_env[:options])
66
68
  Rest.user_agent=PROGRAM_NAME
67
- # must override help methods before parser called (in other constructors)
69
+ # declare and parse global options
68
70
  init_global_options()
69
71
  # secret manager
70
72
  @plugin_env[:secret]=Aspera::Secrets.new
71
73
  # the Config plugin adds the @preset parser
72
- @plugin_env[:config]=Plugins::Config.new(@plugin_env,PROGRAM_NAME,@help_url,Aspera::Cli::VERSION,app_main_folder)
74
+ @plugin_env[:config]=Plugins::Config.new(@plugin_env,PROGRAM_NAME,HELP_URL,Aspera::Cli::VERSION,app_main_folder)
73
75
  # the TransferAgent plugin may use the @preset parser
74
76
  @plugin_env[:transfer]=TransferAgent.new(@plugin_env)
75
- Log.log.debug('created plugin env'.red)
76
77
  # set application folder for modules
77
78
  @plugin_env[:persistency]=PersistencyFolder.new(File.join(@plugin_env[:config].main_folder,'persist_store'))
79
+ Log.log.debug('plugin env created'.red)
78
80
  Oauth.persist_mgr=@plugin_env[:persistency]
79
81
  Fasp::Parameters.file_list_folder=File.join(@plugin_env[:config].main_folder,'filelists')
80
82
  Aspera::RestErrorAnalyzer.instance.log_file=File.join(@plugin_env[:config].main_folder,'rest_exceptions.log')
@@ -88,9 +90,9 @@ module Aspera
88
90
  banner << "\t#{PROGRAM_NAME} COMMANDS [OPTIONS] [ARGS]\n"
89
91
  banner << "\nDESCRIPTION\n"
90
92
  banner << "\tUse Aspera application to perform operations on command line.\n"
91
- banner << "\tDocumentation and examples: #{@gem_url}\n"
93
+ banner << "\tDocumentation and examples: #{GEM_URL}\n"
92
94
  banner << "\texecute: #{PROGRAM_NAME} conf doc\n"
93
- banner << "\tor visit: #{@help_url}\n"
95
+ banner << "\tor visit: #{HELP_URL}\n"
94
96
  banner << "\nENVIRONMENT VARIABLES\n"
95
97
  banner << "\t#{conf_dir_env_var} config folder, default: $HOME/#{Plugins::Config::ASPERA_HOME_FOLDER_NAME}/#{PROGRAM_NAME}\n"
96
98
  banner << "\t#any option can be set as an environment variable, refer to the manual\n"
@@ -188,6 +190,8 @@ module Aspera
188
190
 
189
191
  protected
190
192
 
193
+ # env var name to override the app's main folder
194
+ # default main folder is $HOME/<vendor main app folder>/<program name>
191
195
  def conf_dir_env_var
192
196
  return "#{PROGRAM_NAME}_home".upcase
193
197
  end
@@ -219,10 +223,28 @@ module Aspera
219
223
  return Main.result_nothing
220
224
  end
221
225
 
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
240
+
222
241
  # this is the main function called by initial script just after constructor
223
242
  def process_command_line
224
243
  Log.log.debug('process_command_line')
244
+ # catch exception information , if any
225
245
  exception_info=nil
246
+ # false if command shall not be executed ("once_only")
247
+ execute_command=true
226
248
  begin
227
249
  # find plugins, shall be after parse! ?
228
250
  @plugin_env[:config].add_plugins_from_lookup_folders
@@ -256,7 +278,7 @@ module Aspera
256
278
  @plugin_env[:formater].display_results({:type=>:single_object,:data=>@opt_mgr.declared_options(false)})
257
279
  Process.exit(0)
258
280
  end
259
- # locking for singkle execution (only after "per plugin" option, in case lock port is there)
281
+ # locking for single execution (only after "per plugin" option, in case lock port is there)
260
282
  lock_port=@opt_mgr.get_option(:lock_port,:optional)
261
283
  if !lock_port.nil?
262
284
  begin
@@ -264,11 +286,12 @@ module Aspera
264
286
  Log.log.debug("Opening lock port #{lock_port.to_i}")
265
287
  @tcp_server=TCPServer.new('127.0.0.1',lock_port.to_i)
266
288
  rescue => e
267
- raise CliError,"Another instance is already running (#{e.message})."
289
+ execute_command=false
290
+ Log.log.warn("Another instance is already running (#{e.message}).")
268
291
  end
269
292
  end
270
- # execute and display
271
- @plugin_env[:formater].display_results(command_plugin.execute_action)
293
+ # execute and display (if not exclusive execution)
294
+ @plugin_env[:formater].display_results(command_plugin.execute_action) if execute_command
272
295
  # finish
273
296
  @plugin_env[:transfer].shutdown
274
297
  rescue CliBadArgument => e; exception_info=[e,'Argument',:usage]
@@ -286,10 +309,15 @@ module Aspera
286
309
  unless exception_info.nil?
287
310
  @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" "+exception_info[1]+": "+exception_info[0].message)
288
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
289
315
  end
290
316
  # 2- processing of command not processed (due to exception or bad command line)
291
- @opt_mgr.final_errors.each do |msg|
292
- @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
293
321
  end
294
322
  # 3- in case of error, fail the process status
295
323
  unless exception_info.nil?
@@ -2,10 +2,15 @@ module Aspera
2
2
  module Cli
3
3
  # base class for plugins modules
4
4
  class Plugin
5
+ # operation without id
5
6
  GLOBAL_OPS=[:create,:list]
7
+ # operation on specific instance
6
8
  INSTANCE_OPS=[:modify,:delete,:show]
7
9
  ALL_OPS=[GLOBAL_OPS,INSTANCE_OPS].flatten
8
- #private_constant :GLOBAL_OPS,:INSTANCE_OPS,:ALL_OPS
10
+ # max number of items for list command
11
+ MAX_ITEMS='max'
12
+ # max number of pages for list command
13
+ MAX_PAGES='pmax'
9
14
 
10
15
  @@done=false
11
16
 
@@ -28,7 +33,7 @@ module Aspera
28
33
  end
29
34
  end
30
35
 
31
- def entity_command(command,rest_api,res_class_path,display_fields,id_symb,id_default=nil,subkey=false)
36
+ def entity_command(command,rest_api,res_class_path,display_fields,id_symb,id_default=nil,use_subkey=false)
32
37
  if INSTANCE_OPS.include?(command)
33
38
  begin
34
39
  one_res_id=self.options.get_option(id_symb,:mandatory)
@@ -54,10 +59,11 @@ module Aspera
54
59
  when :list
55
60
  resp=rest_api.read(res_class_path,parameters)
56
61
  data=resp[:data]
62
+ # TODO: not generic : which application is this for ?
57
63
  if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
58
- data=resp[:data][res_class_path]
64
+ data=data[res_class_path]
59
65
  end
60
- data=data[res_class_path] if subkey
66
+ data=data[res_class_path] if use_subkey
61
67
  return {:type => :object_list, :data=>data, :fields=>display_fields}
62
68
  when :modify
63
69
  property=self.options.get_option(:property,:optional)
@@ -74,12 +80,13 @@ module Aspera
74
80
  end
75
81
 
76
82
  # implement generic rest operations on given resource path
77
- def entity_action(rest_api,res_class_path,display_fields,id_symb,id_default=nil,subkey=false)
83
+ def entity_action(rest_api,res_class_path,display_fields,id_symb,id_default=nil,use_subkey=false)
78
84
  #res_name=res_class_path.gsub(%r{^.*/},'').gsub(%r{s$},'').gsub('_',' ')
79
85
  command=self.options.get_next_command(ALL_OPS)
80
- return entity_command(command,rest_api,res_class_path,display_fields,id_symb,id_default,subkey)
86
+ return entity_command(command,rest_api,res_class_path,display_fields,id_symb,id_default,use_subkey)
81
87
  end
82
88
 
89
+ # shortcuts for plugin environment
83
90
  def options; return @agents[:options];end
84
91
 
85
92
  def transfer; return @agents[:transfer];end