aspera-cli 4.3.0 → 4.4.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
- data/README.md +445 -194
- data/docs/README.erb.md +449 -239
- data/docs/doc_tools.rb +58 -0
- data/lib/aspera/aoc.rb +4 -3
- data/lib/aspera/cli/plugin.rb +13 -6
- data/lib/aspera/cli/plugins/aoc.rb +87 -51
- data/lib/aspera/cli/plugins/config.rb +79 -50
- data/lib/aspera/cli/plugins/faspex.rb +8 -6
- data/lib/aspera/cli/plugins/node.rb +51 -23
- data/lib/aspera/cli/plugins/preview.rb +9 -7
- data/lib/aspera/cli/plugins/server.rb +16 -5
- data/lib/aspera/cli/transfer_agent.rb +5 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/fasp/http_gw.rb +47 -26
- data/lib/aspera/fasp/local.rb +0 -2
- data/lib/aspera/fasp/parameters.rb +16 -3
- data/lib/aspera/faspex_gw.rb +10 -9
- data/lib/aspera/node.rb +10 -0
- metadata +3 -3
- data/lib/aspera/fasp/aoc.rb +0 -24
data/lib/aspera/fasp/http_gw.rb
CHANGED
@@ -4,15 +4,14 @@ require 'aspera/log'
|
|
4
4
|
require 'aspera/rest'
|
5
5
|
require 'websocket-client-simple'
|
6
6
|
require 'securerandom'
|
7
|
-
require 'openssl'
|
8
7
|
require 'base64'
|
9
8
|
require 'json'
|
10
|
-
require 'uri'
|
11
9
|
|
12
10
|
# ref: https://api.ibm.com/explorer/catalog/aspera/product/ibm-aspera/api/http-gateway-api/doc/guides-toc
|
11
|
+
# https://developer.ibm.com/apis/catalog?search=%22aspera%20http%22
|
13
12
|
module Aspera
|
14
13
|
module Fasp
|
15
|
-
#
|
14
|
+
# start a transfer using Aspera HTTP Gateway, using web socket session
|
16
15
|
class HttpGW < Manager
|
17
16
|
# message returned by HTTP GW in case of success
|
18
17
|
OK_MESSAGE='end upload'
|
@@ -25,20 +24,36 @@ module Aspera
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def upload(transfer_spec)
|
28
|
-
#
|
27
|
+
# total size of all files
|
29
28
|
total_size=0
|
30
|
-
#
|
31
|
-
|
29
|
+
# we need to keep track of actual file path because transfer spec is modified to be sent in web socket
|
30
|
+
source_paths=[]
|
31
|
+
# get source root or nil
|
32
|
+
source_root = (transfer_spec.has_key?('source_root') and !transfer_spec['source_root'].empty?) ? transfer_spec['source_root'] : nil
|
33
|
+
# source root is ignored by GW, used only here
|
34
|
+
transfer_spec.delete('source_root')
|
35
|
+
# compute total size of files to upload (for progress)
|
36
|
+
# modify transfer spec to be suitable for GW
|
32
37
|
transfer_spec['paths'].each do |item|
|
33
|
-
|
34
|
-
item['source']
|
35
|
-
|
36
|
-
|
38
|
+
# save actual file location to be able read contents later
|
39
|
+
full_src_filepath=item['source']
|
40
|
+
# add source root if needed
|
41
|
+
full_src_filepath=File.join(source_root,full_src_filepath) unless source_root.nil?
|
42
|
+
# 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']
|
46
|
+
# save so that we can actually read the file later
|
47
|
+
source_paths.push(full_src_filepath)
|
37
48
|
end
|
49
|
+
|
38
50
|
session_id=SecureRandom.uuid
|
39
51
|
ws=::WebSocket::Client::Simple::Client.new
|
52
|
+
# error message if any in callback
|
40
53
|
error=nil
|
54
|
+
# number of files totally sent
|
41
55
|
received=0
|
56
|
+
# setup callbacks on websocket
|
42
57
|
ws.on :message do |msg|
|
43
58
|
Log.log.info("ws: message: #{msg.data}")
|
44
59
|
message=msg.data
|
@@ -64,44 +79,49 @@ module Aspera
|
|
64
79
|
end
|
65
80
|
# open web socket to end point
|
66
81
|
ws.connect("#{@gw_api.params[:base_url]}/upload")
|
82
|
+
# async wait ready
|
67
83
|
while !ws.open? and error.nil? do
|
68
84
|
Log.log.info("ws: wait")
|
69
85
|
sleep(0.2)
|
70
86
|
end
|
87
|
+
# notify progress bar
|
71
88
|
notify_begin(session_id,total_size)
|
89
|
+
# first step send transfer spec
|
90
|
+
Log.dump(:ws_spec,transfer_spec)
|
72
91
|
ws_send(ws,:transfer_spec,transfer_spec)
|
73
92
|
# current file index
|
74
|
-
|
93
|
+
file_index=0
|
75
94
|
# aggregate size sent
|
76
95
|
sent_bytes=0
|
77
96
|
# last progress event
|
78
|
-
lastevent=
|
97
|
+
lastevent=nil
|
79
98
|
transfer_spec['paths'].each do |item|
|
80
|
-
# TODO: on destination write same path?
|
81
|
-
destination_path=item['source']
|
82
99
|
# TODO: get mime type?
|
83
100
|
file_mime_type=''
|
84
|
-
|
101
|
+
file_size=item['file_size']
|
102
|
+
file_name=File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
85
103
|
# compute total number of slices
|
86
|
-
numslices=1+(
|
87
|
-
|
88
|
-
|
89
|
-
|
104
|
+
numslices=1+(file_size-1)/@upload_chunksize
|
105
|
+
File.open(source_paths[file_index]) do |file|
|
106
|
+
# current slice index
|
107
|
+
slicenum=0
|
90
108
|
while !file.eof? do
|
91
109
|
data=file.read(@upload_chunksize)
|
92
110
|
slice_data={
|
93
|
-
name:
|
111
|
+
name: file_name,
|
94
112
|
type: file_mime_type,
|
95
|
-
size:
|
113
|
+
size: file_size,
|
96
114
|
data: Base64.strict_encode64(data),
|
97
115
|
slice: slicenum,
|
98
116
|
total_slices: numslices,
|
99
|
-
fileIndex:
|
117
|
+
fileIndex: file_index
|
100
118
|
}
|
119
|
+
# log without data
|
120
|
+
Log.dump(:slide_data,slice_data.keys.inject({}){|m,i|m[i]=i.eql?(:data)?'base64 data':slice_data[i];m}) if slicenum.eql?(0)
|
101
121
|
ws_send(ws,:slice_upload, slice_data)
|
102
122
|
sent_bytes+=data.length
|
103
123
|
currenttime=Time.now
|
104
|
-
if (currenttime-lastevent)>UPLOAD_REFRESH_SEC
|
124
|
+
if lastevent.nil? or (currenttime-lastevent)>UPLOAD_REFRESH_SEC
|
105
125
|
notify_progress(session_id,sent_bytes)
|
106
126
|
lastevent=currenttime
|
107
127
|
end
|
@@ -109,7 +129,7 @@ module Aspera
|
|
109
129
|
raise error unless error.nil?
|
110
130
|
end
|
111
131
|
end
|
112
|
-
|
132
|
+
file_index+=1
|
113
133
|
end
|
114
134
|
ws.close
|
115
135
|
notify_end(session_id)
|
@@ -150,7 +170,8 @@ module Aspera
|
|
150
170
|
raise "GW URL must be set" unless !@gw_api.nil?
|
151
171
|
raise "option: must be hash (or nil)" unless options.is_a?(Hash)
|
152
172
|
raise "paths: must be Array" unless transfer_spec['paths'].is_a?(Array)
|
153
|
-
raise "
|
173
|
+
raise "only token based transfer is supported in GW" unless transfer_spec['token'].is_a?(String)
|
174
|
+
Log.dump(:user_spec,transfer_spec)
|
154
175
|
transfer_spec['authentication']||='token'
|
155
176
|
case transfer_spec['direction']
|
156
177
|
when 'send'
|
@@ -188,6 +209,6 @@ module Aspera
|
|
188
209
|
@upload_chunksize=128000 # TODO: configurable ?
|
189
210
|
end
|
190
211
|
|
191
|
-
end #
|
212
|
+
end # HttpGW
|
192
213
|
end
|
193
214
|
end
|
data/lib/aspera/fasp/local.rb
CHANGED
@@ -17,8 +17,6 @@ require 'securerandom'
|
|
17
17
|
|
18
18
|
module Aspera
|
19
19
|
module Fasp
|
20
|
-
# (public) default transfer username for access key based transfers
|
21
|
-
ACCESS_KEY_TRANSFER_USER='xfer'
|
22
20
|
# executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
|
23
21
|
class Local < Manager
|
24
22
|
# options for initialize (same as values in option transfer_info)
|
@@ -70,10 +70,15 @@ module Aspera
|
|
70
70
|
Base64.strict_encode64(JSON.generate(v))
|
71
71
|
end
|
72
72
|
|
73
|
+
def self.ts_has_file_list(ts)
|
74
|
+
ts.has_key?('EX_ascp_args') and ts['EX_ascp_args'].is_a?(Array) and ['--file-list','--file-pair-list'].any?{|i|ts['EX_ascp_args'].include?(i)}
|
75
|
+
end
|
76
|
+
|
73
77
|
def initialize(job_spec,options)
|
74
78
|
@job_spec=job_spec
|
75
79
|
@options=options
|
76
80
|
@builder=Aspera::CommandLineBuilder.new(@job_spec,self.class.description)
|
81
|
+
Log.log.debug("agent options: #{@options}")
|
77
82
|
end
|
78
83
|
|
79
84
|
public
|
@@ -97,7 +102,7 @@ module Aspera
|
|
97
102
|
@job_spec.delete('source_root') if @job_spec.has_key?('source_root') and @job_spec['source_root'].empty?
|
98
103
|
|
99
104
|
# use web socket session initiation ?
|
100
|
-
if @builder.process_param('wss_enabled',:get_value) and @options[:wss]
|
105
|
+
if @builder.process_param('wss_enabled',:get_value) and ( @options[:wss] or !@job_spec.has_key?('fasp_port') )
|
101
106
|
# by default use web socket session if available, unless removed by user
|
102
107
|
@builder.add_command_line_options(['--ws-connect'])
|
103
108
|
# TODO: option to give order ssh,ws (legacy http is implied bu ssh)
|
@@ -122,9 +127,17 @@ module Aspera
|
|
122
127
|
# destination will be base64 encoded, put before path arguments
|
123
128
|
@builder.add_command_line_options(['--dest64'])
|
124
129
|
end
|
125
|
-
|
130
|
+
# paths is mandatory, unless ...
|
131
|
+
file_list_provided=self.class.ts_has_file_list(@job_spec)
|
132
|
+
@builder.params_definition['paths'][:mandatory]=!@job_spec.has_key?('keepalive') and !file_list_provided
|
126
133
|
paths_array=@builder.process_param('paths',:get_value)
|
127
|
-
|
134
|
+
if file_list_provided and ! paths_array.nil?
|
135
|
+
Log.log.warn("file list provided both in transfer spec and ascp file list. Keeping file list only.")
|
136
|
+
paths_array=nil
|
137
|
+
end
|
138
|
+
if ! paths_array.nil?
|
139
|
+
# it's an array
|
140
|
+
raise "paths is empty in transfer spec" if paths_array.empty?
|
128
141
|
# use file list if there is storage defined for it.
|
129
142
|
if @@file_list_folder.nil?
|
130
143
|
# not safe for special characters ? (maybe not, depends on OS)
|
data/lib/aspera/faspex_gw.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'aspera/log'
|
2
2
|
require 'aspera/aoc'
|
3
|
+
require 'aspera/node'
|
3
4
|
require 'aspera/cli/main'
|
4
5
|
require 'webrick'
|
5
6
|
require 'webrick/https'
|
@@ -73,16 +74,16 @@ module Aspera
|
|
73
74
|
"xfer_retry" => 3600 } }
|
74
75
|
# this transfer spec is for transfer to AoC
|
75
76
|
faspex_transfer_spec={
|
76
|
-
'direction'
|
77
|
-
'remote_user' => 'xfer',
|
77
|
+
'direction' => 'send',
|
78
78
|
'remote_host' => node_info['host'],
|
79
|
-
'
|
80
|
-
'
|
81
|
-
'
|
82
|
-
'
|
83
|
-
'
|
84
|
-
'
|
85
|
-
'
|
79
|
+
'remote_user' => Node::ACCESS_KEY_TRANSFER_USER,
|
80
|
+
'ssh_port' => Node::SSH_PORT_DEFAULT,
|
81
|
+
'fasp_port' => Node::UDP_PORT_DEFAULT
|
82
|
+
'tags' => ts_tags,
|
83
|
+
'token' => node_auth_bearer_token,
|
84
|
+
'paths' => [{'destination' => '/'}],
|
85
|
+
'cookie' => 'unused',
|
86
|
+
'create_dir' => true,
|
86
87
|
'rate_policy' => 'fair',
|
87
88
|
'rate_policy_allowed' => 'fixed',
|
88
89
|
'min_rate_cap_kbps' => nil,
|
data/lib/aspera/node.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'aspera/rest'
|
2
|
+
require 'aspera/oauth'
|
2
3
|
require 'aspera/log'
|
3
4
|
require 'zlib'
|
4
5
|
require 'base64'
|
@@ -9,10 +10,19 @@ module Aspera
|
|
9
10
|
# permissions
|
10
11
|
ACCESS_LEVELS=['delete','list','mkdir','preview','read','rename','write']
|
11
12
|
MATCH_EXEC_PREFIX='exec:'
|
13
|
+
# (public) default transfer username for access key based transfers
|
14
|
+
ACCESS_KEY_TRANSFER_USER='xfer'
|
15
|
+
SSH_PORT_DEFAULT=33001
|
16
|
+
UDP_PORT_DEFAULT=33001
|
12
17
|
|
13
18
|
# register node special token decoder
|
14
19
|
Oauth.register_decoder(lambda{|token|JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition('==SIGNATURE==').first)})
|
15
20
|
|
21
|
+
def self.set_ak_basic_token(ts,ak,secret)
|
22
|
+
raise "ERROR: expected xfer" unless ts['remote_user'].eql?(ACCESS_KEY_TRANSFER_USER)
|
23
|
+
ts['token']="Basic #{Base64.strict_encode64("#{ak}:#{secret}")}"
|
24
|
+
end
|
25
|
+
|
16
26
|
# for access keys: provide expression to match entry in folder
|
17
27
|
# if no prefix: regex
|
18
28
|
# if prefix: ruby code
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspera-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: xml-simple
|
@@ -224,6 +224,7 @@ files:
|
|
224
224
|
- docs/README.erb.md
|
225
225
|
- docs/README.md
|
226
226
|
- docs/diagrams.txt
|
227
|
+
- docs/doc_tools.rb
|
227
228
|
- docs/test_env.conf
|
228
229
|
- examples/aoc.rb
|
229
230
|
- examples/faspex4.rb
|
@@ -273,7 +274,6 @@ files:
|
|
273
274
|
- lib/aspera/data/7
|
274
275
|
- lib/aspera/data_repository.rb
|
275
276
|
- lib/aspera/environment.rb
|
276
|
-
- lib/aspera/fasp/aoc.rb
|
277
277
|
- lib/aspera/fasp/connect.rb
|
278
278
|
- lib/aspera/fasp/error.rb
|
279
279
|
- lib/aspera/fasp/error_info.rb
|
data/lib/aspera/fasp/aoc.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'aspera/fasp/node'
|
2
|
-
require 'aspera/log'
|
3
|
-
require 'aspera/aoc.rb'
|
4
|
-
|
5
|
-
module Aspera
|
6
|
-
module Fasp
|
7
|
-
class Aoc < Node
|
8
|
-
def initialize(aoc_options)
|
9
|
-
@app=aoc_options[:app] || AoC::FILES_APP
|
10
|
-
@api_aoc=AoC.new(aoc_options)
|
11
|
-
Log.log.warn("Under Development")
|
12
|
-
server_node_file = @api_aoc.resolve_node_file(server_home_node_file,server_folder)
|
13
|
-
# force node as transfer agent
|
14
|
-
node_api=Fasp::Node.new(@api_aoc.get_node_api(client_node_file[:node_info],scope: AoC::SCOPE_NODE_USER))
|
15
|
-
super(node_api)
|
16
|
-
# additional node to node TS info
|
17
|
-
@add_ts={
|
18
|
-
'remote_access_key' => server_node_file[:node_info]['access_key'],
|
19
|
-
'destination_root_id' => server_node_file[:file_id]
|
20
|
-
}
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|