aspera-cli 4.4.0 → 4.5.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 +1042 -787
- data/bin/ascli +1 -1
- data/bin/asession +3 -5
- data/docs/Makefile +4 -7
- data/docs/README.erb.md +988 -740
- data/examples/faspex4.rb +4 -6
- data/examples/transfer.rb +2 -2
- data/lib/aspera/aoc.rb +139 -118
- data/lib/aspera/cli/listener/progress_multi.rb +5 -5
- data/lib/aspera/cli/main.rb +64 -34
- data/lib/aspera/cli/manager.rb +19 -20
- data/lib/aspera/cli/plugin.rb +9 -1
- data/lib/aspera/cli/plugins/aoc.rb +156 -143
- data/lib/aspera/cli/plugins/ats.rb +11 -10
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +236 -112
- data/lib/aspera/cli/plugins/faspex.rb +29 -7
- data/lib/aspera/cli/plugins/faspex5.rb +21 -8
- data/lib/aspera/cli/plugins/node.rb +21 -9
- data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
- data/lib/aspera/cli/plugins/preview.rb +2 -2
- data/lib/aspera/cli/plugins/server.rb +3 -3
- data/lib/aspera/cli/plugins/shares.rb +17 -0
- data/lib/aspera/cli/transfer_agent.rb +47 -85
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +4 -4
- data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
- data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
- data/lib/aspera/fasp/{local.rb → agent_direct.rb} +14 -17
- data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +4 -4
- data/lib/aspera/fasp/{node.rb → agent_node.rb} +25 -8
- data/lib/aspera/fasp/agent_trsdk.rb +106 -0
- data/lib/aspera/fasp/default.rb +17 -0
- data/lib/aspera/fasp/installation.rb +64 -48
- data/lib/aspera/fasp/parameters.rb +7 -3
- data/lib/aspera/faspex_gw.rb +6 -6
- data/lib/aspera/keychain/encrypted_hash.rb +120 -0
- data/lib/aspera/keychain/macos_security.rb +94 -0
- data/lib/aspera/log.rb +45 -32
- data/lib/aspera/node.rb +3 -6
- data/lib/aspera/rest.rb +65 -49
- metadata +68 -27
- data/lib/aspera/api_detector.rb +0 -60
- data/lib/aspera/secrets.rb +0 -20
data/lib/aspera/faspex_gw.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'aspera/log'
|
2
2
|
require 'aspera/aoc'
|
3
|
-
require 'aspera/
|
3
|
+
require 'aspera/fasp/default'
|
4
4
|
require 'aspera/cli/main'
|
5
5
|
require 'webrick'
|
6
6
|
require 'webrick/https'
|
@@ -76,9 +76,9 @@ module Aspera
|
|
76
76
|
faspex_transfer_spec={
|
77
77
|
'direction' => 'send',
|
78
78
|
'remote_host' => node_info['host'],
|
79
|
-
'remote_user' =>
|
80
|
-
'ssh_port' =>
|
81
|
-
'fasp_port' =>
|
79
|
+
'remote_user' => Fasp::Default::ACCESS_KEY_TRANSFER_USER,
|
80
|
+
'ssh_port' => Fasp::Default::SSH_PORT,
|
81
|
+
'fasp_port' => Fasp::Default::UDP_PORT,
|
82
82
|
'tags' => ts_tags,
|
83
83
|
'token' => node_auth_bearer_token,
|
84
84
|
'paths' => [{'destination' => '/'}],
|
@@ -95,8 +95,8 @@ module Aspera
|
|
95
95
|
'lock_rate_policy' => true,
|
96
96
|
'source_root' => '',
|
97
97
|
'content_protection' => nil,
|
98
|
-
'target_rate_cap_kbps' => 20000, # TODO
|
99
|
-
'target_rate_kbps' => 10000, # TODO
|
98
|
+
'target_rate_cap_kbps' => 20000, # TODO: is this value useful ?
|
99
|
+
'target_rate_kbps' => 10000, # TODO: get from where?
|
100
100
|
'cipher' => 'aes-128',
|
101
101
|
'cipher_allowed' => nil,
|
102
102
|
'http_fallback' => false,
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Aspera
|
4
|
+
module Keychain
|
5
|
+
class SimpleCipher
|
6
|
+
def initialize(key)
|
7
|
+
@key=Digest::SHA1.hexdigest(key)[0..23]
|
8
|
+
@cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
|
9
|
+
end
|
10
|
+
|
11
|
+
def encrypt(value)
|
12
|
+
@cipher.encrypt
|
13
|
+
@cipher.key = @key
|
14
|
+
s = @cipher.update(value) + @cipher.final
|
15
|
+
s.unpack('H*').first
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt(value)
|
19
|
+
@cipher.decrypt
|
20
|
+
@cipher.key = @key
|
21
|
+
s = [value].pack('H*').unpack('C*').pack('c*')
|
22
|
+
@cipher.update(s) + @cipher.final
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Manage secrets in a simple Hash
|
27
|
+
class EncryptedHash
|
28
|
+
SEPARATOR='%'
|
29
|
+
private_constant :SEPARATOR
|
30
|
+
def initialize(values)
|
31
|
+
raise "values shall be Hash" unless values.is_a?(Hash)
|
32
|
+
@all_secrets=values
|
33
|
+
end
|
34
|
+
|
35
|
+
def set(options)
|
36
|
+
raise "options shall be Hash" unless options.is_a?(Hash)
|
37
|
+
unsupported=options.keys-[:username,:url,:secret,:description]
|
38
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
39
|
+
username=options[:username]
|
40
|
+
raise "options shall have username" if username.nil?
|
41
|
+
url=options[:url]
|
42
|
+
raise "options shall have username" if url.nil?
|
43
|
+
secret=options[:secret]
|
44
|
+
raise "options shall have secret" if secret.nil?
|
45
|
+
key=[url,username].join(SEPARATOR)
|
46
|
+
raise "secret #{key} already exist, delete first" if @all_secrets.has_key?(key)
|
47
|
+
obj={username: username, url: url, secret: SimpleCipher.new(key).encrypt(secret)}
|
48
|
+
obj[:description]=options[:description] if options.has_key?(:description)
|
49
|
+
@all_secrets[key]=obj
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def list
|
54
|
+
result=[]
|
55
|
+
@all_secrets.each do |k,v|
|
56
|
+
case v
|
57
|
+
when String
|
58
|
+
o={username: k, url: '', description: ''}
|
59
|
+
when Hash
|
60
|
+
o=v.clone
|
61
|
+
o.delete(:secret)
|
62
|
+
o[:description]||=''
|
63
|
+
else raise "error"
|
64
|
+
end
|
65
|
+
o[:description]=v[:description] if v.is_a?(Hash) and v[:description].is_a?(String)
|
66
|
+
result.push(o)
|
67
|
+
end
|
68
|
+
return result
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete(options)
|
72
|
+
raise "options shall be Hash" unless options.is_a?(Hash)
|
73
|
+
unsupported=options.keys-[:username,:url]
|
74
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
75
|
+
username=options[:username]
|
76
|
+
raise "options shall have username" if username.nil?
|
77
|
+
url=options[:url]
|
78
|
+
key=nil
|
79
|
+
if !url.nil?
|
80
|
+
extk=[url,username].join(SEPARATOR)
|
81
|
+
key=extk if @all_secrets.has_key?(extk)
|
82
|
+
end
|
83
|
+
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
84
|
+
key=username if key.nil? and @all_secrets.has_key?(username)
|
85
|
+
raise "no such secret" if key.nil?
|
86
|
+
@all_secrets.delete(key)
|
87
|
+
end
|
88
|
+
|
89
|
+
def get(options)
|
90
|
+
raise "options shall be Hash" unless options.is_a?(Hash)
|
91
|
+
unsupported=options.keys-[:username,:url]
|
92
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
93
|
+
username=options[:username]
|
94
|
+
raise "options shall have username" if username.nil?
|
95
|
+
url=options[:url]
|
96
|
+
val=nil
|
97
|
+
if !url.nil?
|
98
|
+
val=@all_secrets[[url,username].join(SEPARATOR)]
|
99
|
+
end
|
100
|
+
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
101
|
+
if val.nil?
|
102
|
+
val=@all_secrets[username]
|
103
|
+
end
|
104
|
+
result=options.clone
|
105
|
+
case val
|
106
|
+
when NilClass
|
107
|
+
raise "no such secret"
|
108
|
+
when String
|
109
|
+
result.merge!({secret: val, description: ''})
|
110
|
+
when Hash
|
111
|
+
key=[url,username].join(SEPARATOR)
|
112
|
+
plain=SimpleCipher.new(key).decrypt(val[:secret])
|
113
|
+
result.merge!({secret: plain, description: val[:description]})
|
114
|
+
else raise "error"
|
115
|
+
end
|
116
|
+
return result
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'security'
|
2
|
+
|
3
|
+
# enhance the gem to support other keychains
|
4
|
+
module Security
|
5
|
+
class Keychain
|
6
|
+
class << self
|
7
|
+
def by_name(name)
|
8
|
+
keychains_from_output(`security list-keychains`).select{|kc|kc.filename.end_with?("/#{name}.keychain-db")}.first
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Password
|
14
|
+
class << self
|
15
|
+
# add some login to original method
|
16
|
+
alias_method :orig_flags_for_options, :flags_for_options
|
17
|
+
def flags_for_options(options = {})
|
18
|
+
keychain=options.delete(:keychain)
|
19
|
+
url=options.delete(:url)
|
20
|
+
if !url.nil?
|
21
|
+
uri=URI.parse(url)
|
22
|
+
raise 'only https' unless uri.scheme.eql?('https')
|
23
|
+
options[:r]='htps'
|
24
|
+
raise 'host required in URL' if uri.host.nil?
|
25
|
+
options[:s]=uri.host
|
26
|
+
options[:p]=uri.path unless ['','/'].include?(uri.path)
|
27
|
+
options[:P]=uri.port unless uri.port.eql?(443) and !url.include?(':443/')
|
28
|
+
end
|
29
|
+
flags=[orig_flags_for_options(options)]
|
30
|
+
flags.push(keychain.filename) unless keychain.nil?
|
31
|
+
flags.join(' ')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Aspera
|
38
|
+
module Keychain
|
39
|
+
# keychain based on macOS keychain, using `security` cmmand line
|
40
|
+
class MacosSecurity
|
41
|
+
def initialize(name=nil)
|
42
|
+
if name.nil?
|
43
|
+
@keychain=Security::Keychain.default_keychain
|
44
|
+
else
|
45
|
+
@keychain=Security::Keychain.by_name(name)
|
46
|
+
end
|
47
|
+
raise "no such keychain #{name}" if @keychain.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def set(options)
|
51
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
52
|
+
unsupported=options.keys-[:username,:url,:secret,:description]
|
53
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
54
|
+
username=options[:username]
|
55
|
+
raise 'options shall have username' if username.nil?
|
56
|
+
url=options[:url]
|
57
|
+
raise 'options shall have url' if url.nil?
|
58
|
+
secret=options[:secret]
|
59
|
+
raise 'options shall have secret' if secret.nil?
|
60
|
+
raise 'set not implemented'
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def get(options)
|
65
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
66
|
+
unsupported=options.keys-[:username,:url]
|
67
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
68
|
+
username=options[:username]
|
69
|
+
raise 'options shall have username' if username.nil?
|
70
|
+
url=options[:url]
|
71
|
+
raise 'options shall have url' if url.nil?
|
72
|
+
info=Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
73
|
+
raise 'not found' if info.nil?
|
74
|
+
result=options.clone
|
75
|
+
result.merge!({secret: info.password, description: info.attributes['icmt']})
|
76
|
+
return result
|
77
|
+
end
|
78
|
+
|
79
|
+
def list
|
80
|
+
raise 'list not implemented'
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete(options)
|
84
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
85
|
+
unsupported=options.keys-[:username,:url]
|
86
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
87
|
+
username=options[:username]
|
88
|
+
raise 'options shall have username' if username.nil?
|
89
|
+
url=options[:url]
|
90
|
+
raise 'delete not implemented'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/aspera/log.rb
CHANGED
@@ -7,33 +7,41 @@ require 'singleton'
|
|
7
7
|
module Aspera
|
8
8
|
# Singleton object for logging
|
9
9
|
class Log
|
10
|
-
|
11
|
-
public
|
12
10
|
include Singleton
|
11
|
+
# class methods
|
12
|
+
class << self
|
13
|
+
# levels are :debug,:info,:warn,:error,fatal,:unknown
|
14
|
+
def levels; Logger::Severity.constants.sort{|a,b|Logger::Severity.const_get(a)<=>Logger::Severity.const_get(b)}.map{|c|c.downcase.to_sym};end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
# levels are :debug,:info,:warn,:error,fatal,:unknown
|
17
|
-
def self.levels; Logger::Severity.constants.sort{|a,b|Logger::Severity.const_get(a)<=>Logger::Severity.const_get(b)}.map{|c|c.downcase.to_sym};end
|
18
|
-
|
19
|
-
# where logs are sent to
|
20
|
-
def self.logtypes; [:stderr,:stdout,:syslog];end
|
16
|
+
# where logs are sent to
|
17
|
+
def logtypes; [:stderr,:stdout,:syslog];end
|
21
18
|
|
22
|
-
|
23
|
-
|
19
|
+
# get the logger object of singleton
|
20
|
+
def log; instance.logger;end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
# dump object in debug mode
|
23
|
+
# @param name string or symbol
|
24
|
+
# @param format either pp or json format
|
25
|
+
def dump(name,object,format=:json)
|
26
|
+
self.log.debug() do
|
27
|
+
result=case format
|
28
|
+
when :json
|
29
|
+
JSON.pretty_generate(object) rescue PP.pp(object,'')
|
30
|
+
when :ruby
|
31
|
+
PP.pp(object,'')
|
32
|
+
else
|
33
|
+
raise "wrong parameter, expect pp or json"
|
34
|
+
end
|
35
|
+
"#{name.to_s.green} (#{format})=\n#{result}"
|
36
|
+
end
|
33
37
|
end
|
34
|
-
self.log.debug("#{name.to_s.green} (#{format})=\n#{result}")
|
35
38
|
end
|
36
39
|
|
40
|
+
attr_reader :logger_type
|
41
|
+
attr_reader :logger
|
42
|
+
attr_writer :program_name
|
43
|
+
attr_accessor :log_passwords
|
44
|
+
|
37
45
|
# set log level of underlying logger given symbol level
|
38
46
|
def level=(new_level)
|
39
47
|
@logger.level=Logger::Severity.const_get(new_level.to_sym.upcase)
|
@@ -44,20 +52,15 @@ module Aspera
|
|
44
52
|
Logger::Severity.constants.each do |name|
|
45
53
|
return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
|
46
54
|
end
|
55
|
+
# should not happen
|
47
56
|
raise "error"
|
48
57
|
end
|
49
58
|
|
50
59
|
# change underlying logger, but keep log level
|
51
60
|
def logger_type=(new_logtype)
|
52
|
-
current_severity_integer
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
Logger::Severity::WARN
|
57
|
-
end
|
58
|
-
else
|
59
|
-
@logger.level
|
60
|
-
end
|
61
|
+
current_severity_integer=@logger.level unless @logger.nil?
|
62
|
+
current_severity_integer=ENV['AS_LOG_LEVEL'] if current_severity_integer.nil? and ENV.has_key?('AS_LOG_LEVEL')
|
63
|
+
current_severity_integer=Logger::Severity::WARN if current_severity_integer.nil?
|
61
64
|
case new_logtype
|
62
65
|
when :stderr
|
63
66
|
@logger = Logger.new(STDERR)
|
@@ -71,17 +74,27 @@ module Aspera
|
|
71
74
|
end
|
72
75
|
@logger.level=current_severity_integer
|
73
76
|
@logger_type=new_logtype
|
77
|
+
original_formatter = @logger.formatter || Logger::Formatter.new
|
78
|
+
# update formatter with password hiding
|
79
|
+
@logger.formatter=proc do |severity, datetime, progname, msg|
|
80
|
+
unless @log_passwords
|
81
|
+
msg=msg.gsub(/("[^"]*(password|secret|private_key)[^"]*"=>")([^"]+)(")/){"#{$1}***#{$4}"}
|
82
|
+
msg=msg.gsub(/("[^"]*(secret)[^"]*"=>{)([^}]+)(})/){"#{$1}***#{$4}"}
|
83
|
+
msg=msg.gsub(/((secrets)={)([^}]+)(})/){"#{$1}***#{$4}"}
|
84
|
+
end
|
85
|
+
original_formatter.call(severity, datetime, progname, msg)
|
86
|
+
end
|
74
87
|
end
|
75
88
|
|
76
|
-
attr_writer :program_name
|
77
|
-
|
78
89
|
private
|
79
90
|
|
80
91
|
def initialize
|
81
92
|
@logger=nil
|
82
93
|
@program_name='aspera'
|
83
|
-
|
94
|
+
@log_passwords=false
|
95
|
+
# this sets @logger and @logger_type (self needed to call method instead of local var)
|
84
96
|
self.logger_type=:stderr
|
97
|
+
raise "error logger shall be defined" if @logger.nil?
|
85
98
|
end
|
86
99
|
|
87
100
|
end
|
data/lib/aspera/node.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'aspera/fasp/default'
|
1
2
|
require 'aspera/rest'
|
2
3
|
require 'aspera/oauth'
|
3
4
|
require 'aspera/log'
|
@@ -10,16 +11,12 @@ module Aspera
|
|
10
11
|
# permissions
|
11
12
|
ACCESS_LEVELS=['delete','list','mkdir','preview','read','rename','write']
|
12
13
|
MATCH_EXEC_PREFIX='exec:'
|
13
|
-
# (public) default transfer username for access key based transfers
|
14
|
-
ACCESS_KEY_TRANSFER_USER='xfer'
|
15
|
-
SSH_PORT_DEFAULT=33001
|
16
|
-
UDP_PORT_DEFAULT=33001
|
17
14
|
|
18
15
|
# register node special token decoder
|
19
16
|
Oauth.register_decoder(lambda{|token|JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition('==SIGNATURE==').first)})
|
20
17
|
|
21
18
|
def self.set_ak_basic_token(ts,ak,secret)
|
22
|
-
raise "ERROR: expected xfer" unless ts['remote_user'].eql?(ACCESS_KEY_TRANSFER_USER)
|
19
|
+
raise "ERROR: expected xfer" unless ts['remote_user'].eql?(Fasp::Default::ACCESS_KEY_TRANSFER_USER)
|
23
20
|
ts['token']="Basic #{Base64.strict_encode64("#{ak}:#{secret}")}"
|
24
21
|
end
|
25
22
|
|
@@ -30,7 +27,7 @@ module Aspera
|
|
30
27
|
def self.file_matcher(match_expression)
|
31
28
|
match_expression||="#{MATCH_EXEC_PREFIX}true"
|
32
29
|
if match_expression.start_with?(MATCH_EXEC_PREFIX)
|
33
|
-
return eval
|
30
|
+
return eval("lambda{|f|#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}")
|
34
31
|
end
|
35
32
|
return lambda{|f|f['name'].match(/#{match_expression}/)}
|
36
33
|
end
|
data/lib/aspera/rest.rb
CHANGED
@@ -35,8 +35,14 @@ module Aspera
|
|
35
35
|
@@insecure=false
|
36
36
|
@@user_agent='Ruby'
|
37
37
|
@@download_partial_suffix='.http_partial'
|
38
|
+
# a lambda which takes the Net::HTTP as arg, use this to change parameters
|
39
|
+
@@session_cb=nil
|
38
40
|
|
39
41
|
public
|
42
|
+
def self.session_cb=(v); @@session_cb=v;Log.log.debug("session_cb => #{@@session_cb}".red);end
|
43
|
+
|
44
|
+
def self.session_cb; @@session_cb;end
|
45
|
+
|
40
46
|
def self.insecure=(v); @@insecure=v;Log.log.debug("insecure => #{@@insecure}".red);end
|
41
47
|
|
42
48
|
def self.insecure; @@insecure;end
|
@@ -85,12 +91,9 @@ module Aspera
|
|
85
91
|
# this honors http_proxy env var
|
86
92
|
@http_session=Net::HTTP.new(uri.host, uri.port)
|
87
93
|
@http_session.use_ssl = uri.scheme.eql?('https')
|
88
|
-
Log.log.debug("insecure=#{@@insecure}")
|
89
94
|
@http_session.verify_mode = OpenSSL::SSL::VERIFY_NONE if @@insecure
|
90
95
|
@http_session.set_debug_output($stdout) if @@debug
|
91
|
-
|
92
|
-
@params[:session_cb].call(@http_session)
|
93
|
-
end
|
96
|
+
@@session_cb.call(@http_session) unless @@session_cb.nil?
|
94
97
|
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
95
98
|
@http_session.start
|
96
99
|
end
|
@@ -107,7 +110,6 @@ module Aspera
|
|
107
110
|
# :username [:basic]
|
108
111
|
# :password [:basic]
|
109
112
|
# :url_creds [:url]
|
110
|
-
# :session_cb a lambda which takes @http_session as arg, use this to change parameters
|
111
113
|
# :* [:oauth2] see Oauth class
|
112
114
|
def initialize(a_rest_params)
|
113
115
|
raise "ERROR: expecting Hash" unless a_rest_params.is_a?(Hash)
|
@@ -129,47 +131,14 @@ module Aspera
|
|
129
131
|
return @oauth.get_authorization(options)
|
130
132
|
end
|
131
133
|
|
132
|
-
|
133
|
-
# call_data has keys:
|
134
|
-
# :auth
|
135
|
-
# :operation
|
136
|
-
# :subpath
|
137
|
-
# :headers
|
138
|
-
# :json_params
|
139
|
-
# :url_params
|
140
|
-
# :www_body_params
|
141
|
-
# :text_body_params
|
142
|
-
# :save_to_file (filepath)
|
143
|
-
# :return_error (bool)
|
144
|
-
def call(call_data)
|
145
|
-
raise "Hash call parameter is required (#{call_data.class})" unless call_data.is_a?(Hash)
|
146
|
-
Log.log.debug("accessing #{call_data[:subpath]}".red.bold.bg_green)
|
147
|
-
call_data[:headers]||={}
|
148
|
-
call_data[:headers]['User-Agent'] ||= @@user_agent
|
149
|
-
# defaults from @params are overriden by call dataz
|
150
|
-
call_data=@params.deep_merge(call_data)
|
151
|
-
case call_data[:auth][:type]
|
152
|
-
when :none
|
153
|
-
# no auth
|
154
|
-
when :basic
|
155
|
-
Log.log.debug("using Basic auth")
|
156
|
-
basic_auth_data=[call_data[:auth][:username],call_data[:auth][:password]]
|
157
|
-
when :oauth2
|
158
|
-
call_data[:headers]['Authorization']=oauth_token unless call_data[:headers].has_key?('Authorization')
|
159
|
-
when :url
|
160
|
-
call_data[:url_params]||={}
|
161
|
-
call_data[:auth][:url_creds].each do |key, value|
|
162
|
-
call_data[:url_params][key]=value
|
163
|
-
end
|
164
|
-
else raise "unsupported auth type: [#{call_data[:auth][:type]}]"
|
165
|
-
end
|
134
|
+
def build_request(call_data)
|
166
135
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
167
136
|
# URI.escape()
|
168
|
-
uri=self.class.build_uri("#{
|
137
|
+
uri=self.class.build_uri("#{call_data[:base_url]}#{['','/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}",call_data[:url_params])
|
169
138
|
Log.log.debug("URI=#{uri}")
|
170
139
|
begin
|
171
140
|
# instanciate request object based on string name
|
172
|
-
req=Object::const_get('Net::HTTP::'+call_data[:operation].capitalize).new(uri
|
141
|
+
req=Object::const_get('Net::HTTP::'+call_data[:operation].capitalize).new(uri)#.request_uri
|
173
142
|
rescue NameError => e
|
174
143
|
raise "unsupported operation : #{call_data[:operation]}"
|
175
144
|
end
|
@@ -196,20 +165,59 @@ module Aspera
|
|
196
165
|
end
|
197
166
|
end
|
198
167
|
# :type = :basic
|
199
|
-
req.basic_auth(
|
168
|
+
req.basic_auth(call_data[:auth][:username],call_data[:auth][:password]) if call_data[:auth][:type].eql?(:basic)
|
169
|
+
return req
|
170
|
+
end
|
200
171
|
|
172
|
+
# HTTP/S REST call
|
173
|
+
# call_data has keys:
|
174
|
+
# :auth
|
175
|
+
# :operation
|
176
|
+
# :subpath
|
177
|
+
# :headers
|
178
|
+
# :json_params
|
179
|
+
# :url_params
|
180
|
+
# :www_body_params
|
181
|
+
# :text_body_params
|
182
|
+
# :save_to_file (filepath)
|
183
|
+
# :return_error (bool)
|
184
|
+
# :redirect_max (int)
|
185
|
+
def call(call_data)
|
186
|
+
raise "Hash call parameter is required (#{call_data.class})" unless call_data.is_a?(Hash)
|
187
|
+
call_data[:subpath]='' if call_data[:subpath].nil?
|
188
|
+
Log.log.debug("accessing #{call_data[:subpath]}".red.bold.bg_green)
|
189
|
+
call_data[:headers]||={}
|
190
|
+
call_data[:headers]['User-Agent'] ||= @@user_agent
|
191
|
+
# defaults from @params are overriden by call data
|
192
|
+
call_data=@params.deep_merge(call_data)
|
193
|
+
case call_data[:auth][:type]
|
194
|
+
when :none
|
195
|
+
# no auth
|
196
|
+
when :basic
|
197
|
+
Log.log.debug("using Basic auth")
|
198
|
+
# done in build_req
|
199
|
+
when :oauth2
|
200
|
+
call_data[:headers]['Authorization']=oauth_token unless call_data[:headers].has_key?('Authorization')
|
201
|
+
when :url
|
202
|
+
call_data[:url_params]||={}
|
203
|
+
call_data[:auth][:url_creds].each do |key, value|
|
204
|
+
call_data[:url_params][key]=value
|
205
|
+
end
|
206
|
+
else raise "unsupported auth type: [#{call_data[:auth][:type]}]"
|
207
|
+
end
|
208
|
+
req=build_request(call_data)
|
201
209
|
Log.log.debug("call_data = #{call_data}")
|
202
210
|
result={:http=>nil}
|
203
211
|
# start a block to be able to retry the actual HTTP request
|
204
212
|
begin
|
205
213
|
# we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
|
206
214
|
oauth_tries ||= 2
|
207
|
-
tries_remain_redirect||=
|
215
|
+
tries_remain_redirect||=call_data[:redirect_max].nil? ? 0 : call_data[:redirect_max].to_i
|
208
216
|
Log.log.debug("send request")
|
209
217
|
# make http request (pipelined)
|
210
218
|
http_session.request(req) do |response|
|
211
219
|
result[:http] = response
|
212
|
-
if call_data.has_key?(:save_to_file)
|
220
|
+
if call_data.has_key?(:save_to_file) and result[:http].code.to_s.start_with?('2')
|
213
221
|
total_size=result[:http]['Content-Length'].to_i
|
214
222
|
progress=ProgressBar.create(
|
215
223
|
:format => '%a %B %p%% %r KB/sec %e',
|
@@ -238,7 +246,7 @@ module Aspera
|
|
238
246
|
progress=nil
|
239
247
|
end # save_to_file
|
240
248
|
end
|
241
|
-
# sometimes there is a
|
249
|
+
# sometimes there is a UTF8 char (e.g. (c) )
|
242
250
|
result[:http].body.force_encoding("UTF-8") if result[:http].body.is_a?(String)
|
243
251
|
Log.log.debug("result: body=#{result[:http].body}")
|
244
252
|
result_mime=(result[:http]['Content-Type']||'text/plain').split(';').first
|
@@ -270,9 +278,18 @@ module Aspera
|
|
270
278
|
if tries_remain_redirect > 0
|
271
279
|
tries_remain_redirect-=1
|
272
280
|
Log.log.info("URL is moved: #{e.response['location']}")
|
273
|
-
|
274
|
-
|
275
|
-
|
281
|
+
current_uri=URI.parse(call_data[:base_url])
|
282
|
+
redir_uri=URI.parse(e.response['location'])
|
283
|
+
call_data[:base_url]=e.response['location']
|
284
|
+
call_data[:subpath]=''
|
285
|
+
if current_uri.host.eql?(redir_uri.host) and current_uri.port.eql?(redir_uri.port)
|
286
|
+
req=build_request(call_data)
|
287
|
+
retry
|
288
|
+
else
|
289
|
+
# change host
|
290
|
+
Log.log.info("Redirect changes host: #{current_uri.host} -> #{redir_uri.host}")
|
291
|
+
return self.class.new(call_data).call(call_data)
|
292
|
+
end
|
276
293
|
else
|
277
294
|
raise "too many redirect"
|
278
295
|
end
|
@@ -284,7 +301,6 @@ module Aspera
|
|
284
301
|
end # begin request
|
285
302
|
Log.log.debug("result=#{result}")
|
286
303
|
return result
|
287
|
-
|
288
304
|
end
|
289
305
|
|
290
306
|
#
|