aspera-cli 4.22.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 (114) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +405 -364
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +1856 -961
  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 +53 -43
  16. data/lib/aspera/api/cos_node.rb +7 -5
  17. data/lib/aspera/api/httpgw.rb +23 -22
  18. data/lib/aspera/api/node.rb +104 -22
  19. data/lib/aspera/ascmd.rb +35 -21
  20. data/lib/aspera/ascp/installation.rb +43 -43
  21. data/lib/aspera/ascp/management.rb +5 -4
  22. data/lib/aspera/assert.rb +55 -24
  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 +38 -4
  28. data/lib/aspera/cli/main.rb +139 -108
  29. data/lib/aspera/cli/manager.rb +51 -31
  30. data/lib/aspera/cli/plugin.rb +149 -78
  31. data/lib/aspera/cli/plugin_factory.rb +2 -2
  32. data/lib/aspera/cli/plugins/aoc.rb +217 -88
  33. data/lib/aspera/cli/plugins/ats.rb +15 -13
  34. data/lib/aspera/cli/plugins/config.rb +105 -227
  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 +233 -247
  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 +29 -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 +55 -58
  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/data_repository.rb +1 -0
  55. data/lib/aspera/environment.rb +144 -100
  56. data/lib/aspera/faspex_gw.rb +1 -1
  57. data/lib/aspera/faspex_postproc.rb +3 -2
  58. data/lib/aspera/hash_ext.rb +1 -1
  59. data/lib/aspera/id_generator.rb +10 -10
  60. data/lib/aspera/keychain/base.rb +18 -0
  61. data/lib/aspera/keychain/encrypted_hash.rb +6 -12
  62. data/lib/aspera/keychain/factory.rb +9 -3
  63. data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
  64. data/lib/aspera/keychain/macos_security.rb +13 -13
  65. data/lib/aspera/log.rb +70 -20
  66. data/lib/aspera/nagios.rb +5 -6
  67. data/lib/aspera/node_simulator.rb +12 -7
  68. data/lib/aspera/oauth/base.rb +6 -2
  69. data/lib/aspera/oauth/factory.rb +25 -18
  70. data/lib/aspera/oauth/jwt.rb +13 -1
  71. data/lib/aspera/oauth/url_json.rb +3 -3
  72. data/lib/aspera/oauth/web.rb +5 -3
  73. data/lib/aspera/persistency_folder.rb +2 -2
  74. data/lib/aspera/preview/file_types.rb +43 -35
  75. data/lib/aspera/preview/generator.rb +26 -13
  76. data/lib/aspera/preview/terminal.rb +10 -7
  77. data/lib/aspera/preview/utils.rb +11 -9
  78. data/lib/aspera/products/connect.rb +2 -1
  79. data/lib/aspera/products/desktop.rb +1 -1
  80. data/lib/aspera/products/other.rb +2 -2
  81. data/lib/aspera/products/transferd.rb +8 -6
  82. data/lib/aspera/proxy_auto_config.rb +1 -1
  83. data/lib/aspera/rest.rb +46 -28
  84. data/lib/aspera/rest_call_error.rb +1 -1
  85. data/lib/aspera/rest_error_analyzer.rb +1 -0
  86. data/lib/aspera/resumer.rb +1 -1
  87. data/lib/aspera/secret_hider.rb +46 -40
  88. data/lib/aspera/ssh.rb +14 -4
  89. data/lib/aspera/sync/args.schema.yaml +102 -0
  90. data/lib/aspera/sync/conf.schema.yaml +701 -0
  91. data/lib/aspera/sync/database.rb +83 -0
  92. data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
  93. data/lib/aspera/temp_file_manager.rb +4 -2
  94. data/lib/aspera/timer_limiter.rb +7 -5
  95. data/lib/aspera/transfer/error.rb +1 -1
  96. data/lib/aspera/transfer/error_info.rb +1 -2
  97. data/lib/aspera/transfer/faux_file.rb +11 -10
  98. data/lib/aspera/transfer/parameters.rb +6 -5
  99. data/lib/aspera/transfer/spec.rb +15 -1
  100. data/lib/aspera/transfer/spec.schema.yaml +316 -293
  101. data/lib/aspera/transfer/spec_doc.rb +34 -16
  102. data/lib/aspera/transfer/uri.rb +5 -5
  103. data/lib/aspera/uri_reader.rb +14 -10
  104. data/lib/aspera/web_auth.rb +2 -2
  105. data/lib/aspera/web_server_simple.rb +2 -2
  106. data.tar.gz.sig +0 -0
  107. metadata +15 -15
  108. metadata.gz.sig +0 -0
  109. data/examples/dascli +0 -30
  110. data/examples/get_proto_file.rb +0 -8
  111. data/examples/proxy.pac +0 -60
  112. data/lib/aspera/transfer/convert.rb +0 -29
  113. data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
  114. data/lib/aspera/transfer/sync_session.schema.yaml +0 -79
@@ -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,45 +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
- # sync direction, default is push
20
+ module Operations
21
+ # sync direction
21
22
  DIRECTIONS = %i[push pull bidi].freeze
22
- # JSON for async instance command line options
23
- INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'instance')
24
- SESSION_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'session')
25
-
26
- CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
27
-
28
- # Translation of transfer spec parameters to async v2 API (asyncs)
29
- TSPEC_TO_ASYNC_CONF = {
30
- 'remote_host' => 'remote.host',
31
- 'remote_user' => 'remote.user',
32
- 'remote_password' => 'remote.pass',
33
- 'sshfp' => 'remote.fingerprint',
34
- 'ssh_port' => 'remote.port',
35
- 'wss_port' => 'remote.ws_port',
36
- 'proxy' => 'remote.proxy',
37
- 'token' => 'remote.token',
38
- 'tags' => 'tags'
39
- }.freeze
40
-
41
- ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
42
-
43
- private_constant :INSTANCE_SCHEMA, :SESSION_SCHEMA, :CMDLINE_PARAMS_KEYS, :TSPEC_TO_ASYNC_CONF, :ASYNC_ADMIN_EXECUTABLE
23
+ # default direction for sync
24
+ DEFAULT_DIRECTION = :push
44
25
 
45
26
  class << self
46
27
  # Set `remote_dir` in sync parameters based on transfer spec
@@ -61,6 +42,9 @@ module Aspera
61
42
  nil
62
43
  end
63
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
64
48
  def remote_certificates(remote)
65
49
  certificates_to_use = []
66
50
  # use web socket secure for session ?
@@ -80,41 +64,40 @@ module Aspera
80
64
  # remove unused parameter (avoid warning)
81
65
  remote.delete('ws_port')
82
66
  # add SSH bypass keys when authentication is token and no auth is provided
83
- if remote.key?('token') && !remote.key?('pass')
84
- certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa))
85
- end
67
+ certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa)) if remote.key?('token') && !remote.key?('pass')
86
68
  end
87
69
  return certificates_to_use
88
70
  end
89
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
90
80
  # @param sync_params [Hash] sync parameters, old or new format
91
81
  # @param &block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
92
- def start(sync_params)
93
- 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)
94
84
  Aspera.assert_type(sync_params, Hash)
95
- Aspera.assert(%w[local sessions].any?{ |k| sync_params.key?(k)}){'At least one of `local` or `sessions` must be present in async parameters'}
96
85
  env_args = {
97
86
  args: [],
98
87
  env: {}
99
88
  }
100
89
  if sync_params.key?('local')
101
- # async native JSON format (conf option)
90
+ # "conf" format
102
91
  Aspera.assert_type(sync_params['local'], Hash){'local'}
103
92
  remote = sync_params['remote']
104
93
  Aspera.assert_type(remote, Hash){'remote'}
105
94
  Aspera.assert_type(remote['path'], String){'remote path'}
106
95
  # get transfer spec if possible, and feed back to new structure
107
96
  if block_given?
108
- transfer_spec = yield((sync_params['direction'] || 'push').to_sym, sync_params['local']['path'], remote['path'])
109
- # translate transfer spec to async parameters
110
- TSPEC_TO_ASYNC_CONF.each do |ts_param, sy_path|
111
- next unless transfer_spec.key?(ts_param)
112
- sy_dig = sy_path.split('.')
113
- param = sy_dig.pop
114
- hash = sy_dig.empty? ? sync_params : sync_params[sy_dig.first]
115
- hash = sync_params[sy_dig.first] = {} if hash.nil?
116
- hash[param] = transfer_spec[ts_param]
117
- end
97
+ transfer_spec = yield(direction_sym(sync_params), sync_params['local']['path'], remote['path'])
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)
118
101
  update_remote_dir(remote, 'path', transfer_spec)
119
102
  end
120
103
  remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
@@ -125,12 +108,11 @@ module Aspera
125
108
  end
126
109
  # '--exclusive-mgmt-port=12345', '--arg-err-path=-',
127
110
  env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"]
128
- Log.log.debug{Log.dump(:sync_conf, sync_params)}
111
+ Log.dump(:sync_conf, sync_params)
129
112
  agent = Agent::Direct.new
130
113
  agent.start_and_monitor_process(session: {}, name: :async, **env_args)
131
- else
132
- # key 'sessions' is present
133
- # ascli JSON format (cmdline)
114
+ elsif sync_params.key?('sessions')
115
+ # "args" format
134
116
  raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
135
117
  sync_params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
136
118
  Aspera.assert_type(sync_params['sessions'], Array)
@@ -139,35 +121,35 @@ module Aspera
139
121
  sync_params['sessions'].each do |session|
140
122
  Aspera.assert_type(session['local_dir'], String){'local_dir'}
141
123
  Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
142
- transfer_spec = yield((session['direction'] || 'push').to_sym, session['local_dir'], session['remote_dir'])
143
- SESSION_SCHEMA['properties'].each do |name, properties|
144
- if properties.key?('x-tspec')
145
- tspec_param = properties['x-tspec'].is_a?(TrueClass) ? name : properties['x-tspec'].to_s
146
- session[name] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
147
- end
148
- end
124
+ transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
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)
149
128
  session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
150
129
  update_remote_dir(session, 'remote_dir', transfer_spec)
151
130
  end
152
131
  end
153
132
  if sync_params.key?('instance')
154
133
  Aspera.assert_type(sync_params['instance'], Hash)
155
- instance_builder = CommandLineBuilder.new(sync_params['instance'], INSTANCE_SCHEMA, Convert)
134
+ instance_builder = CommandLineBuilder.new(sync_params['instance'], INSTANCE_SCHEMA, CommandLineConverter)
156
135
  instance_builder.process_params
157
136
  instance_builder.add_env_args(env_args)
158
137
  end
159
138
  sync_params['sessions'].each do |session_params|
160
139
  Aspera.assert_type(session_params, Hash)
161
- Aspera.assert(session_params.key?('name')){'session must contain at least name'}
162
- session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, Convert)
140
+ Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
141
+ session_builder = CommandLineBuilder.new(session_params, SESSION_SCHEMA, CommandLineConverter)
163
142
  session_builder.process_params
164
143
  session_builder.add_env_args(env_args)
165
144
  end
166
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'
167
148
  end
168
- return nil
149
+ return
169
150
  end
170
151
 
152
+ # Parse output of asyncadmin
171
153
  def parse_status(stdout)
172
154
  Log.log.trace1{"stdout=#{stdout}"}
173
155
  result = {}
@@ -185,38 +167,133 @@ module Aspera
185
167
  return result
186
168
  end
187
169
 
188
- 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)
189
174
  arguments = ['--quiet']
190
175
  if sync_params.key?('local')
191
- Aspera.assert(!sync_params['name'].nil?){'Missing session name'}
192
- Aspera.assert(session_name.nil? || session_name.eql?(sync_params['name'])){'Session not found'}
176
+ # "conf" format
193
177
  arguments.push("--name=#{sync_params['name']}")
194
178
  if sync_params.key?('local_db_dir')
195
179
  arguments.push("--local-db-dir=#{sync_params['local_db_dir']}")
196
180
  elsif sync_params.dig('local', 'path')
197
181
  arguments.push("--local-dir=#{sync_params.dig('local', 'path')}")
198
182
  else
199
- raise 'Missing either local_db_dir or local.path'
183
+ raise Error, 'Missing either local_db_dir or local.path'
200
184
  end
201
185
  elsif sync_params.key?('sessions')
202
- session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{ |s| s['name'].eql?(session_name)}
203
- raise "Session #{session_name} not found in #{sync_params['sessions'].map{ |s| s['name']}.join(',')}" if session.nil?
204
- raise 'Missing session name' if session['name'].nil?
186
+ # "args" format
187
+ session = sync_params['sessions'].first
205
188
  arguments.push("--name=#{session['name']}")
206
189
  if session.key?('local_db_dir')
207
190
  arguments.push("--local-db-dir=#{session['local_db_dir']}")
208
191
  elsif session.key?('local_dir')
209
192
  arguments.push("--local-dir=#{session['local_dir']}")
210
193
  else
211
- raise 'Missing either local_db_dir or local_dir'
194
+ raise Error, 'Missing either local_db_dir or local_dir'
212
195
  end
213
196
  else
214
- 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'
215
198
  end
216
199
  stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
217
200
  return parse_status(stdout)
218
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
219
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
220
297
  end
221
298
  end
222
299
  end
@@ -8,12 +8,14 @@ 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
18
+
17
19
  attr_accessor :cleanup_on_exit
18
20
 
19
21
  def initialize
@@ -43,7 +45,7 @@ module Aspera
43
45
  end
44
46
 
45
47
  # same as above but in global temp folder, with user's name
46
- def new_file_path_global(prefix=nil, suffix: nil)
48
+ def new_file_path_global(prefix = nil, suffix: nil)
47
49
  username =
48
50
  begin
49
51
  Etc.getlogin || Etc.getpwuid(Process.uid).name || 'unknown_user'
@@ -1,20 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aspera
4
- # used to throttle logs
4
+ # trigger returns true only if the delay has passed since the last trigger
5
5
  class TimerLimiter
6
6
  # @param delay in seconds (float)
7
7
  def initialize(delay)
8
8
  @delay = delay
9
- @last_time = nil
9
+ @last_trigger_time = nil
10
10
  @count = 0
11
11
  end
12
12
 
13
+ # Check if the trigger condition is met
14
+ # @return [Boolean] true if the trigger condition is met, false otherwise
13
15
  def trigger?
14
- old_time = @last_time
15
- @last_time = Time.now.to_f
16
+ current_time = Time.now.to_f
16
17
  @count += 1
17
- if old_time.nil? || ((@last_time - old_time) > @delay)
18
+ if @last_trigger_time.nil? || ((current_time - @last_trigger_time) > @delay)
19
+ @last_trigger_time = current_time
18
20
  @count = 0
19
21
  return true
20
22
  end
@@ -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)