aspera-cli 4.24.1 → 4.24.2

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 (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +15 -2
  4. data/README.md +745 -436
  5. data/bin/ascli +20 -1
  6. data/bin/asession +23 -27
  7. data/lib/aspera/agent/base.rb +10 -21
  8. data/lib/aspera/agent/connect.rb +2 -3
  9. data/lib/aspera/agent/desktop.rb +2 -2
  10. data/lib/aspera/agent/direct.rb +49 -32
  11. data/lib/aspera/agent/factory.rb +31 -0
  12. data/lib/aspera/api/aoc.rb +79 -49
  13. data/lib/aspera/api/faspex.rb +212 -0
  14. data/lib/aspera/api/node.rb +99 -84
  15. data/lib/aspera/ascp/installation.rb +22 -21
  16. data/lib/aspera/ascp/management.rb +119 -23
  17. data/lib/aspera/assert.rb +14 -8
  18. data/lib/aspera/cli/extended_value.rb +15 -15
  19. data/lib/aspera/cli/formatter.rb +7 -5
  20. data/lib/aspera/cli/hints.rb +8 -0
  21. data/lib/aspera/cli/info.rb +4 -4
  22. data/lib/aspera/cli/main.rb +55 -70
  23. data/lib/aspera/cli/manager.rb +7 -4
  24. data/lib/aspera/cli/plugins/alee.rb +2 -1
  25. data/lib/aspera/cli/plugins/aoc.rb +110 -186
  26. data/lib/aspera/cli/plugins/ats.rb +4 -4
  27. data/lib/aspera/cli/plugins/base.rb +335 -0
  28. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  29. data/lib/aspera/cli/plugins/config.rb +249 -220
  30. data/lib/aspera/cli/plugins/console.rb +15 -15
  31. data/lib/aspera/cli/plugins/cos.rb +2 -2
  32. data/lib/aspera/cli/plugins/factory.rb +78 -0
  33. data/lib/aspera/cli/plugins/faspex.rb +17 -20
  34. data/lib/aspera/cli/plugins/faspex5.rb +79 -193
  35. data/lib/aspera/cli/plugins/faspio.rb +14 -13
  36. data/lib/aspera/cli/plugins/httpgw.rb +13 -12
  37. data/lib/aspera/cli/plugins/node.rb +34 -32
  38. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  39. data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
  40. data/lib/aspera/cli/plugins/preview.rb +4 -4
  41. data/lib/aspera/cli/plugins/server.rb +15 -13
  42. data/lib/aspera/cli/plugins/shares.rb +18 -15
  43. data/lib/aspera/cli/sync_actions.rb +1 -1
  44. data/lib/aspera/cli/transfer_agent.rb +24 -20
  45. data/lib/aspera/cli/transfer_progress.rb +6 -6
  46. data/lib/aspera/cli/version.rb +3 -3
  47. data/lib/aspera/cli/wizard.rb +65 -53
  48. data/lib/aspera/colors.rb +6 -0
  49. data/lib/aspera/command_line_builder.rb +45 -50
  50. data/lib/aspera/command_line_converter.rb +2 -1
  51. data/lib/aspera/coverage.rb +1 -1
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +10 -7
  54. data/lib/aspera/faspex_gw.rb +6 -4
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/keychain/macos_security.rb +1 -1
  57. data/lib/aspera/log.rb +37 -9
  58. data/lib/aspera/nagios.rb +1 -1
  59. data/lib/aspera/oauth/base.rb +17 -10
  60. data/lib/aspera/oauth/factory.rb +8 -8
  61. data/lib/aspera/oauth/web.rb +2 -2
  62. data/lib/aspera/products/connect.rb +4 -3
  63. data/lib/aspera/products/desktop.rb +1 -4
  64. data/lib/aspera/products/other.rb +9 -1
  65. data/lib/aspera/products/transferd.rb +0 -1
  66. data/lib/aspera/rest.rb +126 -83
  67. data/lib/aspera/ssh.rb +3 -3
  68. data/lib/aspera/sync/args.schema.yaml +46 -3
  69. data/lib/aspera/sync/conf.schema.yaml +130 -94
  70. data/lib/aspera/sync/operations.rb +16 -16
  71. data/lib/aspera/temp_file_manager.rb +17 -5
  72. data/lib/aspera/transfer/error.rb +16 -7
  73. data/lib/aspera/transfer/parameters.rb +34 -20
  74. data/lib/aspera/transfer/resumer.rb +74 -0
  75. data/lib/aspera/transfer/spec.rb +4 -3
  76. data/lib/aspera/transfer/spec.schema.yaml +132 -51
  77. data/lib/aspera/transfer/spec_doc.rb +41 -35
  78. data/lib/aspera/uri_reader.rb +1 -1
  79. data/lib/aspera/web_auth.rb +6 -6
  80. data.tar.gz.sig +0 -0
  81. metadata +9 -7
  82. metadata.gz.sig +0 -0
  83. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  84. data/lib/aspera/cli/plugin.rb +0 -333
  85. data/lib/aspera/cli/plugin_factory.rb +0 -81
  86. data/lib/aspera/resumer.rb +0 -77
  87. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -18,16 +18,16 @@ module Aspera
18
18
  module Sync
19
19
  # builds command line arg for async and execute it
20
20
  module Operations
21
- # sync direction
21
+ # Sync direction
22
22
  DIRECTIONS = %i[push pull bidi].freeze
23
- # default direction for sync
24
- DEFAULT_DIRECTION = :push
23
+ # Default direction for sync
24
+ DEFAULT_DIRECTION = DIRECTIONS.first
25
25
 
26
26
  class << self
27
27
  # Set `remote_dir` in sync parameters based on transfer spec
28
- # @param params [Hash] Sync parameters, old or new format
29
- # @param remote_dir_key [String] key to update in above hash
30
- # @param transfer_spec [Hash] transfer spec
28
+ # @param params [Hash] Sync parameters, in `conf` or `args` format.
29
+ # @param remote_dir_key [String] Key to update in above hash
30
+ # @param transfer_spec [Hash] Transfer spec
31
31
  def update_remote_dir(params, remote_dir_key, transfer_spec)
32
32
  if transfer_spec.dig(*%w[tags aspera node file_id])
33
33
  # in AoC, use gen4
@@ -126,21 +126,21 @@ module Aspera
126
126
  transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
127
127
  Log.dump(:auth_ts, transfer_spec)
128
128
  transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
129
- tspec_to_sync_info(transfer_spec, session, SESSION_SCHEMA)
129
+ tspec_to_sync_info(transfer_spec, session, ARGS_SESSION_SCHEMA)
130
130
  session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
131
131
  update_remote_dir(session, 'remote_dir', transfer_spec)
132
132
  end
133
133
  end
134
134
  if params.key?('instance')
135
135
  Aspera.assert_type(params['instance'], Hash)
136
- instance_builder = CommandLineBuilder.new(params['instance'], INSTANCE_SCHEMA, CommandLineConverter)
136
+ instance_builder = CommandLineBuilder.new(params['instance'], ARGS_INSTANCE_SCHEMA, CommandLineConverter)
137
137
  instance_builder.process_params
138
138
  instance_builder.add_env_args(env_args)
139
139
  end
140
140
  params['sessions'].each do |session_params|
141
141
  Aspera.assert_type(session_params, Hash)
142
142
  Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
143
- session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, CommandLineConverter)
143
+ session_builder = CommandLineBuilder.new(session_params, ARGS_SESSION_SCHEMA, CommandLineConverter)
144
144
  session_builder.process_params
145
145
  session_builder.add_env_args(env_args)
146
146
  end
@@ -277,20 +277,20 @@ module Aspera
277
277
  end
278
278
  # Private stuff:
279
279
  # Read JSON schema and mapping to command line options
280
- INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'args')
281
- SESSION_SCHEMA = INSTANCE_SCHEMA['properties']['sessions']['items']
282
- INSTANCE_SCHEMA['properties'].delete('sessions')
280
+ ARGS_INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'args')
281
+ ARGS_SESSION_SCHEMA = ARGS_INSTANCE_SCHEMA['properties']['sessions']['items']
282
+ ARGS_INSTANCE_SCHEMA['properties'].delete('sessions')
283
283
  CONF_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'conf')
284
- CommandLineBuilder.adjust_properties_defaults(INSTANCE_SCHEMA['properties'])
285
- CommandLineBuilder.adjust_properties_defaults(SESSION_SCHEMA['properties'])
286
- CommandLineBuilder.adjust_properties_defaults(CONF_SCHEMA['properties'])
284
+ CommandLineBuilder.validate_schema(ARGS_INSTANCE_SCHEMA)
285
+ CommandLineBuilder.validate_schema(ARGS_SESSION_SCHEMA)
286
+ CommandLineBuilder.validate_schema(CONF_SCHEMA)
287
287
  CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
288
288
  ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
289
289
  PRIVATE_FOLDER = '.private-asp'
290
290
  ASYNC_DB = 'snap.db'
291
291
  PARAM_KEYS = %w[local sessions].freeze
292
292
 
293
- private_constant :INSTANCE_SCHEMA, :SESSION_SCHEMA, :CONF_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB, :PARAM_KEYS
293
+ private_constant :ARGS_INSTANCE_SCHEMA, :ARGS_SESSION_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB, :PARAM_KEYS
294
294
  end
295
295
  end
296
296
  end
@@ -17,17 +17,29 @@ module Aspera
17
17
  private_constant :SEC_IN_DAY, :FILE_LIST_AGE_MAX_SEC
18
18
 
19
19
  attr_accessor :cleanup_on_exit
20
+ attr_reader :global_temp
20
21
 
21
22
  def initialize
22
23
  @created_files = []
23
24
  @cleanup_on_exit = true
25
+ @global_temp = Etc.systmpdir
26
+ end
27
+
28
+ def global_temp=(value)
29
+ @global_temp = case value
30
+ when '@env' then Dir.tmpdir
31
+ when '@sys' then Etc.systmpdir
32
+ else value
33
+ end
24
34
  end
25
35
 
26
36
  def delete_file(filepath)
27
37
  File.delete(filepath) if @cleanup_on_exit
38
+ rescue => e
39
+ Log.log.warn{"Problem deleting file: #{filepath}: #{e.message}"}
28
40
  end
29
41
 
30
- # call this on process exit
42
+ # Call this on process exit
31
43
  def cleanup
32
44
  @created_files.each do |filepath|
33
45
  delete_file(filepath) if File.file?(filepath)
@@ -35,7 +47,7 @@ module Aspera
35
47
  @created_files = []
36
48
  end
37
49
 
38
- # ensure that provided folder exists, or create it, generate a unique filename
50
+ # Ensure that provided folder exists, or create it, generate a unique filename
39
51
  # @return path to that unique file
40
52
  def new_file_path_in_folder(temp_folder, prefix: nil, suffix: nil)
41
53
  FileUtils.mkdir_p(temp_folder)
@@ -44,7 +56,7 @@ module Aspera
44
56
  new_file
45
57
  end
46
58
 
47
- # same as above but in global temp folder, with user's name
59
+ # Same as above but in global temp folder, with user's name
48
60
  def new_file_path_global(prefix = nil, suffix: nil)
49
61
  username =
50
62
  begin
@@ -53,11 +65,11 @@ module Aspera
53
65
  'unknown_user'
54
66
  end
55
67
  prefix = [prefix, username].compact.join('-')
56
- new_file_path_in_folder(Etc.systmpdir, prefix: prefix, suffix: suffix)
68
+ new_file_path_in_folder(@global_temp, prefix: prefix, suffix: suffix)
57
69
  end
58
70
 
71
+ # Garbage collect undeleted files
59
72
  def cleanup_expired(temp_folder)
60
- # garbage collect undeleted files
61
73
  Dir.entries(temp_folder).each do |name|
62
74
  file_path = File.join(temp_folder, name)
63
75
  age_sec = (Time.now - File.stat(file_path).mtime).to_i
@@ -1,24 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/transfer/error_info'
3
+ require 'aspera/ascp/management'
4
4
 
5
5
  module Aspera
6
6
  module Transfer
7
- # error raised if transfer fails
7
+ # Error raised if transfer fails
8
8
  class Error < StandardError
9
+ # Error code like on management port
9
10
  attr_reader :err_code
10
11
 
11
- def initialize(message, err_code = nil)
12
- super(message)
13
- @err_code = err_code
12
+ # @param description [String] `Description` on management port
13
+ # @param code [Integer] `Description` on management port, use zero if unknown
14
+ def initialize(description, code: nil)
15
+ super(description)
16
+ @err_code = code.to_i
14
17
  end
15
18
 
19
+ # @return [Hash] Information on that error
16
20
  def info
17
- r = ERROR_INFO[@err_code] || {r: false, c: 'UNKNOWN', m: 'unknown', a: 'unknown'}
21
+ r = Ascp::Management::ERRORS[@err_code] || Ascp::Management::ERRORS[0]
18
22
  return r.merge({i: @err_code})
19
23
  end
20
24
 
21
- def retryable?; info[:r]; end
25
+ # Is that transfer error retryable ?
26
+ # @param message [String, nil] Optional actual message on management port
27
+ def retryable?
28
+ return false if @err_code.eql?(14) && message.eql?('Target address not available')
29
+ info[:r]
30
+ end
22
31
  end
23
32
  end
24
33
  end
@@ -18,11 +18,8 @@ require 'openssl'
18
18
 
19
19
  module Aspera
20
20
  module Transfer
21
- # translate transfer specification to ascp parameter list
21
+ # Translate transfer specification to `ascp` parameter list
22
22
  class Parameters
23
- # `ascp` options to provide a file list
24
- FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
25
- private_constant :FILE_LIST_OPTIONS
26
23
  HTTP_FALLBACK_ACTIVATION_VALUES = ['1', 1, true, 'force'].freeze
27
24
 
28
25
  class << self
@@ -30,7 +27,6 @@ module Aspera
30
27
  def file_list_folder=(value)
31
28
  @file_list_folder = value
32
29
  return if @file_list_folder.nil?
33
-
34
30
  FileUtils.mkdir_p(@file_list_folder)
35
31
  TempFileManager.instance.cleanup_expired(@file_list_folder)
36
32
  end
@@ -42,14 +38,19 @@ module Aspera
42
38
  @file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
43
39
  end
44
40
 
45
- # file list is provided directly with ascp arguments
41
+ # File list is provided directly with ascp arguments
46
42
  # @columns ascp_args [Array,NilClass] ascp arguments
47
43
  def ascp_args_file_list?(ascp_args)
48
44
  ascp_args&.any?{ |i| FILE_LIST_OPTIONS.include?(i)}
49
45
  end
50
46
  end
51
47
 
52
- # @columns options [Hash] key: :wss: bool, :ascp_args: array of strings
48
+ # @param job_spec [Hash] Transfer spec
49
+ # @param ascp_args [Array] Other ascp args
50
+ # @param quiet [Bool] Remove ascp output
51
+ # @param trusted_certs [Array] Trusted certificates
52
+ # @param client_ssh_key [Symbol] :rsa or :dsa
53
+ # @param check_ignore_cb [Proc] Callback
53
54
  def initialize(
54
55
  job_spec,
55
56
  ascp_args: nil,
@@ -60,17 +61,17 @@ module Aspera
60
61
  check_ignore_cb: nil
61
62
  )
62
63
  @job_spec = job_spec
64
+ Aspera.assert_type(@job_spec, Hash)
63
65
  @ascp_args = ascp_args.nil? ? [] : ascp_args
66
+ Aspera.assert_type(@ascp_args, Array){'ascp_args'}
67
+ Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
64
68
  @wss = wss
65
69
  @quiet = quiet
66
70
  @trusted_certs = trusted_certs.nil? ? [] : trusted_certs
67
- @client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
68
- @check_ignore_cb = check_ignore_cb
69
- Aspera.assert_type(@job_spec, Hash)
70
- Aspera.assert_type(@ascp_args, Array){'ascp_args'}
71
- Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
72
71
  Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
72
+ @client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
73
73
  Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
74
+ @check_ignore_cb = check_ignore_cb
74
75
  @builder = CommandLineBuilder.new(@job_spec, Spec::SCHEMA, CommandLineConverter)
75
76
  end
76
77
 
@@ -109,7 +110,7 @@ module Aspera
109
110
  Log.log.debug{"#{file_list_option}=\n#{File.read(file_list_file)}".red}
110
111
  end
111
112
  end
112
- @builder.add_command_line_options(["#{file_list_option}=#{file_list_file}"]) unless file_list_option.nil?
113
+ @builder.add_command_line_options("#{file_list_option}=#{file_list_file}") unless file_list_option.nil?
113
114
  end
114
115
 
115
116
  # @return the list of certificates (option `-i`) to use when token/ssh or wss are used
@@ -118,7 +119,7 @@ module Aspera
118
119
  # use web socket secure for session ?
119
120
  if @builder.read_param('wss_enabled') && (@wss || !@job_spec.key?('fasp_port'))
120
121
  # by default use web socket session if available, unless removed by user
121
- @builder.add_command_line_options(['--ws-connect'])
122
+ @builder.add_command_line_options('--ws-connect')
122
123
  # TODO: option to give order ssh,ws (legacy http is implied by ssh)
123
124
  # This will need to be cleaned up in aspera core
124
125
  @job_spec['ssh_port'] = @builder.read_param('wss_port')
@@ -148,7 +149,7 @@ module Aspera
148
149
  return certificates_to_use
149
150
  end
150
151
 
151
- # translate transfer spec to env vars and command line arguments for ascp
152
+ # Translate transfer spec to env vars and command line arguments for `ascp`
152
153
  def ascp_args
153
154
  env_args = {
154
155
  args: [],
@@ -156,18 +157,27 @@ module Aspera
156
157
  name: :ascp
157
158
  }
158
159
 
159
- # special cases
160
+ # Special cases
160
161
  @job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
161
162
 
162
- # notify multi-session was already used, anyway it was deleted by agent direct
163
+ # Notify multi-session was already used, anyway it was deleted by agent direct
163
164
  Aspera.assert(!@builder.read_param('multi_session'))
164
165
 
165
- # add ssh or wss certificates
166
+ # Add ssh or wss certificates
166
167
  # (reverse, to keep order, as we unshift)
167
168
  remote_certificates&.reverse_each do |cert|
168
169
  env_args[:args].unshift('-i', cert)
169
170
  end
170
171
 
172
+ case (delete_source = @builder.read_param('delete_source'))
173
+ when true
174
+ DELETE_EQUIV.each{ |i| @job_spec[i] = true}
175
+ when false
176
+ DELETE_EQUIV.each{ |i| @job_spec.delete(i)}
177
+ when nil
178
+ else Aspera.error_unexpected_value(delete_source){'delete_source'}
179
+ end
180
+
171
181
  # process parameters as specified in table
172
182
  @builder.process_params
173
183
 
@@ -180,7 +190,7 @@ module Aspera
180
190
  base64_destination = true
181
191
  end
182
192
  # destination will be base64 encoded, put this before source path arguments
183
- @builder.add_command_line_options(['--dest64']) if base64_destination
193
+ @builder.add_command_line_options('--dest64') if base64_destination
184
194
  # optional arguments, at the end to override previous ones (to allow override)
185
195
  @builder.add_command_line_options(@ascp_args)
186
196
  # get list of source files to transfer and build arg for ascp
@@ -190,7 +200,7 @@ module Aspera
190
200
  # ascp4 does not support base64 encoding of destination
191
201
  destination_folder = Base64.strict_encode64(destination_folder) if base64_destination
192
202
  # destination MUST be last command line argument to ascp
193
- @builder.add_command_line_options([destination_folder])
203
+ @builder.add_command_line_options(destination_folder)
194
204
  @builder.add_env_args(env_args)
195
205
  env_args[:args].unshift('-q') if @quiet
196
206
  # add fallback cert and key as arguments if needed
@@ -203,6 +213,10 @@ module Aspera
203
213
  Log.log.debug{"ascp args: #{env_args}"}
204
214
  return env_args
205
215
  end
216
+ DELETE_EQUIV = %w[remove_after_transfer remove_empty_directories remove_empty_source_directory]
217
+ # `ascp` options to provide a file list
218
+ FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
219
+ private_constant :DELETE_EQUIV, :FILE_LIST_OPTIONS
206
220
  end
207
221
  end
208
222
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'aspera/log'
5
+ require 'aspera/assert'
6
+ require 'aspera/transfer/error'
7
+
8
+ module Aspera
9
+ module Transfer
10
+ # Implements a simple resume policy
11
+ class Resumer
12
+ # @param iter_max [Integer] Maximum number of executions
13
+ # @param sleep_initial [Integer] Initial wait to re-execute
14
+ # @param sleep_factor [Integer] Multiplier
15
+ # @param sleep_max. [Integer] Max iterations
16
+ def initialize(
17
+ iter_max: 7,
18
+ sleep_initial: 2,
19
+ sleep_factor: 2,
20
+ sleep_max: 60
21
+ )
22
+ Aspera.assert_type(iter_max, Integer){k}
23
+ @iter_max = iter_max
24
+ Aspera.assert_type(sleep_initial, Integer){k}
25
+ @sleep_initial = sleep_initial
26
+ Aspera.assert_type(sleep_factor, Integer){k}
27
+ @sleep_factor = sleep_factor
28
+ Aspera.assert_type(sleep_max, Integer){k}
29
+ @sleep_max = sleep_max
30
+ end
31
+
32
+ # Calls block a number of times (resumes) until success or limit reached
33
+ # This is re-entrant, one resumer can handle multiple transfers in //
34
+ #
35
+ # @param block [Proc]
36
+ def execute_with_resume
37
+ Aspera.assert(block_given?)
38
+ # maximum of retry
39
+ remaining_resumes = @iter_max
40
+ sleep_seconds = @sleep_initial
41
+ Log.log.debug{"retries=#{remaining_resumes}"}
42
+ # try to send the file until ascp is successful
43
+ loop do
44
+ Log.log.debug('Transfer session starting')
45
+ begin
46
+ # Call provided block: execute transfer
47
+ yield
48
+ # Exit retry loop if success
49
+ break
50
+ rescue Error => e
51
+ Log.log.warn{"A transfer error occurred during transfer: #{e.message}"}
52
+ Log.log.debug{"Retryable ? #{e.retryable?}"}
53
+ # do not retry non-retryable
54
+ raise unless e.retryable?
55
+ # exit if we exceed the max number of retry
56
+ raise Error, "Maximum number of retry reached: #{@iter_max}" if remaining_resumes <= 0
57
+ end
58
+
59
+ # take this retry in account
60
+ remaining_resumes -= 1
61
+ Log.log.warn{"Resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})"}
62
+
63
+ # wait a bit before retrying, maybe network condition will be better
64
+ sleep(sleep_seconds)
65
+
66
+ # increase retry period
67
+ sleep_seconds *= @sleep_factor
68
+ # cap value
69
+ sleep_seconds = @sleep_max if sleep_seconds > @sleep_max
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -5,7 +5,8 @@ require 'aspera/assert'
5
5
 
6
6
  module Aspera
7
7
  module Transfer
8
- # parameters for Transfer Spec
8
+ # Parameters for Transfer Spec
9
+ # Parameters are generated from JSON Schema.
9
10
  class Spec
10
11
  # default transfer username for access key based transfers
11
12
  ACCESS_KEY_TRANSFER_USER = 'xfer'
@@ -44,12 +45,12 @@ module Aspera
44
45
  end
45
46
 
46
47
  def fix_transferd_resume_policy(transfer_spec)
47
- # Fix discrepency in transfer spec
48
+ # Fix discrepancy in transfer spec
48
49
  transfer_spec['resume_policy'] = POLICY_FIX[transfer_spec['resume_policy']] if transfer_spec.key?('resume_policy')
49
50
  end
50
51
  end
51
52
  SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'spec')
52
- CommandLineBuilder.adjust_properties_defaults(SCHEMA['properties'])
53
+ CommandLineBuilder.validate_schema(SCHEMA, ascp: true)
53
54
  # define constants for enums of parameters: <parameter>_<enum>, e.g. CIPHER_AES_128, DIRECTION_SEND, ...
54
55
  SCHEMA['properties'].each do |name, description|
55
56
  next unless description['enum'].is_a?(Array)