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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +20 -0
  4. data/CHANGELOG.md +509 -0
  5. data/CONTRIBUTING.md +118 -0
  6. data/README.md +1241 -916
  7. data/bin/ascli +4 -4
  8. data/bin/asession +11 -11
  9. data/docs/test_env.conf +32 -21
  10. data/examples/aoc.rb +4 -4
  11. data/examples/dascli +16 -9
  12. data/examples/faspex4.rb +8 -8
  13. data/examples/node.rb +12 -12
  14. data/examples/server.rb +10 -10
  15. data/lib/aspera/aoc.rb +273 -266
  16. data/lib/aspera/ascmd.rb +56 -54
  17. data/lib/aspera/ats_api.rb +4 -4
  18. data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
  19. data/lib/aspera/cli/extended_value.rb +5 -5
  20. data/lib/aspera/cli/formater.rb +64 -64
  21. data/lib/aspera/cli/info.rb +2 -2
  22. data/lib/aspera/cli/listener/line_dump.rb +1 -1
  23. data/lib/aspera/cli/listener/logger.rb +1 -1
  24. data/lib/aspera/cli/listener/progress.rb +5 -6
  25. data/lib/aspera/cli/listener/progress_multi.rb +14 -19
  26. data/lib/aspera/cli/main.rb +66 -67
  27. data/lib/aspera/cli/manager.rb +112 -110
  28. data/lib/aspera/cli/plugin.rb +57 -36
  29. data/lib/aspera/cli/plugins/alee.rb +4 -4
  30. data/lib/aspera/cli/plugins/aoc.rb +309 -670
  31. data/lib/aspera/cli/plugins/ats.rb +44 -46
  32. data/lib/aspera/cli/plugins/bss.rb +10 -10
  33. data/lib/aspera/cli/plugins/config.rb +497 -378
  34. data/lib/aspera/cli/plugins/console.rb +12 -12
  35. data/lib/aspera/cli/plugins/cos.rb +18 -20
  36. data/lib/aspera/cli/plugins/faspex.rb +112 -114
  37. data/lib/aspera/cli/plugins/faspex5.rb +71 -46
  38. data/lib/aspera/cli/plugins/node.rb +379 -283
  39. data/lib/aspera/cli/plugins/orchestrator.rb +46 -46
  40. data/lib/aspera/cli/plugins/preview.rb +122 -114
  41. data/lib/aspera/cli/plugins/server.rb +137 -83
  42. data/lib/aspera/cli/plugins/shares.rb +30 -29
  43. data/lib/aspera/cli/plugins/sync.rb +13 -33
  44. data/lib/aspera/cli/transfer_agent.rb +60 -59
  45. data/lib/aspera/cli/version.rb +1 -1
  46. data/lib/aspera/colors.rb +3 -3
  47. data/lib/aspera/command_line_builder.rb +27 -27
  48. data/lib/aspera/cos_node.rb +22 -20
  49. data/lib/aspera/data_repository.rb +1 -1
  50. data/lib/aspera/environment.rb +35 -15
  51. data/lib/aspera/fasp/agent_base.rb +15 -15
  52. data/lib/aspera/fasp/agent_connect.rb +23 -21
  53. data/lib/aspera/fasp/agent_direct.rb +66 -64
  54. data/lib/aspera/fasp/agent_httpgw.rb +141 -78
  55. data/lib/aspera/fasp/agent_node.rb +23 -21
  56. data/lib/aspera/fasp/agent_trsdk.rb +20 -20
  57. data/lib/aspera/fasp/error.rb +3 -2
  58. data/lib/aspera/fasp/error_info.rb +11 -8
  59. data/lib/aspera/fasp/installation.rb +79 -79
  60. data/lib/aspera/fasp/listener.rb +1 -1
  61. data/lib/aspera/fasp/parameters.rb +86 -71
  62. data/lib/aspera/fasp/parameters.yaml +7 -4
  63. data/lib/aspera/fasp/resume_policy.rb +8 -8
  64. data/lib/aspera/fasp/transfer_spec.rb +35 -2
  65. data/lib/aspera/fasp/uri.rb +7 -7
  66. data/lib/aspera/faspex_gw.rb +7 -5
  67. data/lib/aspera/hash_ext.rb +3 -3
  68. data/lib/aspera/id_generator.rb +5 -5
  69. data/lib/aspera/keychain/encrypted_hash.rb +38 -105
  70. data/lib/aspera/keychain/macos_security.rb +128 -57
  71. data/lib/aspera/log.rb +7 -7
  72. data/lib/aspera/nagios.rb +19 -18
  73. data/lib/aspera/node.rb +209 -35
  74. data/lib/aspera/oauth.rb +37 -36
  75. data/lib/aspera/open_application.rb +19 -11
  76. data/lib/aspera/persistency_action_once.rb +4 -4
  77. data/lib/aspera/persistency_folder.rb +16 -15
  78. data/lib/aspera/preview/file_types.rb +8 -8
  79. data/lib/aspera/preview/generator.rb +67 -67
  80. data/lib/aspera/preview/utils.rb +27 -27
  81. data/lib/aspera/proxy_auto_config.js +41 -41
  82. data/lib/aspera/proxy_auto_config.rb +21 -14
  83. data/lib/aspera/rest.rb +72 -67
  84. data/lib/aspera/rest_call_error.rb +2 -1
  85. data/lib/aspera/rest_error_analyzer.rb +18 -17
  86. data/lib/aspera/rest_errors_aspera.rb +16 -16
  87. data/lib/aspera/secret_hider.rb +15 -13
  88. data/lib/aspera/ssh.rb +11 -10
  89. data/lib/aspera/sync.rb +158 -44
  90. data/lib/aspera/temp_file_manager.rb +2 -2
  91. data/lib/aspera/uri_reader.rb +4 -4
  92. data/lib/aspera/web_auth.rb +14 -13
  93. data.tar.gz.sig +0 -0
  94. metadata +11 -36
  95. 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,:PAC_MAIN_FUNCTION
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.has_key?(simple_url)
76
- Log.log.debug("PAC: starting javascript for #{service_url}")
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("PAC: result: #{@cache[simple_url]}")
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("PAC: did not return a String, returned #{proxy_list_str.class}")
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
- # we want to return URIs, so add dummy scheme
116
- uri_list.push(URI.parse("proxy://#{addr_port}"))
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("PAC: PROXY must be <address>:<port>, ignoring #{addr_port}")
124
+ Log.log.warn{"PAC: PROXY must be <address>:<port>, ignoring #{addr_port}"}
119
125
  end
120
- rescue StandardError
121
- Log.log.warn("PAC: cannot parse #{addr_port}")
126
+ rescue StandardError => e
127
+ Log.log.warn{"PAC: cannot parse #{addr_port} #{e}"}
122
128
  end
123
- else Log.log.warn("PAC: ignoring proxy type #{parts.first}: not supported")
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.keys.each do |p|
41
+ @@global.each_key do |p|
46
42
  define_method(p){@@global[p]}
47
- define_method("#{p}="){|val|Log.log.debug("#{p} => #{val}".red);@@global[p] = val}
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
- uri = self.class.build_uri(@params[:base_url])
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',@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)',@params)
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("URI=#{uri}")
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.has_key?(:json_params) && !call_data[:json_params].nil?
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.has_key?(:www_body_params)
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("body www data=#{req.body.chomp}")
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.has_key?(:text_body_params)
159
+ if call_data.key?(:text_body_params)
155
160
  req.body = call_data[:text_body_params]
156
- Log.log.debug("body data=#{req.body.chomp}")
161
+ Log.log.debug{"body data=#{req.body.chomp}"}
157
162
  end
158
163
  # set headers
159
- if call_data.has_key?(:headers)
160
- call_data[:headers].keys.each do |key|
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("accessing #{call_data[:subpath]}".red.bold.bg_green)
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].has_key?('Authorization')
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("call_data = #{call_data}")
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("saving to: #{target_file}")
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("result: body=#{result[:http].body}")
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("result: code=#{result[:http].code}")
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("using new token=#{call_data[:headers]['Authorization']}")
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("URL is moved: #{new_url}")
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("Redirect changes host: #{current_uri.host} -> #{redir_uri.host}")
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("result=#{result}")
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("test exception: #{handler[:name]}")
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("ERROR in handler:\n#{e.message}\n#{e.backtrace}")
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,&block)
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,*args)
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("path=#{path}")
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
@@ -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
- ASCP_SECRETS=%w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS].freeze
12
- KEY_SECRETS =%w[password secret private_key passphrase].freeze
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> (#{ASCP_SECRETS.join('|')})=)[^ ]*(?<end> )/,
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,:ASCP_SECRETS,:KEY_SECRETS,:ALL_SECRETS,:REGEX_LOG_REPLACES
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
- # note that @log_secrets may be set AFTER this init is done, so it's done at runtime
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])