aspera-cli 4.11.0 → 4.12.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/BUGS.md +0 -1
- data/CHANGELOG.md +71 -52
- data/CONTRIBUTING.md +31 -6
- data/README.md +404 -259
- data/bin/asession +2 -2
- data/docs/test_env.conf +1 -0
- data/examples/aoc.rb +2 -2
- data/examples/dascli +11 -11
- data/examples/faspex4.rb +7 -7
- data/examples/node.rb +1 -1
- data/examples/proxy.pac +2 -2
- data/examples/server.rb +3 -3
- data/lib/aspera/aoc.rb +105 -40
- data/lib/aspera/cli/extended_value.rb +4 -4
- data/lib/aspera/cli/{formater.rb → formatter.rb} +7 -7
- data/lib/aspera/cli/listener/progress.rb +1 -1
- data/lib/aspera/cli/listener/progress_multi.rb +2 -2
- data/lib/aspera/cli/main.rb +18 -18
- data/lib/aspera/cli/manager.rb +5 -5
- data/lib/aspera/cli/plugin.rb +23 -20
- data/lib/aspera/cli/plugins/aoc.rb +75 -112
- data/lib/aspera/cli/plugins/ats.rb +6 -6
- data/lib/aspera/cli/plugins/config.rb +84 -83
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +38 -38
- data/lib/aspera/cli/plugins/faspex5.rb +187 -43
- data/lib/aspera/cli/plugins/node.rb +30 -37
- data/lib/aspera/cli/plugins/orchestrator.rb +7 -4
- data/lib/aspera/cli/plugins/preview.rb +10 -9
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/plugins/shares.rb +67 -43
- data/lib/aspera/cli/transfer_agent.rb +16 -16
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/command_line_builder.rb +70 -66
- data/lib/aspera/cos_node.rb +9 -9
- data/lib/aspera/fasp/agent_base.rb +3 -1
- data/lib/aspera/fasp/agent_connect.rb +23 -23
- data/lib/aspera/fasp/agent_direct.rb +13 -14
- data/lib/aspera/fasp/agent_httpgw.rb +20 -19
- data/lib/aspera/fasp/agent_node.rb +13 -15
- data/lib/aspera/fasp/agent_trsdk.rb +1 -1
- data/lib/aspera/fasp/installation.rb +5 -5
- data/lib/aspera/fasp/listener.rb +1 -1
- data/lib/aspera/fasp/parameters.rb +49 -41
- data/lib/aspera/fasp/parameters.yaml +311 -212
- data/lib/aspera/fasp/resume_policy.rb +2 -2
- data/lib/aspera/fasp/transfer_spec.rb +0 -13
- data/lib/aspera/faspex_gw.rb +80 -161
- data/lib/aspera/faspex_postproc.rb +77 -0
- data/lib/aspera/log.rb +7 -7
- data/lib/aspera/nagios.rb +6 -6
- data/lib/aspera/node.rb +24 -19
- data/lib/aspera/oauth.rb +50 -47
- data/lib/aspera/proxy_auto_config.js +22 -22
- data/lib/aspera/proxy_auto_config.rb +3 -3
- data/lib/aspera/rest.rb +12 -10
- data/lib/aspera/rest_error_analyzer.rb +5 -5
- data/lib/aspera/secret_hider.rb +4 -3
- data/lib/aspera/ssh.rb +4 -4
- data/lib/aspera/sync.rb +37 -36
- data/lib/aspera/web_auth.rb +7 -59
- data/lib/aspera/web_server_simple.rb +76 -0
- data.tar.gz.sig +0 -0
- metadata +6 -4
- metadata.gz.sig +0 -0
@@ -9,28 +9,28 @@ require 'tty-spinner'
|
|
9
9
|
module Aspera
|
10
10
|
module Fasp
|
11
11
|
class AgentConnect < Aspera::Fasp::AgentBase
|
12
|
-
|
12
|
+
CONNECT_START_URIS = ['fasp://initialize', 'fasp://initialize', 'aspera-drive://initialize', 'https://test-connect.ibmaspera.com/']
|
13
13
|
SLEEP_SEC_BETWEEN_RETRY = 3
|
14
|
-
private_constant :
|
14
|
+
private_constant :CONNECT_START_URIS, :SLEEP_SEC_BETWEEN_RETRY
|
15
15
|
def initialize(_options)
|
16
16
|
super()
|
17
17
|
@connect_settings = {
|
18
18
|
'app_id' => SecureRandom.uuid
|
19
19
|
}
|
20
20
|
raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
|
21
|
-
|
21
|
+
method_index = 0
|
22
22
|
begin
|
23
23
|
connect_url = Installation.instance.connect_uri
|
24
24
|
Log.log.debug{"found: #{connect_url}"}
|
25
25
|
@connect_api = Rest.new({base_url: "#{connect_url}/v5/connect", headers: {'Origin' => Rest.user_agent}}) # could use v6 also now
|
26
|
-
|
27
|
-
Log.log.info('Connect was reached') if
|
28
|
-
Log.dump(:connect_version,
|
26
|
+
connect_info = @connect_api.read('info/version')[:data]
|
27
|
+
Log.log.info('Connect was reached') if method_index > 0
|
28
|
+
Log.dump(:connect_version, connect_info)
|
29
29
|
rescue StandardError => e # Errno::ECONNREFUSED
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
start_url = CONNECT_START_URIS[method_index]
|
31
|
+
method_index += 1
|
32
|
+
raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
|
33
|
+
Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
|
34
34
|
if !OpenApplication.uri_graphical(start_url)
|
35
35
|
OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
|
36
36
|
raise StandardError, 'Connect is not installed'
|
@@ -40,17 +40,17 @@ module Aspera
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
def start_transfer(transfer_spec)
|
43
|
+
def start_transfer(transfer_spec, token_regenerator: nil)
|
44
44
|
if transfer_spec['direction'] == 'send'
|
45
45
|
Log.log.warn{"Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red}
|
46
46
|
transfer_spec.delete('paths')
|
47
|
-
|
47
|
+
selection = @connect_api.create('windows/select-open-file-dialog/', {
|
48
48
|
'aspera_connect_settings' => @connect_settings,
|
49
49
|
'title' => 'Select Files',
|
50
50
|
'suggestedName' => '',
|
51
51
|
'allowMultipleSelection' => true,
|
52
52
|
'allowedFileTypes' => ''})[:data]
|
53
|
-
transfer_spec['paths'] =
|
53
|
+
transfer_spec['paths'] = selection['dataTransfer']['files'].map { |i| {'source' => i['name']}}
|
54
54
|
end
|
55
55
|
@request_id = SecureRandom.uuid
|
56
56
|
# if there is a token, we ask connect client to use well known ssh private keys
|
@@ -77,13 +77,13 @@ module Aspera
|
|
77
77
|
loop do
|
78
78
|
tr_info = @connect_api.create("transfers/info/#{@xfer_id}", connect_activity_args)[:data]
|
79
79
|
if tr_info['transfer_info'].is_a?(Hash)
|
80
|
-
|
81
|
-
if
|
80
|
+
transfer = tr_info['transfer_info']
|
81
|
+
if transfer.nil?
|
82
82
|
Log.log.warn('no session in Connect')
|
83
83
|
break
|
84
84
|
end
|
85
85
|
# TODO: get session id
|
86
|
-
case
|
86
|
+
case transfer['status']
|
87
87
|
when 'completed'
|
88
88
|
notify_end(@connect_settings['app_id'])
|
89
89
|
break
|
@@ -92,22 +92,22 @@ module Aspera
|
|
92
92
|
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
93
93
|
spinner.start
|
94
94
|
end
|
95
|
-
spinner.update(title:
|
95
|
+
spinner.update(title: transfer['status'])
|
96
96
|
spinner.spin
|
97
97
|
when 'running'
|
98
|
-
# puts "running: sessions:#{
|
99
|
-
if !started && (
|
98
|
+
# puts "running: sessions:#{transfer['sessions'].length}, #{transfer['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
|
99
|
+
if !started && (transfer['bytes_expected'] != 0)
|
100
100
|
spinner&.success
|
101
|
-
notify_begin(@connect_settings['app_id'],
|
101
|
+
notify_begin(@connect_settings['app_id'], transfer['bytes_expected'])
|
102
102
|
started = true
|
103
103
|
else
|
104
|
-
notify_progress(@connect_settings['app_id'],
|
104
|
+
notify_progress(@connect_settings['app_id'], transfer['bytes_written'])
|
105
105
|
end
|
106
106
|
when 'failed'
|
107
107
|
spinner&.error
|
108
|
-
raise Fasp::Error,
|
108
|
+
raise Fasp::Error, transfer['error_desc']
|
109
109
|
else
|
110
|
-
raise Fasp::Error, "unknown status: #{
|
110
|
+
raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
|
111
111
|
end
|
112
112
|
end
|
113
113
|
sleep(1)
|
@@ -24,6 +24,7 @@ module Aspera
|
|
24
24
|
wss: true, # true: if both SSH and wss in ts: prefer wss
|
25
25
|
multi_incr_udp: true,
|
26
26
|
resume: {},
|
27
|
+
ascp_args: [],
|
27
28
|
quiet: true # by default no interactive progress bar
|
28
29
|
}.freeze
|
29
30
|
private_constant :DEFAULT_OPTIONS
|
@@ -31,7 +32,7 @@ module Aspera
|
|
31
32
|
# start ascp transfer (non blocking), single or multi-session
|
32
33
|
# job information added to @jobs
|
33
34
|
# @param transfer_spec [Hash] aspera transfer specification
|
34
|
-
def start_transfer(transfer_spec)
|
35
|
+
def start_transfer(transfer_spec, token_regenerator: nil)
|
35
36
|
the_job_id = SecureRandom.uuid
|
36
37
|
# clone transfer spec because we modify it (first level keys)
|
37
38
|
transfer_spec = transfer_spec.clone
|
@@ -52,7 +53,7 @@ module Aspera
|
|
52
53
|
if transfer_spec.key?('token') &&
|
53
54
|
!transfer_spec.key?('remote_password') &&
|
54
55
|
!transfer_spec.key?('EX_ssh_key_paths')
|
55
|
-
# transfer_spec['remote_password'] = Installation.instance.bypass_pass # not used
|
56
|
+
# transfer_spec['remote_password'] = Installation.instance.bypass_pass # not used: no passphrase
|
56
57
|
transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
|
57
58
|
end
|
58
59
|
|
@@ -69,7 +70,7 @@ module Aspera
|
|
69
70
|
Log.log.error{"multi_session(#{transfer_spec['multi_session']}) shall be integer >= 0"}
|
70
71
|
multi_session_info = nil
|
71
72
|
elsif multi_session_info[:count].eql?(0)
|
72
|
-
Log.log.debug('multi_session count is zero: no
|
73
|
+
Log.log.debug('multi_session count is zero: no multi session')
|
73
74
|
multi_session_info = nil
|
74
75
|
elsif @options[:multi_incr_udp] # multi_session_info[:count] > 0
|
75
76
|
# if option not true: keep default udp port for all sessions
|
@@ -81,7 +82,7 @@ module Aspera
|
|
81
82
|
end
|
82
83
|
|
83
84
|
# compute known args
|
84
|
-
env_args = Parameters.ts_to_env_args(transfer_spec, wss: @options[:wss])
|
85
|
+
env_args = Parameters.ts_to_env_args(transfer_spec, wss: @options[:wss], ascp_args: @options[:ascp_args])
|
85
86
|
|
86
87
|
# add fallback cert and key as arguments if needed
|
87
88
|
if %w[1 force].include?(transfer_spec['http_fallback'])
|
@@ -99,11 +100,12 @@ module Aspera
|
|
99
100
|
|
100
101
|
# generic session information
|
101
102
|
session = {
|
102
|
-
thread:
|
103
|
-
error:
|
104
|
-
io:
|
105
|
-
id:
|
106
|
-
|
103
|
+
thread: nil, # Thread object monitoring management port, not nil when pushed to :sessions
|
104
|
+
error: nil, # exception if failed
|
105
|
+
io: nil, # management port server socket
|
106
|
+
id: nil, # SessionId from INIT message in mgt port
|
107
|
+
token_regenerator: token_regenerator, # regenerate bearer token with oauth
|
108
|
+
env_args: env_args # env vars and args to ascp (from transfer spec)
|
107
109
|
}
|
108
110
|
|
109
111
|
if multi_session_info.nil?
|
@@ -261,10 +263,10 @@ module Aspera
|
|
261
263
|
Log.log.error{"code: #{last_status_event['Code']}"}
|
262
264
|
if /bearer token/i.match?(last_status_event['Description'])
|
263
265
|
Log.log.error('need to regenerate token'.red)
|
264
|
-
if
|
266
|
+
if session[:token_regenerator].respond_to?(:refreshed_transfer_token)
|
265
267
|
# regenerate token here, expired, or error on it
|
266
268
|
# Note: in multi-session, each session will have a different one.
|
267
|
-
env_args[:env]['ASPERA_SCP_TOKEN'] =
|
269
|
+
env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
|
268
270
|
end
|
269
271
|
end
|
270
272
|
# cannot resolve address
|
@@ -327,8 +329,6 @@ module Aspera
|
|
327
329
|
session[:io].puts(command)
|
328
330
|
end
|
329
331
|
|
330
|
-
attr_writer :token_regenerator
|
331
|
-
|
332
332
|
private
|
333
333
|
|
334
334
|
# @param options : keys(symbol): see DEFAULT_OPTIONS
|
@@ -349,7 +349,6 @@ module Aspera
|
|
349
349
|
end
|
350
350
|
Log.log.debug{"local options= #{options}"}
|
351
351
|
@resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
|
352
|
-
@token_regenerator = nil
|
353
352
|
end
|
354
353
|
|
355
354
|
# transfer thread entry
|
@@ -18,9 +18,10 @@ module Aspera
|
|
18
18
|
# message returned by HTTP GW in case of success
|
19
19
|
MSG_END_UPLOAD = 'end upload'
|
20
20
|
MSG_END_SLICE = 'end_slice_upload'
|
21
|
+
# options available in CLI (transfer_info)
|
21
22
|
DEFAULT_OPTIONS = {
|
22
23
|
url: nil,
|
23
|
-
|
24
|
+
upload_chunk_size: 64_000,
|
24
25
|
upload_bar_refresh_sec: 0.5
|
25
26
|
}.freeze
|
26
27
|
DEFAULT_BASE_PATH = '/aspera/http-gwy'
|
@@ -142,21 +143,21 @@ module Aspera
|
|
142
143
|
file_size = item['file_size']
|
143
144
|
file_name = File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
144
145
|
# compute total number of slices
|
145
|
-
|
146
|
+
slice_total = ((file_size - 1) / @options[:upload_chunk_size]) + 1
|
146
147
|
File.open(source_paths[file_index]) do |file|
|
147
148
|
# current slice index
|
148
|
-
|
149
|
+
slice_index = 0
|
149
150
|
until file.eof?
|
150
|
-
data = file.read(@options[:
|
151
|
+
data = file.read(@options[:upload_chunk_size])
|
151
152
|
slice_data = {
|
152
153
|
name: file_name,
|
153
154
|
type: file_mime_type,
|
154
155
|
size: file_size,
|
155
|
-
slice:
|
156
|
-
total_slices:
|
156
|
+
slice: slice_index,
|
157
|
+
total_slices: slice_total,
|
157
158
|
fileIndex: file_index
|
158
159
|
}
|
159
|
-
# Log.dump(:slice_data,slice_data) #if
|
160
|
+
# Log.dump(:slice_data,slice_data) #if slice_index.eql?(0)
|
160
161
|
# interrupt main thread if read thread failed
|
161
162
|
raise shared_info[:read_exception] unless shared_info[:read_exception].nil?
|
162
163
|
begin
|
@@ -164,22 +165,22 @@ module Aspera
|
|
164
165
|
slice_data[:data] = Base64.strict_encode64(data)
|
165
166
|
ws_snd_json(slice_upload: slice_data)
|
166
167
|
else
|
167
|
-
ws_snd_json(slice_upload: slice_data) if
|
168
|
+
ws_snd_json(slice_upload: slice_data) if slice_index.eql?(0)
|
168
169
|
ws_send(data, type: :binary)
|
169
|
-
Log.log.debug{"ws: sent buffer: #{file_index} / #{
|
170
|
-
ws_snd_json(slice_upload: slice_data) if
|
170
|
+
Log.log.debug{"ws: sent buffer: #{file_index} / #{slice_index}"}
|
171
|
+
ws_snd_json(slice_upload: slice_data) if slice_index.eql?(slice_total - 1)
|
171
172
|
end
|
172
173
|
rescue Errno::EPIPE => e
|
173
174
|
raise shared_info[:read_exception] unless shared_info[:read_exception].nil?
|
174
175
|
raise e
|
175
176
|
end
|
176
177
|
sent_bytes += data.length
|
177
|
-
|
178
|
-
if last_progress_time.nil? || ((
|
178
|
+
current_time = Time.now
|
179
|
+
if last_progress_time.nil? || ((current_time - last_progress_time) > @options[:upload_bar_refresh_sec])
|
179
180
|
notify_progress(session_id, sent_bytes)
|
180
|
-
last_progress_time =
|
181
|
+
last_progress_time = current_time
|
181
182
|
end
|
182
|
-
|
183
|
+
slice_index += 1
|
183
184
|
end
|
184
185
|
end
|
185
186
|
file_index += 1
|
@@ -201,14 +202,14 @@ module Aspera
|
|
201
202
|
# is normally provided by application, like package name
|
202
203
|
if !transfer_spec.key?('download_name')
|
203
204
|
# by default it is the name of first file
|
204
|
-
|
205
|
+
download_name = File.basename(transfer_spec['paths'].first['source'])
|
205
206
|
# we remove extension
|
206
|
-
|
207
|
+
download_name = download_name.gsub(/\.@gw_api.*$/, '')
|
207
208
|
# ands add indication of number of files if there is more than one
|
208
209
|
if transfer_spec['paths'].length > 1
|
209
|
-
|
210
|
+
download_name += " #{transfer_spec['paths'].length} Files"
|
210
211
|
end
|
211
|
-
transfer_spec['download_name'] =
|
212
|
+
transfer_spec['download_name'] = download_name
|
212
213
|
end
|
213
214
|
creation = @gw_api.create('v1/download', {'transfer_spec' => transfer_spec})[:data]
|
214
215
|
transfer_uuid = creation['url'].split('/').last
|
@@ -227,7 +228,7 @@ module Aspera
|
|
227
228
|
# start FASP transfer based on transfer spec (hash table)
|
228
229
|
# note that it is asynchronous
|
229
230
|
# HTTP download only supports file list
|
230
|
-
def start_transfer(transfer_spec)
|
231
|
+
def start_transfer(transfer_spec, token_regenerator: nil)
|
231
232
|
raise 'GW URL must be set' if @gw_api.nil?
|
232
233
|
raise 'paths: must be Array' unless transfer_spec['paths'].is_a?(Array)
|
233
234
|
raise 'only token based transfer is supported in GW' unless transfer_spec['token'].is_a?(String)
|
@@ -23,8 +23,8 @@ module Aspera
|
|
23
23
|
rest_params = { base_url: options[:url]}
|
24
24
|
if /^Bearer /.match?(options[:password])
|
25
25
|
rest_params[:headers] = {
|
26
|
-
Aspera::Node::
|
27
|
-
'Authorization'
|
26
|
+
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options[:username],
|
27
|
+
'Authorization' => options[:password]
|
28
28
|
}
|
29
29
|
raise 'root_id is required for access key' if @root_id.nil?
|
30
30
|
else
|
@@ -56,7 +56,7 @@ module Aspera
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# generic method
|
59
|
-
def start_transfer(transfer_spec)
|
59
|
+
def start_transfer(transfer_spec, token_regenerator: nil)
|
60
60
|
# add root id if access key
|
61
61
|
if !@root_id.nil?
|
62
62
|
case transfer_spec['direction']
|
@@ -79,7 +79,7 @@ module Aspera
|
|
79
79
|
if transfer_spec['tags'].is_a?(Hash) && transfer_spec['tags']['aspera'].is_a?(Hash)
|
80
80
|
transfer_spec['tags']['aspera']['xfer_retry'] ||= 150
|
81
81
|
end
|
82
|
-
#
|
82
|
+
# Optimization in case of sending to the same node (TODO: probably remove this, as /etc/hosts shall be used for that)
|
83
83
|
if !transfer_spec['wss_enabled'] && transfer_spec['remote_host'].eql?(URI.parse(node_api_.params[:base_url]).host)
|
84
84
|
transfer_spec['remote_host'] = '127.0.0.1'
|
85
85
|
end
|
@@ -96,8 +96,8 @@ module Aspera
|
|
96
96
|
# lets emulate management events to display progress bar
|
97
97
|
loop do
|
98
98
|
# status is empty sometimes with status 200...
|
99
|
-
|
100
|
-
case
|
99
|
+
transfer_data = node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {'status' => 'unknown'} rescue {'status' => 'waiting(read error)'}
|
100
|
+
case transfer_data['status']
|
101
101
|
when 'completed'
|
102
102
|
notify_end(@transfer_id)
|
103
103
|
break
|
@@ -106,21 +106,19 @@ module Aspera
|
|
106
106
|
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
107
107
|
spinner.start
|
108
108
|
end
|
109
|
-
spinner.update(title:
|
109
|
+
spinner.update(title: transfer_data['status'])
|
110
110
|
spinner.spin
|
111
|
-
# puts trdata
|
112
111
|
when 'running'
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
notify_begin(@transfer_id, trdata['precalc']['bytes_expected'])
|
112
|
+
if !started && transfer_data['precalc'].is_a?(Hash) &&
|
113
|
+
transfer_data['precalc']['status'].eql?('ready')
|
114
|
+
notify_begin(@transfer_id, transfer_data['precalc']['bytes_expected'])
|
117
115
|
started = true
|
118
116
|
else
|
119
|
-
notify_progress(@transfer_id,
|
117
|
+
notify_progress(@transfer_id, transfer_data['bytes_transferred'])
|
120
118
|
end
|
121
119
|
else
|
122
|
-
Log.log.warn{"
|
123
|
-
raise Fasp::Error, "#{
|
120
|
+
Log.log.warn{"transfer_data -> #{transfer_data}"}
|
121
|
+
raise Fasp::Error, "#{transfer_data['status']}: #{transfer_data['error_desc']}"
|
124
122
|
end
|
125
123
|
sleep(1)
|
126
124
|
end
|
@@ -58,7 +58,7 @@ module Aspera
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def start_transfer(transfer_spec)
|
61
|
+
def start_transfer(transfer_spec, token_regenerator: nil)
|
62
62
|
# create a transfer request
|
63
63
|
transfer_request = Transfersdk::TransferRequest.new(
|
64
64
|
transferType: Transfersdk::TransferType::FILE_REGULAR, # transfer type (file/stream)
|
@@ -128,7 +128,7 @@ module Aspera
|
|
128
128
|
FILES = %i[ascp ascp4 ssh_bypass_key_dsa ssh_bypass_key_rsa aspera_license aspera_conf fallback_cert fallback_key].freeze
|
129
129
|
|
130
130
|
# get path of one resource file of currently activated product
|
131
|
-
# keys and certs are generated locally... (they are well known values, arch.
|
131
|
+
# keys and certs are generated locally... (they are well known values, arch. independent)
|
132
132
|
def path(k)
|
133
133
|
case k
|
134
134
|
when :ascp, :ascp4
|
@@ -182,7 +182,7 @@ module Aspera
|
|
182
182
|
# @return the file path of local connect where API's URI can be read
|
183
183
|
def connect_uri
|
184
184
|
connect = get_product_folders(PRODUCT_CONNECT)
|
185
|
-
folder = File.join(connect[:run_root],
|
185
|
+
folder = File.join(connect[:run_root], VAR_RUN_SUBFOLDER)
|
186
186
|
['', 's'].each do |ext|
|
187
187
|
uri_file = File.join(folder, "http#{ext}.uri")
|
188
188
|
Log.log.debug{"checking connect port file: #{uri_file}"}
|
@@ -264,7 +264,7 @@ module Aspera
|
|
264
264
|
end
|
265
265
|
end
|
266
266
|
File.unlink(sdk_zip_path) rescue nil # Windows may give error
|
267
|
-
# ensure license file are generated so that ascp
|
267
|
+
# ensure license file are generated so that ascp invocation for version works
|
268
268
|
path(:aspera_license)
|
269
269
|
path(:aspera_conf)
|
270
270
|
ascp_path = File.join(sdk_folder, ascp_filename)
|
@@ -285,13 +285,13 @@ module Aspera
|
|
285
285
|
|
286
286
|
BIN_SUBFOLDER = 'bin'
|
287
287
|
ETC_SUBFOLDER = 'etc'
|
288
|
-
|
288
|
+
VAR_RUN_SUBFOLDER = File.join('var', 'run')
|
289
289
|
# product information manifest: XML (part of aspera product)
|
290
290
|
PRODUCT_INFO = 'product-info.mf'
|
291
291
|
# policy for product selection
|
292
292
|
FIRST_FOUND = 'FIRST'
|
293
293
|
|
294
|
-
private_constant :BIN_SUBFOLDER, :ETC_SUBFOLDER, :
|
294
|
+
private_constant :BIN_SUBFOLDER, :ETC_SUBFOLDER, :VAR_RUN_SUBFOLDER, :PRODUCT_INFO
|
295
295
|
|
296
296
|
def initialize
|
297
297
|
@path_to_ascp = nil
|
data/lib/aspera/fasp/listener.rb
CHANGED
@@ -18,8 +18,9 @@ module Aspera
|
|
18
18
|
SUPPORTED_AGENTS = %i[direct node connect].freeze
|
19
19
|
# Short names of columns in manual
|
20
20
|
SUPPORTED_AGENTS_SHORT = SUPPORTED_AGENTS.map{|a|a.to_s[0].to_sym}
|
21
|
+
FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
|
21
22
|
|
22
|
-
private_constant :SUPPORTED_AGENTS
|
23
|
+
private_constant :SUPPORTED_AGENTS, :FILE_LIST_OPTIONS
|
23
24
|
|
24
25
|
class << self
|
25
26
|
# Temp folder for file lists, must contain only file lists
|
@@ -40,34 +41,34 @@ module Aspera
|
|
40
41
|
# @return a table suitable to display in manual
|
41
42
|
def man_table(to_text: true)
|
42
43
|
result = []
|
43
|
-
description.each do |
|
44
|
-
param = {name:
|
44
|
+
description.each do |name, options|
|
45
|
+
param = {name: name, type: [options[:accepted_types]].flatten.join(','), description: options[:desc]}
|
45
46
|
# add flags for supported agents in doc
|
46
47
|
SUPPORTED_AGENTS.each do |a|
|
47
|
-
param[a.to_s[0].to_sym] =
|
48
|
+
param[a.to_s[0].to_sym] = options[:agents].nil? || options[:agents].include?(a) ? 'Y' : ''
|
48
49
|
end
|
49
50
|
# only keep lines that are usable in supported agents
|
50
51
|
next if SUPPORTED_AGENTS_SHORT.inject(true){|m, j|m && param[j].empty?}
|
51
52
|
param[:cli] =
|
52
|
-
case
|
53
|
-
when :envvar then 'env:' +
|
54
|
-
when :opt_without_arg then
|
53
|
+
case options[:cli][:type]
|
54
|
+
when :envvar then 'env:' + options[:cli][:variable]
|
55
|
+
when :opt_without_arg then options[:cli][:switch]
|
55
56
|
when :opt_with_arg
|
56
|
-
values = if
|
57
|
+
values = if options.key?(:enum)
|
57
58
|
['enum']
|
58
|
-
elsif
|
59
|
-
|
60
|
-
elsif !
|
61
|
-
[
|
59
|
+
elsif options[:accepted_types].is_a?(Array)
|
60
|
+
options[:accepted_types]
|
61
|
+
elsif !options[:accepted_types].nil?
|
62
|
+
[options[:accepted_types]]
|
62
63
|
else
|
63
64
|
raise "error: #{param}"
|
64
65
|
end.map{|n|"{#{n}}"}.join('|')
|
65
|
-
conv =
|
66
|
-
"#{
|
66
|
+
conv = options[:cli].key?(:convert) ? '(conversion)' : ''
|
67
|
+
"#{options[:cli][:switch]} #{conv}#{values}"
|
67
68
|
else ''
|
68
69
|
end
|
69
|
-
if
|
70
|
-
param[:description] += "\nAllowed values: #{
|
70
|
+
if options.key?(:enum)
|
71
|
+
param[:description] += "\nAllowed values: #{options[:enum].join(', ')}"
|
71
72
|
end
|
72
73
|
param[:description] = param[:description].gsub('/', '/') if to_text
|
73
74
|
result.push(param)
|
@@ -75,24 +76,27 @@ module Aspera
|
|
75
76
|
return result
|
76
77
|
end
|
77
78
|
|
78
|
-
# special encoding methods used in YAML (key: :
|
79
|
-
def
|
79
|
+
# special encoding methods used in YAML (key: :convert)
|
80
|
+
def convert_remove_hyphen(v); v.tr('-', ''); end
|
80
81
|
|
81
|
-
# special encoding methods used in YAML (key: :
|
82
|
-
def
|
82
|
+
# special encoding methods used in YAML (key: :convert)
|
83
|
+
def convert_json64(v); Base64.strict_encode64(JSON.generate(v)); end
|
83
84
|
|
84
|
-
# special encoding methods used in YAML (key: :
|
85
|
-
def
|
85
|
+
# special encoding methods used in YAML (key: :convert)
|
86
|
+
def convert_base64(v); Base64.strict_encode64(v); end
|
86
87
|
|
87
88
|
# file list is provided directly with ascp arguments
|
88
|
-
def ts_has_ascp_file_list(ts)
|
89
|
-
|
89
|
+
def ts_has_ascp_file_list(ts, ascp_args)
|
90
|
+
# it can also be option transfer_info
|
91
|
+
ascp_args = ascp_args['ascp_args'] if ascp_args.is_a?(Hash)
|
92
|
+
(ts['EX_ascp_args'].is_a?(Array) && ts['EX_ascp_args'].any?{|i|FILE_LIST_OPTIONS.include?(i)}) ||
|
93
|
+
(ascp_args.is_a?(Array) && ascp_args.any?{|i|FILE_LIST_OPTIONS.include?(i)}) ||
|
90
94
|
ts.key?('EX_file_list') ||
|
91
95
|
ts.key?('EX_file_pair_list')
|
92
96
|
end
|
93
97
|
|
94
|
-
def ts_to_env_args(transfer_spec,
|
95
|
-
return Parameters.new(transfer_spec,
|
98
|
+
def ts_to_env_args(transfer_spec, wss:, ascp_args:)
|
99
|
+
return Parameters.new(transfer_spec, wss: wss, ascp_args: ascp_args).ascp_args
|
96
100
|
end
|
97
101
|
|
98
102
|
# temp file list files are created here
|
@@ -107,29 +111,32 @@ module Aspera
|
|
107
111
|
attr_reader :file_list_folder
|
108
112
|
end # self
|
109
113
|
|
110
|
-
# @param options [Hash] key: :wss: bool
|
111
|
-
def initialize(job_spec,
|
114
|
+
# @param options [Hash] key: :wss: bool, :ascp_args: array of strings
|
115
|
+
def initialize(job_spec, wss:, ascp_args:)
|
112
116
|
@job_spec = job_spec
|
113
|
-
@
|
117
|
+
@opt_wss = wss
|
118
|
+
@opt_args = ascp_args
|
119
|
+
Log.log.debug{"agent options: #{@opt_wss} #{@opt_args}"}
|
120
|
+
raise 'ascp args must be an Array' unless @opt_args.is_a?(Array)
|
121
|
+
raise 'ascp args must be an Array of String' if @opt_args.any?{|i|!i.is_a?(String)}
|
114
122
|
@builder = Aspera::CommandLineBuilder.new(@job_spec, self.class.description)
|
115
|
-
Log.log.debug{"agent options: #{@options}"}
|
116
123
|
end
|
117
124
|
|
118
125
|
def process_file_list
|
119
126
|
# is the file list provided through EX_ parameters?
|
120
|
-
ascp_file_list_provided = self.class.ts_has_ascp_file_list(@job_spec)
|
127
|
+
ascp_file_list_provided = self.class.ts_has_ascp_file_list(@job_spec, @opt_args)
|
121
128
|
# set if paths is mandatory in ts
|
122
129
|
@builder.params_definition['paths'][:mandatory] = !@job_spec.key?('keepalive') && !ascp_file_list_provided
|
123
130
|
# get paths in transfer spec (after setting if it is mandatory)
|
124
|
-
ts_paths_array = @builder.
|
131
|
+
ts_paths_array = @builder.read_param('paths')
|
125
132
|
if ascp_file_list_provided && !ts_paths_array.nil?
|
126
133
|
raise 'file list provided both in transfer spec and ascp file list. Remove one of them.'
|
127
134
|
end
|
128
135
|
# option 1: EX_file_list
|
129
|
-
file_list_file = @builder.
|
136
|
+
file_list_file = @builder.read_param('EX_file_list')
|
130
137
|
if file_list_file.nil?
|
131
138
|
# option 2: EX_file_pair_list
|
132
|
-
file_list_file = @builder.
|
139
|
+
file_list_file = @builder.read_param('EX_file_pair_list')
|
133
140
|
if !file_list_file.nil?
|
134
141
|
option = '--file-pair-list'
|
135
142
|
elsif !ts_paths_array.nil?
|
@@ -140,7 +147,7 @@ module Aspera
|
|
140
147
|
@builder.add_command_line_options(ts_paths_array.map{|i|i['source']})
|
141
148
|
else
|
142
149
|
# safer option: generate a file list file if there is storage defined for it
|
143
|
-
# if there is destination in paths, then use
|
150
|
+
# if there is destination in paths, then use file-pair-list
|
144
151
|
# TODO: well, we test only the first one, but anyway it shall be consistent
|
145
152
|
if ts_paths_array.first.key?('destination')
|
146
153
|
option = '--file-pair-list'
|
@@ -179,12 +186,12 @@ module Aspera
|
|
179
186
|
@job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
|
180
187
|
|
181
188
|
# use web socket session initiation ?
|
182
|
-
if @builder.
|
189
|
+
if @builder.read_param('wss_enabled') && (@opt_wss || !@job_spec.key?('fasp_port'))
|
183
190
|
# by default use web socket session if available, unless removed by user
|
184
191
|
@builder.add_command_line_options(['--ws-connect'])
|
185
192
|
# TODO: option to give order ssh,ws (legacy http is implied bu ssh)
|
186
|
-
#
|
187
|
-
@job_spec['ssh_port'] = @builder.
|
193
|
+
# This will need to be cleaned up in aspera core
|
194
|
+
@job_spec['ssh_port'] = @builder.read_param('wss_port')
|
188
195
|
@job_spec.delete('fasp_port')
|
189
196
|
@job_spec.delete('EX_ssh_key_paths')
|
190
197
|
@job_spec.delete('sshfp')
|
@@ -200,7 +207,7 @@ module Aspera
|
|
200
207
|
@builder.process_params
|
201
208
|
|
202
209
|
# symbol must be index of Installation.paths
|
203
|
-
if @builder.
|
210
|
+
if @builder.read_param('use_ascp4')
|
204
211
|
env_args[:ascp_version] = :ascp4
|
205
212
|
else
|
206
213
|
env_args[:ascp_version] = :ascp
|
@@ -210,9 +217,10 @@ module Aspera
|
|
210
217
|
# get list of files to transfer and build arg for ascp
|
211
218
|
process_file_list
|
212
219
|
# optional args, at the end to override previous ones (to allow override)
|
213
|
-
@builder.add_command_line_options(@builder.
|
220
|
+
@builder.add_command_line_options(@builder.read_param('EX_ascp_args'))
|
221
|
+
@builder.add_command_line_options(@opt_args)
|
214
222
|
# process destination folder
|
215
|
-
destination_folder = @builder.
|
223
|
+
destination_folder = @builder.read_param('destination_root') || '/'
|
216
224
|
# ascp4 does not support base64 encoding of destination
|
217
225
|
destination_folder = Base64.strict_encode64(destination_folder) unless env_args[:ascp_version].eql?(:ascp4)
|
218
226
|
# destination MUST be last command line argument to ascp
|