aspera-cli 4.3.0 → 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|