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.
- checksums.yaml +4 -4
- data/README.md +749 -353
- data/docs/Makefile +4 -4
- data/docs/README.erb.md +743 -283
- data/docs/doc_tools.rb +58 -0
- data/docs/test_env.conf +9 -1
- data/examples/aoc.rb +14 -3
- data/examples/faspex4.rb +89 -0
- data/lib/aspera/aoc.rb +24 -22
- data/lib/aspera/cli/main.rb +48 -20
- data/lib/aspera/cli/plugin.rb +13 -6
- data/lib/aspera/cli/plugins/aoc.rb +117 -78
- data/lib/aspera/cli/plugins/config.rb +127 -80
- data/lib/aspera/cli/plugins/faspex.rb +112 -63
- data/lib/aspera/cli/plugins/faspex5.rb +29 -25
- data/lib/aspera/cli/plugins/node.rb +54 -25
- data/lib/aspera/cli/plugins/preview.rb +94 -68
- data/lib/aspera/cli/plugins/server.rb +16 -5
- data/lib/aspera/cli/transfer_agent.rb +92 -72
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +48 -31
- data/lib/aspera/cos_node.rb +4 -3
- data/lib/aspera/fasp/http_gw.rb +47 -26
- data/lib/aspera/fasp/local.rb +31 -24
- data/lib/aspera/fasp/manager.rb +3 -0
- data/lib/aspera/fasp/node.rb +23 -1
- data/lib/aspera/fasp/parameters.rb +72 -89
- data/lib/aspera/fasp/parameters.yaml +531 -0
- data/lib/aspera/fasp/uri.rb +1 -1
- data/lib/aspera/faspex_gw.rb +10 -9
- data/lib/aspera/id_generator.rb +22 -0
- data/lib/aspera/node.rb +11 -3
- data/lib/aspera/oauth.rb +131 -135
- data/lib/aspera/persistency_action_once.rb +11 -7
- data/lib/aspera/persistency_folder.rb +6 -26
- data/lib/aspera/rest.rb +1 -1
- data/lib/aspera/sync.rb +40 -35
- data/lib/aspera/timer_limiter.rb +22 -0
- data/lib/aspera/web_auth.rb +105 -0
- metadata +22 -4
- data/docs/transfer_spec.html +0 -99
- 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]=' ' 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:
|
|
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:
|
|
19
|
+
url: aoc_url,
|
|
9
20
|
auth: :jwt,
|
|
10
|
-
private_key:
|
|
11
|
-
username:
|
|
21
|
+
private_key: aoc_key_value,
|
|
22
|
+
username: aoc_user,
|
|
12
23
|
scope: 'user:all',
|
|
13
24
|
subpath: 'api/v1')
|
|
14
25
|
|
data/examples/faspex4.rb
ADDED
|
@@ -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' =>
|
|
28
|
-
'ssh_port' =>
|
|
29
|
-
'fasp_port' =>
|
|
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,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
304
|
-
if
|
|
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
|
-
:
|
|
307
|
-
:
|
|
308
|
-
:
|
|
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'],
|
|
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?
|
data/lib/aspera/cli/main.rb
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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: #{
|
|
93
|
+
banner << "\tDocumentation and examples: #{GEM_URL}\n"
|
|
92
94
|
banner << "\texecute: #{PROGRAM_NAME} conf doc\n"
|
|
93
|
-
banner << "\tor visit: #{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
@
|
|
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?
|
data/lib/aspera/cli/plugin.rb
CHANGED
|
@@ -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
|
-
#
|
|
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,
|
|
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=
|
|
64
|
+
data=data[res_class_path]
|
|
59
65
|
end
|
|
60
|
-
data=data[res_class_path] if
|
|
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,
|
|
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,
|
|
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
|