aspera-cli 4.15.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +292 -228
  5. data/CONTRIBUTING.md +69 -18
  6. data/README.md +1102 -952
  7. data/bin/ascli +13 -31
  8. data/bin/asession +3 -1
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/aoc.rb +28 -33
  11. data/lib/aspera/ascmd.rb +3 -6
  12. data/lib/aspera/assert.rb +45 -0
  13. data/lib/aspera/cli/extended_value.rb +5 -5
  14. data/lib/aspera/cli/formatter.rb +26 -13
  15. data/lib/aspera/cli/hints.rb +4 -3
  16. data/lib/aspera/cli/main.rb +16 -3
  17. data/lib/aspera/cli/manager.rb +45 -36
  18. data/lib/aspera/cli/plugin.rb +20 -13
  19. data/lib/aspera/cli/plugins/aoc.rb +103 -73
  20. data/lib/aspera/cli/plugins/ats.rb +4 -3
  21. data/lib/aspera/cli/plugins/config.rb +114 -119
  22. data/lib/aspera/cli/plugins/cos.rb +2 -2
  23. data/lib/aspera/cli/plugins/faspex.rb +23 -19
  24. data/lib/aspera/cli/plugins/faspex5.rb +75 -43
  25. data/lib/aspera/cli/plugins/node.rb +28 -15
  26. data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
  27. data/lib/aspera/cli/plugins/preview.rb +9 -7
  28. data/lib/aspera/cli/plugins/server.rb +6 -3
  29. data/lib/aspera/cli/plugins/shares.rb +30 -26
  30. data/lib/aspera/cli/sync_actions.rb +9 -9
  31. data/lib/aspera/cli/transfer_agent.rb +21 -14
  32. data/lib/aspera/cli/transfer_progress.rb +2 -3
  33. data/lib/aspera/cli/version.rb +1 -1
  34. data/lib/aspera/command_line_builder.rb +13 -11
  35. data/lib/aspera/cos_node.rb +3 -2
  36. data/lib/aspera/coverage.rb +22 -0
  37. data/lib/aspera/data_repository.rb +33 -2
  38. data/lib/aspera/environment.rb +4 -2
  39. data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
  40. data/lib/aspera/fasp/agent_base.rb +17 -7
  41. data/lib/aspera/fasp/agent_direct.rb +88 -84
  42. data/lib/aspera/fasp/agent_httpgw.rb +4 -3
  43. data/lib/aspera/fasp/agent_node.rb +3 -2
  44. data/lib/aspera/fasp/agent_trsdk.rb +79 -37
  45. data/lib/aspera/fasp/installation.rb +51 -12
  46. data/lib/aspera/fasp/management.rb +11 -6
  47. data/lib/aspera/fasp/parameters.rb +53 -47
  48. data/lib/aspera/fasp/resume_policy.rb +7 -5
  49. data/lib/aspera/fasp/sync.rb +273 -0
  50. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  51. data/lib/aspera/fasp/uri.rb +2 -2
  52. data/lib/aspera/faspex_gw.rb +11 -8
  53. data/lib/aspera/faspex_postproc.rb +6 -5
  54. data/lib/aspera/id_generator.rb +3 -1
  55. data/lib/aspera/json_rpc.rb +10 -8
  56. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  57. data/lib/aspera/keychain/macos_security.rb +15 -13
  58. data/lib/aspera/log.rb +4 -3
  59. data/lib/aspera/nagios.rb +7 -2
  60. data/lib/aspera/node.rb +17 -16
  61. data/lib/aspera/node_simulator.rb +214 -0
  62. data/lib/aspera/oauth.rb +22 -19
  63. data/lib/aspera/persistency_action_once.rb +13 -14
  64. data/lib/aspera/persistency_folder.rb +3 -2
  65. data/lib/aspera/preview/file_types.rb +53 -267
  66. data/lib/aspera/preview/generator.rb +7 -5
  67. data/lib/aspera/preview/terminal.rb +14 -5
  68. data/lib/aspera/preview/utils.rb +8 -7
  69. data/lib/aspera/proxy_auto_config.rb +6 -3
  70. data/lib/aspera/rest.rb +29 -13
  71. data/lib/aspera/rest_error_analyzer.rb +1 -0
  72. data/lib/aspera/rest_errors_aspera.rb +2 -0
  73. data/lib/aspera/secret_hider.rb +5 -2
  74. data/lib/aspera/ssh.rb +10 -8
  75. data/lib/aspera/temp_file_manager.rb +1 -1
  76. data/lib/aspera/web_server_simple.rb +2 -1
  77. data.tar.gz.sig +0 -0
  78. metadata +96 -45
  79. metadata.gz.sig +0 -0
  80. data/lib/aspera/sync.rb +0 -219
@@ -5,6 +5,7 @@ require 'aspera/environment'
5
5
  require 'aspera/data_repository'
6
6
  require 'aspera/fasp/products'
7
7
  require 'aspera/log'
8
+ require 'aspera/assert'
8
9
  require 'aspera/web_server_simple'
9
10
  require 'English'
10
11
  require 'singleton'
@@ -107,6 +108,7 @@ module Aspera
107
108
  # get path of one resource file of currently activated product
108
109
  # keys and certs are generated locally... (they are well known values, arch. independent)
109
110
  def path(k)
111
+ file_is_optional = false
110
112
  case k
111
113
  when :ascp, :ascp4
112
114
  use_ascp_from_product(FIRST_FOUND) if @path_to_ascp.nil?
@@ -115,17 +117,13 @@ module Aspera
115
117
  file = file.gsub('ascp', 'ascp4') if k.eql?(:ascp4)
116
118
  when :transferd
117
119
  file = transferd_filepath
120
+ file_is_optional = true
118
121
  when :ssh_private_dsa, :ssh_private_rsa
119
122
  # assume last 3 letters are type
120
- type = k.to_s[-3..-1]
121
- file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") do
122
- # generate PEM from DER
123
- OpenSSL::PKey.const_get(type.upcase).new(DataRepository.instance.data(type.eql?('dsa') ? 1 : 2)).to_pem
124
- end
123
+ type = k.to_s[-3..-1].to_sym
124
+ file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") {DataRepository.instance.item(type)}
125
125
  when :aspera_license
126
- file = check_or_create_sdk_file('aspera-license') do
127
- Zlib::Inflate.inflate(DataRepository.instance.data(6))
128
- end
126
+ file = check_or_create_sdk_file('aspera-license') {DataRepository.instance.item(:license)}
129
127
  when :aspera_conf
130
128
  file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
131
129
  when :fallback_certificate, :fallback_private_key
@@ -141,16 +139,16 @@ module Aspera
141
139
  check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
142
140
  end
143
141
  file = k.eql?(:fallback_certificate) ? file_cert : file_key
144
- else
145
- raise "INTERNAL ERROR: #{k}"
142
+ else error_unexpected_value(k)
146
143
  end
147
- raise "no such file: #{file}" unless File.exist?(file)
144
+ return nil if file_is_optional && !File.exist?(file)
145
+ assert(File.exist?(file)){"no such file: #{file}"}
148
146
  return file
149
147
  end
150
148
 
151
149
  # default bypass key phrase
152
150
  def ssh_cert_uuid
153
- return format('%08x-%04x-%04x-%04x-%04x%08x', *DataRepository.instance.data(3).unpack('NnnnnN'))
151
+ return DataRepository.instance.item(:uuid)
154
152
  end
155
153
 
156
154
  def aspera_token_ssh_key_paths
@@ -175,6 +173,47 @@ module Aspera
175
173
  return exe_version
176
174
  end
177
175
 
176
+ def ascp_info
177
+ data = file_paths
178
+ # read PATHs from ascp directly, and pvcl modules as well
179
+ Open3.popen3(data['ascp'], '-DDL-') do |_stdin, _stdout, stderr, thread|
180
+ last_line = ''
181
+ while (line = stderr.gets)
182
+ line.chomp!
183
+ last_line = line
184
+ case line
185
+ when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
186
+ data[Regexp.last_match(1)] = Regexp.last_match(3)
187
+ when /^DBG Added module group:"(?<module>[^"]+)" name:"(?<scheme>[^"]+)", version:"(?<version>[^"]+)" interface:"(?<interface>[^"]+)"$/
188
+ c = Regexp.last_match.named_captures.symbolize_keys
189
+ data[c[:interface]] ||= {}
190
+ data[c[:interface]][c[:module]] ||= []
191
+ data[c[:interface]][c[:module]].push("#{c[:scheme]} v#{c[:version]}")
192
+ when %r{^DBG License result \(/license/(\S+)\): (.+)$}
193
+ data[Regexp.last_match(1)] = Regexp.last_match(2)
194
+ when /^LOG (.+) version ([0-9.]+)$/
195
+ data['product_name'] = Regexp.last_match(1)
196
+ data['product_version'] = Regexp.last_match(2)
197
+ when /^LOG Initializing FASP version ([^,]+),/
198
+ data['ascp_version'] = Regexp.last_match(1)
199
+ end
200
+ end
201
+ if !thread.value.exitstatus.eql?(1) && !data.key?('root')
202
+ raise last_line
203
+ end
204
+ end
205
+ # ascp's openssl directory
206
+ ascp_file = data['ascp']
207
+ File.binread(ascp_file).scan(/[\x20-\x7E]{4,}/) do |match|
208
+ if (m = match.match(/OPENSSLDIR.*"(.*)"/))
209
+ data['openssldir'] = m[1]
210
+ end
211
+ end if File.file?(ascp_file)
212
+ # log is "-" no need to display
213
+ data.delete('log')
214
+ return data
215
+ end
216
+
178
217
  # download aspera SDK or use local file
179
218
  # extracts ascp binary for current system architecture
180
219
  # @return ascp version (from execution)
@@ -185,6 +185,8 @@ module Aspera
185
185
  ExtraCreatePolicy]
186
186
  # Management port start message
187
187
  MGT_HEADER = 'FASPMGR 2'
188
+ # empty line is separator to end event information
189
+ MGT_FRAME_SEPARATOR = ''
188
190
  # fields description for JSON generation
189
191
  # spellchecker: disable
190
192
  INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
@@ -193,10 +195,11 @@ module Aspera
193
195
  ArgScansCompleted PathScansAttempted FileScansCompleted TransfersAttempted TransfersPassed Delay].freeze
194
196
  BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
195
197
  MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
198
+ BOOLEAN_TRUE = 'Yes'
196
199
  # cspell: enable
197
200
 
198
201
  class << self
199
- # translates legacy event into enhanced (JSON) event
202
+ # translates mgt port event into (enhanced) typed event
200
203
  def enhanced_event_format(event)
201
204
  return event.keys.each_with_object({}) do |e, h|
202
205
  # capital_to_snake_case
@@ -207,14 +210,16 @@ module Aspera
207
210
  .downcase
208
211
  value = event[e]
209
212
  value = value.to_i if INTEGER_FIELDS.include?(e)
210
- value = value.eql?('Yes') if BOOLEAN_FIELDS.include?(e)
213
+ value = value.eql?(BOOLEAN_TRUE) if BOOLEAN_FIELDS.include?(e)
211
214
  h[new_name] = value
212
215
  end
213
216
  end
214
217
  end # class << self
215
218
 
216
219
  def initialize
220
+ # current event being parsed line by line
217
221
  @event_build = nil
222
+ # last fully built event
218
223
  @last_event = nil
219
224
  end
220
225
  attr_reader :last_event
@@ -226,16 +231,16 @@ module Aspera
226
231
  # begin event
227
232
  @event_build = {}
228
233
  when /^([^:]+): (.*)$/
234
+ raise 'mgt port: unexpected line: data without header' if @event_build.nil?
229
235
  # event field
230
236
  @event_build[Regexp.last_match(1)] = Regexp.last_match(2)
231
- when ''
232
- # empty line is separator to end event information
233
- raise 'unexpected empty line' if @event_build.nil?
237
+ when MGT_FRAME_SEPARATOR
238
+ raise 'mgt port: unexpected line: end frame without header' if @event_build.nil?
234
239
  @last_event = @event_build
235
240
  @event_build = nil
236
241
  return @last_event
237
242
  else
238
- raise "unexpected line:[#{line}]"
243
+ raise "mgt port: unexpected line: [#{line}]"
239
244
  end # case
240
245
  return nil
241
246
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/log'
4
+ require 'aspera/assert'
4
5
  require 'aspera/command_line_builder'
5
6
  require 'aspera/temp_file_manager'
6
7
  require 'aspera/fasp/error'
@@ -23,9 +24,10 @@ module Aspera
23
24
  # Short names of columns in manual
24
25
  SUPPORTED_AGENTS_SHORT = SUPPORTED_AGENTS.map{|a|a.to_s[0].to_sym}
25
26
  FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
26
- SUPPORTED_OPTIONS = %i[ascp_args wss check_ignore quiet].freeze
27
+ # options that can be provided to the constructor, and then in @options
28
+ SUPPORTED_OPTIONS = %i[ascp_args wss check_ignore quiet trusted_certs].freeze
27
29
 
28
- private_constant :SUPPORTED_AGENTS, :FILE_LIST_OPTIONS
30
+ private_constant :SUPPORTED_AGENTS, :FILE_LIST_OPTIONS, :SUPPORTED_OPTIONS
29
31
 
30
32
  class << self
31
33
  # Temp folder for file lists, must contain only file lists
@@ -125,17 +127,21 @@ module Aspera
125
127
 
126
128
  # @param options [Hash] key: :wss: bool, :ascp_args: array of strings
127
129
  def initialize(job_spec, options)
130
+ assert_type(job_spec, Hash)
131
+ assert_type(options, Hash)
128
132
  @job_spec = job_spec
129
133
  # check necessary options
130
134
  missing_options = SUPPORTED_OPTIONS - options.keys
131
- raise "Internal: missing options: #{missing_options.join(', ')}" unless missing_options.empty?
135
+ assert(missing_options.empty?){"missing options: #{missing_options.join(', ')}"}
132
136
  @options = SUPPORTED_OPTIONS.each_with_object({}){|o, h| h[o] = options[o]}
133
137
  Log.log.debug{Log.dump(:parameters_options, @options)}
134
- raise 'ascp arguments must be an Array' unless @options[:ascp_args].is_a?(Array)
135
- raise 'ascp arguments must be an Array of String' if @options[:ascp_args].any?{|i|!i.is_a?(String)}
138
+ Log.log.debug{Log.dump(:dismiss_options, options.keys - SUPPORTED_OPTIONS)}
139
+ assert_type(@options[:ascp_args], Array){'ascp_args'}
140
+ assert(@options[:ascp_args].all?(String)){'ascp arguments must Strings'}
136
141
  @builder = Aspera::CommandLineBuilder.new(@job_spec, self.class.description)
137
142
  end
138
143
 
144
+ # either place source files on command line, or add file list file
139
145
  def process_file_list
140
146
  # is the file list provided through EX_ parameters?
141
147
  ascp_file_list_provided = self.class.ts_has_ascp_file_list(@job_spec, @options[:ascp_args])
@@ -160,7 +166,7 @@ module Aspera
160
166
  Log.log.debug('placing source file list on command line (no file list file)')
161
167
  @builder.add_command_line_options(ts_paths_array.map{|i|i['source']})
162
168
  else
163
- raise "All elements of paths must have a 'source' key" unless ts_paths_array.all?{|i|i.key?('source')}
169
+ assert(ts_paths_array.all?{|i|i.key?('source')}){"All elements of paths must have a 'source' key"}
164
170
  is_pair_list = ts_paths_array.any?{|i|i.key?('destination')}
165
171
  raise "All elements of paths must be consistent with 'destination' key" if is_pair_list && !ts_paths_array.all?{|i|i.key?('destination')}
166
172
  # safer option: generate a file list file if there is storage defined for it
@@ -184,92 +190,92 @@ module Aspera
184
190
  @builder.add_command_line_options(["#{option}=#{file_list_file}"]) unless option.nil?
185
191
  end
186
192
 
187
- # translate transfer spec to env vars and command line arguments for ascp
188
- # NOTE: parameters starting with "EX_" (extended) are not standard
189
- def ascp_args
190
- env_args = {
191
- args: [],
192
- env: {},
193
- ascp_version: :ascp
194
- }
195
-
196
- # special cases
197
- @job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
198
-
199
- # notify multi-session was already used, anyway it was deleted by agent direct
200
- raise 'internal error' if @builder.read_param('multi_session')
201
-
193
+ def remote_certificates
194
+ certificates_to_use = []
202
195
  # use web socket secure for session ?
203
196
  if @builder.read_param('wss_enabled') && (@options[:wss] || !@job_spec.key?('fasp_port'))
204
197
  # by default use web socket session if available, unless removed by user
205
198
  @builder.add_command_line_options(['--ws-connect'])
206
- # TODO: option to give order ssh,ws (legacy http is implied bu ssh)
199
+ # TODO: option to give order ssh,ws (legacy http is implied by ssh)
207
200
  # This will need to be cleaned up in aspera core
208
201
  @job_spec['ssh_port'] = @builder.read_param('wss_port')
209
202
  @job_spec.delete('fasp_port')
210
203
  @job_spec.delete('EX_ssh_key_paths')
211
204
  @job_spec.delete('sshfp')
205
+ # ignore cert for wss ?
212
206
  if @options[:check_ignore]&.call(@job_spec['remote_host'], @job_spec['wss_port'])
213
- http_session = Rest.start_http_session("https://#{@job_spec['remote_host']}:#{@job_spec['wss_port']}")
214
- # wss_api = Rest.new(base_url: "/v1/transfer")
215
- # wss_api.read('start') rescue nil
216
207
  wss_cert_file = TempFileManager.instance.new_file_path_global('wss_cert')
217
- File.write(wss_cert_file, http_session.peer_cert.to_pem)
218
- http_session.finish
219
- env_args[:args].unshift('-i', wss_cert_file)
220
- Log.log.debug{"CA certs for wss: remote cert: #{wss_cert_file}"}
221
- else
222
- # set location for CA bundle to be the one of Ruby, see env var SSL_CERT_FILE / SSL_CERT_DIR
223
- @options[:trusted_certs].each do |file|
224
- env_args[:args].unshift('-i', file)
225
- Log.log.debug{"trusted certs for wss: #{file}"}
226
- end
208
+ wss_url = "https://#{@job_spec['remote_host']}:#{@job_spec['wss_port']}"
209
+ File.write(wss_cert_file, Rest.remote_certificates(wss_url))
210
+ certificates_to_use.push(wss_cert_file)
227
211
  end
212
+ # set location for CA bundle to be the one of Ruby, see env var SSL_CERT_FILE / SSL_CERT_DIR
213
+ certificates_to_use.concat(@options[:trusted_certs]) if @options[:trusted_certs]
228
214
  else
229
215
  # remove unused parameter (avoid warning)
230
216
  @job_spec.delete('wss_port')
231
217
  # add SSH bypass keys when authentication is token and no auth is provided
232
218
  if @job_spec.key?('token') && !@job_spec.key?('remote_password')
233
219
  # @job_spec['remote_password'] = Installation.instance.ssh_cert_uuid # not used: no passphrase
234
- Installation.instance.aspera_token_ssh_key_paths.each { |key| env_args[:args].unshift('-i', key) }
220
+ certificates_to_use.concat(Installation.instance.aspera_token_ssh_key_paths)
235
221
  end
236
222
  end
223
+ return certificates_to_use
224
+ end
225
+
226
+ # translate transfer spec to env vars and command line arguments for ascp
227
+ # NOTE: parameters starting with "EX_" (extended) are not standard
228
+ def ascp_args
229
+ env_args = {
230
+ args: [],
231
+ env: {},
232
+ ascp_version: :ascp
233
+ }
234
+
235
+ # special cases
236
+ @job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
237
+
238
+ # notify multi-session was already used, anyway it was deleted by agent direct
239
+ assert(!@builder.read_param('multi_session'))
240
+
241
+ # add ssh or wss certificates
242
+ remote_certificates.each do |cert|
243
+ Log.log.trace1{"adding certificate: #{cert}"}
244
+ env_args[:args].unshift('-i', cert)
245
+ end
237
246
 
238
247
  # process parameters as specified in table
239
248
  @builder.process_params
240
249
 
250
+ base64_destination = false
241
251
  # symbol must be index of Installation.paths
242
252
  if @builder.read_param('use_ascp4')
243
253
  env_args[:ascp_version] = :ascp4
244
254
  else
245
255
  env_args[:ascp_version] = :ascp
246
- # destination will be base64 encoded, put before path arguments
247
- @builder.add_command_line_options(['--dest64'])
256
+ base64_destination = true
248
257
  end
249
- # get list of files to transfer and build arg for ascp
250
- process_file_list
258
+ # destination will be base64 encoded, put this before source path arguments
259
+ @builder.add_command_line_options(['--dest64']) if base64_destination
251
260
  # optional arguments, at the end to override previous ones (to allow override)
252
261
  @builder.add_command_line_options(@builder.read_param('EX_ascp_args'))
253
262
  @builder.add_command_line_options(@options[:ascp_args])
263
+ # get list of source files to transfer and build arg for ascp
264
+ process_file_list
254
265
  # process destination folder
255
266
  destination_folder = @builder.read_param('destination_root') || '/'
256
267
  # ascp4 does not support base64 encoding of destination
257
- destination_folder = Base64.strict_encode64(destination_folder) unless env_args[:ascp_version].eql?(:ascp4)
268
+ destination_folder = Base64.strict_encode64(destination_folder) if base64_destination
258
269
  # destination MUST be last command line argument to ascp
259
270
  @builder.add_command_line_options([destination_folder])
260
-
261
- Log.log.debug{"ascp args: #{env_args}"}
262
-
263
271
  @builder.add_env_args(env_args)
264
-
265
272
  env_args[:args].unshift('-q') if @options[:quiet]
266
-
267
273
  # add fallback cert and key as arguments if needed
268
274
  if ['1', 1, true, 'force'].include?(@job_spec['http_fallback'])
269
275
  env_args[:args].unshift('-Y', Installation.instance.path(:fallback_private_key))
270
276
  env_args[:args].unshift('-I', Installation.instance.path(:fallback_certificate))
271
277
  end
272
-
278
+ Log.log.debug{"ascp args: #{env_args}"}
273
279
  return env_args
274
280
  end
275
281
  end # Parameters
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'singleton'
4
4
  require 'aspera/log'
5
+ require 'aspera/assert'
5
6
 
6
7
  module Aspera
7
8
  module Fasp
@@ -19,10 +20,10 @@ module Aspera
19
20
  def initialize(params=nil)
20
21
  @parameters = DEFAULTS.dup
21
22
  if !params.nil?
22
- raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
23
+ assert_type(params, Hash)
23
24
  params.each do |k, v|
24
- raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.key?(k)
25
- raise "#{k} must be Integer" unless v.is_a?(Integer)
25
+ assert_values(k, DEFAULTS.keys){'resume parameter'}
26
+ assert_type(v, Integer){k}
26
27
  @parameters[k] = v
27
28
  end
28
29
  end
@@ -32,7 +33,7 @@ module Aspera
32
33
  # calls block a number of times (resumes) until success or limit reached
33
34
  # this is re-entrant, one resumer can handle multiple transfers in //
34
35
  def execute_with_resume
35
- raise 'block mandatory' unless block_given?
36
+ assert(block_given?)
36
37
  # maximum of retry
37
38
  remaining_resumes = @parameters[:iter_max]
38
39
  sleep_seconds = @parameters[:sleep_initial]
@@ -43,9 +44,10 @@ module Aspera
43
44
  begin
44
45
  # call provided block
45
46
  yield
47
+ # exit retry loop if success
46
48
  break
47
49
  rescue Fasp::Error => e
48
- Log.log.warn{"An error occurred: #{e.message}"}
50
+ Log.log.warn{"An error occurred during transfer: #{e.message}"}
49
51
  # failure in ascp
50
52
  if e.retryable?
51
53
  # exit if we exceed the max number of retry