aspera-cli 4.5.0 → 4.8.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 +1 -0
- data/README.md +1894 -1574
- data/bin/ascli +21 -1
- data/bin/asession +38 -34
- data/docs/test_env.conf +14 -3
- data/examples/aoc.rb +17 -15
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +42 -35
- data/examples/proxy.pac +1 -1
- data/examples/transfer.rb +38 -37
- data/lib/aspera/aoc.rb +245 -205
- data/lib/aspera/ascmd.rb +111 -90
- data/lib/aspera/ats_api.rb +16 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +19 -18
- data/lib/aspera/cli/extended_value.rb +50 -39
- data/lib/aspera/cli/formater.rb +161 -135
- data/lib/aspera/cli/info.rb +18 -0
- data/lib/aspera/cli/listener/line_dump.rb +4 -2
- data/lib/aspera/cli/listener/logger.rb +3 -1
- data/lib/aspera/cli/listener/progress.rb +20 -21
- data/lib/aspera/cli/listener/progress_multi.rb +29 -31
- data/lib/aspera/cli/main.rb +194 -183
- data/lib/aspera/cli/manager.rb +213 -206
- data/lib/aspera/cli/plugin.rb +71 -49
- data/lib/aspera/cli/plugins/alee.rb +8 -7
- data/lib/aspera/cli/plugins/aoc.rb +675 -558
- data/lib/aspera/cli/plugins/ats.rb +116 -109
- data/lib/aspera/cli/plugins/bss.rb +35 -34
- data/lib/aspera/cli/plugins/config.rb +722 -542
- data/lib/aspera/cli/plugins/console.rb +28 -22
- data/lib/aspera/cli/plugins/cos.rb +28 -37
- data/lib/aspera/cli/plugins/faspex.rb +281 -227
- data/lib/aspera/cli/plugins/faspex5.rb +129 -84
- data/lib/aspera/cli/plugins/node.rb +426 -232
- data/lib/aspera/cli/plugins/orchestrator.rb +106 -98
- data/lib/aspera/cli/plugins/preview.rb +196 -191
- data/lib/aspera/cli/plugins/server.rb +131 -126
- data/lib/aspera/cli/plugins/shares.rb +49 -36
- data/lib/aspera/cli/plugins/sync.rb +27 -28
- data/lib/aspera/cli/transfer_agent.rb +84 -79
- data/lib/aspera/cli/version.rb +3 -1
- data/lib/aspera/colors.rb +37 -28
- data/lib/aspera/command_line_builder.rb +84 -63
- data/lib/aspera/cos_node.rb +68 -34
- data/lib/aspera/data_repository.rb +4 -2
- data/lib/aspera/environment.rb +61 -46
- data/lib/aspera/fasp/agent_base.rb +36 -31
- data/lib/aspera/fasp/agent_connect.rb +44 -37
- data/lib/aspera/fasp/agent_direct.rb +101 -104
- data/lib/aspera/fasp/agent_httpgw.rb +91 -90
- data/lib/aspera/fasp/agent_node.rb +36 -33
- data/lib/aspera/fasp/agent_trsdk.rb +28 -31
- data/lib/aspera/fasp/error.rb +3 -1
- data/lib/aspera/fasp/error_info.rb +81 -54
- data/lib/aspera/fasp/installation.rb +171 -151
- data/lib/aspera/fasp/listener.rb +2 -0
- data/lib/aspera/fasp/parameters.rb +105 -111
- data/lib/aspera/fasp/parameters.yaml +305 -249
- data/lib/aspera/fasp/resume_policy.rb +20 -20
- data/lib/aspera/fasp/transfer_spec.rb +27 -0
- data/lib/aspera/fasp/uri.rb +31 -29
- data/lib/aspera/faspex_gw.rb +95 -118
- data/lib/aspera/hash_ext.rb +12 -13
- data/lib/aspera/id_generator.rb +11 -9
- data/lib/aspera/keychain/encrypted_hash.rb +73 -57
- data/lib/aspera/keychain/macos_security.rb +27 -29
- data/lib/aspera/log.rb +40 -39
- data/lib/aspera/nagios.rb +24 -22
- data/lib/aspera/node.rb +38 -30
- data/lib/aspera/oauth.rb +217 -248
- data/lib/aspera/open_application.rb +9 -7
- data/lib/aspera/persistency_action_once.rb +15 -14
- data/lib/aspera/persistency_folder.rb +15 -18
- data/lib/aspera/preview/file_types.rb +266 -270
- data/lib/aspera/preview/generator.rb +94 -92
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +20 -17
- data/lib/aspera/preview/utils.rb +99 -102
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
- data/lib/aspera/proxy_auto_config.rb +114 -21
- data/lib/aspera/rest.rb +144 -142
- data/lib/aspera/rest_call_error.rb +3 -2
- data/lib/aspera/rest_error_analyzer.rb +31 -31
- data/lib/aspera/rest_errors_aspera.rb +18 -16
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +20 -16
- data/lib/aspera/sync.rb +57 -54
- data/lib/aspera/temp_file_manager.rb +20 -14
- data/lib/aspera/timer_limiter.rb +10 -8
- data/lib/aspera/uri_reader.rb +14 -15
- data/lib/aspera/web_auth.rb +85 -80
- data.tar.gz.sig +0 -0
- metadata +169 -40
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
- data/docs/Makefile +0 -63
- data/docs/README.erb.md +0 -4221
- data/docs/README.md +0 -13
- data/docs/diagrams.txt +0 -49
- data/docs/doc_tools.rb +0 -58
- data/lib/aspera/cli/plugins/shares2.rb +0 -114
- data/lib/aspera/fasp/default.rb +0 -17
@@ -1,5 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'aspera/fasp/agent_base'
|
4
|
+
require 'aspera/fasp/transfer_spec'
|
3
5
|
require 'aspera/log'
|
4
6
|
require 'aspera/rest'
|
5
7
|
require 'websocket-client-simple'
|
@@ -14,9 +16,9 @@ module Aspera
|
|
14
16
|
# start a transfer using Aspera HTTP Gateway, using web socket session
|
15
17
|
class AgentHttpgw < AgentBase
|
16
18
|
# message returned by HTTP GW in case of success
|
17
|
-
OK_MESSAGE='end upload'
|
19
|
+
OK_MESSAGE = 'end upload'
|
18
20
|
# refresh rate for progress
|
19
|
-
UPLOAD_REFRESH_SEC=0.5
|
21
|
+
UPLOAD_REFRESH_SEC = 0.5
|
20
22
|
private_constant :OK_MESSAGE,:UPLOAD_REFRESH_SEC
|
21
23
|
# send message on http gw web socket
|
22
24
|
def ws_send(ws,type,data)
|
@@ -25,63 +27,64 @@ module Aspera
|
|
25
27
|
|
26
28
|
def upload(transfer_spec)
|
27
29
|
# total size of all files
|
28
|
-
total_size=0
|
30
|
+
total_size = 0
|
29
31
|
# we need to keep track of actual file path because transfer spec is modified to be sent in web socket
|
30
|
-
source_paths=[]
|
32
|
+
source_paths = []
|
31
33
|
# get source root or nil
|
32
|
-
source_root =
|
34
|
+
source_root = transfer_spec.has_key?('source_root') && !transfer_spec['source_root'].empty? ? transfer_spec['source_root'] : nil
|
33
35
|
# source root is ignored by GW, used only here
|
34
36
|
transfer_spec.delete('source_root')
|
35
37
|
# compute total size of files to upload (for progress)
|
36
38
|
# modify transfer spec to be suitable for GW
|
37
39
|
transfer_spec['paths'].each do |item|
|
38
40
|
# save actual file location to be able read contents later
|
39
|
-
full_src_filepath=item['source']
|
41
|
+
full_src_filepath = item['source']
|
40
42
|
# add source root if needed
|
41
|
-
full_src_filepath=File.join(source_root,full_src_filepath) unless source_root.nil?
|
43
|
+
full_src_filepath = File.join(source_root,full_src_filepath) unless source_root.nil?
|
42
44
|
# GW expects a simple file name in 'source' but if user wants to change the name, we take it
|
43
|
-
item['source']=File.basename(item['destination'].nil? ? item['source'] : item['destination'])
|
44
|
-
item['file_size']=File.size(full_src_filepath)
|
45
|
-
total_size+=item['file_size']
|
45
|
+
item['source'] = File.basename(item['destination'].nil? ? item['source'] : item['destination'])
|
46
|
+
item['file_size'] = File.size(full_src_filepath)
|
47
|
+
total_size += item['file_size']
|
46
48
|
# save so that we can actually read the file later
|
47
49
|
source_paths.push(full_src_filepath)
|
48
50
|
end
|
49
51
|
|
50
|
-
session_id=SecureRandom.uuid
|
51
|
-
ws
|
52
|
+
session_id = SecureRandom.uuid
|
53
|
+
ws = ::WebSocket::Client::Simple::Client.new
|
52
54
|
# error message if any in callback
|
53
|
-
error=nil
|
55
|
+
error = nil
|
54
56
|
# number of files totally sent
|
55
|
-
received=0
|
57
|
+
received = 0
|
56
58
|
# setup callbacks on websocket
|
57
|
-
ws.on
|
59
|
+
ws.on(:message) do |msg|
|
58
60
|
Log.log.info("ws: message: #{msg.data}")
|
59
|
-
message=msg.data
|
61
|
+
message = msg.data
|
60
62
|
if message.eql?(OK_MESSAGE)
|
61
|
-
received+=1
|
63
|
+
received += 1
|
62
64
|
else
|
63
65
|
message.chomp!
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
error =
|
67
|
+
if message.start_with?('"') && message.end_with?('"')
|
68
|
+
JSON.parse(Base64.strict_decode64(message.chomp[1..-2]))['message']
|
69
|
+
else
|
70
|
+
"expecting quotes in [#{message}]"
|
71
|
+
end
|
69
72
|
end
|
70
73
|
end
|
71
|
-
ws.on
|
72
|
-
error=e
|
74
|
+
ws.on(:error) do |e|
|
75
|
+
error = e
|
73
76
|
end
|
74
|
-
ws.on
|
75
|
-
Log.log.info(
|
77
|
+
ws.on(:open) do
|
78
|
+
Log.log.info('ws: open')
|
76
79
|
end
|
77
|
-
ws.on
|
78
|
-
Log.log.info(
|
80
|
+
ws.on(:close) do
|
81
|
+
Log.log.info('ws: close')
|
79
82
|
end
|
80
83
|
# open web socket to end point
|
81
84
|
ws.connect("#{@gw_api.params[:base_url]}/upload")
|
82
85
|
# async wait ready
|
83
|
-
while !ws.open?
|
84
|
-
Log.log.info(
|
86
|
+
while !ws.open? && error.nil?
|
87
|
+
Log.log.info('ws: wait')
|
85
88
|
sleep(0.2)
|
86
89
|
end
|
87
90
|
# notify progress bar
|
@@ -90,93 +93,94 @@ module Aspera
|
|
90
93
|
Log.dump(:ws_spec,transfer_spec)
|
91
94
|
ws_send(ws,:transfer_spec,transfer_spec)
|
92
95
|
# current file index
|
93
|
-
file_index=0
|
96
|
+
file_index = 0
|
94
97
|
# aggregate size sent
|
95
|
-
sent_bytes=0
|
98
|
+
sent_bytes = 0
|
96
99
|
# last progress event
|
97
|
-
lastevent=nil
|
100
|
+
lastevent = nil
|
98
101
|
transfer_spec['paths'].each do |item|
|
99
102
|
# TODO: get mime type?
|
100
|
-
file_mime_type=''
|
101
|
-
file_size=item['file_size']
|
102
|
-
file_name=File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
103
|
+
file_mime_type = ''
|
104
|
+
file_size = item['file_size']
|
105
|
+
file_name = File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
103
106
|
# compute total number of slices
|
104
|
-
numslices=1+(file_size-1)
|
107
|
+
numslices = 1 + ((file_size - 1) / @upload_chunksize)
|
105
108
|
File.open(source_paths[file_index]) do |file|
|
106
109
|
# current slice index
|
107
|
-
slicenum=0
|
108
|
-
while !file.eof?
|
109
|
-
data=file.read(@upload_chunksize)
|
110
|
-
slice_data={
|
111
|
-
name:
|
112
|
-
type:
|
113
|
-
size:
|
114
|
-
data:
|
115
|
-
slice:
|
110
|
+
slicenum = 0
|
111
|
+
while !file.eof?
|
112
|
+
data = file.read(@upload_chunksize)
|
113
|
+
slice_data = {
|
114
|
+
name: file_name,
|
115
|
+
type: file_mime_type,
|
116
|
+
size: file_size,
|
117
|
+
data: Base64.strict_encode64(data),
|
118
|
+
slice: slicenum,
|
116
119
|
total_slices: numslices,
|
117
|
-
fileIndex:
|
120
|
+
fileIndex: file_index
|
118
121
|
}
|
119
122
|
# log without data
|
120
|
-
Log.dump(:slide_data,slice_data.keys.
|
123
|
+
Log.dump(:slide_data,slice_data.keys.each_with_object({}){|i,m|m[i] = i.eql?(:data) ? 'base64 data' : slice_data[i];}) if slicenum.eql?(0)
|
121
124
|
ws_send(ws,:slice_upload, slice_data)
|
122
|
-
sent_bytes+=data.length
|
123
|
-
currenttime=Time.now
|
124
|
-
if lastevent.nil?
|
125
|
+
sent_bytes += data.length
|
126
|
+
currenttime = Time.now
|
127
|
+
if lastevent.nil? || ((currenttime - lastevent) > UPLOAD_REFRESH_SEC)
|
125
128
|
notify_progress(session_id,sent_bytes)
|
126
|
-
lastevent=currenttime
|
129
|
+
lastevent = currenttime
|
127
130
|
end
|
128
|
-
slicenum+=1
|
131
|
+
slicenum += 1
|
129
132
|
raise error unless error.nil?
|
130
133
|
end
|
131
134
|
end
|
132
|
-
file_index+=1
|
135
|
+
file_index += 1
|
133
136
|
end
|
134
137
|
ws.close
|
135
138
|
notify_end(session_id)
|
136
139
|
end
|
137
140
|
|
138
141
|
def download(transfer_spec)
|
139
|
-
transfer_spec['zip_required']||=false
|
140
|
-
transfer_spec['source_root']||='/'
|
142
|
+
transfer_spec['zip_required'] ||= false
|
143
|
+
transfer_spec['source_root'] ||= '/'
|
141
144
|
# is normally provided by application, like package name
|
142
145
|
if !transfer_spec.has_key?('download_name')
|
143
146
|
# by default it is the name of first file
|
144
|
-
dname=File.basename(transfer_spec['paths'].first['source'])
|
147
|
+
dname = File.basename(transfer_spec['paths'].first['source'])
|
145
148
|
# we remove extension
|
146
|
-
dname=dname.gsub(/\.@gw_api.*$/,'')
|
149
|
+
dname = dname.gsub(/\.@gw_api.*$/,'')
|
147
150
|
# ands add indication of number of files if there is more than one
|
148
151
|
if transfer_spec['paths'].length > 1
|
149
|
-
dname
|
152
|
+
dname += " #{transfer_spec['paths'].length} Files"
|
150
153
|
end
|
151
|
-
transfer_spec['download_name']=dname
|
152
|
-
end
|
153
|
-
creation=@gw_api.create('download',{'transfer_spec'=>transfer_spec})[:data]
|
154
|
-
transfer_uuid=creation['url'].split('/').last
|
155
|
-
if transfer_spec['zip_required'] or transfer_spec['paths'].length > 1
|
156
|
-
# it is a zip file if zip is required or there is more than 1 file
|
157
|
-
file_dest=transfer_spec['download_name']+'.zip'
|
158
|
-
else
|
159
|
-
# it is a plain file if we don't require zip and there is only one file
|
160
|
-
file_dest=File.basename(transfer_spec['paths'].first['source'])
|
154
|
+
transfer_spec['download_name'] = dname
|
161
155
|
end
|
162
|
-
|
163
|
-
|
156
|
+
creation = @gw_api.create('download',{'transfer_spec' => transfer_spec})[:data]
|
157
|
+
transfer_uuid = creation['url'].split('/').last
|
158
|
+
file_dest =
|
159
|
+
if transfer_spec['zip_required'] || transfer_spec['paths'].length > 1
|
160
|
+
# it is a zip file if zip is required or there is more than 1 file
|
161
|
+
transfer_spec['download_name'] + '.zip'
|
162
|
+
else
|
163
|
+
# it is a plain file if we don't require zip and there is only one file
|
164
|
+
File.basename(transfer_spec['paths'].first['source'])
|
165
|
+
end
|
166
|
+
file_dest = File.join(transfer_spec['destination_root'],file_dest)
|
167
|
+
@gw_api.call({operation: 'GET',subpath: "download/#{transfer_uuid}",save_to_file: file_dest})
|
164
168
|
end
|
165
169
|
|
166
170
|
# start FASP transfer based on transfer spec (hash table)
|
167
171
|
# note that it is asynchronous
|
168
172
|
# HTTP download only supports file list
|
169
173
|
def start_transfer(transfer_spec,options={})
|
170
|
-
raise
|
171
|
-
raise
|
172
|
-
raise
|
173
|
-
raise
|
174
|
+
raise 'GW URL must be set' if @gw_api.nil?
|
175
|
+
raise 'option: must be hash (or nil)' unless options.is_a?(Hash)
|
176
|
+
raise 'paths: must be Array' unless transfer_spec['paths'].is_a?(Array)
|
177
|
+
raise 'only token based transfer is supported in GW' unless transfer_spec['token'].is_a?(String)
|
174
178
|
Log.dump(:user_spec,transfer_spec)
|
175
|
-
transfer_spec['authentication']||='token'
|
179
|
+
transfer_spec['authentication'] ||= 'token'
|
176
180
|
case transfer_spec['direction']
|
177
|
-
when
|
181
|
+
when Fasp::TransferSpec::DIRECTION_SEND
|
178
182
|
upload(transfer_spec)
|
179
|
-
when
|
183
|
+
when Fasp::TransferSpec::DIRECTION_RECEIVE
|
180
184
|
download(transfer_spec)
|
181
185
|
else
|
182
186
|
raise "unexpected direction: [#{transfer_spec['direction']}]"
|
@@ -190,25 +194,22 @@ module Aspera
|
|
190
194
|
end
|
191
195
|
|
192
196
|
# terminates monitor thread
|
193
|
-
def shutdown
|
194
|
-
end
|
197
|
+
def shutdown; end
|
195
198
|
|
196
|
-
def url=(api_url)
|
197
|
-
end
|
199
|
+
def url=(api_url); end
|
198
200
|
|
199
201
|
private
|
200
202
|
|
201
203
|
def initialize(params)
|
202
|
-
raise
|
203
|
-
params=params.symbolize_keys
|
204
|
-
raise
|
204
|
+
raise 'params must be Hash' unless params.is_a?(Hash)
|
205
|
+
params = params.symbolize_keys
|
206
|
+
raise 'must have only one param: url' unless params.keys.eql?([:url])
|
205
207
|
super()
|
206
|
-
@gw_api=Rest.new({:
|
208
|
+
@gw_api = Rest.new({base_url: params[:url]})
|
207
209
|
api_info = @gw_api.read('info')[:data]
|
208
|
-
Log.log.info(
|
209
|
-
@upload_chunksize=
|
210
|
+
Log.log.info(api_info.to_s)
|
211
|
+
@upload_chunksize = 128_000 # TODO: configurable ?
|
210
212
|
end
|
211
|
-
|
212
213
|
end # AgentHttpgw
|
213
214
|
end
|
214
215
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/fasp/agent_base'
|
4
|
+
require 'aspera/fasp/transfer_spec'
|
2
5
|
require 'aspera/log'
|
3
6
|
require 'tty-spinner'
|
4
7
|
|
@@ -10,28 +13,28 @@ module Aspera
|
|
10
13
|
# option include: root_id if the node is an access key
|
11
14
|
attr_writer :options
|
12
15
|
def initialize(options)
|
13
|
-
raise
|
14
|
-
[
|
16
|
+
raise 'node specification must be Hash' unless options.is_a?(Hash)
|
17
|
+
%i[url username password].each { |k| raise "missing parameter [#{k}] in node specification: #{options}" unless options.has_key?(k) }
|
15
18
|
super()
|
16
19
|
# root id is required for access key
|
17
|
-
@root_id=options[:root_id]
|
18
|
-
rest_params={ base_url: options[:url]}
|
19
|
-
if options[:password]
|
20
|
-
rest_params[:headers]={
|
21
|
-
'X-Aspera-AccessKey'=>options[:username],
|
22
|
-
'Authorization'
|
20
|
+
@root_id = options[:root_id]
|
21
|
+
rest_params = { base_url: options[:url]}
|
22
|
+
if /^Bearer /.match?(options[:password])
|
23
|
+
rest_params[:headers] = {
|
24
|
+
'X-Aspera-AccessKey' => options[:username],
|
25
|
+
'Authorization' => options[:password]
|
23
26
|
}
|
24
|
-
raise
|
27
|
+
raise 'root_id is required for access key' if @root_id.nil?
|
25
28
|
else
|
26
|
-
rest_params[:auth]={
|
29
|
+
rest_params[:auth] = {
|
27
30
|
type: :basic,
|
28
31
|
username: options[:username],
|
29
32
|
password: options[:password]
|
30
33
|
}
|
31
34
|
end
|
32
|
-
@node_api=Rest.new(rest_params)
|
35
|
+
@node_api = Rest.new(rest_params)
|
33
36
|
# TODO: currently only supports one transfer. This is bad shortcut. but ok for CLI.
|
34
|
-
@transfer_id=nil
|
37
|
+
@transfer_id = nil
|
35
38
|
end
|
36
39
|
|
37
40
|
# used internally to ensure node api is set before using.
|
@@ -44,61 +47,61 @@ module Aspera
|
|
44
47
|
|
45
48
|
# use this to set the node_api end point before using the class.
|
46
49
|
def node_api=(new_value)
|
47
|
-
if !@node_api.nil?
|
50
|
+
if !@node_api.nil? && !new_value.nil?
|
48
51
|
Log.log.warn('overriding existing node api value')
|
49
52
|
end
|
50
|
-
@node_api=new_value
|
53
|
+
@node_api = new_value
|
51
54
|
end
|
52
55
|
|
53
56
|
# generic method
|
54
|
-
def start_transfer(transfer_spec,
|
57
|
+
def start_transfer(transfer_spec,_options=nil)
|
55
58
|
# add root id if access key
|
56
|
-
if
|
59
|
+
if !@root_id.nil?
|
57
60
|
case transfer_spec['direction']
|
58
|
-
when
|
59
|
-
when
|
61
|
+
when Fasp::TransferSpec::DIRECTION_SEND then transfer_spec['source_root_id'] = @root_id
|
62
|
+
when Fasp::TransferSpec::DIRECTION_RECEIVE then transfer_spec['destination_root_id'] = @root_id
|
60
63
|
else raise "unexpected direction in ts: #{transfer_spec['direction']}"
|
61
64
|
end
|
62
65
|
end
|
63
66
|
# manage special additional parameter
|
64
|
-
if transfer_spec.has_key?('EX_ssh_key_paths')
|
67
|
+
if transfer_spec.has_key?('EX_ssh_key_paths') && transfer_spec['EX_ssh_key_paths'].is_a?(Array) && !transfer_spec['EX_ssh_key_paths'].empty?
|
65
68
|
# not standard, so place standard field
|
66
69
|
if transfer_spec.has_key?('ssh_private_key')
|
67
70
|
Log.log.warn('Both ssh_private_key and EX_ssh_key_paths are present, using ssh_private_key')
|
68
71
|
else
|
69
72
|
Log.log.warn('EX_ssh_key_paths has multiple keys, using first one only') unless transfer_spec['EX_ssh_key_paths'].length.eql?(1)
|
70
|
-
transfer_spec['ssh_private_key']=File.read(transfer_spec['EX_ssh_key_paths'].first)
|
73
|
+
transfer_spec['ssh_private_key'] = File.read(transfer_spec['EX_ssh_key_paths'].first)
|
71
74
|
transfer_spec.delete('EX_ssh_key_paths')
|
72
75
|
end
|
73
76
|
end
|
74
|
-
if transfer_spec['tags'].is_a?(Hash)
|
75
|
-
transfer_spec['tags']['aspera']['xfer_retry']||=150
|
77
|
+
if transfer_spec['tags'].is_a?(Hash) && transfer_spec['tags']['aspera'].is_a?(Hash)
|
78
|
+
transfer_spec['tags']['aspera']['xfer_retry'] ||= 150
|
76
79
|
end
|
77
80
|
# optimisation in case of sending to the same node
|
78
81
|
if transfer_spec['remote_host'].eql?(URI.parse(node_api_.params[:base_url]).host)
|
79
|
-
transfer_spec['remote_host']='localhost'
|
82
|
+
transfer_spec['remote_host'] = 'localhost'
|
80
83
|
end
|
81
|
-
resp=node_api_.create('ops/transfers',transfer_spec)[:data]
|
82
|
-
@transfer_id=resp['id']
|
84
|
+
resp = node_api_.create('ops/transfers',transfer_spec)[:data]
|
85
|
+
@transfer_id = resp['id']
|
83
86
|
Log.log.debug("tr_id=#{@transfer_id}")
|
84
87
|
return @transfer_id
|
85
88
|
end
|
86
89
|
|
87
90
|
# generic method
|
88
91
|
def wait_for_transfers_completion
|
89
|
-
started=false
|
90
|
-
spinner=nil
|
92
|
+
started = false
|
93
|
+
spinner = nil
|
91
94
|
# lets emulate management events to display progress bar
|
92
95
|
loop do
|
93
96
|
# status is empty sometimes with status 200...
|
94
|
-
trdata=node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {
|
97
|
+
trdata = node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {'status' => 'unknown'} rescue {'status' => 'waiting(read error)'}
|
95
98
|
case trdata['status']
|
96
99
|
when 'completed'
|
97
100
|
notify_end(@transfer_id)
|
98
101
|
break
|
99
102
|
when 'waiting','partially_completed','unknown','waiting(read error)'
|
100
103
|
if spinner.nil?
|
101
|
-
spinner = TTY::Spinner.new(
|
104
|
+
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
102
105
|
spinner.start
|
103
106
|
end
|
104
107
|
spinner.update(title: trdata['status'])
|
@@ -106,18 +109,18 @@ module Aspera
|
|
106
109
|
#puts trdata
|
107
110
|
when 'running'
|
108
111
|
#puts "running: sessions:#{trdata["sessions"].length}, #{trdata["sessions"].map{|i| i['bytes_transferred']}.join(',')}"
|
109
|
-
if !started
|
112
|
+
if !started && trdata['precalc'].is_a?(Hash) &&
|
110
113
|
trdata['precalc']['status'].eql?('ready')
|
111
114
|
notify_begin(@transfer_id,trdata['precalc']['bytes_expected'])
|
112
|
-
started=true
|
115
|
+
started = true
|
113
116
|
else
|
114
117
|
notify_progress(@transfer_id,trdata['bytes_transferred'])
|
115
118
|
end
|
116
119
|
else
|
117
120
|
Log.log.warn("trdata -> #{trdata}")
|
118
|
-
raise Fasp::Error
|
121
|
+
raise Fasp::Error, "#{trdata['status']}: #{trdata['error_desc']}"
|
119
122
|
end
|
120
|
-
sleep
|
123
|
+
sleep(1)
|
121
124
|
end
|
122
125
|
#TODO get status of sessions
|
123
126
|
return []
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/fasp/agent_base'
|
2
4
|
require 'aspera/fasp/installation'
|
3
5
|
require 'json'
|
@@ -7,23 +9,18 @@ module Aspera
|
|
7
9
|
class AgentTrsdk < AgentBase
|
8
10
|
DEFAULT_OPTIONS = {
|
9
11
|
address: '127.0.0.1',
|
10
|
-
port:
|
11
|
-
}
|
12
|
+
port: 55_002
|
13
|
+
}.freeze
|
12
14
|
private_constant :DEFAULT_OPTIONS
|
13
15
|
|
14
16
|
# options come from transfer_info
|
15
17
|
def initialize(user_opts)
|
16
|
-
raise "expecting Hash (or nil), but have #{user_opts.class}" unless user_opts.nil?
|
18
|
+
raise "expecting Hash (or nil), but have #{user_opts.class}" unless user_opts.nil? || user_opts.is_a?(Hash)
|
17
19
|
# set default options and override if specified
|
18
|
-
options=DEFAULT_OPTIONS.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
options[k]=v
|
23
|
-
else
|
24
|
-
raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map{|i|i.to_s}.join(",")}"
|
25
|
-
end
|
26
|
-
end
|
20
|
+
options = DEFAULT_OPTIONS.dup
|
21
|
+
user_opts&.each do |k,v|
|
22
|
+
raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.has_key?(k)
|
23
|
+
options[k] = v
|
27
24
|
end
|
28
25
|
Log.log.debug("options= #{options}")
|
29
26
|
super()
|
@@ -34,39 +31,39 @@ module Aspera
|
|
34
31
|
begin
|
35
32
|
get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
|
36
33
|
Log.log.debug("daemon info: #{get_info_response}")
|
37
|
-
rescue GRPC::Unavailable
|
38
|
-
Log.log.warn(
|
34
|
+
rescue GRPC::Unavailable
|
35
|
+
Log.log.warn('no daemon present, starting daemon...')
|
39
36
|
# location of daemon binary
|
40
|
-
bin_folder=File.realpath(File.join(Installation.instance.sdk_ruby_folder,'..'))
|
37
|
+
bin_folder = File.realpath(File.join(Installation.instance.sdk_ruby_folder,'..'))
|
41
38
|
# config file and logs are created in same folder
|
42
39
|
conf_file = File.join(bin_folder,'sdk.conf')
|
43
40
|
log_base = File.join(bin_folder,'transferd')
|
44
41
|
# create a config file for daemon
|
45
42
|
config = {
|
46
|
-
address:
|
47
|
-
port:
|
43
|
+
address: options[:address],
|
44
|
+
port: options[:port],
|
48
45
|
fasp_runtime: {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
46
|
+
use_embedded: false,
|
47
|
+
user_defined: {
|
48
|
+
bin: bin_folder,
|
49
|
+
etc: bin_folder
|
50
|
+
}
|
54
51
|
}
|
55
52
|
}
|
56
53
|
File.write(conf_file,config.to_json)
|
57
|
-
trd_pid = Process.spawn(Installation.instance.path(:transferd),'--config'
|
54
|
+
trd_pid = Process.spawn(Installation.instance.path(:transferd),'--config', conf_file, out: "#{log_base}.out", err: "#{log_base}.err")
|
58
55
|
Process.detach(trd_pid)
|
59
56
|
sleep(2.0)
|
60
57
|
retry
|
61
58
|
end
|
62
59
|
end
|
63
60
|
|
64
|
-
def start_transfer(transfer_spec,
|
61
|
+
def start_transfer(transfer_spec,_options=nil)
|
65
62
|
# create a transfer request
|
66
63
|
transfer_request = Transfersdk::TransferRequest.new(
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
transferType: Transfersdk::TransferType::FILE_REGULAR, # transfer type (file/stream)
|
65
|
+
config: Transfersdk::TransferConfig.new, # transfer configuration
|
66
|
+
transferSpec: transfer_spec.to_json) # transfer definition
|
70
67
|
# send start transfer request to the transfer manager daemon
|
71
68
|
start_transfer_response = @transfer_client.start_transfer(transfer_request)
|
72
69
|
Log.log.debug("start transfer response #{start_transfer_response}")
|
@@ -75,22 +72,22 @@ module Aspera
|
|
75
72
|
end
|
76
73
|
|
77
74
|
def wait_for_transfers_completion
|
78
|
-
started=false
|
75
|
+
started = false
|
79
76
|
# monitor transfer status
|
80
77
|
@transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
|
81
78
|
Log.dump(:response, response.to_h)
|
82
79
|
#Log.log.debug("#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}")
|
83
80
|
case response.status
|
84
81
|
when :RUNNING
|
85
|
-
if !started
|
82
|
+
if !started && !response.sessionInfo.preTransferBytes.eql?(0)
|
86
83
|
notify_begin(@transfer_id,response.sessionInfo.preTransferBytes)
|
87
|
-
started=true
|
84
|
+
started = true
|
88
85
|
elsif started
|
89
86
|
notify_progress(@transfer_id,response.transferInfo.bytesTransferred)
|
90
87
|
end
|
91
88
|
when :FAILED, :COMPLETED, :CANCELED
|
92
89
|
notify_end(@transfer_id)
|
93
|
-
raise Fasp::Error
|
90
|
+
raise Fasp::Error, JSON.parse(response.message)['Description'] unless :COMPLETED.eql?(response.status)
|
94
91
|
break
|
95
92
|
when :QUEUED,:UNKNOWN_STATUS,:PAUSED,:ORPHANED
|
96
93
|
# ignore
|
data/lib/aspera/fasp/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/fasp/error_info'
|
2
4
|
|
3
5
|
module Aspera
|
@@ -11,7 +13,7 @@ module Aspera
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def info
|
14
|
-
r=Fasp::ERROR_INFO[@err_code] || {r: false
|
16
|
+
r = Fasp::ERROR_INFO[@err_code] || {r: false, c: 'UNKNOWN', m: 'unknown', a: 'unknown'}
|
15
17
|
return r.merge({i: @err_code})
|
16
18
|
end
|
17
19
|
|