aspera-cli 4.23.0 → 4.24.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 (109) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +32 -1
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +1651 -856
  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 +86 -213
  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 +162 -163
  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 +160 -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 +144 -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 +69 -20
  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/{transfer/sync.rb → sync/operations.rb} +132 -65
  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 +2 -2
  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_instance.schema.yaml +0 -20
  109. 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
@@ -2,48 +2,26 @@
2
2
 
3
3
  # cspell:words logdir bidi watchd cooloff asyncadmin
4
4
 
5
- require 'aspera/command_line_builder'
6
5
  require 'aspera/ascp/installation'
7
6
  require 'aspera/agent/direct'
8
- require 'aspera/transfer/convert'
7
+ require 'aspera/command_line_converter'
8
+ require 'aspera/command_line_builder'
9
9
  require 'aspera/log'
10
10
  require 'aspera/assert'
11
+ require 'aspera/environment'
11
12
  require 'json'
12
13
  require 'base64'
13
14
  require 'open3'
14
15
  require 'English'
15
16
 
16
17
  module Aspera
17
- module Transfer
18
+ module Sync
18
19
  # builds command line arg for async and execute it
19
- module Sync
20
+ module Operations
20
21
  # sync direction
21
22
  DIRECTIONS = %i[push pull bidi].freeze
22
23
  # default direction for sync
23
24
  DEFAULT_DIRECTION = :push
24
- # JSON for async instance command line options
25
- INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'instance')
26
- SESSION_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'session')
27
-
28
- CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
29
-
30
- # Translation of transfer spec parameters to async v2 API (asyncs)
31
- # TODO: complete this list with all transfer spec parameters or include in async json schema with x- parameters
32
- TSPEC_TO_ASYNC_CONF = {
33
- 'remote_host' => 'remote.host',
34
- 'remote_user' => 'remote.user',
35
- 'remote_password' => 'remote.pass',
36
- 'sshfp' => 'remote.fingerprint',
37
- 'ssh_port' => 'remote.port',
38
- 'wss_port' => 'remote.ws_port',
39
- 'proxy' => 'remote.proxy',
40
- 'token' => 'remote.token',
41
- 'tags' => 'tags'
42
- }.freeze
43
-
44
- ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
45
-
46
- private_constant :INSTANCE_SCHEMA, :SESSION_SCHEMA, :CMDLINE_PARAMS_KEYS, :TSPEC_TO_ASYNC_CONF, :ASYNC_ADMIN_EXECUTABLE
47
25
 
48
26
  class << self
49
27
  # Set `remote_dir` in sync parameters based on transfer spec
@@ -64,6 +42,9 @@ module Aspera
64
42
  nil
65
43
  end
66
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
67
48
  def remote_certificates(remote)
68
49
  certificates_to_use = []
69
50
  # use web socket secure for session ?
@@ -83,9 +64,7 @@ module Aspera
83
64
  # remove unused parameter (avoid warning)
84
65
  remote.delete('ws_port')
85
66
  # add SSH bypass keys when authentication is token and no auth is provided
86
- if remote.key?('token') && !remote.key?('pass')
87
- certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa))
88
- end
67
+ certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa)) if remote.key?('token') && !remote.key?('pass')
89
68
  end
90
69
  return certificates_to_use
91
70
  end
@@ -97,18 +76,18 @@ module Aspera
97
76
  (params['direction'] || DEFAULT_DIRECTION).to_sym
98
77
  end
99
78
 
79
+ # Start the sync process
100
80
  # @param sync_params [Hash] sync parameters, old or new format
101
81
  # @param &block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
102
- def start(sync_params)
103
- Log.log.debug{Log.dump(:sync_params_initial, sync_params)}
82
+ def start(sync_params, opt_ts = nil)
83
+ Log.dump(:sync_params_initial, sync_params)
104
84
  Aspera.assert_type(sync_params, Hash)
105
- Aspera.assert(%w[local sessions].any?{ |k| sync_params.key?(k)}){'At least one of `local` or `sessions` must be present in async parameters'}
106
85
  env_args = {
107
86
  args: [],
108
87
  env: {}
109
88
  }
110
89
  if sync_params.key?('local')
111
- # async native JSON format (conf option)
90
+ # "conf" format
112
91
  Aspera.assert_type(sync_params['local'], Hash){'local'}
113
92
  remote = sync_params['remote']
114
93
  Aspera.assert_type(remote, Hash){'remote'}
@@ -116,15 +95,9 @@ module Aspera
116
95
  # get transfer spec if possible, and feed back to new structure
117
96
  if block_given?
118
97
  transfer_spec = yield(direction_sym(sync_params), sync_params['local']['path'], remote['path'])
119
- # translate transfer spec to async parameters
120
- TSPEC_TO_ASYNC_CONF.each do |ts_param, sy_path|
121
- next unless transfer_spec.key?(ts_param)
122
- sy_dig = sy_path.split('.')
123
- param = sy_dig.pop
124
- hash = sy_dig.empty? ? sync_params : sync_params[sy_dig.first]
125
- hash = sync_params[sy_dig.first] = {} if hash.nil?
126
- hash[param] = transfer_spec[ts_param]
127
- end
98
+ Log.dump(:auth_ts, transfer_spec)
99
+ transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
100
+ tspec_to_sync_info(transfer_spec, sync_params, CONF_SCHEMA)
128
101
  update_remote_dir(remote, 'path', transfer_spec)
129
102
  end
130
103
  remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
@@ -135,12 +108,11 @@ module Aspera
135
108
  end
136
109
  # '--exclusive-mgmt-port=12345', '--arg-err-path=-',
137
110
  env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"]
138
- Log.log.debug{Log.dump(:sync_conf, sync_params)}
111
+ Log.dump(:sync_conf, sync_params)
139
112
  agent = Agent::Direct.new
140
113
  agent.start_and_monitor_process(session: {}, name: :async, **env_args)
141
- else
142
- # key 'sessions' is present
143
- # ascli JSON format (cmdline)
114
+ elsif sync_params.key?('sessions')
115
+ # "args" format
144
116
  raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
145
117
  sync_params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
146
118
  Aspera.assert_type(sync_params['sessions'], Array)
@@ -150,34 +122,34 @@ module Aspera
150
122
  Aspera.assert_type(session['local_dir'], String){'local_dir'}
151
123
  Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
152
124
  transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
153
- SESSION_SCHEMA['properties'].each do |name, properties|
154
- if properties.key?('x-tspec')
155
- tspec_param = properties['x-tspec'].is_a?(TrueClass) ? name : properties['x-tspec'].to_s
156
- session[name] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
157
- end
158
- end
125
+ Log.dump(:auth_ts, transfer_spec)
126
+ transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
127
+ tspec_to_sync_info(transfer_spec, session, SESSION_SCHEMA)
159
128
  session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
160
129
  update_remote_dir(session, 'remote_dir', transfer_spec)
161
130
  end
162
131
  end
163
132
  if sync_params.key?('instance')
164
133
  Aspera.assert_type(sync_params['instance'], Hash)
165
- instance_builder = CommandLineBuilder.new(sync_params['instance'], INSTANCE_SCHEMA, Convert)
134
+ instance_builder = CommandLineBuilder.new(sync_params['instance'], INSTANCE_SCHEMA, CommandLineConverter)
166
135
  instance_builder.process_params
167
136
  instance_builder.add_env_args(env_args)
168
137
  end
169
138
  sync_params['sessions'].each do |session_params|
170
139
  Aspera.assert_type(session_params, Hash)
171
140
  Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
172
- session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, Convert)
141
+ session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, CommandLineConverter)
173
142
  session_builder.process_params
174
143
  session_builder.add_env_args(env_args)
175
144
  end
176
145
  Environment.secure_execute(exec: Ascp::Installation.instance.path(:async), **env_args)
146
+ else
147
+ raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
177
148
  end
178
- return nil
149
+ return
179
150
  end
180
151
 
152
+ # Parse output of asyncadmin
181
153
  def parse_status(stdout)
182
154
  Log.log.trace1{"stdout=#{stdout}"}
183
155
  result = {}
@@ -195,38 +167,133 @@ module Aspera
195
167
  return result
196
168
  end
197
169
 
198
- def admin_status(sync_params, session_name)
170
+ # Run `asyncadmin` to get status of sync session
171
+ # @param sync_params [Hash] sync parameters in conf or args format
172
+ # @return [Hash] parsed output of asyncadmin
173
+ def admin_status(sync_params)
199
174
  arguments = ['--quiet']
200
175
  if sync_params.key?('local')
201
- Aspera.assert(!sync_params['name'].nil?){'Missing session name'}
202
- Aspera.assert(session_name.nil? || session_name.eql?(sync_params['name'])){'Session not found'}
176
+ # "conf" format
203
177
  arguments.push("--name=#{sync_params['name']}")
204
178
  if sync_params.key?('local_db_dir')
205
179
  arguments.push("--local-db-dir=#{sync_params['local_db_dir']}")
206
180
  elsif sync_params.dig('local', 'path')
207
181
  arguments.push("--local-dir=#{sync_params.dig('local', 'path')}")
208
182
  else
209
- raise 'Missing either local_db_dir or local.path'
183
+ raise Error, 'Missing either local_db_dir or local.path'
210
184
  end
211
185
  elsif sync_params.key?('sessions')
212
- session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{ |s| s['name'].eql?(session_name)}
213
- raise "Session #{session_name} not found in #{sync_params['sessions'].map{ |s| s['name']}.join(',')}" if session.nil?
214
- raise 'Missing session name' if session['name'].nil?
186
+ # "args" format
187
+ session = sync_params['sessions'].first
215
188
  arguments.push("--name=#{session['name']}")
216
189
  if session.key?('local_db_dir')
217
190
  arguments.push("--local-db-dir=#{session['local_db_dir']}")
218
191
  elsif session.key?('local_dir')
219
192
  arguments.push("--local-dir=#{session['local_dir']}")
220
193
  else
221
- raise 'Missing either local_db_dir or local_dir'
194
+ raise Error, 'Missing either local_db_dir or local_dir'
222
195
  end
223
196
  else
224
- raise 'At least one of `local` or `sessions` must be present in async parameters'
197
+ raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
225
198
  end
226
199
  stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
227
200
  return parse_status(stdout)
228
201
  end
202
+
203
+ # Find the local database folder based on sync_params
204
+ # @param sync_params [Hash] sync parameters in conf or args format
205
+ # @param exception [Bool] Raise exception in case of problem, else return nil
206
+ # @return [String, nil] path to "local DB dir", i.e. folder that contains folders that contain snap.db
207
+ def local_db_folder(sync_params, exception: true)
208
+ if sync_params.key?('local')
209
+ # "conf" format
210
+ if sync_params.key?('local_db_dir')
211
+ return sync_params['local_db_dir']
212
+ elsif (local_path = sync_params.dig('local', 'path'))
213
+ return local_path
214
+ elsif exception
215
+ raise Error, 'Missing either local_db_dir or local.path'
216
+ end
217
+ elsif sync_params.key?('sessions')
218
+ # "args" format
219
+ session = sync_params['sessions'].first
220
+ if session.key?('local_db_dir')
221
+ return session['local_db_dir']
222
+ elsif session.key?('local_dir')
223
+ return session['local_dir']
224
+ elsif exception
225
+ raise Error, 'Missing either local_db_dir or local_dir'
226
+ end
227
+ elsif exception
228
+ raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
229
+ end
230
+ nil
231
+ end
232
+
233
+ def session_name(sync_params)
234
+ if sync_params.key?('local')
235
+ # "conf" format
236
+ return sync_params['name']
237
+ elsif sync_params.key?('sessions')
238
+ # "args" format
239
+ return sync_params['sessions'].first['name']
240
+ else
241
+ raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
242
+ end
243
+ end
244
+
245
+ def session_db_file(sync_params)
246
+ db_file = File.join(local_db_folder(sync_params), PRIVATE_FOLDER, session_name(sync_params), ASYNC_DB)
247
+ Aspera.assert(File.exist?(db_file)){"Database file #{db_file} does not exist"}
248
+ db_file
249
+ end
250
+
251
+ def list_db_files(local_db_dir)
252
+ private = File.join(local_db_dir, PRIVATE_FOLDER)
253
+ Dir.children(private).filter_map do |name|
254
+ db_file = File.join(private, name, ASYNC_DB)
255
+ [name, db_file] if File.exist?(db_file)
256
+ end.to_h
257
+ end
258
+
259
+ # private
260
+
261
+ # Transfer specification to synchronization information
262
+ # tag `x-ts-name` in schema is used to map transfer spec parameters to async `sync_info`
263
+ # @param transfer_spec [Hash] transfer specification
264
+ # @param sync_info [Hash] synchronization information
265
+ # @param schema [Hash] schema definition
266
+ def tspec_to_sync_info(transfer_spec, sync_info, schema)
267
+ Log.dump(:tspec_to_sync_info, transfer_spec)
268
+ schema['properties'].each do |name, property|
269
+ if property.key?('x-ts-name')
270
+ tspec_param = property['x-ts-name']
271
+ if transfer_spec.key?(tspec_param) && !sync_info.key?(name)
272
+ sync_info[name] = property['x-ts-convert'] ? CommandLineConverter.send(property['x-ts-convert'], transfer_spec[tspec_param]) : transfer_spec[tspec_param]
273
+ end
274
+ end
275
+ if property['type'].eql?('object') && property.key?('properties')
276
+ sync_info[name] ||= {}
277
+ tspec_to_sync_info(transfer_spec, sync_info[name], property)
278
+ end
279
+ end
280
+ end
229
281
  end
282
+ # Private stuff:
283
+ # Read JSON schema and mapping to command line options
284
+ INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'args')
285
+ SESSION_SCHEMA = INSTANCE_SCHEMA['properties']['sessions']['items']
286
+ INSTANCE_SCHEMA['properties'].delete('sessions')
287
+ CONF_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'conf')
288
+ CommandLineBuilder.adjust_properties_defaults(INSTANCE_SCHEMA['properties'])
289
+ CommandLineBuilder.adjust_properties_defaults(SESSION_SCHEMA['properties'])
290
+ CommandLineBuilder.adjust_properties_defaults(CONF_SCHEMA['properties'])
291
+ CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
292
+ ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
293
+ PRIVATE_FOLDER = '.private-asp'
294
+ ASYNC_DB = 'snap.db'
295
+
296
+ private_constant :INSTANCE_SCHEMA, :SESSION_SCHEMA, :CONF_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB
230
297
  end
231
298
  end
232
299
  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)