aspera-cli 4.0.0.pre1
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 +7 -0
- data/README.md +3592 -0
- data/bin/ascli +7 -0
- data/bin/asession +89 -0
- data/docs/Makefile +59 -0
- data/docs/README.erb.md +3012 -0
- data/docs/README.md +13 -0
- data/docs/diagrams.txt +49 -0
- data/docs/secrets.make +38 -0
- data/docs/test_env.conf +117 -0
- data/docs/transfer_spec.html +99 -0
- data/examples/aoc.rb +17 -0
- data/examples/proxy.pac +60 -0
- data/examples/transfer.rb +115 -0
- data/lib/aspera/api_detector.rb +60 -0
- data/lib/aspera/ascmd.rb +151 -0
- data/lib/aspera/ats_api.rb +43 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
- data/lib/aspera/cli/extended_value.rb +88 -0
- data/lib/aspera/cli/formater.rb +238 -0
- data/lib/aspera/cli/listener/line_dump.rb +17 -0
- data/lib/aspera/cli/listener/logger.rb +20 -0
- data/lib/aspera/cli/listener/progress.rb +52 -0
- data/lib/aspera/cli/listener/progress_multi.rb +91 -0
- data/lib/aspera/cli/main.rb +304 -0
- data/lib/aspera/cli/manager.rb +440 -0
- data/lib/aspera/cli/plugin.rb +90 -0
- data/lib/aspera/cli/plugins/alee.rb +24 -0
- data/lib/aspera/cli/plugins/ats.rb +231 -0
- data/lib/aspera/cli/plugins/bss.rb +71 -0
- data/lib/aspera/cli/plugins/config.rb +806 -0
- data/lib/aspera/cli/plugins/console.rb +62 -0
- data/lib/aspera/cli/plugins/cos.rb +106 -0
- data/lib/aspera/cli/plugins/faspex.rb +377 -0
- data/lib/aspera/cli/plugins/faspex5.rb +93 -0
- data/lib/aspera/cli/plugins/node.rb +438 -0
- data/lib/aspera/cli/plugins/oncloud.rb +937 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
- data/lib/aspera/cli/plugins/preview.rb +464 -0
- data/lib/aspera/cli/plugins/server.rb +216 -0
- data/lib/aspera/cli/plugins/shares.rb +63 -0
- data/lib/aspera/cli/plugins/shares2.rb +114 -0
- data/lib/aspera/cli/plugins/sync.rb +65 -0
- data/lib/aspera/cli/plugins/xnode.rb +115 -0
- data/lib/aspera/cli/transfer_agent.rb +251 -0
- data/lib/aspera/cli/version.rb +5 -0
- data/lib/aspera/colors.rb +39 -0
- data/lib/aspera/command_line_builder.rb +137 -0
- data/lib/aspera/fasp/aoc.rb +24 -0
- data/lib/aspera/fasp/connect.rb +99 -0
- data/lib/aspera/fasp/error.rb +21 -0
- data/lib/aspera/fasp/error_info.rb +60 -0
- data/lib/aspera/fasp/http_gw.rb +81 -0
- data/lib/aspera/fasp/installation.rb +240 -0
- data/lib/aspera/fasp/listener.rb +11 -0
- data/lib/aspera/fasp/local.rb +377 -0
- data/lib/aspera/fasp/manager.rb +69 -0
- data/lib/aspera/fasp/node.rb +88 -0
- data/lib/aspera/fasp/parameters.rb +235 -0
- data/lib/aspera/fasp/resume_policy.rb +76 -0
- data/lib/aspera/fasp/uri.rb +51 -0
- data/lib/aspera/faspex_gw.rb +196 -0
- data/lib/aspera/hash_ext.rb +28 -0
- data/lib/aspera/log.rb +80 -0
- data/lib/aspera/nagios.rb +71 -0
- data/lib/aspera/node.rb +14 -0
- data/lib/aspera/oauth.rb +319 -0
- data/lib/aspera/on_cloud.rb +421 -0
- data/lib/aspera/open_application.rb +72 -0
- data/lib/aspera/persistency_action_once.rb +42 -0
- data/lib/aspera/persistency_folder.rb +91 -0
- data/lib/aspera/preview/file_types.rb +300 -0
- data/lib/aspera/preview/generator.rb +258 -0
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +35 -0
- data/lib/aspera/preview/utils.rb +131 -0
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.erb.js +287 -0
- data/lib/aspera/proxy_auto_config.rb +34 -0
- data/lib/aspera/rest.rb +296 -0
- data/lib/aspera/rest_call_error.rb +13 -0
- data/lib/aspera/rest_error_analyzer.rb +98 -0
- data/lib/aspera/rest_errors_aspera.rb +58 -0
- data/lib/aspera/ssh.rb +53 -0
- data/lib/aspera/sync.rb +82 -0
- data/lib/aspera/temp_file_manager.rb +37 -0
- data/lib/aspera/uri_reader.rb +25 -0
- metadata +288 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'resolv'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Aspera
|
6
|
+
# evaluate a proxy autoconfig script
|
7
|
+
class ProxyAutoConfig
|
8
|
+
# template file is read once, it contains functions that can be used in a proxy autoconf script
|
9
|
+
PAC_FUNC_TEMPLATE=File.read(__FILE__.gsub(/\.rb$/,'.erb.js'))
|
10
|
+
private_constant :PAC_FUNC_TEMPLATE
|
11
|
+
# @param proxy_auto_config the proxy auto config script to be evaluated
|
12
|
+
def initialize(proxy_auto_config)
|
13
|
+
@proxy_auto_config=proxy_auto_config
|
14
|
+
end
|
15
|
+
|
16
|
+
# execut proxy auto config script for the given URL
|
17
|
+
def get_proxy(service_url)
|
18
|
+
# require at runtime, in case there is no js engine
|
19
|
+
require 'execjs'
|
20
|
+
# variables starting with "context_" are replaced in the ERB template file
|
21
|
+
# I did not find an easy way for the javascript to callback ruby
|
22
|
+
# and anyway, it only needs to get DNS translation
|
23
|
+
context_self='127.0.0.1'
|
24
|
+
context_host=URI.parse(service_url).host
|
25
|
+
context_ip=nil
|
26
|
+
Resolv::DNS.open{|dns|dns.each_address(context_host){|r_addr|context_ip=r_addr.to_s if r_addr.is_a?(Resolv::IPv4)}}
|
27
|
+
raise "DNS name not found: #{context_host}" if context_ip.nil?
|
28
|
+
pac_functions=ERB.new(PAC_FUNC_TEMPLATE).result(binding)
|
29
|
+
context = ExecJS.compile(pac_functions+@proxy_auto_config)
|
30
|
+
return context.call("FindProxyForURL", service_url, context_host)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/lib/aspera/rest.rb
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'aspera/log'
|
2
|
+
require 'aspera/oauth'
|
3
|
+
require 'aspera/rest_error_analyzer'
|
4
|
+
require 'aspera/hash_ext'
|
5
|
+
require 'aspera/rest_errors_aspera'
|
6
|
+
require 'net/http'
|
7
|
+
require 'net/https'
|
8
|
+
require 'json'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
# add cancel method to http
|
12
|
+
class Net::HTTP::Cancel < Net::HTTPRequest
|
13
|
+
METHOD = 'CANCEL'
|
14
|
+
REQUEST_HAS_BODY = false
|
15
|
+
RESPONSE_HAS_BODY = false
|
16
|
+
end
|
17
|
+
|
18
|
+
#class Net::HTTP::Delete < Net::HTTPRequest
|
19
|
+
# METHOD = 'DELETE'
|
20
|
+
# REQUEST_HAS_BODY = false
|
21
|
+
# RESPONSE_HAS_BODY = false
|
22
|
+
#end
|
23
|
+
|
24
|
+
module Aspera
|
25
|
+
# a simple class to make HTTP calls, equivalent to rest-client
|
26
|
+
# rest call errors are raised as exception RestCallError
|
27
|
+
# and error are analyzed in RestErrorAnalyzer
|
28
|
+
class Rest
|
29
|
+
private
|
30
|
+
# create and start keep alive connection on demand
|
31
|
+
def http_session
|
32
|
+
if @http_session.nil?
|
33
|
+
uri=self.class.build_uri(@params[:base_url])
|
34
|
+
# this honors http_proxy env var
|
35
|
+
@http_session=Net::HTTP.new(uri.host, uri.port)
|
36
|
+
@http_session.use_ssl = uri.scheme.eql?('https')
|
37
|
+
Log.log.debug("insecure=#{@@insecure}")
|
38
|
+
@http_session.verify_mode = OpenSSL::SSL::VERIFY_NONE if @@insecure
|
39
|
+
@http_session.set_debug_output($stdout) if @@debug
|
40
|
+
if @params.has_key?(:session_cb)
|
41
|
+
@params[:session_cb].call(@http_session)
|
42
|
+
end
|
43
|
+
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
44
|
+
@http_session.start
|
45
|
+
end
|
46
|
+
return @http_session
|
47
|
+
end
|
48
|
+
|
49
|
+
# set to true enables debug in HTTP class
|
50
|
+
@@debug=false
|
51
|
+
# true if https ignore certificate
|
52
|
+
@@insecure=false
|
53
|
+
@@user_agent='Ruby'
|
54
|
+
|
55
|
+
public
|
56
|
+
|
57
|
+
def self.insecure=(v); @@insecure=v;Log.log.debug("insecure => #{@@insecure}".red);end
|
58
|
+
|
59
|
+
def self.insecure; @@insecure;end
|
60
|
+
|
61
|
+
def self.user_agent=(v); @@user_agent=v;Log.log.debug("user_agent => #{@@user_agent}".red);end
|
62
|
+
|
63
|
+
def self.user_agent; @@user_agent;end
|
64
|
+
|
65
|
+
def self.debug=(flag); @@debug=flag; Log.log.debug("debug http => #{flag}"); end
|
66
|
+
|
67
|
+
def self.basic_creds(user,pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}";end
|
68
|
+
|
69
|
+
attr_reader :params
|
70
|
+
|
71
|
+
# @param a_rest_params default call parameters and authentication (:auth) :
|
72
|
+
# :type (:basic, :oauth2, :url)
|
73
|
+
# :username [:basic]
|
74
|
+
# :password [:basic]
|
75
|
+
# :url_creds [:url]
|
76
|
+
# :session_cb a lambda which takes @http_session as arg, use this to change parameters
|
77
|
+
# :* [:oauth2] see Oauth class
|
78
|
+
def initialize(a_rest_params)
|
79
|
+
raise "ERROR: expecting Hash" unless a_rest_params.is_a?(Hash)
|
80
|
+
raise "ERROR: expecting base_url" unless a_rest_params[:base_url].is_a?(String)
|
81
|
+
@params=a_rest_params.clone
|
82
|
+
Log.dump('REST params',@params)
|
83
|
+
# base url without trailing slashes (note: string may be frozen)
|
84
|
+
@params[:base_url]=@params[:base_url].gsub(/\/+$/,'')
|
85
|
+
@http_session=nil
|
86
|
+
# default is no auth
|
87
|
+
@params[:auth]||={:type=>:none}
|
88
|
+
@params[:not_auth_codes]||=['401']
|
89
|
+
# translate old auth parameters, remove prefix, place in auth
|
90
|
+
[:auth,:basic,:oauth].each do |p_sym|
|
91
|
+
p_str=p_sym.to_s+'_'
|
92
|
+
@params.keys.select{|k|k.to_s.start_with?(p_str)}.each do |k_sym|
|
93
|
+
name=k_sym.to_s[p_str.length..-1]
|
94
|
+
name='grant' if k_sym.eql?(:oauth_type)
|
95
|
+
@params[:auth][name.to_sym]=@params[k_sym]
|
96
|
+
@params.delete(k_sym)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@oauth=Oauth.new(@params[:auth]) if @params[:auth][:type].eql?(:oauth2)
|
100
|
+
Log.dump('REST params(2)',@params)
|
101
|
+
end
|
102
|
+
|
103
|
+
def oauth_token(options={})
|
104
|
+
raise "ERROR: not Oauth" unless @oauth.is_a?(Oauth)
|
105
|
+
return @oauth.get_authorization(options)
|
106
|
+
end
|
107
|
+
|
108
|
+
# build URI from URL and parameters and check it is http or https
|
109
|
+
def self.build_uri(url,params=nil)
|
110
|
+
uri=URI.parse(url)
|
111
|
+
raise "REST endpoint shall be http(s)" unless ['http','https'].include?(uri.scheme)
|
112
|
+
if !params.nil?
|
113
|
+
# support array url params, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
|
114
|
+
if params.is_a?(Hash)
|
115
|
+
orig=params
|
116
|
+
params=[]
|
117
|
+
orig.each do |k,v|
|
118
|
+
case v
|
119
|
+
when Array
|
120
|
+
suffix=v.first.eql?('[]') ? v.shift : ''
|
121
|
+
v.each do |e|
|
122
|
+
params.push([k+suffix,e])
|
123
|
+
end
|
124
|
+
else
|
125
|
+
params.push([k,v])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# CGI.unescape to transform back %5D into []
|
130
|
+
uri.query=CGI.unescape(URI.encode_www_form(params))
|
131
|
+
end
|
132
|
+
return uri
|
133
|
+
end
|
134
|
+
|
135
|
+
# HTTP/S REST call
|
136
|
+
# call_data has keys:
|
137
|
+
# :auth
|
138
|
+
# :operation
|
139
|
+
# :subpath
|
140
|
+
# :headers
|
141
|
+
# :json_params
|
142
|
+
# :url_params
|
143
|
+
# :www_body_params
|
144
|
+
# :text_body_params
|
145
|
+
# :save_to_file (filepath)
|
146
|
+
# :return_error (bool)
|
147
|
+
def call(call_data)
|
148
|
+
raise "Hash call parameter is required (#{call_data.class})" unless call_data.is_a?(Hash)
|
149
|
+
Log.log.debug("accessing #{call_data[:subpath]}".red.bold.bg_green)
|
150
|
+
call_data[:headers]||={}
|
151
|
+
call_data[:headers]['User-Agent'] ||= @@user_agent
|
152
|
+
call_data=@params.deep_merge(call_data)
|
153
|
+
case call_data[:auth][:type]
|
154
|
+
when :none
|
155
|
+
# no auth
|
156
|
+
when :basic
|
157
|
+
Log.log.debug("using Basic auth")
|
158
|
+
basic_auth_data=[call_data[:auth][:username],call_data[:auth][:password]]
|
159
|
+
when :oauth2
|
160
|
+
call_data[:headers]['Authorization']=oauth_token unless call_data[:headers].has_key?('Authorization')
|
161
|
+
when :url
|
162
|
+
call_data[:url_params]||={}
|
163
|
+
call_data[:auth][:url_creds].each do |key, value|
|
164
|
+
call_data[:url_params][key]=value
|
165
|
+
end
|
166
|
+
else raise "unsupported auth type: [#{call_data[:auth][:type]}]"
|
167
|
+
end
|
168
|
+
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
169
|
+
# URI.escape()
|
170
|
+
uri=self.class.build_uri("#{@params[:base_url]}/#{call_data[:subpath]}",call_data[:url_params])
|
171
|
+
Log.log.debug("URI=#{uri}")
|
172
|
+
begin
|
173
|
+
# instanciate request object based on string name
|
174
|
+
req=Object::const_get('Net::HTTP::'+call_data[:operation].capitalize).new(uri.request_uri)
|
175
|
+
rescue NameError => e
|
176
|
+
raise "unsupported operation : #{call_data[:operation]}"
|
177
|
+
end
|
178
|
+
if call_data.has_key?(:json_params) and !call_data[:json_params].nil? then
|
179
|
+
req.body=JSON.generate(call_data[:json_params])
|
180
|
+
Log.dump('body JSON data',call_data[:json_params])
|
181
|
+
#Log.log.debug("body JSON data=#{JSON.pretty_generate(call_data[:json_params])}")
|
182
|
+
req['Content-Type'] = 'application/json'
|
183
|
+
#call_data[:headers]['Accept']='application/json'
|
184
|
+
end
|
185
|
+
if call_data.has_key?(:www_body_params) then
|
186
|
+
req.body=URI.encode_www_form(call_data[:www_body_params])
|
187
|
+
Log.log.debug("body www data=#{req.body.chomp}")
|
188
|
+
req['Content-Type'] = 'application/x-www-form-urlencoded'
|
189
|
+
end
|
190
|
+
if call_data.has_key?(:text_body_params) then
|
191
|
+
req.body=call_data[:text_body_params]
|
192
|
+
Log.log.debug("body data=#{req.body.chomp}")
|
193
|
+
end
|
194
|
+
# set headers
|
195
|
+
if call_data.has_key?(:headers) then
|
196
|
+
call_data[:headers].keys.each do |key|
|
197
|
+
req[key] = call_data[:headers][key]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
# :type = :basic
|
201
|
+
req.basic_auth(*basic_auth_data) unless basic_auth_data.nil?
|
202
|
+
|
203
|
+
Log.log.debug("call_data = #{call_data}")
|
204
|
+
result={:http=>nil}
|
205
|
+
begin
|
206
|
+
# we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
|
207
|
+
oauth_tries ||= 2
|
208
|
+
Log.log.debug("send request")
|
209
|
+
http_session.request(req) do |response|
|
210
|
+
result[:http] = response
|
211
|
+
if call_data.has_key?(:save_to_file)
|
212
|
+
require 'ruby-progressbar'
|
213
|
+
total_size=result[:http]['Content-Length'].to_i
|
214
|
+
progress=ProgressBar.create(
|
215
|
+
:format => '%a %B %p%% %r KB/sec %e',
|
216
|
+
:rate_scale => lambda{|rate|rate/1024},
|
217
|
+
:title => 'progress',
|
218
|
+
:total => total_size)
|
219
|
+
Log.log.debug("before write file")
|
220
|
+
target_file=call_data[:save_to_file]
|
221
|
+
if !response['Content-Disposition'].nil? and m=response['Content-Disposition'].match(/filename="([^"]+)"/)
|
222
|
+
target_file=m[1]
|
223
|
+
end
|
224
|
+
Log.log.debug("saving to: #{target_file}")
|
225
|
+
File.open(target_file, "wb") do |file|
|
226
|
+
result[:http].read_body do |fragment|
|
227
|
+
file.write(fragment)
|
228
|
+
new_process=progress.progress+fragment.length
|
229
|
+
new_process = total_size if new_process > total_size
|
230
|
+
progress.progress=new_process
|
231
|
+
end
|
232
|
+
end
|
233
|
+
progress=nil
|
234
|
+
end
|
235
|
+
end
|
236
|
+
# sometimes there is a ITF8 char (e.g. (c) )
|
237
|
+
result[:http].body.force_encoding("UTF-8") if result[:http].body.is_a?(String)
|
238
|
+
Log.log.debug("result: body=#{result[:http].body}")
|
239
|
+
result_mime=(result[:http]['Content-Type']||'text/plain').split(';').first
|
240
|
+
case result_mime
|
241
|
+
when 'application/json','application/vnd.api+json'
|
242
|
+
result[:data]=JSON.parse(result[:http].body) rescue nil
|
243
|
+
else #when 'text/plain'
|
244
|
+
result[:data]=result[:http].body
|
245
|
+
end
|
246
|
+
Log.dump("result: parsed: #{result_mime}",result[:data])
|
247
|
+
Log.log.debug("result: code=#{result[:http].code}")
|
248
|
+
RestErrorAnalyzer.instance.raiseOnError(req,result)
|
249
|
+
rescue RestCallError => e
|
250
|
+
# not authorized: oauth token expired
|
251
|
+
if @params[:not_auth_codes].include?(result[:http].code.to_s) and call_data[:auth][:type].eql?(:oauth2)
|
252
|
+
begin
|
253
|
+
# try to use refresh token
|
254
|
+
req['Authorization']=oauth_token(refresh: true)
|
255
|
+
rescue RestCallError => e
|
256
|
+
Log.log.error("refresh failed".bg_red)
|
257
|
+
# regenerate a brand new token
|
258
|
+
req['Authorization']=oauth_token
|
259
|
+
end
|
260
|
+
Log.log.debug("using new token=#{call_data[:headers]['Authorization']}")
|
261
|
+
retry unless (oauth_tries -= 1).zero?
|
262
|
+
end # if
|
263
|
+
# raise exception if could not retry and not return error in result
|
264
|
+
raise e unless call_data[:return_error]
|
265
|
+
end
|
266
|
+
Log.log.debug("result=#{result}")
|
267
|
+
return result
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# CRUD methods here
|
273
|
+
#
|
274
|
+
|
275
|
+
# @param encoding : one of: :json_params, :url_params
|
276
|
+
def create(subpath,params,encoding=:json_params)
|
277
|
+
return call({:operation=>'POST',:subpath=>subpath,:headers=>{'Accept'=>'application/json'},encoding=>params})
|
278
|
+
end
|
279
|
+
|
280
|
+
def read(subpath,args=nil)
|
281
|
+
return call({:operation=>'GET',:subpath=>subpath,:headers=>{'Accept'=>'application/json'},:url_params=>args})
|
282
|
+
end
|
283
|
+
|
284
|
+
def update(subpath,params)
|
285
|
+
return call({:operation=>'PUT',:subpath=>subpath,:headers=>{'Accept'=>'application/json'},:json_params=>params})
|
286
|
+
end
|
287
|
+
|
288
|
+
def delete(subpath)
|
289
|
+
return call({:operation=>'DELETE',:subpath=>subpath,:headers=>{'Accept'=>'application/json'}})
|
290
|
+
end
|
291
|
+
|
292
|
+
def cancel(subpath)
|
293
|
+
return call({:operation=>'CANCEL',:subpath=>subpath,:headers=>{'Accept'=>'application/json'}})
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end #module Aspera
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Aspera
|
2
|
+
# raised on error after REST call
|
3
|
+
class RestCallError < StandardError
|
4
|
+
attr_accessor :request
|
5
|
+
attr_accessor :response
|
6
|
+
# @param http response
|
7
|
+
def initialize(req,resp,msg)
|
8
|
+
@request = req
|
9
|
+
@response = resp
|
10
|
+
super(msg)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'aspera/log'
|
2
|
+
require 'aspera/rest_call_error'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Aspera
|
6
|
+
# analyze error codes returned by REST calls and raise ruby exception
|
7
|
+
class RestErrorAnalyzer
|
8
|
+
include Singleton
|
9
|
+
# the singleton object is registered with application specific handlers
|
10
|
+
def initialize
|
11
|
+
# list of handlers
|
12
|
+
@error_handlers=[]
|
13
|
+
self.add_handler('Type Generic') do |type,context|
|
14
|
+
if !context[:response].code.start_with?('2')
|
15
|
+
# add generic information
|
16
|
+
RestErrorAnalyzer.add_error(context,type,"#{context[:request]['host']} #{context[:response].code} #{context[:response].message}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use this method to analyze a EST result and raise an exception
|
22
|
+
# Analyzes REST call response and raises a RestCallError exception
|
23
|
+
# if HTTP result code is not 2XX
|
24
|
+
def raiseOnError(req,res)
|
25
|
+
context={
|
26
|
+
messages: [],
|
27
|
+
request: req,
|
28
|
+
response: res[:http],
|
29
|
+
data: res[:data]
|
30
|
+
}
|
31
|
+
# multiple error messages can be found
|
32
|
+
# analyze errors from provided handlers
|
33
|
+
# note that there can be an error even if code is 2XX
|
34
|
+
@error_handlers.each do |handler|
|
35
|
+
begin
|
36
|
+
#Log.log.debug("test exception: #{handler[:name]}")
|
37
|
+
handler[:block].call(handler[:name],context)
|
38
|
+
rescue => e
|
39
|
+
Log.log.error("ERROR in handler:\n#{e.message}\n#{e.backtrace}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
unless context[:messages].empty?
|
43
|
+
raise RestCallError.new(context[:request],context[:response],context[:messages].join("\n"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# add a new error handler (done at application initialisation)
|
48
|
+
# @param name : name of error handler (for logs)
|
49
|
+
# @param block : processing of response: takes two parameters: name, context
|
50
|
+
# name is the one provided here
|
51
|
+
# context is built in method raiseOnError
|
52
|
+
def add_handler(name,&block)
|
53
|
+
@error_handlers.unshift({name: name, block: block})
|
54
|
+
end
|
55
|
+
|
56
|
+
# add a simple error handler
|
57
|
+
# check that key exists and is string under specified path (hash)
|
58
|
+
# adds other keys as secondary information
|
59
|
+
def add_simple_handler(name,*args)
|
60
|
+
add_handler(name) do |type,context|
|
61
|
+
# need to clone because we modify and same array is used subsequently
|
62
|
+
path=args.clone
|
63
|
+
#Log.log.debug("path=#{path}")
|
64
|
+
# if last in path is boolean it tells if the error is only with http error code or always
|
65
|
+
always=[true, false].include?(path.last) ? path.pop : false
|
66
|
+
if context[:data].is_a?(Hash) and (!context[:response].code.start_with?('2') or always)
|
67
|
+
msg_key=path.pop
|
68
|
+
# dig and find sub entry corresponding to path in deep hash
|
69
|
+
error_struct=path.inject(context[:data]) { |subhash, key| subhash.respond_to?(:keys) ? subhash[key] : nil }
|
70
|
+
if error_struct.is_a?(Hash) and error_struct[msg_key].is_a?(String)
|
71
|
+
RestErrorAnalyzer.add_error(context,type,error_struct[msg_key])
|
72
|
+
error_struct.each do |k,v|
|
73
|
+
next if k.eql?(msg_key)
|
74
|
+
RestErrorAnalyzer.add_error(context,"#{type}(sub)","#{k}: #{v}") if [String,Integer].include?(v.class)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end # add_simple_handler
|
80
|
+
|
81
|
+
# used by handler to add an error description to list of errors
|
82
|
+
# for logging and tracing : collect error descriptions (create file to activate)
|
83
|
+
# @param context a Hash containing the result context, provided to handler
|
84
|
+
# @param type a string describing type of exception, for logging purpose
|
85
|
+
# @param msg one error message to add to list
|
86
|
+
def self.add_error(context,type,msg)
|
87
|
+
context[:messages].push(msg)
|
88
|
+
# log error for further analysis (file must exist to activate)
|
89
|
+
exc_log_file=File.join(Fasp::Installation.instance.config_folder,'exceptions.log')
|
90
|
+
if File.exist?(exc_log_file)
|
91
|
+
File.open(exc_log_file,'a+') do |f|
|
92
|
+
f.write("\n=#{type}=====\n#{context[:request].method} #{context[:request].path}\n#{context[:response].code}\n#{JSON.generate(context[:data])}\n#{context[:messages].join("\n")}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'aspera/rest_error_analyzer'
|
2
|
+
require 'aspera/log'
|
3
|
+
|
4
|
+
module Aspera
|
5
|
+
# REST error handlers for various Aspera REST APIs
|
6
|
+
class RestErrorsAspera
|
7
|
+
# handlers should probably be defined by plugins for modularity
|
8
|
+
def self.registerHandlers
|
9
|
+
Log.log.debug("registering Aspera REST error handlers")
|
10
|
+
# Faspex 4: both user_message and internal_message, and code 200
|
11
|
+
# example: missing meta data on package creation
|
12
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 1: error:user_message','error','user_message',true)
|
13
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 2: error:description','error','description')
|
14
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 3: error:internal_message','error','internal_message')
|
15
|
+
# AoC Automation
|
16
|
+
RestErrorAnalyzer.instance.add_simple_handler('AoC Automation','error')
|
17
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 5','error_description')
|
18
|
+
RestErrorAnalyzer.instance.add_simple_handler('Type 6','message')
|
19
|
+
RestErrorAnalyzer.instance.add_handler('Type 7: errors[]') do |name,context|
|
20
|
+
if context[:data].is_a?(Hash) and context[:data]['errors'].is_a?(Hash)
|
21
|
+
context[:data]['errors'].each do |k,v|
|
22
|
+
RestErrorAnalyzer.add_error(context,name,"#{k}: #{v}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
# call to upload_setup and download_setup of node api
|
27
|
+
RestErrorAnalyzer.instance.add_handler('T8:node: *_setup') do |type,context|
|
28
|
+
if context[:data].is_a?(Hash)
|
29
|
+
d_t_s=context[:data]['transfer_specs']
|
30
|
+
if d_t_s.is_a?(Array)
|
31
|
+
d_t_s.each do |res|
|
32
|
+
#r_err=res['transfer_spec']['error']
|
33
|
+
r_err=res['error']
|
34
|
+
if r_err.is_a?(Hash)
|
35
|
+
RestErrorAnalyzer.add_error(context,type,"#{r_err['code']}: #{r_err['reason']}: #{r_err['user_message']}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
RestErrorAnalyzer.instance.add_simple_handler('T9:IBM cloud IAM','errorMessage')
|
42
|
+
RestErrorAnalyzer.instance.add_simple_handler('T10:faspex v4','user_message')
|
43
|
+
RestErrorAnalyzer.instance.add_handler('bss graphql') do |type,context|
|
44
|
+
if context[:data].is_a?(Hash)
|
45
|
+
d_t_s=context[:data]['errors']
|
46
|
+
if d_t_s.is_a?(Array)
|
47
|
+
d_t_s.each do |res|
|
48
|
+
r_err=res['message']
|
49
|
+
if r_err.is_a?(String)
|
50
|
+
RestErrorAnalyzer.add_error(context,type,r_err)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end # registerErrorTypes
|
57
|
+
end
|
58
|
+
end
|