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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +37 -1
- data/CONTRIBUTING.md +86 -29
- data/README.md +2109 -1300
- data/bin/ascli +2 -1
- data/bin/asession +4 -4
- data/lib/aspera/agent/base.rb +4 -0
- data/lib/aspera/agent/connect.rb +20 -18
- data/lib/aspera/agent/desktop.rb +14 -11
- data/lib/aspera/agent/direct.rb +39 -31
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +9 -11
- data/lib/aspera/agent/transferd.rb +18 -11
- data/lib/aspera/api/aoc.rb +44 -31
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +15 -18
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +22 -16
- data/lib/aspera/ascp/installation.rb +37 -40
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +54 -23
- data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
- data/lib/aspera/cli/error.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +28 -29
- data/lib/aspera/cli/formatter.rb +191 -168
- data/lib/aspera/cli/hints.rb +29 -3
- data/lib/aspera/cli/main.rb +138 -107
- data/lib/aspera/cli/manager.rb +50 -30
- data/lib/aspera/cli/plugin.rb +148 -77
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +189 -70
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +100 -214
- data/lib/aspera/cli/plugins/console.rb +49 -18
- data/lib/aspera/cli/plugins/cos.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +45 -51
- data/lib/aspera/cli/plugins/faspex5.rb +164 -165
- data/lib/aspera/cli/plugins/faspio.rb +6 -5
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +144 -162
- data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
- data/lib/aspera/cli/plugins/preview.rb +26 -29
- data/lib/aspera/cli/plugins/server.rb +28 -28
- data/lib/aspera/cli/plugins/shares.rb +40 -28
- data/lib/aspera/cli/sync_actions.rb +101 -80
- data/lib/aspera/cli/transfer_agent.rb +51 -50
- data/lib/aspera/cli/transfer_progress.rb +29 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +157 -0
- data/lib/aspera/colors.rb +13 -8
- data/lib/aspera/command_line_builder.rb +28 -22
- data/lib/aspera/command_line_converter.rb +31 -0
- data/lib/aspera/environment.rb +145 -101
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +3 -2
- data/lib/aspera/hash_ext.rb +1 -1
- data/lib/aspera/id_generator.rb +10 -10
- data/lib/aspera/keychain/base.rb +18 -0
- data/lib/aspera/keychain/encrypted_hash.rb +6 -12
- data/lib/aspera/keychain/factory.rb +9 -3
- data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +91 -19
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +5 -3
- data/lib/aspera/oauth/factory.rb +24 -18
- data/lib/aspera/oauth/jwt.rb +13 -1
- data/lib/aspera/oauth/url_json.rb +3 -3
- data/lib/aspera/oauth/web.rb +5 -3
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -3
- data/lib/aspera/preview/generator.rb +25 -12
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +8 -6
- data/lib/aspera/proxy_auto_config.rb +1 -1
- data/lib/aspera/rest.rb +29 -22
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +13 -3
- data/lib/aspera/sync/args.schema.yaml +102 -0
- data/lib/aspera/sync/conf.schema.yaml +701 -0
- data/lib/aspera/sync/database.rb +83 -0
- data/lib/aspera/sync/operations.rb +296 -0
- data/lib/aspera/temp_file_manager.rb +3 -2
- data/lib/aspera/transfer/error.rb +1 -1
- data/lib/aspera/transfer/error_info.rb +1 -2
- data/lib/aspera/transfer/faux_file.rb +11 -10
- data/lib/aspera/transfer/parameters.rb +6 -5
- data/lib/aspera/transfer/spec.rb +15 -1
- data/lib/aspera/transfer/spec.schema.yaml +316 -293
- data/lib/aspera/transfer/spec_doc.rb +34 -16
- data/lib/aspera/transfer/uri.rb +5 -5
- data/lib/aspera/uri_reader.rb +14 -10
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +15 -13
- metadata.gz.sig +0 -0
- data/lib/aspera/transfer/async_conf.schema.yaml +0 -716
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync.rb +0 -232
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -20
- 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'
|
@@ -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/
|
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
|
-
#
|
5
|
+
# Generates a pseudo file stream.
|
6
6
|
class FauxFile
|
7
|
+
SCHEME = 'faux'
|
7
8
|
# marker for faux file
|
8
|
-
PREFIX =
|
9
|
-
# size
|
10
|
-
|
11
|
-
private_constant :PREFIX, :
|
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
|
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>[#{
|
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
|
-
|
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
|
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/
|
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,
|
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.
|
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
|
data/lib/aspera/transfer/spec.rb
CHANGED
@@ -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)
|