aspera-cli 4.9.0 → 4.11.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
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +20 -0
- data/CHANGELOG.md +509 -0
- data/CONTRIBUTING.md +118 -0
- data/README.md +1241 -916
- data/bin/ascli +4 -4
- data/bin/asession +11 -11
- data/docs/test_env.conf +32 -21
- data/examples/aoc.rb +4 -4
- data/examples/dascli +16 -9
- data/examples/faspex4.rb +8 -8
- data/examples/node.rb +12 -12
- data/examples/server.rb +10 -10
- data/lib/aspera/aoc.rb +273 -266
- data/lib/aspera/ascmd.rb +56 -54
- data/lib/aspera/ats_api.rb +4 -4
- data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formater.rb +64 -64
- data/lib/aspera/cli/info.rb +2 -2
- data/lib/aspera/cli/listener/line_dump.rb +1 -1
- data/lib/aspera/cli/listener/logger.rb +1 -1
- data/lib/aspera/cli/listener/progress.rb +5 -6
- data/lib/aspera/cli/listener/progress_multi.rb +14 -19
- data/lib/aspera/cli/main.rb +66 -67
- data/lib/aspera/cli/manager.rb +112 -110
- data/lib/aspera/cli/plugin.rb +57 -36
- data/lib/aspera/cli/plugins/alee.rb +4 -4
- data/lib/aspera/cli/plugins/aoc.rb +309 -670
- data/lib/aspera/cli/plugins/ats.rb +44 -46
- data/lib/aspera/cli/plugins/bss.rb +10 -10
- data/lib/aspera/cli/plugins/config.rb +497 -378
- data/lib/aspera/cli/plugins/console.rb +12 -12
- data/lib/aspera/cli/plugins/cos.rb +18 -20
- data/lib/aspera/cli/plugins/faspex.rb +112 -114
- data/lib/aspera/cli/plugins/faspex5.rb +71 -46
- data/lib/aspera/cli/plugins/node.rb +379 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +46 -46
- data/lib/aspera/cli/plugins/preview.rb +122 -114
- data/lib/aspera/cli/plugins/server.rb +137 -83
- data/lib/aspera/cli/plugins/shares.rb +30 -29
- data/lib/aspera/cli/plugins/sync.rb +13 -33
- data/lib/aspera/cli/transfer_agent.rb +60 -59
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +27 -27
- data/lib/aspera/cos_node.rb +22 -20
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +35 -15
- data/lib/aspera/fasp/agent_base.rb +15 -15
- data/lib/aspera/fasp/agent_connect.rb +23 -21
- data/lib/aspera/fasp/agent_direct.rb +66 -64
- data/lib/aspera/fasp/agent_httpgw.rb +141 -78
- data/lib/aspera/fasp/agent_node.rb +23 -21
- data/lib/aspera/fasp/agent_trsdk.rb +20 -20
- data/lib/aspera/fasp/error.rb +3 -2
- data/lib/aspera/fasp/error_info.rb +11 -8
- data/lib/aspera/fasp/installation.rb +79 -79
- data/lib/aspera/fasp/listener.rb +1 -1
- data/lib/aspera/fasp/parameters.rb +86 -71
- data/lib/aspera/fasp/parameters.yaml +7 -4
- data/lib/aspera/fasp/resume_policy.rb +8 -8
- data/lib/aspera/fasp/transfer_spec.rb +35 -2
- data/lib/aspera/fasp/uri.rb +7 -7
- data/lib/aspera/faspex_gw.rb +7 -5
- data/lib/aspera/hash_ext.rb +3 -3
- data/lib/aspera/id_generator.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +38 -105
- data/lib/aspera/keychain/macos_security.rb +128 -57
- data/lib/aspera/log.rb +7 -7
- data/lib/aspera/nagios.rb +19 -18
- data/lib/aspera/node.rb +209 -35
- data/lib/aspera/oauth.rb +37 -36
- data/lib/aspera/open_application.rb +19 -11
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +16 -15
- data/lib/aspera/preview/file_types.rb +8 -8
- data/lib/aspera/preview/generator.rb +67 -67
- data/lib/aspera/preview/utils.rb +27 -27
- data/lib/aspera/proxy_auto_config.js +41 -41
- data/lib/aspera/proxy_auto_config.rb +21 -14
- data/lib/aspera/rest.rb +72 -67
- data/lib/aspera/rest_call_error.rb +2 -1
- data/lib/aspera/rest_error_analyzer.rb +18 -17
- data/lib/aspera/rest_errors_aspera.rb +16 -16
- data/lib/aspera/secret_hider.rb +15 -13
- data/lib/aspera/ssh.rb +11 -10
- data/lib/aspera/sync.rb +158 -44
- data/lib/aspera/temp_file_manager.rb +2 -2
- data/lib/aspera/uri_reader.rb +4 -4
- data/lib/aspera/web_auth.rb +14 -13
- data.tar.gz.sig +0 -0
- metadata +11 -36
- metadata.gz.sig +0 -0
@@ -22,9 +22,9 @@ module Aspera
|
|
22
22
|
class ProxyAutoConfig
|
23
23
|
# template file is read once, it contains functions that can be used in a proxy autoconf script
|
24
24
|
# it is similar to mozilla ascii_pac_utils.inc
|
25
|
-
PAC_FUNCTIONS_FILE = __FILE__.gsub(/\.rb$/,'.js').freeze
|
25
|
+
PAC_FUNCTIONS_FILE = __FILE__.gsub(/\.rb$/, '.js').freeze
|
26
26
|
PAC_MAIN_FUNCTION = 'FindProxyForURL'
|
27
|
-
private_constant :PAC_FUNCTIONS_FILE
|
27
|
+
private_constant :PAC_FUNCTIONS_FILE, :PAC_MAIN_FUNCTION
|
28
28
|
|
29
29
|
private
|
30
30
|
|
@@ -52,13 +52,15 @@ END_OF_JAVASCRIPT
|
|
52
52
|
|
53
53
|
public
|
54
54
|
|
55
|
+
attr_writer :proxy_user, :proxy_pass
|
56
|
+
|
55
57
|
# @param proxy_auto_config the proxy auto config script to be evaluated
|
56
58
|
def initialize(proxy_auto_config)
|
57
59
|
# user provided javascript with FindProxyForURL function
|
58
60
|
@proxy_auto_config = proxy_auto_config
|
59
61
|
# avoid multiple execution, this does not support load balancing
|
60
62
|
@cache = {}
|
61
|
-
@pac_functions = nil
|
63
|
+
@proxy_user = @proxy_pass = @pac_functions = nil
|
62
64
|
end
|
63
65
|
|
64
66
|
def register_uri_generic
|
@@ -72,8 +74,8 @@ END_OF_JAVASCRIPT
|
|
72
74
|
def find_proxy_for_url(service_url)
|
73
75
|
uri = URI.parse(service_url)
|
74
76
|
simple_url = "#{uri.scheme}://#{uri.host}"
|
75
|
-
if !@cache.
|
76
|
-
Log.log.debug
|
77
|
+
if !@cache.key?(simple_url)
|
78
|
+
Log.log.debug{"PAC: starting javascript for #{service_url}"}
|
77
79
|
# require at runtime, in case there is no js engine
|
78
80
|
require 'execjs'
|
79
81
|
# read template lib
|
@@ -82,7 +84,7 @@ END_OF_JAVASCRIPT
|
|
82
84
|
js_to_execute = "#{pac_dns_functions(uri.host)}#{@pac_functions}#{@proxy_auto_config}"
|
83
85
|
executable_js = ExecJS.compile(js_to_execute)
|
84
86
|
@cache[simple_url] = executable_js.call(PAC_MAIN_FUNCTION, simple_url, uri.host)
|
85
|
-
Log.log.debug
|
87
|
+
Log.log.debug{"PAC: result: #{@cache[simple_url]}"}
|
86
88
|
end
|
87
89
|
return @cache[simple_url]
|
88
90
|
end
|
@@ -95,34 +97,39 @@ END_OF_JAVASCRIPT
|
|
95
97
|
# execute PAC script
|
96
98
|
proxy_list_str = find_proxy_for_url(service_url)
|
97
99
|
if !proxy_list_str.is_a?(String)
|
98
|
-
Log.log.warn
|
100
|
+
Log.log.warn{"PAC: did not return a String, returned #{proxy_list_str.class}"}
|
99
101
|
return uri_list
|
100
102
|
end
|
101
103
|
proxy_list_str.strip!
|
102
|
-
proxy_list_str.gsub!(/\s+/,' ')
|
104
|
+
proxy_list_str.gsub!(/\s+/, ' ')
|
103
105
|
proxy_list_str.split(';').each do |item|
|
104
106
|
# strip and split by space
|
105
107
|
parts = item.strip.split
|
106
108
|
case parts.shift
|
107
109
|
when 'DIRECT'
|
108
110
|
raise 'DIRECT has no param' unless parts.empty?
|
111
|
+
Log.log.debug('ignoring proxy DIRECT')
|
109
112
|
when 'PROXY'
|
110
113
|
addr_port = parts.shift
|
111
114
|
raise 'PROXY shall have one param' unless addr_port.is_a?(String) && parts.empty?
|
112
115
|
begin
|
113
116
|
# PAC proxy addresses are <host>:<port>
|
114
117
|
if /:[0-9]+$/.match?(addr_port)
|
115
|
-
|
116
|
-
|
118
|
+
uri = URI.parse("proxy://#{addr_port}")
|
119
|
+
# ruby v>2.6 allows
|
120
|
+
uri.user = @proxy_user
|
121
|
+
uri.password = @proxy_pass
|
122
|
+
uri_list.push(uri)
|
117
123
|
else
|
118
|
-
Log.log.warn
|
124
|
+
Log.log.warn{"PAC: PROXY must be <address>:<port>, ignoring #{addr_port}"}
|
119
125
|
end
|
120
|
-
rescue StandardError
|
121
|
-
Log.log.warn
|
126
|
+
rescue StandardError => e
|
127
|
+
Log.log.warn{"PAC: cannot parse #{addr_port} #{e}"}
|
122
128
|
end
|
123
|
-
else Log.log.warn
|
129
|
+
else Log.log.warn{"PAC: ignoring proxy type #{parts.first}: not supported"}
|
124
130
|
end
|
125
131
|
end
|
132
|
+
Log.log.debug{"Proxies: #{uri_list}"}
|
126
133
|
return uri_list
|
127
134
|
end
|
128
135
|
end
|
data/lib/aspera/rest.rb
CHANGED
@@ -13,18 +13,12 @@ require 'cgi'
|
|
13
13
|
require 'ruby-progressbar'
|
14
14
|
|
15
15
|
# add cancel method to http
|
16
|
-
class Net::HTTP::Cancel < Net::HTTPRequest
|
16
|
+
class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
|
17
17
|
METHOD = 'CANCEL'
|
18
18
|
REQUEST_HAS_BODY = false
|
19
19
|
RESPONSE_HAS_BODY = false
|
20
20
|
end
|
21
21
|
|
22
|
-
#class Net::HTTP::Delete < Net::HTTPRequest
|
23
|
-
# METHOD = 'DELETE'
|
24
|
-
# REQUEST_HAS_BODY = false
|
25
|
-
# RESPONSE_HAS_BODY = false
|
26
|
-
#end
|
27
|
-
|
28
22
|
module Aspera
|
29
23
|
# a simple class to make HTTP calls, equivalent to rest-client
|
30
24
|
# rest call errors are raised as exception RestCallError
|
@@ -37,20 +31,25 @@ module Aspera
|
|
37
31
|
user_agent: 'Ruby',
|
38
32
|
download_partial_suffix: '.http_partial',
|
39
33
|
# a lambda which takes the Net::HTTP as arg, use this to change parameters
|
40
|
-
session_cb: nil
|
34
|
+
session_cb: nil,
|
35
|
+
proxy_user: nil,
|
36
|
+
proxy_pass: nil
|
41
37
|
}
|
42
38
|
|
43
39
|
class << self
|
44
40
|
# define accessors
|
45
|
-
@@global.
|
41
|
+
@@global.each_key do |p|
|
46
42
|
define_method(p){@@global[p]}
|
47
|
-
define_method("#{p}=")
|
43
|
+
define_method("#{p}=") do |val|
|
44
|
+
Log.log.debug{"#{p} => #{val}".red}
|
45
|
+
@@global[p] = val
|
46
|
+
end
|
48
47
|
end
|
49
48
|
|
50
|
-
def basic_creds(user,pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}";end
|
49
|
+
def basic_creds(user, pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"; end
|
51
50
|
|
52
51
|
# build URI from URL and parameters and check it is http or https
|
53
|
-
def build_uri(url,params=nil)
|
52
|
+
def build_uri(url, params=nil)
|
54
53
|
uri = URI.parse(url)
|
55
54
|
raise "REST endpoint shall be http/s not #{uri.scheme}" unless %w[http https].include?(uri.scheme)
|
56
55
|
if !params.nil?
|
@@ -58,15 +57,15 @@ module Aspera
|
|
58
57
|
if params.is_a?(Hash)
|
59
58
|
orig = params
|
60
59
|
params = []
|
61
|
-
orig.each do |k,v|
|
60
|
+
orig.each do |k, v|
|
62
61
|
case v
|
63
62
|
when Array
|
64
63
|
suffix = v.first.eql?('[]') ? v.shift : ''
|
65
64
|
v.each do |e|
|
66
|
-
params.push([k + suffix,e])
|
65
|
+
params.push([k + suffix, e])
|
67
66
|
end
|
68
67
|
else
|
69
|
-
params.push([k,v])
|
68
|
+
params.push([k, v])
|
70
69
|
end
|
71
70
|
end
|
72
71
|
end
|
@@ -75,6 +74,21 @@ module Aspera
|
|
75
74
|
end
|
76
75
|
return uri
|
77
76
|
end
|
77
|
+
|
78
|
+
def start_http_session(base_url)
|
79
|
+
uri = build_uri(base_url)
|
80
|
+
# this honors http_proxy env var
|
81
|
+
http_session = Net::HTTP.new(uri.host, uri.port)
|
82
|
+
http_session.proxy_user = proxy_user
|
83
|
+
http_session.proxy_pass = proxy_pass
|
84
|
+
http_session.use_ssl = uri.scheme.eql?('https')
|
85
|
+
http_session.set_debug_output($stdout) if debug
|
86
|
+
# set http options in callback, such as timeout and cert. verification
|
87
|
+
session_cb&.call(http_session)
|
88
|
+
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
89
|
+
http_session.start
|
90
|
+
return http_session
|
91
|
+
end
|
78
92
|
end
|
79
93
|
|
80
94
|
private
|
@@ -82,15 +96,7 @@ module Aspera
|
|
82
96
|
# create and start keep alive connection on demand
|
83
97
|
def http_session
|
84
98
|
if @http_session.nil?
|
85
|
-
|
86
|
-
# this honors http_proxy env var
|
87
|
-
@http_session = Net::HTTP.new(uri.host, uri.port)
|
88
|
-
@http_session.use_ssl = uri.scheme.eql?('https')
|
89
|
-
@http_session.set_debug_output($stdout) if self.class.debug
|
90
|
-
# set http options in callback, such as timeout and cert. verification
|
91
|
-
self.class.session_cb&.call(@http_session)
|
92
|
-
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
93
|
-
@http_session.start
|
99
|
+
@http_session = self.class.start_http_session(@params[:base_url])
|
94
100
|
end
|
95
101
|
return @http_session
|
96
102
|
end
|
@@ -112,57 +118,56 @@ module Aspera
|
|
112
118
|
raise 'ERROR: expecting Hash' unless a_rest_params.is_a?(Hash)
|
113
119
|
raise 'ERROR: expecting base_url' unless a_rest_params[:base_url].is_a?(String)
|
114
120
|
@params = a_rest_params.clone
|
115
|
-
Log.dump('REST params'
|
121
|
+
Log.dump('REST params', @params)
|
116
122
|
# base url without trailing slashes (note: string may be frozen)
|
117
|
-
@params[:base_url] = @params[:base_url].gsub(
|
123
|
+
@params[:base_url] = @params[:base_url].gsub(%r{/+$}, '')
|
118
124
|
@http_session = nil
|
119
125
|
# default is no auth
|
120
126
|
@params[:auth] ||= {type: :none}
|
121
127
|
@params[:not_auth_codes] ||= ['401']
|
122
128
|
@oauth = nil
|
123
|
-
Log.dump('REST params(2)'
|
129
|
+
Log.dump('REST params(2)', @params)
|
124
130
|
end
|
125
131
|
|
126
132
|
def oauth_token(force_refresh: false)
|
127
|
-
raise "ERROR: expecting boolean, have #{force_refresh}" unless [true,false].include?(force_refresh)
|
133
|
+
raise "ERROR: expecting boolean, have #{force_refresh}" unless [true, false].include?(force_refresh)
|
128
134
|
return oauth.get_authorization(use_refresh_token: force_refresh)
|
129
135
|
end
|
130
136
|
|
131
137
|
def build_request(call_data)
|
132
138
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
133
139
|
# URI.escape()
|
134
|
-
uri = self.class.build_uri("#{call_data[:base_url]}#{['','/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}",call_data[:url_params])
|
135
|
-
Log.log.debug
|
140
|
+
uri = self.class.build_uri("#{call_data[:base_url]}#{['', '/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}", call_data[:url_params])
|
141
|
+
Log.log.debug{"URI=#{uri}"}
|
136
142
|
begin
|
137
143
|
# instanciate request object based on string name
|
138
144
|
req = Net::HTTP.const_get(call_data[:operation].capitalize).new(uri)
|
139
145
|
rescue NameError
|
140
146
|
raise "unsupported operation : #{call_data[:operation]}"
|
141
147
|
end
|
142
|
-
if call_data.
|
148
|
+
if call_data.key?(:json_params) && !call_data[:json_params].nil?
|
143
149
|
req.body = JSON.generate(call_data[:json_params])
|
144
|
-
Log.dump('body JSON data',call_data[:json_params])
|
145
|
-
#Log.log.debug("body JSON data=#{JSON.pretty_generate(call_data[:json_params])}")
|
150
|
+
Log.dump('body JSON data', call_data[:json_params])
|
146
151
|
req['Content-Type'] = 'application/json'
|
147
|
-
#call_data[:headers]['Accept']='application/json'
|
152
|
+
# call_data[:headers]['Accept']='application/json'
|
148
153
|
end
|
149
|
-
if call_data.
|
154
|
+
if call_data.key?(:www_body_params)
|
150
155
|
req.body = URI.encode_www_form(call_data[:www_body_params])
|
151
|
-
Log.log.debug
|
156
|
+
Log.log.debug{"body www data=#{req.body.chomp}"}
|
152
157
|
req['Content-Type'] = 'application/x-www-form-urlencoded'
|
153
158
|
end
|
154
|
-
if call_data.
|
159
|
+
if call_data.key?(:text_body_params)
|
155
160
|
req.body = call_data[:text_body_params]
|
156
|
-
Log.log.debug
|
161
|
+
Log.log.debug{"body data=#{req.body.chomp}"}
|
157
162
|
end
|
158
163
|
# set headers
|
159
|
-
if call_data.
|
160
|
-
call_data[:headers].
|
164
|
+
if call_data.key?(:headers)
|
165
|
+
call_data[:headers].each_key do |key|
|
161
166
|
req[key] = call_data[:headers][key]
|
162
167
|
end
|
163
168
|
end
|
164
169
|
# :type = :basic
|
165
|
-
req.basic_auth(call_data[:auth][:username],call_data[:auth][:password]) if call_data[:auth][:type].eql?(:basic)
|
170
|
+
req.basic_auth(call_data[:auth][:username], call_data[:auth][:password]) if call_data[:auth][:type].eql?(:basic)
|
166
171
|
return req
|
167
172
|
end
|
168
173
|
|
@@ -190,7 +195,7 @@ module Aspera
|
|
190
195
|
def call(call_data)
|
191
196
|
raise "Hash call parameter is required (#{call_data.class})" unless call_data.is_a?(Hash)
|
192
197
|
call_data[:subpath] = '' if call_data[:subpath].nil?
|
193
|
-
Log.log.debug
|
198
|
+
Log.log.debug{"accessing #{call_data[:subpath]}".red.bold.bg_green}
|
194
199
|
call_data[:headers] ||= {}
|
195
200
|
call_data[:headers]['User-Agent'] ||= self.class.user_agent
|
196
201
|
# defaults from @params are overriden by call data
|
@@ -202,7 +207,7 @@ module Aspera
|
|
202
207
|
Log.log.debug('using Basic auth')
|
203
208
|
# done in build_req
|
204
209
|
when :oauth2
|
205
|
-
call_data[:headers]['Authorization'] = oauth_token unless call_data[:headers].
|
210
|
+
call_data[:headers]['Authorization'] = oauth_token unless call_data[:headers].key?('Authorization')
|
206
211
|
when :url
|
207
212
|
call_data[:url_params] ||= {}
|
208
213
|
call_data[:auth][:url_creds].each do |key, value|
|
@@ -211,7 +216,7 @@ module Aspera
|
|
211
216
|
else raise "unsupported auth type: [#{call_data[:auth][:type]}]"
|
212
217
|
end
|
213
218
|
req = build_request(call_data)
|
214
|
-
Log.log.debug
|
219
|
+
Log.log.debug{"call_data = #{call_data}"}
|
215
220
|
result = {http: nil}
|
216
221
|
# start a block to be able to retry the actual HTTP request
|
217
222
|
begin
|
@@ -233,11 +238,11 @@ module Aspera
|
|
233
238
|
target_file = call_data[:save_to_file]
|
234
239
|
# override user's path to path in header
|
235
240
|
if !response['Content-Disposition'].nil? && (m = response['Content-Disposition'].match(/filename="([^"]+)"/))
|
236
|
-
target_file = File.join(File.dirname(target_file),m[1])
|
241
|
+
target_file = File.join(File.dirname(target_file), m[1])
|
237
242
|
end
|
238
243
|
# download with temp filename
|
239
244
|
target_file_tmp = "#{target_file}#{self.class.download_partial_suffix}"
|
240
|
-
Log.log.debug
|
245
|
+
Log.log.debug{"saving to: #{target_file}"}
|
241
246
|
File.open(target_file_tmp, 'wb') do |file|
|
242
247
|
result[:http].read_body do |fragment|
|
243
248
|
file.write(fragment)
|
@@ -253,17 +258,17 @@ module Aspera
|
|
253
258
|
end
|
254
259
|
# sometimes there is a UTF8 char (e.g. (c) )
|
255
260
|
result[:http].body.force_encoding('UTF-8') if result[:http].body.is_a?(String)
|
256
|
-
Log.log.debug
|
261
|
+
Log.log.debug{"result: body=#{result[:http].body}"}
|
257
262
|
result_mime = (result[:http]['Content-Type'] || 'text/plain').split(';').first
|
258
263
|
result[:data] = case result_mime
|
259
|
-
when 'application/json','application/vnd.api+json'
|
264
|
+
when 'application/json', 'application/vnd.api+json'
|
260
265
|
JSON.parse(result[:http].body) rescue nil
|
261
|
-
else #when 'text/plain'
|
266
|
+
else # when 'text/plain'
|
262
267
|
result[:http].body
|
263
268
|
end
|
264
|
-
Log.dump("result: parsed: #{result_mime}",result[:data])
|
265
|
-
Log.log.debug
|
266
|
-
RestErrorAnalyzer.instance.raise_on_error(req,result)
|
269
|
+
Log.dump("result: parsed: #{result_mime}", result[:data])
|
270
|
+
Log.log.debug{"result: code=#{result[:http].code}"}
|
271
|
+
RestErrorAnalyzer.instance.raise_on_error(req, result)
|
267
272
|
rescue RestCallError => e
|
268
273
|
# not authorized: oauth token expired
|
269
274
|
if call_data[:not_auth_codes].include?(result[:http].code.to_s) && call_data[:auth][:type].eql?(:oauth2)
|
@@ -276,16 +281,16 @@ module Aspera
|
|
276
281
|
# regenerate a brand new token
|
277
282
|
req['Authorization'] = oauth_token(use_cache: false)
|
278
283
|
end
|
279
|
-
Log.log.debug
|
284
|
+
Log.log.debug{"using new token=#{call_data[:headers]['Authorization']}"}
|
280
285
|
retry unless (oauth_tries -= 1).zero?
|
281
286
|
end # if oauth
|
282
287
|
# moved ?
|
283
288
|
if e.response.is_a?(Net::HTTPRedirection) && tries_remain_redirect.positive?
|
284
289
|
tries_remain_redirect -= 1
|
285
290
|
current_uri = URI.parse(call_data[:base_url])
|
286
|
-
new_url=e.response['location']
|
287
|
-
new_url="#{current_uri.scheme}:#{new_url}" unless new_url.start_with?('http')
|
288
|
-
Log.log.info
|
291
|
+
new_url = e.response['location']
|
292
|
+
new_url = "#{current_uri.scheme}:#{new_url}" unless new_url.start_with?('http')
|
293
|
+
Log.log.info{"URL is moved: #{new_url}"}
|
289
294
|
redir_uri = URI.parse(new_url)
|
290
295
|
call_data[:base_url] = new_url
|
291
296
|
call_data[:subpath] = ''
|
@@ -294,14 +299,14 @@ module Aspera
|
|
294
299
|
retry
|
295
300
|
else
|
296
301
|
# change host
|
297
|
-
Log.log.info
|
302
|
+
Log.log.info{"Redirect changes host: #{current_uri.host} -> #{redir_uri.host}"}
|
298
303
|
return self.class.new(call_data).call(call_data)
|
299
304
|
end
|
300
305
|
end
|
301
306
|
# raise exception if could not retry and not return error in result
|
302
307
|
raise e unless call_data[:return_error]
|
303
308
|
end # begin request
|
304
|
-
Log.log.debug
|
309
|
+
Log.log.debug{"result=#{result}"}
|
305
310
|
return result
|
306
311
|
end
|
307
312
|
|
@@ -310,24 +315,24 @@ module Aspera
|
|
310
315
|
#
|
311
316
|
|
312
317
|
# @param encoding : one of: :json_params, :url_params
|
313
|
-
def create(subpath,params,encoding=:json_params)
|
314
|
-
return call({operation: 'POST',subpath: subpath,headers: {'Accept' => 'application/json'},encoding => params})
|
318
|
+
def create(subpath, params, encoding=:json_params)
|
319
|
+
return call({operation: 'POST', subpath: subpath, headers: {'Accept' => 'application/json'}, encoding => params})
|
315
320
|
end
|
316
321
|
|
317
|
-
def read(subpath,args=nil)
|
318
|
-
return call({operation: 'GET',subpath: subpath,headers: {'Accept' => 'application/json'},url_params: args})
|
322
|
+
def read(subpath, args=nil)
|
323
|
+
return call({operation: 'GET', subpath: subpath, headers: {'Accept' => 'application/json'}, url_params: args})
|
319
324
|
end
|
320
325
|
|
321
|
-
def update(subpath,params)
|
322
|
-
return call({operation: 'PUT',subpath: subpath,headers: {'Accept' => 'application/json'},json_params: params})
|
326
|
+
def update(subpath, params)
|
327
|
+
return call({operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, json_params: params})
|
323
328
|
end
|
324
329
|
|
325
330
|
def delete(subpath)
|
326
|
-
return call({operation: 'DELETE',subpath: subpath,headers: {'Accept' => 'application/json'}})
|
331
|
+
return call({operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}})
|
327
332
|
end
|
328
333
|
|
329
334
|
def cancel(subpath)
|
330
|
-
return call({operation: 'CANCEL',subpath: subpath,headers: {'Accept' => 'application/json'}})
|
335
|
+
return call({operation: 'CANCEL', subpath: subpath, headers: {'Accept' => 'application/json'}})
|
331
336
|
end
|
332
337
|
end
|
333
|
-
end #module Aspera
|
338
|
+
end # module Aspera
|
@@ -4,8 +4,9 @@ module Aspera
|
|
4
4
|
# raised on error after REST call
|
5
5
|
class RestCallError < StandardError
|
6
6
|
attr_accessor :request, :response
|
7
|
+
|
7
8
|
# @param http response
|
8
|
-
def initialize(req,resp,msg)
|
9
|
+
def initialize(req, resp, msg)
|
9
10
|
@request = req
|
10
11
|
@response = resp
|
11
12
|
super(msg)
|
@@ -9,15 +9,16 @@ module Aspera
|
|
9
9
|
class RestErrorAnalyzer
|
10
10
|
include Singleton
|
11
11
|
attr_accessor :log_file
|
12
|
+
|
12
13
|
# the singleton object is registered with application specific handlers
|
13
14
|
def initialize
|
14
15
|
# list of handlers
|
15
16
|
@error_handlers = []
|
16
17
|
@log_file = nil
|
17
|
-
add_handler('Type Generic') do |type,call_context|
|
18
|
+
add_handler('Type Generic') do |type, call_context|
|
18
19
|
if !call_context[:response].code.start_with?('2')
|
19
20
|
# add generic information
|
20
|
-
RestErrorAnalyzer.add_error(call_context,type,"#{call_context[:request]['host']} #{call_context[:response].code} #{call_context[:response].message}")
|
21
|
+
RestErrorAnalyzer.add_error(call_context, type, "#{call_context[:request]['host']} #{call_context[:response].code} #{call_context[:response].message}")
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -25,7 +26,7 @@ module Aspera
|
|
25
26
|
# Use this method to analyze a EST result and raise an exception
|
26
27
|
# Analyzes REST call response and raises a RestCallError exception
|
27
28
|
# if HTTP result code is not 2XX
|
28
|
-
def raise_on_error(req,res)
|
29
|
+
def raise_on_error(req, res)
|
29
30
|
call_context = {
|
30
31
|
messages: [],
|
31
32
|
request: req,
|
@@ -36,14 +37,14 @@ module Aspera
|
|
36
37
|
# analyze errors from provided handlers
|
37
38
|
# note that there can be an error even if code is 2XX
|
38
39
|
@error_handlers.each do |handler|
|
39
|
-
begin
|
40
|
-
#Log.log.debug
|
41
|
-
handler[:block].call(handler[:name],call_context)
|
40
|
+
begin # rubocop:disable Style/RedundantBegin
|
41
|
+
# Log.log.debug{"test exception: #{handler[:name]}"}
|
42
|
+
handler[:block].call(handler[:name], call_context)
|
42
43
|
rescue StandardError => e
|
43
|
-
Log.log.error
|
44
|
+
Log.log.error{"ERROR in handler:\n#{e.message}\n#{e.backtrace}"}
|
44
45
|
end
|
45
46
|
end
|
46
|
-
raise RestCallError.new(call_context[:request],call_context[:response],call_context[:messages].join("\n")) unless call_context[:messages].empty?
|
47
|
+
raise RestCallError.new(call_context[:request], call_context[:response], call_context[:messages].join("\n")) unless call_context[:messages].empty?
|
47
48
|
end
|
48
49
|
|
49
50
|
# add a new error handler (done at application initialisation)
|
@@ -51,18 +52,18 @@ module Aspera
|
|
51
52
|
# @param block : processing of response: takes two parameters: name, call_context
|
52
53
|
# name is the one provided here
|
53
54
|
# call_context is built in method raise_on_error
|
54
|
-
def add_handler(name
|
55
|
+
def add_handler(name, &block)
|
55
56
|
@error_handlers.unshift({name: name, block: block})
|
56
57
|
end
|
57
58
|
|
58
59
|
# add a simple error handler
|
59
60
|
# check that key exists and is string under specified path (hash)
|
60
61
|
# adds other keys as secondary information
|
61
|
-
def add_simple_handler(name
|
62
|
-
add_handler(name) do |type,call_context|
|
62
|
+
def add_simple_handler(name, *args)
|
63
|
+
add_handler(name) do |type, call_context|
|
63
64
|
# need to clone because we modify and same array is used subsequently
|
64
65
|
path = args.clone
|
65
|
-
#Log.log.debug
|
66
|
+
# Log.log.debug{"path=#{path}"}
|
66
67
|
# if last in path is boolean it tells if the error is only with http error code or always
|
67
68
|
always = [true, false].include?(path.last) ? path.pop : false
|
68
69
|
if call_context[:data].is_a?(Hash) && (!call_context[:response].code.start_with?('2') || always)
|
@@ -70,10 +71,10 @@ module Aspera
|
|
70
71
|
# dig and find sub entry corresponding to path in deep hash
|
71
72
|
error_struct = path.inject(call_context[:data]) { |subhash, key| subhash.respond_to?(:keys) ? subhash[key] : nil }
|
72
73
|
if error_struct.is_a?(Hash) && error_struct[msg_key].is_a?(String)
|
73
|
-
RestErrorAnalyzer.add_error(call_context,type,error_struct[msg_key])
|
74
|
-
error_struct.each do |k,v|
|
74
|
+
RestErrorAnalyzer.add_error(call_context, type, error_struct[msg_key])
|
75
|
+
error_struct.each do |k, v|
|
75
76
|
next if k.eql?(msg_key)
|
76
|
-
RestErrorAnalyzer.add_error(call_context,"#{type}(sub)","#{k}: #{v}") if [String,Integer].include?(v.class)
|
77
|
+
RestErrorAnalyzer.add_error(call_context, "#{type}(sub)", "#{k}: #{v}") if [String, Integer].include?(v.class)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
end
|
@@ -86,12 +87,12 @@ module Aspera
|
|
86
87
|
# @param call_context a Hash containing the result call_context, provided to handler
|
87
88
|
# @param type a string describing type of exception, for logging purpose
|
88
89
|
# @param msg one error message to add to list
|
89
|
-
def add_error(call_context,type,msg)
|
90
|
+
def add_error(call_context, type, msg)
|
90
91
|
call_context[:messages].push(msg)
|
91
92
|
logfile = instance.log_file
|
92
93
|
# log error for further analysis (file must exist to activate)
|
93
94
|
return if logfile.nil? || !File.exist?(logfile)
|
94
|
-
File.open(logfile,'a+') do |f|
|
95
|
+
File.open(logfile, 'a+') do |f|
|
95
96
|
f.write("\n=#{type}=====\n#{call_context[:request].method} #{call_context[:request].path}\n#{call_context[:response].code}\n"\
|
96
97
|
"#{JSON.generate(call_context[:data])}\n#{call_context[:messages].join("\n")}")
|
97
98
|
end
|
@@ -12,45 +12,45 @@ module Aspera
|
|
12
12
|
Log.log.debug('registering Aspera REST error handlers')
|
13
13
|
# Faspex 4: both user_message and internal_message, and code 200
|
14
14
|
# example: missing meta data on package creation
|
15
|
-
RestErrorAnalyzer.instance.add_simple_handler('Type 1: error:user_message','error','user_message',true)
|
16
|
-
RestErrorAnalyzer.instance.add_simple_handler('Type 2: error:description','error','description')
|
17
|
-
RestErrorAnalyzer.instance.add_simple_handler('Type 3: error:internal_message','error','internal_message')
|
15
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 1: error:user_message', 'error', 'user_message', true)
|
16
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 2: error:description', 'error', 'description')
|
17
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 3: error:internal_message', 'error', 'internal_message')
|
18
18
|
# AoC Automation
|
19
|
-
RestErrorAnalyzer.instance.add_simple_handler('AoC Automation','error')
|
20
|
-
RestErrorAnalyzer.instance.add_simple_handler('Type 5','error_description')
|
21
|
-
RestErrorAnalyzer.instance.add_simple_handler('Type 6','message')
|
22
|
-
RestErrorAnalyzer.instance.add_handler('Type 7: errors[]') do |name,call_context|
|
19
|
+
RestErrorAnalyzer.instance.add_simple_handler('AoC Automation', 'error')
|
20
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 5', 'error_description')
|
21
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 6', 'message')
|
22
|
+
RestErrorAnalyzer.instance.add_handler('Type 7: errors[]') do |name, call_context|
|
23
23
|
if call_context[:data].is_a?(Hash) && call_context[:data]['errors'].is_a?(Hash)
|
24
|
-
call_context[:data]['errors'].each do |k,v|
|
25
|
-
RestErrorAnalyzer.add_error(call_context,name,"#{k}: #{v}")
|
24
|
+
call_context[:data]['errors'].each do |k, v|
|
25
|
+
RestErrorAnalyzer.add_error(call_context, name, "#{k}: #{v}")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
29
|
# call to upload_setup and download_setup of node api
|
30
|
-
RestErrorAnalyzer.instance.add_handler('T8:node: *_setup') do |type,call_context|
|
30
|
+
RestErrorAnalyzer.instance.add_handler('T8:node: *_setup') do |type, call_context|
|
31
31
|
if call_context[:data].is_a?(Hash)
|
32
32
|
d_t_s = call_context[:data]['transfer_specs']
|
33
33
|
if d_t_s.is_a?(Array)
|
34
34
|
d_t_s.each do |res|
|
35
|
-
#r_err=res['transfer_spec']['error']
|
35
|
+
# r_err=res['transfer_spec']['error']
|
36
36
|
r_err = res['error']
|
37
37
|
if r_err.is_a?(Hash)
|
38
|
-
RestErrorAnalyzer.add_error(call_context,type,"#{r_err['code']}: #{r_err['reason']}: #{r_err['user_message']}")
|
38
|
+
RestErrorAnalyzer.add_error(call_context, type, "#{r_err['code']}: #{r_err['reason']}: #{r_err['user_message']}")
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
|
-
RestErrorAnalyzer.instance.add_simple_handler('T9:IBM cloud IAM','errorMessage')
|
45
|
-
RestErrorAnalyzer.instance.add_simple_handler('T10:faspex v4','user_message')
|
46
|
-
RestErrorAnalyzer.instance.add_handler('bss graphql') do |type,call_context|
|
44
|
+
RestErrorAnalyzer.instance.add_simple_handler('T9:IBM cloud IAM', 'errorMessage')
|
45
|
+
RestErrorAnalyzer.instance.add_simple_handler('T10:faspex v4', 'user_message')
|
46
|
+
RestErrorAnalyzer.instance.add_handler('bss graphql') do |type, call_context|
|
47
47
|
if call_context[:data].is_a?(Hash)
|
48
48
|
d_t_s = call_context[:data]['errors']
|
49
49
|
if d_t_s.is_a?(Array)
|
50
50
|
d_t_s.each do |res|
|
51
51
|
r_err = res['message']
|
52
52
|
if r_err.is_a?(String)
|
53
|
-
RestErrorAnalyzer.add_error(call_context,type,r_err)
|
53
|
+
RestErrorAnalyzer.add_error(call_context, type, r_err)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
data/lib/aspera/secret_hider.rb
CHANGED
@@ -7,16 +7,17 @@ module Aspera
|
|
7
7
|
class SecretHider
|
8
8
|
# display string for hidden secrets
|
9
9
|
HIDDEN_PASSWORD = '🔑'
|
10
|
+
# env vars for ascp with secrets
|
11
|
+
ASCP_ENV_SECRETS = %w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS ASPERA_SCP_TOKEN].freeze
|
10
12
|
# keys in hash that contain secrets
|
11
|
-
|
12
|
-
|
13
|
-
ALL_SECRETS =[ASCP_SECRETS,KEY_SECRETS].flatten.freeze
|
13
|
+
KEY_SECRETS = %w[password secret private_key passphrase].freeze
|
14
|
+
ALL_SECRETS = [].concat(ASCP_ENV_SECRETS, KEY_SECRETS).freeze
|
14
15
|
# regex that define namec captures :begin and :end
|
15
|
-
REGEX_LOG_REPLACES=[
|
16
|
+
REGEX_LOG_REPLACES = [
|
16
17
|
# CLI manager get/set options
|
17
18
|
/(?<begin>[sg]et (#{KEY_SECRETS.join('|')})=).*(?<end>)/,
|
18
19
|
# env var ascp exec
|
19
|
-
/(?<begin> (#{
|
20
|
+
/(?<begin> (#{ASCP_ENV_SECRETS.join('|')})=)(\\.|[^ ])*(?<end> )/,
|
20
21
|
# rendered JSON
|
21
22
|
/(?<begin>["':][^"]*(#{ALL_SECRETS.join('|')})[^"]*["']?[=>: ]+")[^"]+(?<end>")/,
|
22
23
|
# option "secret"
|
@@ -26,13 +27,14 @@ module Aspera
|
|
26
27
|
# private key values
|
27
28
|
/(?<begin>--+BEGIN .+ KEY--+)[[:ascii:]]+?(?<end>--+?END .+ KEY--+)/
|
28
29
|
].freeze
|
29
|
-
private_constant :HIDDEN_PASSWORD
|
30
|
+
private_constant :HIDDEN_PASSWORD, :ASCP_ENV_SECRETS, :KEY_SECRETS, :ALL_SECRETS, :REGEX_LOG_REPLACES
|
30
31
|
@log_secrets = false
|
31
32
|
class << self
|
32
33
|
attr_accessor :log_secrets
|
34
|
+
|
33
35
|
def log_formatter(original_formatter)
|
34
36
|
original_formatter ||= Logger::Formatter.new
|
35
|
-
#
|
37
|
+
# NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
|
36
38
|
return lambda do |severity, datetime, progname, msg|
|
37
39
|
if msg.is_a?(String) && !@log_secrets
|
38
40
|
REGEX_LOG_REPLACES.each do |regx|
|
@@ -43,25 +45,25 @@ module Aspera
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
def secret?(keyword,value)
|
47
|
-
keyword=keyword.to_s if keyword.is_a?(Symbol)
|
48
|
+
def secret?(keyword, value)
|
49
|
+
keyword = keyword.to_s if keyword.is_a?(Symbol)
|
48
50
|
# only Strings can be secrets, not booleans, or hash, arrays
|
49
51
|
keyword.is_a?(String) && ALL_SECRETS.any?{|kw|keyword.include?(kw)} && value.is_a?(String)
|
50
52
|
end
|
51
53
|
|
52
|
-
def deep_remove_secret(obj,is_name_value: false)
|
54
|
+
def deep_remove_secret(obj, is_name_value: false)
|
53
55
|
case obj
|
54
56
|
when Array
|
55
57
|
if is_name_value
|
56
58
|
obj.each do |i|
|
57
|
-
i['value']=HIDDEN_PASSWORD if secret?(i['parameter'],i['value'])
|
59
|
+
i['value'] = HIDDEN_PASSWORD if secret?(i['parameter'], i['value'])
|
58
60
|
end
|
59
61
|
else
|
60
62
|
obj.each{|i|deep_remove_secret(i)}
|
61
63
|
end
|
62
64
|
when Hash
|
63
|
-
obj.each do |k,v|
|
64
|
-
if secret?(k,v)
|
65
|
+
obj.each do |k, v|
|
66
|
+
if secret?(k, v)
|
65
67
|
obj[k] = HIDDEN_PASSWORD
|
66
68
|
elsif obj[k].is_a?(Hash)
|
67
69
|
deep_remove_secret(obj[k])
|