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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +32 -1
- data/CONTRIBUTING.md +86 -29
- data/README.md +1651 -856
- 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 +86 -213
- 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 +162 -163
- 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 +160 -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 +144 -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 +69 -20
- 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/{transfer/sync.rb → sync/operations.rb} +132 -65
- 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 +2 -2
- data/lib/aspera/transfer/async_conf.schema.yaml +0 -716
- data/lib/aspera/transfer/convert.rb +0 -29
- 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
|
@@ -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/
|
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
|
18
|
+
module Sync
|
18
19
|
# builds command line arg for async and execute it
|
19
|
-
module
|
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.
|
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
|
-
#
|
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
|
-
|
120
|
-
|
121
|
-
|
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.
|
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
|
-
|
142
|
-
#
|
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
|
-
|
154
|
-
|
155
|
-
|
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,
|
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,
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
213
|
-
|
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'
|
@@ -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)
|