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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +405 -364
- data/CONTRIBUTING.md +86 -29
- data/README.md +1856 -961
- 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 +53 -43
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +23 -22
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +35 -21
- data/lib/aspera/ascp/installation.rb +43 -43
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +55 -24
- 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 +38 -4
- data/lib/aspera/cli/main.rb +139 -108
- data/lib/aspera/cli/manager.rb +51 -31
- data/lib/aspera/cli/plugin.rb +149 -78
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +217 -88
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +105 -227
- 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 +233 -247
- 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 +29 -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 +55 -58
- 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/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +144 -100
- 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 +70 -20
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +6 -2
- data/lib/aspera/oauth/factory.rb +25 -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 +43 -35
- data/lib/aspera/preview/generator.rb +26 -13
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +2 -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 +46 -28
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +14 -4
- 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} +145 -68
- data/lib/aspera/temp_file_manager.rb +4 -2
- data/lib/aspera/timer_limiter.rb +7 -5
- 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 -15
- metadata.gz.sig +0 -0
- data/examples/dascli +0 -30
- data/examples/get_proto_file.rb +0 -8
- data/examples/proxy.pac +0 -60
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
- 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/
|
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
|
-
# sync direction
|
20
|
+
module Operations
|
21
|
+
# sync direction
|
21
22
|
DIRECTIONS = %i[push pull bidi].freeze
|
22
|
-
#
|
23
|
-
|
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.
|
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
|
-
#
|
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
|
109
|
-
|
110
|
-
|
111
|
-
|
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.
|
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
|
-
|
132
|
-
#
|
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
|
143
|
-
|
144
|
-
|
145
|
-
|
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,
|
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,
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
203
|
-
|
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
|
-
|
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'
|
data/lib/aspera/timer_limiter.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Aspera
|
4
|
-
#
|
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
|
-
@
|
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
|
-
|
15
|
-
@last_time = Time.now.to_f
|
16
|
+
current_time = Time.now.to_f
|
16
17
|
@count += 1
|
17
|
-
if
|
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
|
@@ -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)
|