aspera-cli 4.24.1 → 4.25.0.pre
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +1064 -745
- data/CONTRIBUTING.md +43 -100
- data/README.md +1281 -720
- data/bin/ascli +20 -1
- data/bin/asession +23 -27
- data/lib/aspera/agent/base.rb +10 -21
- data/lib/aspera/agent/connect.rb +2 -3
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +49 -32
- data/lib/aspera/agent/factory.rb +31 -0
- data/lib/aspera/api/aoc.rb +134 -76
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +213 -0
- data/lib/aspera/api/node.rb +107 -94
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +73 -58
- data/lib/aspera/ascp/management.rb +119 -23
- data/lib/aspera/assert.rb +39 -11
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +91 -67
- data/lib/aspera/cli/formatter.rb +62 -27
- data/lib/aspera/cli/hints.rb +8 -0
- data/lib/aspera/cli/info.rb +4 -4
- data/lib/aspera/cli/main.rb +76 -84
- data/lib/aspera/cli/manager.rb +352 -248
- data/lib/aspera/cli/plugins/alee.rb +5 -4
- data/lib/aspera/cli/plugins/aoc.rb +175 -195
- data/lib/aspera/cli/plugins/ats.rb +4 -4
- data/lib/aspera/cli/plugins/base.rb +343 -0
- data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
- data/lib/aspera/cli/plugins/config.rb +283 -269
- data/lib/aspera/cli/plugins/console.rb +27 -22
- data/lib/aspera/cli/plugins/cos.rb +3 -3
- data/lib/aspera/cli/plugins/factory.rb +78 -0
- data/lib/aspera/cli/plugins/faspex.rb +49 -46
- data/lib/aspera/cli/plugins/faspex5.rb +113 -225
- data/lib/aspera/cli/plugins/faspio.rb +19 -18
- data/lib/aspera/cli/plugins/httpgw.rb +14 -13
- data/lib/aspera/cli/plugins/node.rb +162 -149
- data/lib/aspera/cli/plugins/oauth.rb +48 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
- data/lib/aspera/cli/plugins/preview.rb +30 -50
- data/lib/aspera/cli/plugins/server.rb +21 -21
- data/lib/aspera/cli/plugins/shares.rb +45 -47
- data/lib/aspera/cli/sync_actions.rb +50 -39
- data/lib/aspera/cli/transfer_agent.rb +35 -49
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +3 -3
- data/lib/aspera/cli/wizard.rb +70 -55
- data/lib/aspera/colors.rb +6 -0
- data/lib/aspera/command_line_builder.rb +59 -61
- data/lib/aspera/command_line_converter.rb +2 -1
- data/lib/aspera/coverage.rb +2 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +51 -41
- data/lib/aspera/faspex_gw.rb +7 -5
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +37 -9
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +7 -6
- data/lib/aspera/oauth/base.rb +25 -28
- data/lib/aspera/oauth/factory.rb +9 -9
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/products/connect.rb +7 -6
- data/lib/aspera/products/desktop.rb +1 -4
- data/lib/aspera/products/other.rb +9 -1
- data/lib/aspera/products/transferd.rb +0 -1
- data/lib/aspera/rest.rb +168 -113
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +7 -4
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/args.schema.yaml +46 -3
- data/lib/aspera/sync/conf.schema.yaml +307 -123
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +135 -79
- data/lib/aspera/temp_file_manager.rb +17 -5
- data/lib/aspera/transfer/error.rb +16 -7
- data/lib/aspera/transfer/parameters.rb +35 -22
- data/lib/aspera/transfer/resumer.rb +74 -0
- data/lib/aspera/transfer/spec.rb +5 -5
- data/lib/aspera/transfer/spec.schema.yaml +170 -59
- data/lib/aspera/transfer/spec_doc.rb +49 -43
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +6 -6
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +26 -11
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
- data/lib/aspera/cli/plugin.rb +0 -333
- data/lib/aspera/cli/plugin_factory.rb +0 -81
- data/lib/aspera/resumer.rb +0 -77
- data/lib/aspera/transfer/error_info.rb +0 -91
data/lib/aspera/sync/database.rb
CHANGED
|
@@ -8,7 +8,7 @@ else
|
|
|
8
8
|
require 'sqlite3'
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# A wrapper class that provides common API for Ruby and JRuby
|
|
11
|
+
# A wrapper class that provides common API for sqlite in both Ruby and JRuby
|
|
12
12
|
class SqLite3Wrapper
|
|
13
13
|
def initialize(db_path)
|
|
14
14
|
@db_path = db_path
|
|
@@ -52,6 +52,7 @@ end
|
|
|
52
52
|
|
|
53
53
|
module Aspera
|
|
54
54
|
module Sync
|
|
55
|
+
# Access `async` sqlite database
|
|
55
56
|
class Database
|
|
56
57
|
def initialize(db_path)
|
|
57
58
|
@db = SqLite3Wrapper.new(db_path)
|
|
@@ -18,26 +18,28 @@ module Aspera
|
|
|
18
18
|
module Sync
|
|
19
19
|
# builds command line arg for async and execute it
|
|
20
20
|
module Operations
|
|
21
|
-
#
|
|
21
|
+
# Sync direction
|
|
22
22
|
DIRECTIONS = %i[push pull bidi].freeze
|
|
23
|
-
#
|
|
24
|
-
DEFAULT_DIRECTION =
|
|
23
|
+
# Default direction for sync
|
|
24
|
+
DEFAULT_DIRECTION = DIRECTIONS.first
|
|
25
|
+
|
|
26
|
+
SCP_REMOTE_REGEX = /\A(?:(?:(?<user>[^@:\s]+)@)?(?<host>[^:\s]+):)?(?<path>.+)\z/
|
|
25
27
|
|
|
26
28
|
class << self
|
|
27
29
|
# Set `remote_dir` in sync parameters based on transfer spec
|
|
28
|
-
# @param
|
|
29
|
-
# @param remote_dir_key [String]
|
|
30
|
-
# @param transfer_spec
|
|
31
|
-
def update_remote_dir(
|
|
30
|
+
# @param sync_info [Hash] Sync parameters, in `conf` or `args` format.
|
|
31
|
+
# @param remote_dir_key [String] Key to update in above hash
|
|
32
|
+
# @param transfer_spec [Hash] Transfer spec
|
|
33
|
+
def update_remote_dir(sync_info, remote_dir_key, transfer_spec)
|
|
32
34
|
if transfer_spec.dig(*%w[tags aspera node file_id])
|
|
33
35
|
# in AoC, use gen4
|
|
34
|
-
|
|
36
|
+
sync_info[remote_dir_key] = '/'
|
|
35
37
|
elsif transfer_spec['cookie']&.start_with?('aspera.shares2')
|
|
36
38
|
# TODO : something more generic, independent of Shares
|
|
37
39
|
# in Shares, the actual folder on remote end is not always the same as the name of the share
|
|
38
40
|
remote_key = transfer_spec['direction'].eql?('send') ? 'destination' : 'source'
|
|
39
41
|
actual_remote = transfer_spec['paths']&.first&.[](remote_key)
|
|
40
|
-
|
|
42
|
+
sync_info[remote_dir_key] = actual_remote if actual_remote
|
|
41
43
|
end
|
|
42
44
|
nil
|
|
43
45
|
end
|
|
@@ -70,36 +72,36 @@ module Aspera
|
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
# Get symbol of sync direction, defaulting to :push
|
|
73
|
-
# @param
|
|
75
|
+
# @param sync_info [Hash] Sync parameters, `conf` or `args` format
|
|
74
76
|
# @return [Symbol] direction symbol, one of :push, :pull, :bidi
|
|
75
|
-
def direction_sym(
|
|
76
|
-
(
|
|
77
|
+
def direction_sym(sync_info)
|
|
78
|
+
(sync_info['direction'] || DEFAULT_DIRECTION).to_sym
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
# Start the sync process
|
|
80
|
-
# @param
|
|
81
|
-
# @param opt_ts
|
|
82
|
-
# @param &block
|
|
83
|
-
def start(
|
|
84
|
-
Log.dump(:sync_params_initial,
|
|
85
|
-
Aspera.assert_type(
|
|
86
|
-
Aspera.assert(PARAM_KEYS.any?{ |k|
|
|
82
|
+
# @param sync_info [Hash] Sync parameters, old or new format
|
|
83
|
+
# @param opt_ts [Hash] Optional transfer spec
|
|
84
|
+
# @param &block [nil, Proc] block to generate transfer spec, takes: `direction` (one of DIRECTIONS), `local_dir`, `remote_dir`
|
|
85
|
+
def start(sync_info, opt_ts = nil)
|
|
86
|
+
Log.dump(:sync_params_initial, sync_info)
|
|
87
|
+
Aspera.assert_type(sync_info, Hash)
|
|
88
|
+
Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
|
|
87
89
|
env_args = {
|
|
88
90
|
args: [],
|
|
89
91
|
env: {}
|
|
90
92
|
}
|
|
91
|
-
if
|
|
92
|
-
#
|
|
93
|
-
Aspera.assert_type(
|
|
94
|
-
remote =
|
|
93
|
+
if sync_info.key?('local')
|
|
94
|
+
# `conf` format
|
|
95
|
+
Aspera.assert_type(sync_info['local'], Hash){'local'}
|
|
96
|
+
remote = sync_info['remote']
|
|
95
97
|
Aspera.assert_type(remote, Hash){'remote'}
|
|
96
98
|
Aspera.assert_type(remote['path'], String){'remote path'}
|
|
97
99
|
# get transfer spec if possible, and feed back to new structure
|
|
98
100
|
if block_given?
|
|
99
|
-
transfer_spec = yield(direction_sym(
|
|
101
|
+
transfer_spec = yield(direction_sym(sync_info), sync_info['local']['path'], remote['path'])
|
|
100
102
|
Log.dump(:auth_ts, transfer_spec)
|
|
101
103
|
transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
|
|
102
|
-
tspec_to_sync_info(transfer_spec,
|
|
104
|
+
tspec_to_sync_info(transfer_spec, sync_info, CONF_SCHEMA)
|
|
103
105
|
update_remote_dir(remote, 'path', transfer_spec)
|
|
104
106
|
end
|
|
105
107
|
remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
|
|
@@ -109,38 +111,38 @@ module Aspera
|
|
|
109
111
|
remote['private_key_paths'].concat(add_certificates)
|
|
110
112
|
end
|
|
111
113
|
# '--exclusive-mgmt-port=12345', '--arg-err-path=-',
|
|
112
|
-
env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(
|
|
113
|
-
Log.dump(:sync_conf,
|
|
114
|
+
env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_info))}"]
|
|
115
|
+
Log.dump(:sync_conf, sync_info)
|
|
114
116
|
agent = Agent::Direct.new
|
|
115
117
|
agent.start_and_monitor_process(session: {}, name: :async, **env_args)
|
|
116
118
|
else
|
|
117
|
-
#
|
|
119
|
+
# `args` format
|
|
118
120
|
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
|
119
|
-
|
|
120
|
-
Aspera.assert_type(
|
|
121
|
-
Aspera.assert_type(
|
|
121
|
+
sync_info.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
|
|
122
|
+
Aspera.assert_type(sync_info['sessions'], Array)
|
|
123
|
+
Aspera.assert_type(sync_info['sessions'].first, Hash)
|
|
122
124
|
if block_given?
|
|
123
|
-
|
|
125
|
+
sync_info['sessions'].each do |session|
|
|
124
126
|
Aspera.assert_type(session['local_dir'], String){'local_dir'}
|
|
125
127
|
Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
|
|
126
128
|
transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
|
|
127
129
|
Log.dump(:auth_ts, transfer_spec)
|
|
128
130
|
transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
|
|
129
|
-
tspec_to_sync_info(transfer_spec, session,
|
|
131
|
+
tspec_to_sync_info(transfer_spec, session, ARGS_SESSION_SCHEMA)
|
|
130
132
|
session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
|
|
131
133
|
update_remote_dir(session, 'remote_dir', transfer_spec)
|
|
132
134
|
end
|
|
133
135
|
end
|
|
134
|
-
if
|
|
135
|
-
Aspera.assert_type(
|
|
136
|
-
instance_builder = CommandLineBuilder.new(
|
|
136
|
+
if sync_info.key?('instance')
|
|
137
|
+
Aspera.assert_type(sync_info['instance'], Hash)
|
|
138
|
+
instance_builder = CommandLineBuilder.new(sync_info['instance'], ARGS_INSTANCE_SCHEMA, CommandLineConverter)
|
|
137
139
|
instance_builder.process_params
|
|
138
140
|
instance_builder.add_env_args(env_args)
|
|
139
141
|
end
|
|
140
|
-
|
|
142
|
+
sync_info['sessions'].each do |session_params|
|
|
141
143
|
Aspera.assert_type(session_params, Hash)
|
|
142
144
|
Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
|
|
143
|
-
session_builder = CommandLineBuilder.new(session_params,
|
|
145
|
+
session_builder = CommandLineBuilder.new(session_params, ARGS_SESSION_SCHEMA, CommandLineConverter)
|
|
144
146
|
session_builder.process_params
|
|
145
147
|
session_builder.add_env_args(env_args)
|
|
146
148
|
end
|
|
@@ -168,24 +170,24 @@ module Aspera
|
|
|
168
170
|
end
|
|
169
171
|
|
|
170
172
|
# Run `asyncadmin` to get status of sync session
|
|
171
|
-
# @param
|
|
173
|
+
# @param sync_info [Hash] sync parameters in conf or args format
|
|
172
174
|
# @return [Hash] parsed output of asyncadmin
|
|
173
|
-
def admin_status(
|
|
174
|
-
Aspera.assert(PARAM_KEYS.any?{ |k|
|
|
175
|
+
def admin_status(sync_info)
|
|
176
|
+
Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
|
|
175
177
|
arguments = ['--quiet']
|
|
176
|
-
if
|
|
177
|
-
#
|
|
178
|
-
arguments.push("--name=#{
|
|
179
|
-
if
|
|
180
|
-
arguments.push("--local-db-dir=#{
|
|
181
|
-
elsif
|
|
182
|
-
arguments.push("--local-dir=#{
|
|
178
|
+
if sync_info.key?('local')
|
|
179
|
+
# `conf` format
|
|
180
|
+
arguments.push("--name=#{sync_info['name']}")
|
|
181
|
+
if sync_info.key?('local_db_dir')
|
|
182
|
+
arguments.push("--local-db-dir=#{sync_info['local_db_dir']}")
|
|
183
|
+
elsif sync_info.dig('local', 'path')
|
|
184
|
+
arguments.push("--local-dir=#{sync_info.dig('local', 'path')}")
|
|
183
185
|
else
|
|
184
186
|
raise Error, 'Missing either local_db_dir or local.path'
|
|
185
187
|
end
|
|
186
188
|
else
|
|
187
|
-
#
|
|
188
|
-
session =
|
|
189
|
+
# `args` format
|
|
190
|
+
session = sync_info['sessions'].first
|
|
189
191
|
arguments.push("--name=#{session['name']}")
|
|
190
192
|
if session.key?('local_db_dir')
|
|
191
193
|
arguments.push("--local-db-dir=#{session['local_db_dir']}")
|
|
@@ -199,23 +201,23 @@ module Aspera
|
|
|
199
201
|
return parse_status(stdout)
|
|
200
202
|
end
|
|
201
203
|
|
|
202
|
-
# Find the local database folder based on
|
|
203
|
-
# @param
|
|
204
|
-
# @return [String, nil]
|
|
205
|
-
def local_db_folder(
|
|
206
|
-
Aspera.assert(PARAM_KEYS.any?{ |k|
|
|
207
|
-
if
|
|
208
|
-
#
|
|
209
|
-
if
|
|
210
|
-
return
|
|
211
|
-
elsif (local_path =
|
|
204
|
+
# Find the local database folder based on sync_info
|
|
205
|
+
# @param sync_info [Hash] sync parameters in conf or args format
|
|
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_info)
|
|
208
|
+
Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
|
|
209
|
+
if sync_info.key?('local')
|
|
210
|
+
# `conf` format
|
|
211
|
+
if sync_info.key?('local_db_dir')
|
|
212
|
+
return sync_info['local_db_dir']
|
|
213
|
+
elsif (local_path = sync_info.dig('local', 'path'))
|
|
212
214
|
return local_path
|
|
213
215
|
elsif exception
|
|
214
216
|
raise Error, 'Missing either local_db_dir or local.path'
|
|
215
217
|
end
|
|
216
218
|
else
|
|
217
|
-
#
|
|
218
|
-
session =
|
|
219
|
+
# `args` format
|
|
220
|
+
session = sync_info['sessions'].first
|
|
219
221
|
if session.key?('local_db_dir')
|
|
220
222
|
return session['local_db_dir']
|
|
221
223
|
elsif session.key?('local_dir')
|
|
@@ -227,19 +229,19 @@ module Aspera
|
|
|
227
229
|
nil
|
|
228
230
|
end
|
|
229
231
|
|
|
230
|
-
def session_name(
|
|
231
|
-
Aspera.assert(PARAM_KEYS.any?{ |k|
|
|
232
|
-
if
|
|
233
|
-
#
|
|
234
|
-
return
|
|
232
|
+
def session_name(sync_info)
|
|
233
|
+
Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
|
|
234
|
+
if sync_info.key?('local')
|
|
235
|
+
# `conf` format
|
|
236
|
+
return sync_info['name']
|
|
235
237
|
else
|
|
236
|
-
#
|
|
237
|
-
return
|
|
238
|
+
# `args` format
|
|
239
|
+
return sync_info['sessions'].first['name']
|
|
238
240
|
end
|
|
239
241
|
end
|
|
240
242
|
|
|
241
|
-
def session_db_file(
|
|
242
|
-
db_file = File.join(local_db_folder(
|
|
243
|
+
def session_db_file(sync_info)
|
|
244
|
+
db_file = File.join(local_db_folder(sync_info), PRIVATE_FOLDER, session_name(sync_info), ASYNC_DB)
|
|
243
245
|
Aspera.assert(File.exist?(db_file)){"Database file #{db_file} does not exist"}
|
|
244
246
|
db_file
|
|
245
247
|
end
|
|
@@ -274,23 +276,77 @@ module Aspera
|
|
|
274
276
|
end
|
|
275
277
|
end
|
|
276
278
|
end
|
|
279
|
+
|
|
280
|
+
# Search given option in JSON Schema tree
|
|
281
|
+
# @param schema [Hash] JSON Schema tree (root or sub-tree)
|
|
282
|
+
# @param path [Array] Path to subtree
|
|
283
|
+
# @param option [String] Option to search
|
|
284
|
+
# @return [Array,nil] with path/schema for that option
|
|
285
|
+
def find_option(schema, path, option)
|
|
286
|
+
if %w[x-cli-option x-cli-short].any?{ |i| schema[i].eql?(option)}
|
|
287
|
+
Log.log.debug('Special') if schema['x-cli-special']
|
|
288
|
+
return [path, schema]
|
|
289
|
+
end
|
|
290
|
+
if schema['type'].eql?('object')
|
|
291
|
+
schema['properties']&.each do |name, props|
|
|
292
|
+
res = find_option(props, path + [name], option)
|
|
293
|
+
return res unless res.nil?
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
return
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Translate `async` native command line arguments to `conf` JSON
|
|
300
|
+
def args_to_conf(args)
|
|
301
|
+
result = {}
|
|
302
|
+
while args.any?
|
|
303
|
+
option = args.shift
|
|
304
|
+
if option =~ /^(--[^=]+)=(.*)$/
|
|
305
|
+
option = ::Regexp.last_match(1) # "--toto"
|
|
306
|
+
args.unshift(::Regexp.last_match(2))
|
|
307
|
+
end
|
|
308
|
+
if option.eql?('--preserve-time') || option.eql?('-t')
|
|
309
|
+
args.unshift('--preserve-creation-time') if Environment.instance.os.eql?(Environment::OS_WINDOWS)
|
|
310
|
+
option = '--preserve-modification-time'
|
|
311
|
+
end
|
|
312
|
+
if option.eql?('--remote') || option.eql?('-r')
|
|
313
|
+
value = args.first
|
|
314
|
+
if (m = SCP_REMOTE_REGEX.match(value))
|
|
315
|
+
if m[:host]
|
|
316
|
+
args.shift
|
|
317
|
+
args.unshift("--host=#{m[:host]}")
|
|
318
|
+
args.unshift("--user=#{m[:user]}") if m[:user]
|
|
319
|
+
args.unshift(m[:path])
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
path, props = find_option(CONF_SCHEMA, [], option)
|
|
324
|
+
raise "Option not found: #{option}" if path.nil?
|
|
325
|
+
last_key = path.pop
|
|
326
|
+
# navigate in the current result to insert the value
|
|
327
|
+
current = result
|
|
328
|
+
path.each do |key|
|
|
329
|
+
current[key] ||= {}
|
|
330
|
+
current = current[key]
|
|
331
|
+
end
|
|
332
|
+
current[last_key] = props['x-cli-switch'] ? true : args.shift
|
|
333
|
+
end
|
|
334
|
+
return result
|
|
335
|
+
end
|
|
277
336
|
end
|
|
278
337
|
# Private stuff:
|
|
279
338
|
# Read JSON schema and mapping to command line options
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
CONF_SCHEMA = CommandLineBuilder.read_schema(
|
|
284
|
-
CommandLineBuilder.adjust_properties_defaults(INSTANCE_SCHEMA['properties'])
|
|
285
|
-
CommandLineBuilder.adjust_properties_defaults(SESSION_SCHEMA['properties'])
|
|
286
|
-
CommandLineBuilder.adjust_properties_defaults(CONF_SCHEMA['properties'])
|
|
339
|
+
ARGS_INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'args')
|
|
340
|
+
ARGS_SESSION_SCHEMA = ARGS_INSTANCE_SCHEMA['properties']['sessions']['items']
|
|
341
|
+
ARGS_INSTANCE_SCHEMA['properties'].delete('sessions')
|
|
342
|
+
CONF_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'conf')
|
|
287
343
|
CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
|
|
288
344
|
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
|
289
345
|
PRIVATE_FOLDER = '.private-asp'
|
|
290
346
|
ASYNC_DB = 'snap.db'
|
|
291
347
|
PARAM_KEYS = %w[local sessions].freeze
|
|
292
348
|
|
|
293
|
-
private_constant :
|
|
349
|
+
private_constant :ARGS_INSTANCE_SCHEMA, :ARGS_SESSION_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB, :PARAM_KEYS
|
|
294
350
|
end
|
|
295
351
|
end
|
|
296
352
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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(
|
|
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/
|
|
3
|
+
require 'aspera/ascp/management'
|
|
4
4
|
|
|
5
5
|
module Aspera
|
|
6
6
|
module Transfer
|
|
7
|
-
#
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
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,16 @@ 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_array_all(@ascp_args, String){'ascp_args'}
|
|
64
67
|
@wss = wss
|
|
65
68
|
@quiet = quiet
|
|
66
69
|
@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
70
|
Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
|
|
71
|
+
@client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
|
|
73
72
|
Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
|
|
73
|
+
@check_ignore_cb = check_ignore_cb
|
|
74
74
|
@builder = CommandLineBuilder.new(@job_spec, Spec::SCHEMA, CommandLineConverter)
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -109,7 +109,7 @@ module Aspera
|
|
|
109
109
|
Log.log.debug{"#{file_list_option}=\n#{File.read(file_list_file)}".red}
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
-
@builder.add_command_line_options(
|
|
112
|
+
@builder.add_command_line_options("#{file_list_option}=#{file_list_file}") unless file_list_option.nil?
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# @return the list of certificates (option `-i`) to use when token/ssh or wss are used
|
|
@@ -118,7 +118,7 @@ module Aspera
|
|
|
118
118
|
# use web socket secure for session ?
|
|
119
119
|
if @builder.read_param('wss_enabled') && (@wss || !@job_spec.key?('fasp_port'))
|
|
120
120
|
# by default use web socket session if available, unless removed by user
|
|
121
|
-
@builder.add_command_line_options(
|
|
121
|
+
@builder.add_command_line_options('--ws-connect')
|
|
122
122
|
# TODO: option to give order ssh,ws (legacy http is implied by ssh)
|
|
123
123
|
# This will need to be cleaned up in aspera core
|
|
124
124
|
@job_spec['ssh_port'] = @builder.read_param('wss_port')
|
|
@@ -148,7 +148,7 @@ module Aspera
|
|
|
148
148
|
return certificates_to_use
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
#
|
|
151
|
+
# Translate transfer spec to env vars and command line arguments for `ascp`
|
|
152
152
|
def ascp_args
|
|
153
153
|
env_args = {
|
|
154
154
|
args: [],
|
|
@@ -156,18 +156,27 @@ module Aspera
|
|
|
156
156
|
name: :ascp
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
#
|
|
159
|
+
# Special cases
|
|
160
160
|
@job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
|
|
161
161
|
|
|
162
|
-
#
|
|
162
|
+
# Notify multi-session was already used, anyway it was deleted by agent direct
|
|
163
163
|
Aspera.assert(!@builder.read_param('multi_session'))
|
|
164
164
|
|
|
165
|
-
#
|
|
165
|
+
# Add ssh or wss certificates
|
|
166
166
|
# (reverse, to keep order, as we unshift)
|
|
167
167
|
remote_certificates&.reverse_each do |cert|
|
|
168
168
|
env_args[:args].unshift('-i', cert)
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
+
case (delete_source = @builder.read_param('delete_source'))
|
|
172
|
+
when true
|
|
173
|
+
DELETE_EQUIV.each{ |i| @job_spec[i] = true}
|
|
174
|
+
when false
|
|
175
|
+
DELETE_EQUIV.each{ |i| @job_spec.delete(i)}
|
|
176
|
+
when nil
|
|
177
|
+
else Aspera.error_unexpected_value(delete_source){'delete_source'}
|
|
178
|
+
end
|
|
179
|
+
|
|
171
180
|
# process parameters as specified in table
|
|
172
181
|
@builder.process_params
|
|
173
182
|
|
|
@@ -180,7 +189,7 @@ module Aspera
|
|
|
180
189
|
base64_destination = true
|
|
181
190
|
end
|
|
182
191
|
# destination will be base64 encoded, put this before source path arguments
|
|
183
|
-
@builder.add_command_line_options(
|
|
192
|
+
@builder.add_command_line_options('--dest64') if base64_destination
|
|
184
193
|
# optional arguments, at the end to override previous ones (to allow override)
|
|
185
194
|
@builder.add_command_line_options(@ascp_args)
|
|
186
195
|
# get list of source files to transfer and build arg for ascp
|
|
@@ -190,7 +199,7 @@ module Aspera
|
|
|
190
199
|
# ascp4 does not support base64 encoding of destination
|
|
191
200
|
destination_folder = Base64.strict_encode64(destination_folder) if base64_destination
|
|
192
201
|
# destination MUST be last command line argument to ascp
|
|
193
|
-
@builder.add_command_line_options(
|
|
202
|
+
@builder.add_command_line_options(destination_folder)
|
|
194
203
|
@builder.add_env_args(env_args)
|
|
195
204
|
env_args[:args].unshift('-q') if @quiet
|
|
196
205
|
# add fallback cert and key as arguments if needed
|
|
@@ -198,11 +207,15 @@ module Aspera
|
|
|
198
207
|
env_args[:args].unshift('-Y', Ascp::Installation.instance.path(:fallback_private_key))
|
|
199
208
|
env_args[:args].unshift('-I', Ascp::Installation.instance.path(:fallback_certificate))
|
|
200
209
|
end
|
|
201
|
-
# disable redis in client
|
|
202
|
-
env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true'
|
|
210
|
+
# disable redis in client, only for ascp, this makes ascp4 fail
|
|
211
|
+
env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true' if env_args[:name].eql?(:ascp)
|
|
203
212
|
Log.log.debug{"ascp args: #{env_args}"}
|
|
204
213
|
return env_args
|
|
205
214
|
end
|
|
215
|
+
DELETE_EQUIV = %w[remove_after_transfer remove_empty_directories remove_empty_source_directory]
|
|
216
|
+
# `ascp` options to provide a file list
|
|
217
|
+
FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
|
|
218
|
+
private_constant :DELETE_EQUIV, :FILE_LIST_OPTIONS
|
|
206
219
|
end
|
|
207
220
|
end
|
|
208
221
|
end
|