aspera-cli 4.14.0 → 4.15.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 +54 -3
- data/CONTRIBUTING.md +7 -7
- data/README.md +1457 -880
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +198 -127
- data/lib/aspera/ascmd.rb +24 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -171
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +101 -147
- data/lib/aspera/cli/manager.rb +160 -124
- data/lib/aspera/cli/plugin.rb +70 -59
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +239 -273
- data/lib/aspera/cli/plugins/ats.rb +8 -5
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +516 -375
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +99 -84
- data/lib/aspera/cli/plugins/faspex5.rb +179 -148
- data/lib/aspera/cli/plugins/node.rb +219 -153
- data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
- data/lib/aspera/cli/plugins/preview.rb +46 -32
- data/lib/aspera/cli/plugins/server.rb +57 -17
- data/lib/aspera/cli/plugins/shares.rb +34 -12
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +45 -55
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/environment.rb +17 -6
- data/lib/aspera/fasp/agent_aspera.rb +126 -0
- data/lib/aspera/fasp/agent_base.rb +31 -77
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +88 -102
- data/lib/aspera/fasp/agent_httpgw.rb +196 -192
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -34
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +43 -184
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +59 -26
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/transfer_spec.rb +1 -1
- data/lib/aspera/fasp/uri.rb +4 -4
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +57 -16
- data/lib/aspera/node.rb +97 -14
- data/lib/aspera/oauth.rb +36 -18
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -2
- data/lib/aspera/preview/generator.rb +22 -35
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +24 -13
- data/lib/aspera/preview/utils.rb +19 -26
- data/lib/aspera/rest.rb +103 -72
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +15 -14
- data/lib/aspera/rest_errors_aspera.rb +37 -34
- data/lib/aspera/secret_hider.rb +14 -16
- data/lib/aspera/ssh.rb +4 -1
- data/lib/aspera/sync.rb +128 -122
- data/lib/aspera/temp_file_manager.rb +10 -3
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +9 -4
- data.tar.gz.sig +0 -0
- metadata +33 -15
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
@@ -2,93 +2,47 @@
|
|
2
2
|
|
3
3
|
module Aspera
|
4
4
|
module Fasp
|
5
|
-
# Base class for
|
6
|
-
# sub classes shall implement start_transfer and shutdown
|
5
|
+
# Base class for transfer agents
|
7
6
|
class AgentBase
|
8
|
-
# fields description for JSON generation
|
9
|
-
# spellchecker: disable
|
10
|
-
INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
|
11
|
-
DatagramSize XoptFlags VLinkVersion PeerVLinkVersion DSPipelineDepth PeerDSPipelineDepth ReadBlockSize WriteBlockSize
|
12
|
-
ClusterNumNodes ClusterNodeId Size Written Loss FileBytes PreTransferBytes TransferBytes PMTU Elapsedusec ArgScansAttempted
|
13
|
-
ArgScansCompleted PathScansAttempted FileScansCompleted TransfersAttempted TransfersPassed Delay].freeze
|
14
|
-
BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
|
15
|
-
MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
|
16
|
-
EXPECTED_METHODS = %i[text struct enhanced].freeze
|
17
|
-
private_constant :INTEGER_FIELDS, :BOOLEAN_FIELDS, :EXPECTED_METHODS
|
18
|
-
# spellchecker: enable
|
19
|
-
|
20
7
|
class << self
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# translates legacy event into enhanced (JSON) event
|
32
|
-
def enhanced_event_format(event)
|
33
|
-
return event.keys.each_with_object({}) do |e, h|
|
34
|
-
# capital_to_snake_case
|
35
|
-
new_name = e
|
36
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
37
|
-
.gsub(/([a-z\d])(usec)$/, '\1_\2')
|
38
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
39
|
-
.downcase
|
40
|
-
value = event[e]
|
41
|
-
value = value.to_i if INTEGER_FIELDS.include?(e)
|
42
|
-
value = value.eql?('Yes') if BOOLEAN_FIELDS.include?(e)
|
43
|
-
h[new_name] = value
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def initialize
|
48
|
-
@listeners = []
|
49
|
-
end
|
50
|
-
|
51
|
-
def notify_listeners(current_event_text, current_event_data)
|
52
|
-
Log.log.debug('send event to listeners')
|
53
|
-
enhanced_event = nil
|
54
|
-
@listeners.each do |listener|
|
55
|
-
listener.event_text(current_event_text) if listener.respond_to?(:event_text)
|
56
|
-
listener.event_struct(current_event_data) if listener.respond_to?(:event_struct)
|
57
|
-
if listener.respond_to?(:event_enhanced)
|
58
|
-
enhanced_event = enhanced_event_format(current_event_data) if enhanced_event.nil?
|
59
|
-
listener.event_enhanced(enhanced_event)
|
8
|
+
# compute options from user provided and default options
|
9
|
+
def options(default:, options:)
|
10
|
+
result = options.symbolize_keys
|
11
|
+
available = default.map{|k, v|"#{k}(#{v})"}.join(', ')
|
12
|
+
result.each do |k, _v|
|
13
|
+
raise "Unknown transfer agent parameter: #{k}, expect one of #{available}" unless default.key?(k)
|
14
|
+
end
|
15
|
+
default.each do |k, v|
|
16
|
+
raise "Missing required agent parameter: #{k}. Parameters: #{available}" if v.eql?(:required) && !result.key?(k)
|
17
|
+
result[k] = v unless result.key?(k)
|
60
18
|
end
|
19
|
+
return result
|
61
20
|
end
|
62
|
-
end # notify_listeners
|
63
|
-
|
64
|
-
def notify_begin(id, size)
|
65
|
-
notify_listeners('emulated', {LISTENER_SESSION_ID_B => id, 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size})
|
66
21
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
22
|
+
def wait_for_completion
|
23
|
+
# list of: :success or "error message string"
|
24
|
+
statuses = wait_for_transfers_completion
|
25
|
+
@progress&.reset
|
26
|
+
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
27
|
+
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?
|
28
|
+
return statuses
|
70
29
|
end
|
71
30
|
|
72
|
-
|
73
|
-
notify_listeners('emulated', {LISTENER_SESSION_ID_B => id, 'Type' => 'DONE'})
|
74
|
-
end
|
75
|
-
|
76
|
-
public
|
77
|
-
|
78
|
-
LISTENER_SESSION_ID_B = 'ListenerSessionId'
|
79
|
-
LISTENER_SESSION_ID_S = 'listener_session_id'
|
31
|
+
private
|
80
32
|
|
81
|
-
|
82
|
-
|
83
|
-
raise
|
84
|
-
|
85
|
-
|
33
|
+
def initialize(options)
|
34
|
+
raise 'internal error' unless respond_to?(:start_transfer)
|
35
|
+
raise 'internal error' unless respond_to?(:wait_for_transfers_completion)
|
36
|
+
# method `shutdown` is optional
|
37
|
+
Log.log.debug{Log.dump(:agent_options, options)}
|
38
|
+
raise "transfer agent options expecting Hash, but have #{options.class}" unless options.is_a?(Hash)
|
39
|
+
@progress = options[:progress]
|
40
|
+
options.delete(:progress)
|
86
41
|
end
|
87
42
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# optional: shutdown
|
43
|
+
def notify_progress(**parameters)
|
44
|
+
@progress&.event(**parameters)
|
45
|
+
end
|
92
46
|
end
|
93
47
|
end
|
94
48
|
end
|
@@ -4,7 +4,6 @@ require 'aspera/fasp/agent_base'
|
|
4
4
|
require 'aspera/rest'
|
5
5
|
require 'aspera/open_application'
|
6
6
|
require 'securerandom'
|
7
|
-
require 'tty-spinner'
|
8
7
|
|
9
8
|
module Aspera
|
10
9
|
module Fasp
|
@@ -14,20 +13,20 @@ module Aspera
|
|
14
13
|
# delay between each try to start connect
|
15
14
|
SLEEP_SEC_BETWEEN_RETRY = 3
|
16
15
|
private_constant :CONNECT_START_URIS, :SLEEP_SEC_BETWEEN_RETRY
|
17
|
-
def initialize(
|
18
|
-
super()
|
16
|
+
def initialize(options)
|
17
|
+
super(options)
|
19
18
|
@connect_settings = {
|
20
19
|
'app_id' => SecureRandom.uuid
|
21
20
|
}
|
22
21
|
raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
|
23
22
|
method_index = 0
|
24
23
|
begin
|
25
|
-
connect_url =
|
24
|
+
connect_url = Products.connect_uri
|
26
25
|
Log.log.debug{"found: #{connect_url}"}
|
27
26
|
@connect_api = Rest.new({base_url: "#{connect_url}/v5/connect", headers: {'Origin' => Rest.user_agent}}) # could use v6 also now
|
28
27
|
connect_info = @connect_api.read('info/version')[:data]
|
29
28
|
Log.log.info('Connect was reached') if method_index > 0
|
30
|
-
Log.dump(:connect_version, connect_info)
|
29
|
+
Log.log.debug{Log.dump(:connect_version, connect_info)}
|
31
30
|
rescue StandardError => e # Errno::ECONNREFUSED
|
32
31
|
start_url = CONNECT_START_URIS[method_index]
|
33
32
|
method_index += 1
|
@@ -74,10 +73,12 @@ module Aspera
|
|
74
73
|
def wait_for_transfers_completion
|
75
74
|
connect_activity_args = {'aspera_connect_settings' => @connect_settings}
|
76
75
|
started = false
|
77
|
-
|
76
|
+
pre_calc = false
|
77
|
+
session_id = @xfer_id
|
78
78
|
begin
|
79
79
|
loop do
|
80
80
|
tr_info = @connect_api.create("transfers/info/#{@xfer_id}", connect_activity_args)[:data]
|
81
|
+
Log.log.trace1{Log.dump(:tr_info, tr_info)}
|
81
82
|
if tr_info['transfer_info'].is_a?(Hash)
|
82
83
|
transfer = tr_info['transfer_info']
|
83
84
|
if transfer.nil?
|
@@ -86,32 +87,30 @@ module Aspera
|
|
86
87
|
end
|
87
88
|
# TODO: get session id
|
88
89
|
case transfer['status']
|
89
|
-
when 'completed'
|
90
|
-
notify_end(@connect_settings['app_id'])
|
91
|
-
break
|
92
90
|
when 'initiating', 'queued'
|
93
|
-
|
94
|
-
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
95
|
-
spinner.start
|
96
|
-
end
|
97
|
-
spinner.update(title: transfer['status'])
|
98
|
-
spinner.spin
|
91
|
+
notify_progress(session_id: nil, type: :pre_start, info: transfer['status'])
|
99
92
|
when 'running'
|
100
|
-
|
101
|
-
|
102
|
-
spinner&.success
|
103
|
-
notify_begin(@connect_settings['app_id'], transfer['bytes_expected'])
|
93
|
+
if !started
|
94
|
+
notify_progress(session_id: session_id, type: :session_start)
|
104
95
|
started = true
|
96
|
+
end
|
97
|
+
if !pre_calc && (transfer['bytes_expected'] != 0)
|
98
|
+
notify_progress(type: :session_size, session_id: session_id, info: transfer['bytes_expected'])
|
99
|
+
pre_calc = true
|
105
100
|
else
|
106
|
-
notify_progress(
|
101
|
+
notify_progress(type: :transfer, session_id: session_id, info: transfer['bytes_written'])
|
107
102
|
end
|
103
|
+
when 'completed'
|
104
|
+
notify_progress(type: :end, session_id: session_id)
|
105
|
+
break
|
108
106
|
when 'failed'
|
109
|
-
|
107
|
+
notify_progress(type: :end, session_id: session_id)
|
110
108
|
raise Fasp::Error, transfer['error_desc']
|
111
109
|
when 'cancelled'
|
112
|
-
|
110
|
+
notify_progress(type: :end, session_id: session_id)
|
113
111
|
raise Fasp::Error, 'Transfer cancelled by user'
|
114
112
|
else
|
113
|
+
notify_progress(type: :end, session_id: session_id)
|
115
114
|
raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
|
116
115
|
end
|
117
116
|
end
|
@@ -1,17 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'English'
|
4
3
|
require 'aspera/fasp/agent_base'
|
5
4
|
require 'aspera/fasp/error'
|
6
5
|
require 'aspera/fasp/parameters'
|
7
6
|
require 'aspera/fasp/installation'
|
8
7
|
require 'aspera/fasp/resume_policy'
|
9
8
|
require 'aspera/fasp/transfer_spec'
|
9
|
+
require 'aspera/fasp/management'
|
10
10
|
require 'aspera/log'
|
11
11
|
require 'socket'
|
12
12
|
require 'timeout'
|
13
13
|
require 'securerandom'
|
14
14
|
require 'shellwords'
|
15
|
+
require 'English'
|
15
16
|
|
16
17
|
module Aspera
|
17
18
|
module Fasp
|
@@ -25,8 +26,11 @@ module Aspera
|
|
25
26
|
multi_incr_udp: true,
|
26
27
|
resume: {},
|
27
28
|
ascp_args: [],
|
28
|
-
|
29
|
+
check_ignore: nil, # callback with host,port
|
30
|
+
quiet: true, # by default no native ascp progress bar
|
31
|
+
trusted_certs: [] # list of files with trusted certificates (stores)
|
29
32
|
}.freeze
|
33
|
+
# spellchecker: enable
|
30
34
|
private_constant :DEFAULT_OPTIONS
|
31
35
|
|
32
36
|
# start ascp transfer (non blocking), single or multi-session
|
@@ -47,15 +51,7 @@ module Aspera
|
|
47
51
|
# TODO: useful ? node only ?
|
48
52
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_retry'] ||= 3600
|
49
53
|
end
|
50
|
-
Log.dump('ts', transfer_spec)
|
51
|
-
|
52
|
-
# add bypass keys when authentication is token and no auth is provided
|
53
|
-
if transfer_spec.key?('token') &&
|
54
|
-
!transfer_spec.key?('remote_password') &&
|
55
|
-
!transfer_spec.key?('EX_ssh_key_paths')
|
56
|
-
# transfer_spec['remote_password'] = Installation.instance.bypass_pass # not used: no passphrase
|
57
|
-
transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
|
58
|
-
end
|
54
|
+
Log.log.debug{Log.dump('ts', transfer_spec)}
|
59
55
|
|
60
56
|
# Compute this before using transfer spec because it potentially modifies the transfer spec
|
61
57
|
# (even if the var is not used in single session)
|
@@ -81,16 +77,8 @@ module Aspera
|
|
81
77
|
end
|
82
78
|
end
|
83
79
|
|
84
|
-
# compute known
|
85
|
-
env_args =
|
86
|
-
|
87
|
-
# add fallback cert and key as arguments if needed
|
88
|
-
if ['1', 1, true, 'force'].include?(transfer_spec['http_fallback'])
|
89
|
-
env_args[:args].unshift('-Y', Installation.instance.path(:fallback_cert_privkey))
|
90
|
-
env_args[:args].unshift('-I', Installation.instance.path(:fallback_certificate))
|
91
|
-
end
|
92
|
-
|
93
|
-
env_args[:args].unshift('-q') if @options[:quiet]
|
80
|
+
# compute known arguments and environment variables
|
81
|
+
env_args = Parameters.new(transfer_spec, @options).ascp_args
|
94
82
|
|
95
83
|
# transfer job can be multi session
|
96
84
|
xfer_job = {
|
@@ -162,6 +150,49 @@ module Aspera
|
|
162
150
|
Log.log.debug('fasp local shutdown')
|
163
151
|
end
|
164
152
|
|
153
|
+
# cspell:disable
|
154
|
+
# begin 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size
|
155
|
+
# progress 'Type' => 'STATS', 'Bytescont' => size
|
156
|
+
# end 'Type' => 'DONE'
|
157
|
+
# cspell:enable
|
158
|
+
|
159
|
+
# @param event management port event
|
160
|
+
def process_progress(event)
|
161
|
+
session_id = event['SessionId']
|
162
|
+
case event['Type']
|
163
|
+
when 'INIT'
|
164
|
+
@pre_calc_sent = false
|
165
|
+
@pre_calc_last_size = nil
|
166
|
+
notify_progress(session_id: session_id, type: :session_start)
|
167
|
+
when 'NOTIFICATION' # sent from remote
|
168
|
+
if event.key?('PreTransferBytes')
|
169
|
+
@pre_calc_sent = true
|
170
|
+
notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
|
171
|
+
end
|
172
|
+
when 'STATS' # during transfer
|
173
|
+
@pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
|
174
|
+
notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
|
175
|
+
when 'DONE', 'ERROR' # end of session
|
176
|
+
total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
|
177
|
+
if !@pre_calc_sent && !total_size.zero?
|
178
|
+
notify_progress(session_id: session_id, type: :session_size, info: total_size)
|
179
|
+
end
|
180
|
+
if @pre_calc_last_size != total_size
|
181
|
+
notify_progress(session_id: session_id, type: :transfer, info: total_size)
|
182
|
+
end
|
183
|
+
notify_progress(session_id: session_id, type: :end)
|
184
|
+
# cspell:disable
|
185
|
+
when 'SESSION'
|
186
|
+
when 'ARGSTOP'
|
187
|
+
when 'FILEERROR'
|
188
|
+
when 'STOP'
|
189
|
+
# cspell:enable
|
190
|
+
# stop event when one file is completed
|
191
|
+
else
|
192
|
+
Log.log.debug{"unknown event type #{event['Type']}"}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
165
196
|
# This is the low level method to start the "ascp" process
|
166
197
|
# currently, relies on command line arguments
|
167
198
|
# start ascp with management port.
|
@@ -173,8 +204,6 @@ module Aspera
|
|
173
204
|
def start_transfer_with_args_env(env_args, session)
|
174
205
|
raise 'env_args must be Hash' unless env_args.is_a?(Hash)
|
175
206
|
raise 'session must be Hash' unless session.is_a?(Hash)
|
176
|
-
# by default we assume an exception will be raised (for ensure block)
|
177
|
-
exception_raised = true
|
178
207
|
begin
|
179
208
|
Log.log.debug{"env_args=#{env_args.inspect}"}
|
180
209
|
# get location of ascp executable
|
@@ -183,13 +212,14 @@ module Aspera
|
|
183
212
|
end
|
184
213
|
# (optional) check it exists
|
185
214
|
raise Fasp::Error, "no such file: #{ascp_path}" unless File.exist?(ascp_path)
|
215
|
+
notify_progress(session_id: nil, type: :pre_start, info: 'starting ascp')
|
186
216
|
# open an available (0) local TCP port as ascp management
|
187
217
|
mgt_sock = TCPServer.new('127.0.0.1', 0)
|
188
218
|
# clone arguments as we eed to modify with mgt port
|
189
219
|
ascp_arguments = env_args[:args].clone
|
190
220
|
# add management port on the selected local port
|
191
221
|
ascp_arguments.unshift('-M', mgt_sock.addr[1].to_s)
|
192
|
-
#
|
222
|
+
# display ascp command line
|
193
223
|
Log.log.debug do
|
194
224
|
[
|
195
225
|
'execute:',
|
@@ -198,12 +228,13 @@ module Aspera
|
|
198
228
|
ascp_arguments.map{|a|Shellwords.shellescape(a)}
|
199
229
|
].flatten.join(' ')
|
200
230
|
end
|
201
|
-
# start process
|
231
|
+
# start ascp in separate process
|
202
232
|
ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments)
|
203
233
|
# in parent, wait for connection to socket max 3 seconds
|
204
234
|
Log.log.debug{"before accept for pid (#{ascp_pid})"}
|
205
235
|
# init management socket
|
206
236
|
ascp_mgt_io = nil
|
237
|
+
notify_progress(session_id: nil, type: :pre_start, info: 'waiting for ascp')
|
207
238
|
Timeout.timeout(@options[:spawn_timeout_sec]) do
|
208
239
|
ascp_mgt_io = mgt_sock.accept
|
209
240
|
# management messages include file names which may be utf8
|
@@ -213,73 +244,35 @@ module Aspera
|
|
213
244
|
end
|
214
245
|
Log.log.debug{"after accept (#{ascp_mgt_io})"}
|
215
246
|
session[:io] = ascp_mgt_io
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
current_event_text += line
|
229
|
-
line.chomp!
|
230
|
-
Log.log.debug{"line=[#{line}]"}
|
231
|
-
case line
|
232
|
-
when 'FASPMGR 2'
|
233
|
-
# begin event
|
234
|
-
current_event_data = {}
|
235
|
-
current_event_text = ''
|
236
|
-
when /^([^:]+): (.*)$/
|
237
|
-
# event field
|
238
|
-
current_event_data[Regexp.last_match(1)] = Regexp.last_match(2)
|
239
|
-
when ''
|
240
|
-
# empty line is separator to end event information
|
241
|
-
raise 'unexpected empty line' if current_event_data.nil?
|
242
|
-
current_event_data[AgentBase::LISTENER_SESSION_ID_B] = ascp_pid
|
243
|
-
notify_listeners(current_event_text, current_event_data)
|
244
|
-
case current_event_data['Type']
|
245
|
-
when 'INIT'
|
246
|
-
session[:id] = current_event_data['SessionId']
|
247
|
-
Log.log.debug{"session id: #{session[:id]}"}
|
248
|
-
when 'DONE', 'ERROR'
|
249
|
-
# TODO: check if this is always the last event
|
250
|
-
last_status_event = current_event_data
|
251
|
-
end # event type
|
252
|
-
else
|
253
|
-
raise "unexpected line:[#{line}]"
|
254
|
-
end # case
|
255
|
-
end # loop (process mgt port lines)
|
247
|
+
processor = Management.new
|
248
|
+
# read management port, until socket is closed (gets returns nil)
|
249
|
+
while (line = ascp_mgt_io.gets)
|
250
|
+
event = processor.process_line(line.chomp)
|
251
|
+
next unless event
|
252
|
+
# event is ready
|
253
|
+
Log.log.debug{Log.dump(:management_port, event)}
|
254
|
+
# Log.log.trace1{"event: #{JSON.generate(Management.enhanced_event_format(event))}"}
|
255
|
+
process_progress(event)
|
256
|
+
Log.log.error((event['Description']).to_s) if event['Type'].eql?('FILEERROR') # cspell:disable-line
|
257
|
+
end
|
258
|
+
last_event = processor.last_event
|
256
259
|
# check that last status was received before process exit
|
257
|
-
if
|
258
|
-
case
|
259
|
-
when 'DONE'
|
260
|
-
# all went well
|
261
|
-
exception_raised = false
|
260
|
+
if last_event.is_a?(Hash)
|
261
|
+
case last_event['Type']
|
262
262
|
when 'ERROR'
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
|
270
|
-
end
|
263
|
+
if /bearer token/i.match?(last_event['Description']) &&
|
264
|
+
session[:token_regenerator].respond_to?(:refreshed_transfer_token)
|
265
|
+
# regenerate token here, expired, or error on it
|
266
|
+
# Note: in multi-session, each session will have a different one.
|
267
|
+
Log.log.warn('Regenerating bearer token')
|
268
|
+
env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
|
271
269
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
raise
|
277
|
-
|
278
|
-
raise "unexpected last event type: #{last_status_event['Type']}"
|
279
|
-
end
|
280
|
-
else
|
281
|
-
exception_raised = false
|
282
|
-
Log.log.debug('no status read from ascp mgt port')
|
270
|
+
raise Fasp::Error.new(last_event['Description'], last_event['Code'].to_i)
|
271
|
+
when 'DONE'
|
272
|
+
nil
|
273
|
+
else
|
274
|
+
raise "unexpected last event type: #{last_event['Type']}"
|
275
|
+
end # case
|
283
276
|
end
|
284
277
|
rescue SystemCallError => e
|
285
278
|
# Process.spawn
|
@@ -299,7 +292,7 @@ module Aspera
|
|
299
292
|
if !status.success?
|
300
293
|
message = "ascp failed with code #{status.exitstatus}"
|
301
294
|
# raise error only if there was not already an exception
|
302
|
-
raise Fasp::Error, message unless
|
295
|
+
raise Fasp::Error, message unless $ERROR_INFO
|
303
296
|
# else just debug, as main exception is already here
|
304
297
|
Log.log.debug(message)
|
305
298
|
end
|
@@ -323,7 +316,7 @@ module Aspera
|
|
323
316
|
command = data
|
324
317
|
.keys
|
325
318
|
.map{|k|"#{k.capitalize}: #{data[k]}"}
|
326
|
-
.unshift(
|
319
|
+
.unshift(MGT_HEADER)
|
327
320
|
.push('', '')
|
328
321
|
.join("\n")
|
329
322
|
session[:io].puts(command)
|
@@ -332,23 +325,16 @@ module Aspera
|
|
332
325
|
private
|
333
326
|
|
334
327
|
# @param options : keys(symbol): see DEFAULT_OPTIONS
|
335
|
-
def initialize(options=
|
336
|
-
super()
|
328
|
+
def initialize(options={})
|
329
|
+
super(options)
|
337
330
|
# all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
|
338
331
|
@jobs = {}
|
339
332
|
# mutex protects global data accessed by threads
|
340
333
|
@mutex = Mutex.new
|
341
334
|
# set default options and override if specified
|
342
|
-
@options = DEFAULT_OPTIONS
|
343
|
-
if !options.nil?
|
344
|
-
raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
|
345
|
-
options.each do |k, v|
|
346
|
-
raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.key?(k)
|
347
|
-
@options[k] = v
|
348
|
-
end
|
349
|
-
end
|
350
|
-
Log.log.debug{"local options= #{options}"}
|
335
|
+
@options = AgentBase.options(default: DEFAULT_OPTIONS, options: options)
|
351
336
|
@resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
|
337
|
+
Log.log.debug{Log.dump(:agent_options, @options)}
|
352
338
|
end
|
353
339
|
|
354
340
|
# transfer thread entry
|