aspera-cli 4.5.0 → 4.8.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 +1 -0
- data/README.md +1894 -1574
- data/bin/ascli +21 -1
- data/bin/asession +38 -34
- data/docs/test_env.conf +14 -3
- data/examples/aoc.rb +17 -15
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +42 -35
- data/examples/proxy.pac +1 -1
- data/examples/transfer.rb +38 -37
- data/lib/aspera/aoc.rb +245 -205
- data/lib/aspera/ascmd.rb +111 -90
- data/lib/aspera/ats_api.rb +16 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +19 -18
- data/lib/aspera/cli/extended_value.rb +50 -39
- data/lib/aspera/cli/formater.rb +161 -135
- data/lib/aspera/cli/info.rb +18 -0
- data/lib/aspera/cli/listener/line_dump.rb +4 -2
- data/lib/aspera/cli/listener/logger.rb +3 -1
- data/lib/aspera/cli/listener/progress.rb +20 -21
- data/lib/aspera/cli/listener/progress_multi.rb +29 -31
- data/lib/aspera/cli/main.rb +194 -183
- data/lib/aspera/cli/manager.rb +213 -206
- data/lib/aspera/cli/plugin.rb +71 -49
- data/lib/aspera/cli/plugins/alee.rb +8 -7
- data/lib/aspera/cli/plugins/aoc.rb +675 -558
- data/lib/aspera/cli/plugins/ats.rb +116 -109
- data/lib/aspera/cli/plugins/bss.rb +35 -34
- data/lib/aspera/cli/plugins/config.rb +722 -542
- data/lib/aspera/cli/plugins/console.rb +28 -22
- data/lib/aspera/cli/plugins/cos.rb +28 -37
- data/lib/aspera/cli/plugins/faspex.rb +281 -227
- data/lib/aspera/cli/plugins/faspex5.rb +129 -84
- data/lib/aspera/cli/plugins/node.rb +426 -232
- data/lib/aspera/cli/plugins/orchestrator.rb +106 -98
- data/lib/aspera/cli/plugins/preview.rb +196 -191
- data/lib/aspera/cli/plugins/server.rb +131 -126
- data/lib/aspera/cli/plugins/shares.rb +49 -36
- data/lib/aspera/cli/plugins/sync.rb +27 -28
- data/lib/aspera/cli/transfer_agent.rb +84 -79
- data/lib/aspera/cli/version.rb +3 -1
- data/lib/aspera/colors.rb +37 -28
- data/lib/aspera/command_line_builder.rb +84 -63
- data/lib/aspera/cos_node.rb +68 -34
- data/lib/aspera/data_repository.rb +4 -2
- data/lib/aspera/environment.rb +61 -46
- data/lib/aspera/fasp/agent_base.rb +36 -31
- data/lib/aspera/fasp/agent_connect.rb +44 -37
- data/lib/aspera/fasp/agent_direct.rb +101 -104
- data/lib/aspera/fasp/agent_httpgw.rb +91 -90
- data/lib/aspera/fasp/agent_node.rb +36 -33
- data/lib/aspera/fasp/agent_trsdk.rb +28 -31
- data/lib/aspera/fasp/error.rb +3 -1
- data/lib/aspera/fasp/error_info.rb +81 -54
- data/lib/aspera/fasp/installation.rb +171 -151
- data/lib/aspera/fasp/listener.rb +2 -0
- data/lib/aspera/fasp/parameters.rb +105 -111
- data/lib/aspera/fasp/parameters.yaml +305 -249
- data/lib/aspera/fasp/resume_policy.rb +20 -20
- data/lib/aspera/fasp/transfer_spec.rb +27 -0
- data/lib/aspera/fasp/uri.rb +31 -29
- data/lib/aspera/faspex_gw.rb +95 -118
- data/lib/aspera/hash_ext.rb +12 -13
- data/lib/aspera/id_generator.rb +11 -9
- data/lib/aspera/keychain/encrypted_hash.rb +73 -57
- data/lib/aspera/keychain/macos_security.rb +27 -29
- data/lib/aspera/log.rb +40 -39
- data/lib/aspera/nagios.rb +24 -22
- data/lib/aspera/node.rb +38 -30
- data/lib/aspera/oauth.rb +217 -248
- data/lib/aspera/open_application.rb +9 -7
- data/lib/aspera/persistency_action_once.rb +15 -14
- data/lib/aspera/persistency_folder.rb +15 -18
- data/lib/aspera/preview/file_types.rb +266 -270
- data/lib/aspera/preview/generator.rb +94 -92
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +20 -17
- data/lib/aspera/preview/utils.rb +99 -102
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
- data/lib/aspera/proxy_auto_config.rb +114 -21
- data/lib/aspera/rest.rb +144 -142
- data/lib/aspera/rest_call_error.rb +3 -2
- data/lib/aspera/rest_error_analyzer.rb +31 -31
- data/lib/aspera/rest_errors_aspera.rb +18 -16
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +20 -16
- data/lib/aspera/sync.rb +57 -54
- data/lib/aspera/temp_file_manager.rb +20 -14
- data/lib/aspera/timer_limiter.rb +10 -8
- data/lib/aspera/uri_reader.rb +14 -15
- data/lib/aspera/web_auth.rb +85 -80
- data.tar.gz.sig +0 -0
- metadata +169 -40
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
- data/docs/Makefile +0 -63
- data/docs/README.erb.md +0 -4221
- data/docs/README.md +0 -13
- data/docs/diagrams.txt +0 -49
- data/docs/doc_tools.rb +0 -58
- data/lib/aspera/cli/plugins/shares2.rb +0 -114
- data/lib/aspera/fasp/default.rb +0 -17
data/lib/aspera/rest.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/log'
|
2
4
|
require 'aspera/oauth'
|
3
5
|
require 'aspera/rest_error_analyzer'
|
@@ -28,58 +30,51 @@ module Aspera
|
|
28
30
|
# rest call errors are raised as exception RestCallError
|
29
31
|
# and error are analyzed in RestErrorAnalyzer
|
30
32
|
class Rest
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
33
|
+
# global settings also valid for any subclass
|
34
|
+
@@global = { # rubocop:disable Style/ClassVars
|
35
|
+
debug: false,
|
36
|
+
# true if https ignore certificate
|
37
|
+
user_agent: 'Ruby',
|
38
|
+
download_partial_suffix: '.http_partial',
|
39
|
+
# a lambda which takes the Net::HTTP as arg, use this to change parameters
|
40
|
+
session_cb: nil
|
41
|
+
}
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
def self.user_agent; @@user_agent;end
|
53
|
-
|
54
|
-
def self.debug=(flag); @@debug=flag; Log.log.debug("debug http => #{flag}"); end
|
43
|
+
class << self
|
44
|
+
# define accessors
|
45
|
+
@@global.keys.each do |p|
|
46
|
+
define_method(p){@@global[p]}
|
47
|
+
define_method("#{p}="){|val|Log.log.debug("#{p} => #{val}".red);@@global[p] = val}
|
48
|
+
end
|
55
49
|
|
56
|
-
|
50
|
+
def basic_creds(user,pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}";end
|
57
51
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
52
|
+
# build URI from URL and parameters and check it is http or https
|
53
|
+
def build_uri(url,params=nil)
|
54
|
+
uri = URI.parse(url)
|
55
|
+
raise "REST endpoint shall be http/s not #{uri.scheme}" unless %w[http https].include?(uri.scheme)
|
56
|
+
if !params.nil?
|
57
|
+
# support array url params, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
|
58
|
+
if params.is_a?(Hash)
|
59
|
+
orig = params
|
60
|
+
params = []
|
61
|
+
orig.each do |k,v|
|
62
|
+
case v
|
63
|
+
when Array
|
64
|
+
suffix = v.first.eql?('[]') ? v.shift : ''
|
65
|
+
v.each do |e|
|
66
|
+
params.push([k + suffix,e])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
params.push([k,v])
|
73
70
|
end
|
74
|
-
else
|
75
|
-
params.push([k,v])
|
76
71
|
end
|
77
72
|
end
|
73
|
+
# CGI.unescape to transform back %5D into []
|
74
|
+
uri.query = CGI.unescape(URI.encode_www_form(params))
|
78
75
|
end
|
79
|
-
|
80
|
-
uri.query=CGI.unescape(URI.encode_www_form(params))
|
76
|
+
return uri
|
81
77
|
end
|
82
|
-
return uri
|
83
78
|
end
|
84
79
|
|
85
80
|
private
|
@@ -87,13 +82,13 @@ module Aspera
|
|
87
82
|
# create and start keep alive connection on demand
|
88
83
|
def http_session
|
89
84
|
if @http_session.nil?
|
90
|
-
uri=self.class.build_uri(@params[:base_url])
|
85
|
+
uri = self.class.build_uri(@params[:base_url])
|
91
86
|
# this honors http_proxy env var
|
92
|
-
@http_session=Net::HTTP.new(uri.host, uri.port)
|
87
|
+
@http_session = Net::HTTP.new(uri.host, uri.port)
|
93
88
|
@http_session.use_ssl = uri.scheme.eql?('https')
|
94
|
-
@http_session.
|
95
|
-
|
96
|
-
|
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)
|
97
92
|
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
98
93
|
@http_session.start
|
99
94
|
end
|
@@ -103,63 +98,65 @@ module Aspera
|
|
103
98
|
public
|
104
99
|
|
105
100
|
attr_reader :params
|
106
|
-
attr_reader :oauth
|
107
101
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
102
|
+
def oauth
|
103
|
+
if @oauth.nil?
|
104
|
+
raise 'ERROR: no OAuth defined' unless @params[:auth][:type].eql?(:oauth2)
|
105
|
+
@oauth = Oauth.new(@params[:auth])
|
106
|
+
end
|
107
|
+
return @oauth
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param a_rest_params [Hash] default call parameters (merged at call)
|
114
111
|
def initialize(a_rest_params)
|
115
|
-
raise
|
116
|
-
raise
|
117
|
-
@params=a_rest_params.clone
|
112
|
+
raise 'ERROR: expecting Hash' unless a_rest_params.is_a?(Hash)
|
113
|
+
raise 'ERROR: expecting base_url' unless a_rest_params[:base_url].is_a?(String)
|
114
|
+
@params = a_rest_params.clone
|
118
115
|
Log.dump('REST params',@params)
|
119
116
|
# base url without trailing slashes (note: string may be frozen)
|
120
|
-
@params[:base_url]
|
121
|
-
@http_session=nil
|
117
|
+
@params[:base_url] = @params[:base_url].gsub(/\/+$/,'')
|
118
|
+
@http_session = nil
|
122
119
|
# default is no auth
|
123
|
-
@params[:auth]||={:
|
124
|
-
@params[:not_auth_codes]||=['401']
|
125
|
-
@oauth=
|
120
|
+
@params[:auth] ||= {type: :none}
|
121
|
+
@params[:not_auth_codes] ||= ['401']
|
122
|
+
@oauth = nil
|
126
123
|
Log.dump('REST params(2)',@params)
|
127
124
|
end
|
128
125
|
|
129
|
-
def oauth_token(
|
130
|
-
raise "ERROR: expecting
|
131
|
-
return
|
126
|
+
def oauth_token(force_refresh: false)
|
127
|
+
raise "ERROR: expecting boolean, have #{force_refresh}" unless [true,false].include?(force_refresh)
|
128
|
+
return oauth.get_authorization(use_refresh_token: force_refresh)
|
132
129
|
end
|
133
130
|
|
134
131
|
def build_request(call_data)
|
135
132
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
136
133
|
# URI.escape()
|
137
|
-
uri=self.class.build_uri("#{call_data[:base_url]}#{['','/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}",call_data[:url_params])
|
134
|
+
uri = self.class.build_uri("#{call_data[:base_url]}#{['','/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}",call_data[:url_params])
|
138
135
|
Log.log.debug("URI=#{uri}")
|
139
136
|
begin
|
140
137
|
# instanciate request object based on string name
|
141
|
-
req=
|
142
|
-
rescue NameError
|
138
|
+
req = Net::HTTP.const_get(call_data[:operation].capitalize).new(uri)
|
139
|
+
rescue NameError
|
143
140
|
raise "unsupported operation : #{call_data[:operation]}"
|
144
141
|
end
|
145
|
-
if call_data.has_key?(:json_params)
|
146
|
-
req.body=JSON.generate(call_data[:json_params])
|
142
|
+
if call_data.has_key?(:json_params) && !call_data[:json_params].nil?
|
143
|
+
req.body = JSON.generate(call_data[:json_params])
|
147
144
|
Log.dump('body JSON data',call_data[:json_params])
|
148
145
|
#Log.log.debug("body JSON data=#{JSON.pretty_generate(call_data[:json_params])}")
|
149
146
|
req['Content-Type'] = 'application/json'
|
150
147
|
#call_data[:headers]['Accept']='application/json'
|
151
148
|
end
|
152
|
-
if call_data.has_key?(:www_body_params)
|
153
|
-
req.body=URI.encode_www_form(call_data[:www_body_params])
|
149
|
+
if call_data.has_key?(:www_body_params)
|
150
|
+
req.body = URI.encode_www_form(call_data[:www_body_params])
|
154
151
|
Log.log.debug("body www data=#{req.body.chomp}")
|
155
152
|
req['Content-Type'] = 'application/x-www-form-urlencoded'
|
156
153
|
end
|
157
|
-
if call_data.has_key?(:text_body_params)
|
158
|
-
req.body=call_data[:text_body_params]
|
154
|
+
if call_data.has_key?(:text_body_params)
|
155
|
+
req.body = call_data[:text_body_params]
|
159
156
|
Log.log.debug("body data=#{req.body.chomp}")
|
160
157
|
end
|
161
158
|
# set headers
|
162
|
-
if call_data.has_key?(:headers)
|
159
|
+
if call_data.has_key?(:headers)
|
163
160
|
call_data[:headers].keys.each do |key|
|
164
161
|
req[key] = call_data[:headers][key]
|
165
162
|
end
|
@@ -179,122 +176,127 @@ module Aspera
|
|
179
176
|
# :url_params
|
180
177
|
# :www_body_params
|
181
178
|
# :text_body_params
|
182
|
-
# :save_to_file (filepath)
|
183
|
-
# :return_error (bool)
|
184
|
-
# :redirect_max (int)
|
179
|
+
# :save_to_file (filepath) default: nil
|
180
|
+
# :return_error (bool) default: nil
|
181
|
+
# :redirect_max (int) default: 0
|
182
|
+
# :not_auth_codes (array) codes that trigger a refresh/regeneration of bearer token
|
183
|
+
# ----
|
184
|
+
# authentication (:auth) :
|
185
|
+
# :type (:none, :basic, :oauth2, :url)
|
186
|
+
# :username [:basic]
|
187
|
+
# :password [:basic]
|
188
|
+
# :url_creds [:url] a hash
|
189
|
+
# :* [:oauth2] see Oauth class
|
185
190
|
def call(call_data)
|
186
191
|
raise "Hash call parameter is required (#{call_data.class})" unless call_data.is_a?(Hash)
|
187
|
-
call_data[:subpath]='' if call_data[:subpath].nil?
|
192
|
+
call_data[:subpath] = '' if call_data[:subpath].nil?
|
188
193
|
Log.log.debug("accessing #{call_data[:subpath]}".red.bold.bg_green)
|
189
|
-
call_data[:headers]||={}
|
190
|
-
call_data[:headers]['User-Agent'] ||=
|
194
|
+
call_data[:headers] ||= {}
|
195
|
+
call_data[:headers]['User-Agent'] ||= self.class.user_agent
|
191
196
|
# defaults from @params are overriden by call data
|
192
|
-
call_data
|
197
|
+
call_data = @params.deep_merge(call_data)
|
193
198
|
case call_data[:auth][:type]
|
194
199
|
when :none
|
195
200
|
# no auth
|
196
201
|
when :basic
|
197
|
-
Log.log.debug(
|
202
|
+
Log.log.debug('using Basic auth')
|
198
203
|
# done in build_req
|
199
204
|
when :oauth2
|
200
|
-
call_data[:headers]['Authorization']=oauth_token unless call_data[:headers].has_key?('Authorization')
|
205
|
+
call_data[:headers]['Authorization'] = oauth_token unless call_data[:headers].has_key?('Authorization')
|
201
206
|
when :url
|
202
|
-
call_data[:url_params]||={}
|
207
|
+
call_data[:url_params] ||= {}
|
203
208
|
call_data[:auth][:url_creds].each do |key, value|
|
204
|
-
call_data[:url_params][key]=value
|
209
|
+
call_data[:url_params][key] = value
|
205
210
|
end
|
206
211
|
else raise "unsupported auth type: [#{call_data[:auth][:type]}]"
|
207
212
|
end
|
208
|
-
req=build_request(call_data)
|
213
|
+
req = build_request(call_data)
|
209
214
|
Log.log.debug("call_data = #{call_data}")
|
210
|
-
result={:
|
215
|
+
result = {http: nil}
|
211
216
|
# start a block to be able to retry the actual HTTP request
|
212
217
|
begin
|
213
218
|
# we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
|
214
219
|
oauth_tries ||= 2
|
215
|
-
tries_remain_redirect||=call_data[:redirect_max].nil? ? 0 : call_data[:redirect_max].to_i
|
216
|
-
Log.log.debug(
|
220
|
+
tries_remain_redirect ||= call_data[:redirect_max].nil? ? 0 : call_data[:redirect_max].to_i
|
221
|
+
Log.log.debug('send request')
|
217
222
|
# make http request (pipelined)
|
218
223
|
http_session.request(req) do |response|
|
219
224
|
result[:http] = response
|
220
|
-
if call_data.
|
221
|
-
total_size=result[:http]['Content-Length'].to_i
|
222
|
-
progress=ProgressBar.create(
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
Log.log.debug(
|
228
|
-
target_file=call_data[:save_to_file]
|
225
|
+
if !call_data[:save_to_file].nil? && result[:http].code.to_s.start_with?('2')
|
226
|
+
total_size = result[:http]['Content-Length'].to_i
|
227
|
+
progress = ProgressBar.create(
|
228
|
+
format: '%a %B %p%% %r KB/sec %e',
|
229
|
+
rate_scale: lambda{|rate|rate / 1024},
|
230
|
+
title: 'progress',
|
231
|
+
total: total_size)
|
232
|
+
Log.log.debug('before write file')
|
233
|
+
target_file = call_data[:save_to_file]
|
229
234
|
# override user's path to path in header
|
230
|
-
if !response['Content-Disposition'].nil?
|
231
|
-
target_file=File.join(File.dirname(target_file),m[1])
|
235
|
+
if !response['Content-Disposition'].nil? && (m = response['Content-Disposition'].match(/filename="([^"]+)"/))
|
236
|
+
target_file = File.join(File.dirname(target_file),m[1])
|
232
237
|
end
|
233
238
|
# download with temp filename
|
234
|
-
target_file_tmp="#{target_file}#{
|
239
|
+
target_file_tmp = "#{target_file}#{self.class.download_partial_suffix}"
|
235
240
|
Log.log.debug("saving to: #{target_file}")
|
236
241
|
File.open(target_file_tmp, 'wb') do |file|
|
237
242
|
result[:http].read_body do |fragment|
|
238
243
|
file.write(fragment)
|
239
|
-
new_process=progress.progress+fragment.length
|
244
|
+
new_process = progress.progress + fragment.length
|
240
245
|
new_process = total_size if new_process > total_size
|
241
|
-
progress.progress=new_process
|
246
|
+
progress.progress = new_process
|
242
247
|
end
|
243
248
|
end
|
244
249
|
# rename at the end
|
245
250
|
File.rename(target_file_tmp, target_file)
|
246
|
-
progress=nil
|
251
|
+
progress = nil
|
247
252
|
end # save_to_file
|
248
253
|
end
|
249
254
|
# sometimes there is a UTF8 char (e.g. (c) )
|
250
|
-
result[:http].body.force_encoding(
|
255
|
+
result[:http].body.force_encoding('UTF-8') if result[:http].body.is_a?(String)
|
251
256
|
Log.log.debug("result: body=#{result[:http].body}")
|
252
|
-
result_mime=(result[:http]['Content-Type']||'text/plain').split(';').first
|
253
|
-
case result_mime
|
257
|
+
result_mime = (result[:http]['Content-Type'] || 'text/plain').split(';').first
|
258
|
+
result[:data] = case result_mime
|
254
259
|
when 'application/json','application/vnd.api+json'
|
255
|
-
|
260
|
+
JSON.parse(result[:http].body) rescue nil
|
256
261
|
else #when 'text/plain'
|
257
|
-
result[:
|
262
|
+
result[:http].body
|
258
263
|
end
|
259
264
|
Log.dump("result: parsed: #{result_mime}",result[:data])
|
260
265
|
Log.log.debug("result: code=#{result[:http].code}")
|
261
|
-
RestErrorAnalyzer.instance.
|
266
|
+
RestErrorAnalyzer.instance.raise_on_error(req,result)
|
262
267
|
rescue RestCallError => e
|
263
268
|
# not authorized: oauth token expired
|
264
|
-
if
|
269
|
+
if call_data[:not_auth_codes].include?(result[:http].code.to_s) && call_data[:auth][:type].eql?(:oauth2)
|
265
270
|
begin
|
266
271
|
# try to use refresh token
|
267
|
-
req['Authorization']=oauth_token(
|
268
|
-
rescue RestCallError =>
|
269
|
-
|
272
|
+
req['Authorization'] = oauth_token(force_refresh: true)
|
273
|
+
rescue RestCallError => e_tok
|
274
|
+
e = e_tok
|
275
|
+
Log.log.error('refresh failed'.bg_red)
|
270
276
|
# regenerate a brand new token
|
271
|
-
req['Authorization']=oauth_token
|
277
|
+
req['Authorization'] = oauth_token(use_cache: false)
|
272
278
|
end
|
273
279
|
Log.log.debug("using new token=#{call_data[:headers]['Authorization']}")
|
274
280
|
retry unless (oauth_tries -= 1).zero?
|
275
281
|
end # if oauth
|
276
282
|
# moved ?
|
277
|
-
if e.response.is_a?(Net::HTTPRedirection)
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
283
|
+
if e.response.is_a?(Net::HTTPRedirection) && tries_remain_redirect.positive?
|
284
|
+
tries_remain_redirect -= 1
|
285
|
+
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("URL is moved: #{new_url}")
|
289
|
+
redir_uri = URI.parse(new_url)
|
290
|
+
call_data[:base_url] = new_url
|
291
|
+
call_data[:subpath] = ''
|
292
|
+
if current_uri.host.eql?(redir_uri.host) && current_uri.port.eql?(redir_uri.port)
|
293
|
+
req = build_request(call_data)
|
294
|
+
retry
|
293
295
|
else
|
294
|
-
|
296
|
+
# change host
|
297
|
+
Log.log.info("Redirect changes host: #{current_uri.host} -> #{redir_uri.host}")
|
298
|
+
return self.class.new(call_data).call(call_data)
|
295
299
|
end
|
296
|
-
else
|
297
|
-
raise e
|
298
300
|
end
|
299
301
|
# raise exception if could not retry and not return error in result
|
300
302
|
raise e unless call_data[:return_error]
|
@@ -309,23 +311,23 @@ module Aspera
|
|
309
311
|
|
310
312
|
# @param encoding : one of: :json_params, :url_params
|
311
313
|
def create(subpath,params,encoding=:json_params)
|
312
|
-
return call({:
|
314
|
+
return call({operation: 'POST',subpath: subpath,headers: {'Accept' => 'application/json'},encoding => params})
|
313
315
|
end
|
314
316
|
|
315
317
|
def read(subpath,args=nil)
|
316
|
-
return call({:
|
318
|
+
return call({operation: 'GET',subpath: subpath,headers: {'Accept' => 'application/json'},url_params: args})
|
317
319
|
end
|
318
320
|
|
319
321
|
def update(subpath,params)
|
320
|
-
return call({:
|
322
|
+
return call({operation: 'PUT',subpath: subpath,headers: {'Accept' => 'application/json'},json_params: params})
|
321
323
|
end
|
322
324
|
|
323
325
|
def delete(subpath)
|
324
|
-
return call({:
|
326
|
+
return call({operation: 'DELETE',subpath: subpath,headers: {'Accept' => 'application/json'}})
|
325
327
|
end
|
326
328
|
|
327
329
|
def cancel(subpath)
|
328
|
-
return call({:
|
330
|
+
return call({operation: 'CANCEL',subpath: subpath,headers: {'Accept' => 'application/json'}})
|
329
331
|
end
|
330
332
|
end
|
331
333
|
end #module Aspera
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aspera
|
2
4
|
# raised on error after REST call
|
3
5
|
class RestCallError < StandardError
|
4
|
-
attr_accessor :request
|
5
|
-
attr_accessor :response
|
6
|
+
attr_accessor :request, :response
|
6
7
|
# @param http response
|
7
8
|
def initialize(req,resp,msg)
|
8
9
|
@request = req
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/log'
|
2
4
|
require 'aspera/rest_call_error'
|
3
5
|
require 'singleton'
|
@@ -10,12 +12,12 @@ module Aspera
|
|
10
12
|
# the singleton object is registered with application specific handlers
|
11
13
|
def initialize
|
12
14
|
# list of handlers
|
13
|
-
@error_handlers=[]
|
14
|
-
@log_file=nil
|
15
|
-
|
16
|
-
if !
|
15
|
+
@error_handlers = []
|
16
|
+
@log_file = nil
|
17
|
+
add_handler('Type Generic') do |type,call_context|
|
18
|
+
if !call_context[:response].code.start_with?('2')
|
17
19
|
# add generic information
|
18
|
-
RestErrorAnalyzer.add_error(
|
20
|
+
RestErrorAnalyzer.add_error(call_context,type,"#{call_context[:request]['host']} #{call_context[:response].code} #{call_context[:response].message}")
|
19
21
|
end
|
20
22
|
end
|
21
23
|
end
|
@@ -23,8 +25,8 @@ module Aspera
|
|
23
25
|
# Use this method to analyze a EST result and raise an exception
|
24
26
|
# Analyzes REST call response and raises a RestCallError exception
|
25
27
|
# if HTTP result code is not 2XX
|
26
|
-
def
|
27
|
-
|
28
|
+
def raise_on_error(req,res)
|
29
|
+
call_context = {
|
28
30
|
messages: [],
|
29
31
|
request: req,
|
30
32
|
response: res[:http],
|
@@ -36,21 +38,19 @@ module Aspera
|
|
36
38
|
@error_handlers.each do |handler|
|
37
39
|
begin
|
38
40
|
#Log.log.debug("test exception: #{handler[:name]}")
|
39
|
-
handler[:block].call(handler[:name],
|
40
|
-
rescue => e
|
41
|
+
handler[:block].call(handler[:name],call_context)
|
42
|
+
rescue StandardError => e
|
41
43
|
Log.log.error("ERROR in handler:\n#{e.message}\n#{e.backtrace}")
|
42
44
|
end
|
43
45
|
end
|
44
|
-
unless
|
45
|
-
raise RestCallError.new(context[:request],context[:response],context[:messages].join("\n"))
|
46
|
-
end
|
46
|
+
raise RestCallError.new(call_context[:request],call_context[:response],call_context[:messages].join("\n")) unless call_context[:messages].empty?
|
47
47
|
end
|
48
48
|
|
49
49
|
# add a new error handler (done at application initialisation)
|
50
50
|
# @param name : name of error handler (for logs)
|
51
|
-
# @param block : processing of response: takes two parameters: name,
|
51
|
+
# @param block : processing of response: takes two parameters: name, call_context
|
52
52
|
# name is the one provided here
|
53
|
-
#
|
53
|
+
# call_context is built in method raise_on_error
|
54
54
|
def add_handler(name,&block)
|
55
55
|
@error_handlers.unshift({name: name, block: block})
|
56
56
|
end
|
@@ -59,21 +59,21 @@ module Aspera
|
|
59
59
|
# check that key exists and is string under specified path (hash)
|
60
60
|
# adds other keys as secondary information
|
61
61
|
def add_simple_handler(name,*args)
|
62
|
-
add_handler(name) do |type,
|
62
|
+
add_handler(name) do |type,call_context|
|
63
63
|
# need to clone because we modify and same array is used subsequently
|
64
|
-
path=args.clone
|
64
|
+
path = args.clone
|
65
65
|
#Log.log.debug("path=#{path}")
|
66
66
|
# if last in path is boolean it tells if the error is only with http error code or always
|
67
|
-
always=[true, false].include?(path.last) ? path.pop : false
|
68
|
-
if
|
69
|
-
msg_key=path.pop
|
67
|
+
always = [true, false].include?(path.last) ? path.pop : false
|
68
|
+
if call_context[:data].is_a?(Hash) && (!call_context[:response].code.start_with?('2') || always)
|
69
|
+
msg_key = path.pop
|
70
70
|
# dig and find sub entry corresponding to path in deep hash
|
71
|
-
error_struct=path.inject(
|
72
|
-
if error_struct.is_a?(Hash)
|
73
|
-
RestErrorAnalyzer.add_error(
|
71
|
+
error_struct = path.inject(call_context[:data]) { |subhash, key| subhash.respond_to?(:keys) ? subhash[key] : nil }
|
72
|
+
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
74
|
error_struct.each do |k,v|
|
75
75
|
next if k.eql?(msg_key)
|
76
|
-
RestErrorAnalyzer.add_error(
|
76
|
+
RestErrorAnalyzer.add_error(call_context,"#{type}(sub)","#{k}: #{v}") if [String,Integer].include?(v.class)
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
@@ -82,17 +82,17 @@ module Aspera
|
|
82
82
|
|
83
83
|
# used by handler to add an error description to list of errors
|
84
84
|
# for logging and tracing : collect error descriptions (create file to activate)
|
85
|
-
# @param
|
85
|
+
# @param call_context a Hash containing the result call_context, provided to handler
|
86
86
|
# @param type a string describing type of exception, for logging purpose
|
87
87
|
# @param msg one error message to add to list
|
88
|
-
def self.add_error(
|
89
|
-
|
90
|
-
logfile=instance.log_file
|
88
|
+
def self.add_error(call_context,type,msg)
|
89
|
+
call_context[:messages].push(msg)
|
90
|
+
logfile = instance.log_file
|
91
91
|
# log error for further analysis (file must exist to activate)
|
92
|
-
if
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
return if logfile.nil? || !File.exist?(logfile)
|
93
|
+
File.open(logfile,'a+') do |f|
|
94
|
+
f.write("\n=#{type}=====\n#{call_context[:request].method} #{call_context[:request].path}\n#{call_context[:response].code}\n"\
|
95
|
+
"#{JSON.generate(call_context[:data])}\n#{call_context[:messages].join("\n")}")
|
96
96
|
end
|
97
97
|
end
|
98
98
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/rest_error_analyzer'
|
2
4
|
require 'aspera/log'
|
3
5
|
|
@@ -5,8 +7,8 @@ module Aspera
|
|
5
7
|
# REST error handlers for various Aspera REST APIs
|
6
8
|
class RestErrorsAspera
|
7
9
|
# handlers should probably be defined by plugins for modularity
|
8
|
-
def self.
|
9
|
-
Log.log.debug(
|
10
|
+
def self.register_handlers
|
11
|
+
Log.log.debug('registering Aspera REST error handlers')
|
10
12
|
# Faspex 4: both user_message and internal_message, and code 200
|
11
13
|
# example: missing meta data on package creation
|
12
14
|
RestErrorAnalyzer.instance.add_simple_handler('Type 1: error:user_message','error','user_message',true)
|
@@ -16,23 +18,23 @@ module Aspera
|
|
16
18
|
RestErrorAnalyzer.instance.add_simple_handler('AoC Automation','error')
|
17
19
|
RestErrorAnalyzer.instance.add_simple_handler('Type 5','error_description')
|
18
20
|
RestErrorAnalyzer.instance.add_simple_handler('Type 6','message')
|
19
|
-
RestErrorAnalyzer.instance.add_handler('Type 7: errors[]') do |name,
|
20
|
-
if
|
21
|
-
|
22
|
-
RestErrorAnalyzer.add_error(
|
21
|
+
RestErrorAnalyzer.instance.add_handler('Type 7: errors[]') do |name,call_context|
|
22
|
+
if call_context[:data].is_a?(Hash) && call_context[:data]['errors'].is_a?(Hash)
|
23
|
+
call_context[:data]['errors'].each do |k,v|
|
24
|
+
RestErrorAnalyzer.add_error(call_context,name,"#{k}: #{v}")
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
# call to upload_setup and download_setup of node api
|
27
|
-
RestErrorAnalyzer.instance.add_handler('T8:node: *_setup') do |type,
|
28
|
-
if
|
29
|
-
d_t_s=
|
29
|
+
RestErrorAnalyzer.instance.add_handler('T8:node: *_setup') do |type,call_context|
|
30
|
+
if call_context[:data].is_a?(Hash)
|
31
|
+
d_t_s = call_context[:data]['transfer_specs']
|
30
32
|
if d_t_s.is_a?(Array)
|
31
33
|
d_t_s.each do |res|
|
32
34
|
#r_err=res['transfer_spec']['error']
|
33
|
-
r_err=res['error']
|
35
|
+
r_err = res['error']
|
34
36
|
if r_err.is_a?(Hash)
|
35
|
-
RestErrorAnalyzer.add_error(
|
37
|
+
RestErrorAnalyzer.add_error(call_context,type,"#{r_err['code']}: #{r_err['reason']}: #{r_err['user_message']}")
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
@@ -40,14 +42,14 @@ module Aspera
|
|
40
42
|
end
|
41
43
|
RestErrorAnalyzer.instance.add_simple_handler('T9:IBM cloud IAM','errorMessage')
|
42
44
|
RestErrorAnalyzer.instance.add_simple_handler('T10:faspex v4','user_message')
|
43
|
-
RestErrorAnalyzer.instance.add_handler('bss graphql') do |type,
|
44
|
-
if
|
45
|
-
d_t_s=
|
45
|
+
RestErrorAnalyzer.instance.add_handler('bss graphql') do |type,call_context|
|
46
|
+
if call_context[:data].is_a?(Hash)
|
47
|
+
d_t_s = call_context[:data]['errors']
|
46
48
|
if d_t_s.is_a?(Array)
|
47
49
|
d_t_s.each do |res|
|
48
|
-
r_err=res['message']
|
50
|
+
r_err = res['message']
|
49
51
|
if r_err.is_a?(String)
|
50
|
-
RestErrorAnalyzer.add_error(
|
52
|
+
RestErrorAnalyzer.add_error(call_context,type,r_err)
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|