aspera-cli 4.23.0 → 4.24.1

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 (110) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +37 -1
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +2109 -1300
  6. data/bin/ascli +2 -1
  7. data/bin/asession +4 -4
  8. data/lib/aspera/agent/base.rb +4 -0
  9. data/lib/aspera/agent/connect.rb +20 -18
  10. data/lib/aspera/agent/desktop.rb +14 -11
  11. data/lib/aspera/agent/direct.rb +39 -31
  12. data/lib/aspera/agent/httpgw.rb +2 -2
  13. data/lib/aspera/agent/node.rb +9 -11
  14. data/lib/aspera/agent/transferd.rb +18 -11
  15. data/lib/aspera/api/aoc.rb +44 -31
  16. data/lib/aspera/api/cos_node.rb +7 -5
  17. data/lib/aspera/api/httpgw.rb +15 -18
  18. data/lib/aspera/api/node.rb +104 -22
  19. data/lib/aspera/ascmd.rb +22 -16
  20. data/lib/aspera/ascp/installation.rb +37 -40
  21. data/lib/aspera/ascp/management.rb +5 -4
  22. data/lib/aspera/assert.rb +54 -23
  23. data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
  24. data/lib/aspera/cli/error.rb +1 -1
  25. data/lib/aspera/cli/extended_value.rb +28 -29
  26. data/lib/aspera/cli/formatter.rb +191 -168
  27. data/lib/aspera/cli/hints.rb +29 -3
  28. data/lib/aspera/cli/main.rb +138 -107
  29. data/lib/aspera/cli/manager.rb +50 -30
  30. data/lib/aspera/cli/plugin.rb +148 -77
  31. data/lib/aspera/cli/plugin_factory.rb +2 -2
  32. data/lib/aspera/cli/plugins/aoc.rb +189 -70
  33. data/lib/aspera/cli/plugins/ats.rb +15 -13
  34. data/lib/aspera/cli/plugins/config.rb +100 -214
  35. data/lib/aspera/cli/plugins/console.rb +49 -18
  36. data/lib/aspera/cli/plugins/cos.rb +4 -4
  37. data/lib/aspera/cli/plugins/faspex.rb +45 -51
  38. data/lib/aspera/cli/plugins/faspex5.rb +164 -165
  39. data/lib/aspera/cli/plugins/faspio.rb +6 -5
  40. data/lib/aspera/cli/plugins/httpgw.rb +2 -2
  41. data/lib/aspera/cli/plugins/node.rb +144 -162
  42. data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
  43. data/lib/aspera/cli/plugins/preview.rb +26 -29
  44. data/lib/aspera/cli/plugins/server.rb +28 -28
  45. data/lib/aspera/cli/plugins/shares.rb +40 -28
  46. data/lib/aspera/cli/sync_actions.rb +101 -80
  47. data/lib/aspera/cli/transfer_agent.rb +51 -50
  48. data/lib/aspera/cli/transfer_progress.rb +29 -20
  49. data/lib/aspera/cli/version.rb +1 -1
  50. data/lib/aspera/cli/wizard.rb +157 -0
  51. data/lib/aspera/colors.rb +13 -8
  52. data/lib/aspera/command_line_builder.rb +28 -22
  53. data/lib/aspera/command_line_converter.rb +31 -0
  54. data/lib/aspera/environment.rb +145 -101
  55. data/lib/aspera/faspex_gw.rb +1 -1
  56. data/lib/aspera/faspex_postproc.rb +3 -2
  57. data/lib/aspera/hash_ext.rb +1 -1
  58. data/lib/aspera/id_generator.rb +10 -10
  59. data/lib/aspera/keychain/base.rb +18 -0
  60. data/lib/aspera/keychain/encrypted_hash.rb +6 -12
  61. data/lib/aspera/keychain/factory.rb +9 -3
  62. data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
  63. data/lib/aspera/keychain/macos_security.rb +13 -13
  64. data/lib/aspera/log.rb +91 -19
  65. data/lib/aspera/nagios.rb +5 -6
  66. data/lib/aspera/node_simulator.rb +12 -7
  67. data/lib/aspera/oauth/base.rb +5 -3
  68. data/lib/aspera/oauth/factory.rb +24 -18
  69. data/lib/aspera/oauth/jwt.rb +13 -1
  70. data/lib/aspera/oauth/url_json.rb +3 -3
  71. data/lib/aspera/oauth/web.rb +5 -3
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -3
  74. data/lib/aspera/preview/generator.rb +25 -12
  75. data/lib/aspera/preview/terminal.rb +10 -7
  76. data/lib/aspera/preview/utils.rb +11 -9
  77. data/lib/aspera/products/connect.rb +1 -1
  78. data/lib/aspera/products/desktop.rb +1 -1
  79. data/lib/aspera/products/other.rb +2 -2
  80. data/lib/aspera/products/transferd.rb +8 -6
  81. data/lib/aspera/proxy_auto_config.rb +1 -1
  82. data/lib/aspera/rest.rb +29 -22
  83. data/lib/aspera/rest_call_error.rb +1 -1
  84. data/lib/aspera/resumer.rb +1 -1
  85. data/lib/aspera/secret_hider.rb +46 -40
  86. data/lib/aspera/ssh.rb +13 -3
  87. data/lib/aspera/sync/args.schema.yaml +102 -0
  88. data/lib/aspera/sync/conf.schema.yaml +701 -0
  89. data/lib/aspera/sync/database.rb +83 -0
  90. data/lib/aspera/sync/operations.rb +296 -0
  91. data/lib/aspera/temp_file_manager.rb +3 -2
  92. data/lib/aspera/transfer/error.rb +1 -1
  93. data/lib/aspera/transfer/error_info.rb +1 -2
  94. data/lib/aspera/transfer/faux_file.rb +11 -10
  95. data/lib/aspera/transfer/parameters.rb +6 -5
  96. data/lib/aspera/transfer/spec.rb +15 -1
  97. data/lib/aspera/transfer/spec.schema.yaml +316 -293
  98. data/lib/aspera/transfer/spec_doc.rb +34 -16
  99. data/lib/aspera/transfer/uri.rb +5 -5
  100. data/lib/aspera/uri_reader.rb +14 -10
  101. data/lib/aspera/web_auth.rb +2 -2
  102. data/lib/aspera/web_server_simple.rb +2 -2
  103. data.tar.gz.sig +0 -0
  104. metadata +15 -13
  105. metadata.gz.sig +0 -0
  106. data/lib/aspera/transfer/async_conf.schema.yaml +0 -716
  107. data/lib/aspera/transfer/convert.rb +0 -29
  108. data/lib/aspera/transfer/sync.rb +0 -232
  109. data/lib/aspera/transfer/sync_instance.schema.yaml +0 -20
  110. data/lib/aspera/transfer/sync_session.schema.yaml +0 -86
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(JRUBY_VERSION)
4
+ require 'jdbc/sqlite3'
5
+ Jdbc::SQLite3.load_driver
6
+ require 'sequel'
7
+ else
8
+ require 'sqlite3'
9
+ end
10
+
11
+ # A wrapper class that provides common API for Ruby and JRuby
12
+ class SqLite3Wrapper
13
+ def initialize(db_path)
14
+ @db_path = db_path
15
+ end
16
+
17
+ if defined?(JRUBY_VERSION)
18
+ def execute(sql)
19
+ db = Sequel.connect("jdbc:sqlite:#{@db_path}")
20
+ begin
21
+ normalize_rows(db.fetch(sql).all)
22
+ ensure
23
+ db.disconnect
24
+ end
25
+ end
26
+ else
27
+ def execute(sql)
28
+ db = SQLite3::Database.new(@db_path).tap{ |d| d.results_as_hash = true}
29
+ begin
30
+ normalize_rows(db.execute(sql))
31
+ ensure
32
+ db.close
33
+ end
34
+ end
35
+ end
36
+
37
+ # The table contains a single row
38
+ def single_table(table_name)
39
+ execute("SELECT * FROM #{table_name} LIMIT 1").first
40
+ end
41
+
42
+ def full_table(table_name)
43
+ execute("SELECT * FROM #{table_name}")
44
+ end
45
+
46
+ private
47
+
48
+ def normalize_rows(rows)
49
+ rows.map{ |r| r.transform_keys(&:to_s)}
50
+ end
51
+ end
52
+
53
+ module Aspera
54
+ module Sync
55
+ class Database
56
+ def initialize(db_path)
57
+ @db = SqLite3Wrapper.new(db_path)
58
+ end
59
+
60
+ def overview
61
+ tables = @db.execute("SELECT name FROM sqlite_master WHERE type='table';")
62
+ tables.flat_map do |table_row|
63
+ table_name = table_row['name']
64
+ @db.execute("PRAGMA table_info(#{table_name});").map do |col|
65
+ {'table' => table_name}.merge(col)
66
+ end
67
+ end
68
+ end
69
+
70
+ def meta
71
+ @db.single_table('sync_snapmeta_table')
72
+ end
73
+
74
+ def counters
75
+ @db.single_table('sync_snap_counters_table')
76
+ end
77
+
78
+ def file_info
79
+ @db.full_table('sync_snapdb_table')
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ # cspell:words logdir bidi watchd cooloff asyncadmin
4
+
5
+ require 'aspera/ascp/installation'
6
+ require 'aspera/agent/direct'
7
+ require 'aspera/command_line_converter'
8
+ require 'aspera/command_line_builder'
9
+ require 'aspera/log'
10
+ require 'aspera/assert'
11
+ require 'aspera/environment'
12
+ require 'json'
13
+ require 'base64'
14
+ require 'open3'
15
+ require 'English'
16
+
17
+ module Aspera
18
+ module Sync
19
+ # builds command line arg for async and execute it
20
+ module Operations
21
+ # sync direction
22
+ DIRECTIONS = %i[push pull bidi].freeze
23
+ # default direction for sync
24
+ DEFAULT_DIRECTION = :push
25
+
26
+ class << self
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
31
+ def update_remote_dir(params, remote_dir_key, transfer_spec)
32
+ if transfer_spec.dig(*%w[tags aspera node file_id])
33
+ # in AoC, use gen4
34
+ params[remote_dir_key] = '/'
35
+ elsif transfer_spec['cookie']&.start_with?('aspera.shares2')
36
+ # TODO : something more generic, independent of Shares
37
+ # in Shares, the actual folder on remote end is not always the same as the name of the share
38
+ remote_key = transfer_spec['direction'].eql?('send') ? 'destination' : 'source'
39
+ actual_remote = transfer_spec['paths']&.first&.[](remote_key)
40
+ params[remote_dir_key] = actual_remote if actual_remote
41
+ end
42
+ nil
43
+ end
44
+
45
+ # Get certificates to use for remote connection
46
+ # @param remote [Hash] remote connection parameters
47
+ # @return [Array<String>] list of certificate file paths
48
+ def remote_certificates(remote)
49
+ certificates_to_use = []
50
+ # use web socket secure for session ?
51
+ if remote['connect_mode']&.eql?('ws')
52
+ remote.delete('port')
53
+ remote.delete('fingerprint')
54
+ # ignore cert for wss ?
55
+ # if @options[:check_ignore_cb]&.call(remote['host'], remote['ws_port'])
56
+ # wss_cert_file = TempFileManager.instance.new_file_path_global('wss_cert')
57
+ # wss_url = "https://#{remote['host']}:#{remote['ws_port']}"
58
+ # File.write(wss_cert_file, Rest.remote_certificate_chain(wss_url))
59
+ # certificates_to_use.push(wss_cert_file)
60
+ # end
61
+ # set location for CA bundle to be the one of Ruby, see env var SSL_CERT_FILE / SSL_CERT_DIR
62
+ # certificates_to_use.concat(@options[:trusted_certs]) if @options[:trusted_certs]
63
+ else
64
+ # remove unused parameter (avoid warning)
65
+ remote.delete('ws_port')
66
+ # add SSH bypass keys when authentication is token and no auth is provided
67
+ certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa)) if remote.key?('token') && !remote.key?('pass')
68
+ end
69
+ return certificates_to_use
70
+ end
71
+
72
+ # Get symbol of sync direction, defaulting to :push
73
+ # @param params [Hash] Sync parameters, old or new format
74
+ # @return [Symbol] direction symbol, one of :push, :pull, :bidi
75
+ def direction_sym(params)
76
+ (params['direction'] || DEFAULT_DIRECTION).to_sym
77
+ end
78
+
79
+ # Start the sync process
80
+ # @param params [Hash] Sync parameters, old or new format
81
+ # @param opt_ts [Hash] Optional transfer spec
82
+ # @param &block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
83
+ def start(params, opt_ts = nil)
84
+ Log.dump(:sync_params_initial, params)
85
+ Aspera.assert_type(params, Hash)
86
+ Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
87
+ env_args = {
88
+ args: [],
89
+ env: {}
90
+ }
91
+ if params.key?('local')
92
+ # "conf" format
93
+ Aspera.assert_type(params['local'], Hash){'local'}
94
+ remote = params['remote']
95
+ Aspera.assert_type(remote, Hash){'remote'}
96
+ Aspera.assert_type(remote['path'], String){'remote path'}
97
+ # get transfer spec if possible, and feed back to new structure
98
+ if block_given?
99
+ transfer_spec = yield(direction_sym(params), params['local']['path'], remote['path'])
100
+ Log.dump(:auth_ts, transfer_spec)
101
+ transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
102
+ tspec_to_sync_info(transfer_spec, params, CONF_SCHEMA)
103
+ update_remote_dir(remote, 'path', transfer_spec)
104
+ end
105
+ remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
106
+ add_certificates = remote_certificates(remote)
107
+ if !add_certificates.empty?
108
+ remote['private_key_paths'] ||= []
109
+ remote['private_key_paths'].concat(add_certificates)
110
+ end
111
+ # '--exclusive-mgmt-port=12345', '--arg-err-path=-',
112
+ env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(params))}"]
113
+ Log.dump(:sync_conf, params)
114
+ agent = Agent::Direct.new
115
+ agent.start_and_monitor_process(session: {}, name: :async, **env_args)
116
+ else
117
+ # "args" format
118
+ raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
119
+ params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
120
+ Aspera.assert_type(params['sessions'], Array)
121
+ Aspera.assert_type(params['sessions'].first, Hash)
122
+ if block_given?
123
+ params['sessions'].each do |session|
124
+ Aspera.assert_type(session['local_dir'], String){'local_dir'}
125
+ Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
126
+ transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
127
+ Log.dump(:auth_ts, transfer_spec)
128
+ transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
129
+ tspec_to_sync_info(transfer_spec, session, SESSION_SCHEMA)
130
+ session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
131
+ update_remote_dir(session, 'remote_dir', transfer_spec)
132
+ end
133
+ end
134
+ if params.key?('instance')
135
+ Aspera.assert_type(params['instance'], Hash)
136
+ instance_builder = CommandLineBuilder.new(params['instance'], INSTANCE_SCHEMA, CommandLineConverter)
137
+ instance_builder.process_params
138
+ instance_builder.add_env_args(env_args)
139
+ end
140
+ params['sessions'].each do |session_params|
141
+ Aspera.assert_type(session_params, Hash)
142
+ Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
143
+ session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, CommandLineConverter)
144
+ session_builder.process_params
145
+ session_builder.add_env_args(env_args)
146
+ end
147
+ Environment.secure_execute(exec: Ascp::Installation.instance.path(:async), **env_args)
148
+ end
149
+ return
150
+ end
151
+
152
+ # Parse output of asyncadmin
153
+ def parse_status(stdout)
154
+ Log.log.trace1{"stdout=#{stdout}"}
155
+ result = {}
156
+ ids = nil
157
+ stdout.split("\n").each do |line|
158
+ info = line.split(':', 2).map(&:lstrip)
159
+ if info[1].eql?('')
160
+ info[1] = ids = []
161
+ elsif info[1].nil?
162
+ ids.push(info[0])
163
+ next
164
+ end
165
+ result[info[0]] = info[1]
166
+ end
167
+ return result
168
+ end
169
+
170
+ # Run `asyncadmin` to get status of sync session
171
+ # @param params [Hash] sync parameters in conf or args format
172
+ # @return [Hash] parsed output of asyncadmin
173
+ def admin_status(params)
174
+ Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
175
+ arguments = ['--quiet']
176
+ if params.key?('local')
177
+ # "conf" format
178
+ arguments.push("--name=#{params['name']}")
179
+ if params.key?('local_db_dir')
180
+ arguments.push("--local-db-dir=#{params['local_db_dir']}")
181
+ elsif params.dig('local', 'path')
182
+ arguments.push("--local-dir=#{params.dig('local', 'path')}")
183
+ else
184
+ raise Error, 'Missing either local_db_dir or local.path'
185
+ end
186
+ else
187
+ # "args" format
188
+ session = params['sessions'].first
189
+ arguments.push("--name=#{session['name']}")
190
+ if session.key?('local_db_dir')
191
+ arguments.push("--local-db-dir=#{session['local_db_dir']}")
192
+ elsif session.key?('local_dir')
193
+ arguments.push("--local-dir=#{session['local_dir']}")
194
+ else
195
+ raise Error, 'Missing either local_db_dir or local_dir'
196
+ end
197
+ end
198
+ stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
199
+ return parse_status(stdout)
200
+ end
201
+
202
+ # Find the local database folder based on params
203
+ # @param params [Hash] sync parameters in conf or args format
204
+ # @return [String, nil] path to "local DB dir", i.e. folder that contains folders that contain snap.db
205
+ def local_db_folder(params)
206
+ Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
207
+ if params.key?('local')
208
+ # "conf" format
209
+ if params.key?('local_db_dir')
210
+ return params['local_db_dir']
211
+ elsif (local_path = params.dig('local', 'path'))
212
+ return local_path
213
+ elsif exception
214
+ raise Error, 'Missing either local_db_dir or local.path'
215
+ end
216
+ else
217
+ # "args" format
218
+ session = params['sessions'].first
219
+ if session.key?('local_db_dir')
220
+ return session['local_db_dir']
221
+ elsif session.key?('local_dir')
222
+ return session['local_dir']
223
+ elsif exception
224
+ raise Error, 'Missing either local_db_dir or local_dir'
225
+ end
226
+ end
227
+ nil
228
+ end
229
+
230
+ def session_name(params)
231
+ Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
232
+ if params.key?('local')
233
+ # "conf" format
234
+ return params['name']
235
+ else
236
+ # "args" format
237
+ return params['sessions'].first['name']
238
+ end
239
+ end
240
+
241
+ def session_db_file(params)
242
+ db_file = File.join(local_db_folder(params), PRIVATE_FOLDER, session_name(params), ASYNC_DB)
243
+ Aspera.assert(File.exist?(db_file)){"Database file #{db_file} does not exist"}
244
+ db_file
245
+ end
246
+
247
+ def list_db_files(local_db_dir)
248
+ private = File.join(local_db_dir, PRIVATE_FOLDER)
249
+ Dir.children(private).filter_map do |name|
250
+ db_file = File.join(private, name, ASYNC_DB)
251
+ [name, db_file] if File.exist?(db_file)
252
+ end.to_h
253
+ end
254
+
255
+ # private
256
+
257
+ # Transfer specification to synchronization information
258
+ # tag `x-ts-name` in schema is used to map transfer spec parameters to async `sync_info`
259
+ # @param transfer_spec [Hash] transfer specification
260
+ # @param sync_info [Hash] synchronization information
261
+ # @param schema [Hash] schema definition
262
+ def tspec_to_sync_info(transfer_spec, sync_info, schema)
263
+ Log.dump(:tspec_to_sync_info, transfer_spec)
264
+ schema['properties'].each do |name, property|
265
+ if property.key?('x-ts-name')
266
+ tspec_param = property['x-ts-name']
267
+ if transfer_spec.key?(tspec_param) && !sync_info.key?(name)
268
+ sync_info[name] = property['x-ts-convert'] ? CommandLineConverter.send(property['x-ts-convert'], transfer_spec[tspec_param]) : transfer_spec[tspec_param]
269
+ end
270
+ end
271
+ if property['type'].eql?('object') && property.key?('properties')
272
+ sync_info[name] ||= {}
273
+ tspec_to_sync_info(transfer_spec, sync_info[name], property)
274
+ end
275
+ end
276
+ end
277
+ end
278
+ # Private stuff:
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')
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'])
287
+ CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
288
+ ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
289
+ PRIVATE_FOLDER = '.private-asp'
290
+ ASYNC_DB = 'snap.db'
291
+ PARAM_KEYS = %w[local sessions].freeze
292
+
293
+ private_constant :INSTANCE_SCHEMA, :SESSION_SCHEMA, :CONF_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB, :PARAM_KEYS
294
+ end
295
+ end
296
+ end
@@ -8,12 +8,13 @@ module Aspera
8
8
  # create a temp file name for a given folder
9
9
  # files can be deleted on process exit by calling cleanup
10
10
  class TempFileManager
11
+ include Singleton
12
+
11
13
  SEC_IN_DAY = 86_400
12
14
  # assume no transfer last longer than this
13
15
  # (garbage collect file list which were not deleted after transfer)
14
16
  FILE_LIST_AGE_MAX_SEC = SEC_IN_DAY * 5
15
17
  private_constant :SEC_IN_DAY, :FILE_LIST_AGE_MAX_SEC
16
- include Singleton
17
18
 
18
19
  attr_accessor :cleanup_on_exit
19
20
 
@@ -44,7 +45,7 @@ module Aspera
44
45
  end
45
46
 
46
47
  # same as above but in global temp folder, with user's name
47
- def new_file_path_global(prefix=nil, suffix: nil)
48
+ def new_file_path_global(prefix = nil, suffix: nil)
48
49
  username =
49
50
  begin
50
51
  Etc.getlogin || Etc.getpwuid(Process.uid).name || 'unknown_user'
@@ -8,7 +8,7 @@ module Aspera
8
8
  class Error < StandardError
9
9
  attr_reader :err_code
10
10
 
11
- def initialize(message, err_code=nil)
11
+ def initialize(message, err_code = nil)
12
12
  super(message)
13
13
  @err_code = err_code
14
14
  end
@@ -6,7 +6,6 @@ module Aspera
6
6
  module Transfer
7
7
  # from https://www.google.com/search?q=FASP+error+codes
8
8
  # Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
9
- # rubocop:disable Layout/MultilineHashKeyLineBreaks
10
9
  # rubocop:disable Layout/FirstHashElementLineBreak
11
10
  ERROR_INFO = {
12
11
  # id retry-able mnemo message additional info
@@ -87,6 +86,6 @@ module Aspera
87
86
  66 => {r: false, c: 'PEER_REQUIRES_FIPS', m: 'Peer rejects cipher due to FIPS mode enabled on peer',
88
87
  a: 'Peer rejects cipher due to FIPS mode enabled on peer'}
89
88
  }.freeze
90
- # rubocop:enable Layout/MultilineHashKeyLineBreaks
89
+ # rubocop:enable Layout/FirstHashElementLineBreak
91
90
  end
92
91
  end
@@ -2,23 +2,24 @@
2
2
 
3
3
  module Aspera
4
4
  module Transfer
5
- # generates a pseudo file stream
5
+ # Generates a pseudo file stream.
6
6
  class FauxFile
7
+ SCHEME = 'faux'
7
8
  # marker for faux file
8
- PREFIX = 'faux:///'
9
- # size suffix
10
- SUFFIX = %w[k m g t p e]
11
- private_constant :PREFIX, :SUFFIX
9
+ PREFIX = "#{SCHEME}:///"
10
+ # size units, kilo, mega ...
11
+ SIZE_UNITS = %w[k m g t p e].freeze
12
+ private_constant :SCHEME, :PREFIX, :SIZE_UNITS
12
13
  class << self
13
14
  # @return nil if not a faux: scheme, else a FauxFile instance
14
15
  def create(name)
15
- return nil unless name.start_with?(PREFIX)
16
+ return unless name.start_with?(PREFIX)
16
17
  name_params = name[PREFIX.length..-1].split('?', 2)
17
- raise 'Format: #{PREFIX}<file path>?<size>' unless name_params.length.eql?(2)
18
- raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = name_params[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
18
+ raise Error, 'Format: #{PREFIX}<file path>?<size>' unless name_params.length.eql?(2)
19
+ raise Error, "Format: <integer>[#{SIZE_UNITS.join(',')}]" unless (m = name_params[1].downcase.match(/^(\d+)([#{SIZE_UNITS.join('')}])$/))
19
20
  size = m[1].to_i
20
21
  suffix = m[2]
21
- SUFFIX.each do |s|
22
+ SIZE_UNITS.each do |s|
22
23
  size *= 1024
23
24
  break if s.eql?(suffix)
24
25
  end
@@ -36,7 +37,7 @@ module Aspera
36
37
  end
37
38
 
38
39
  def read(chunk_size)
39
- return nil if eof?
40
+ return if eof?
40
41
  bytes_to_read = [chunk_size, @size - @offset].min
41
42
  @offset += bytes_to_read
42
43
  @chunk_by_size[bytes_to_read] = "\x00" * bytes_to_read unless @chunk_by_size.key?(bytes_to_read)
@@ -6,7 +6,7 @@ require 'aspera/command_line_builder'
6
6
  require 'aspera/temp_file_manager'
7
7
  require 'aspera/transfer/error'
8
8
  require 'aspera/transfer/spec'
9
- require 'aspera/transfer/convert'
9
+ require 'aspera/command_line_converter'
10
10
  require 'aspera/ascp/installation'
11
11
  require 'aspera/cli/formatter'
12
12
  require 'aspera/rest'
@@ -20,6 +20,7 @@ module Aspera
20
20
  module Transfer
21
21
  # translate transfer specification to ascp parameter list
22
22
  class Parameters
23
+ # `ascp` options to provide a file list
23
24
  FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
24
25
  private_constant :FILE_LIST_OPTIONS
25
26
  HTTP_FALLBACK_ACTIVATION_VALUES = ['1', 1, true, 'force'].freeze
@@ -70,7 +71,7 @@ module Aspera
70
71
  Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
71
72
  Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
72
73
  Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
73
- @builder = CommandLineBuilder.new(@job_spec, Spec::SCHEMA, Convert)
74
+ @builder = CommandLineBuilder.new(@job_spec, Spec::SCHEMA, CommandLineConverter)
74
75
  end
75
76
 
76
77
  # either place source files on command line, or add file list file
@@ -103,7 +104,7 @@ module Aspera
103
104
  lines = ts_paths_array.map{ |i| i['source']}
104
105
  end
105
106
  file_list_file = TempFileManager.instance.new_file_path_in_folder(self.class.file_list_folder)
106
- Log.log.debug{Log.dump(:file_list, lines)}
107
+ Log.dump(:file_list, lines)
107
108
  File.write(file_list_file, lines.join("\n"), encoding: 'UTF-8')
108
109
  Log.log.debug{"#{file_list_option}=\n#{File.read(file_list_file)}".red}
109
110
  end
@@ -111,7 +112,7 @@ module Aspera
111
112
  @builder.add_command_line_options(["#{file_list_option}=#{file_list_file}"]) unless file_list_option.nil?
112
113
  end
113
114
 
114
- # @return the list of certificates to use when token/ssh or wss are used
115
+ # @return the list of certificates (option `-i`) to use when token/ssh or wss are used
115
116
  def remote_certificates
116
117
  certificates_to_use = []
117
118
  # use web socket secure for session ?
@@ -139,7 +140,7 @@ module Aspera
139
140
  # remove unused parameter (avoid warning)
140
141
  @job_spec.delete('wss_port')
141
142
  # add SSH bypass keys when authentication is token and no auth is provided
142
- if @job_spec.key?('token') && !@job_spec.key?('remote_password')
143
+ if @job_spec.key?('token') && !@job_spec.key?('remote_password') && !@job_spec.key?('ssh_private_key')
143
144
  # @job_spec['remote_password'] = Ascp::Installation.instance.ssh_cert_uuid # not used: no passphrase
144
145
  certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(@client_ssh_key))
145
146
  end
@@ -25,6 +25,14 @@ module Aspera
25
25
  # reserved tag for Aspera
26
26
  TAG_RESERVED = 'aspera'
27
27
  class << self
28
+ # wrong def in transferd
29
+ POLICY_FIX = {
30
+ 'none' => 'none',
31
+ 'attrs' => 'attributes',
32
+ 'sparse_csum' => 'sparse_checksum',
33
+ 'full_csum' => 'full_checksum'
34
+ }
35
+ private_constant :POLICY_FIX
28
36
  # translate upload/download to send/receive
29
37
  def transfer_type_to_direction(transfer_type)
30
38
  XFER_TYPE_TO_DIR.fetch(transfer_type)
@@ -34,8 +42,14 @@ module Aspera
34
42
  def direction_to_transfer_type(direction)
35
43
  XFER_DIR_TO_TYPE.fetch(direction)
36
44
  end
45
+
46
+ def fix_transferd_resume_policy(transfer_spec)
47
+ # Fix discrepency in transfer spec
48
+ transfer_spec['resume_policy'] = POLICY_FIX[transfer_spec['resume_policy']] if transfer_spec.key?('resume_policy')
49
+ end
37
50
  end
38
- SCHEMA = CommandLineBuilder.read_schema(__FILE__)
51
+ SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'spec')
52
+ CommandLineBuilder.adjust_properties_defaults(SCHEMA['properties'])
39
53
  # define constants for enums of parameters: <parameter>_<enum>, e.g. CIPHER_AES_128, DIRECTION_SEND, ...
40
54
  SCHEMA['properties'].each do |name, description|
41
55
  next unless description['enum'].is_a?(Array)