aspera-cli 4.18.1 → 4.20.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 +33 -0
- data/CONTRIBUTING.md +17 -12
- data/README.md +396 -185
- data/bin/asession +26 -19
- data/examples/build_exec +74 -0
- data/examples/{rubyc → build_exec_rubyc} +18 -2
- data/examples/get_proto_file.rb +7 -0
- data/lib/aspera/agent/alpha.rb +8 -8
- data/lib/aspera/agent/base.rb +4 -18
- data/lib/aspera/agent/connect.rb +14 -13
- data/lib/aspera/agent/direct.rb +123 -120
- data/lib/aspera/agent/httpgw.rb +2 -3
- data/lib/aspera/agent/node.rb +10 -10
- data/lib/aspera/agent/trsdk.rb +17 -20
- data/lib/aspera/api/alee.rb +15 -0
- data/lib/aspera/api/aoc.rb +128 -99
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/httpgw.rb +104 -64
- data/lib/aspera/api/node.rb +33 -12
- data/lib/aspera/ascmd.rb +56 -48
- data/lib/aspera/ascp/installation.rb +142 -70
- data/lib/aspera/ascp/management.rb +7 -3
- data/lib/aspera/ascp/products.rb +13 -7
- data/lib/aspera/assert.rb +10 -5
- data/lib/aspera/cli/formatter.rb +42 -26
- data/lib/aspera/cli/hints.rb +2 -1
- data/lib/aspera/cli/info.rb +12 -10
- data/lib/aspera/cli/main.rb +16 -13
- data/lib/aspera/cli/manager.rb +15 -10
- data/lib/aspera/cli/plugin.rb +17 -31
- data/lib/aspera/cli/plugin_factory.rb +10 -1
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +222 -194
- data/lib/aspera/cli/plugins/ats.rb +16 -14
- data/lib/aspera/cli/plugins/config.rb +66 -53
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +11 -21
- data/lib/aspera/cli/plugins/faspex5.rb +44 -42
- data/lib/aspera/cli/plugins/faspio.rb +2 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/node.rb +155 -96
- data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
- data/lib/aspera/cli/plugins/preview.rb +8 -9
- data/lib/aspera/cli/plugins/server.rb +6 -10
- data/lib/aspera/cli/plugins/shares.rb +13 -9
- data/lib/aspera/cli/sync_actions.rb +72 -31
- data/lib/aspera/cli/transfer_agent.rb +13 -14
- data/lib/aspera/cli/transfer_progress.rb +36 -18
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +3 -4
- data/lib/aspera/coverage.rb +13 -1
- data/lib/aspera/environment.rb +59 -10
- data/lib/aspera/faspex_gw.rb +3 -3
- data/lib/aspera/json_rpc.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -0
- data/lib/aspera/keychain/macos_security.rb +7 -12
- data/lib/aspera/log.rb +4 -4
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/base.rb +39 -45
- data/lib/aspera/oauth/factory.rb +11 -4
- data/lib/aspera/oauth/generic.rb +4 -8
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/url_json.rb +3 -2
- data/lib/aspera/oauth/web.rb +10 -6
- data/lib/aspera/persistency_action_once.rb +16 -8
- data/lib/aspera/preview/utils.rb +5 -16
- data/lib/aspera/rest.rb +100 -76
- data/lib/aspera/secret_hider.rb +3 -2
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +7 -5
- data/lib/aspera/transfer/parameters.rb +41 -35
- data/lib/aspera/transfer/spec.rb +16 -18
- data/lib/aspera/transfer/sync.rb +51 -50
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +166 -18
- data/lib/aspera/web_server_simple.rb +27 -15
- data/lib/transfer_pb.rb +84 -0
- data/lib/transfer_services_pb.rb +82 -0
- data.tar.gz.sig +0 -0
- metadata +25 -6
- metadata.gz.sig +0 -0
data/lib/aspera/preview/utils.rb
CHANGED
@@ -32,7 +32,7 @@ module Aspera
|
|
32
32
|
tools_to_check.delete(:unoconv) if skip_types.include?(:office)
|
33
33
|
# Check for binaries
|
34
34
|
tools_to_check.each do |command_sym|
|
35
|
-
external_command(command_sym, ['-h']
|
35
|
+
external_command(command_sym, ['-h'])
|
36
36
|
rescue Errno::ENOENT => e
|
37
37
|
raise "missing #{command_sym} binary: #{e}"
|
38
38
|
rescue
|
@@ -43,19 +43,9 @@ module Aspera
|
|
43
43
|
# execute external command
|
44
44
|
# one could use "system", but we would need to redirect stdout/err
|
45
45
|
# @return true if su
|
46
|
-
def external_command(command_sym, command_args
|
46
|
+
def external_command(command_sym, command_args)
|
47
47
|
Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
|
48
|
-
|
49
|
-
command_line = command_args.clone.unshift(command_sym).map{|i| shell_quote(i.to_s)}.join(' ')
|
50
|
-
Log.log.debug{"cmd=#{command_line}".blue}
|
51
|
-
stdout, stderr, status = Open3.capture3(command_line)
|
52
|
-
if check_code && !status.success?
|
53
|
-
Log.log.error{"status: #{status}"}
|
54
|
-
Log.log.error{"stdout: #{stdout}"}
|
55
|
-
Log.log.error{"stderr: #{stderr}"}
|
56
|
-
raise "#{command_sym} error #{status}"
|
57
|
-
end
|
58
|
-
return {status: status, stdout: stdout}
|
48
|
+
return Environment.secure_capture(command_sym.to_s, *command_args)
|
59
49
|
end
|
60
50
|
|
61
51
|
def ffmpeg(a)
|
@@ -73,12 +63,11 @@ module Aspera
|
|
73
63
|
|
74
64
|
# @return Float in seconds
|
75
65
|
def video_get_duration(input_file)
|
76
|
-
|
66
|
+
return external_command(:ffprobe, [
|
77
67
|
'-loglevel', 'error',
|
78
68
|
'-show_entries', 'format=duration',
|
79
69
|
'-print_format', 'default=noprint_wrappers=1:nokey=1', # cspell:disable-line
|
80
|
-
input_file])
|
81
|
-
return result[:stdout].to_f
|
70
|
+
input_file]).to_f
|
82
71
|
end
|
83
72
|
|
84
73
|
def ffmpeg_fmt(temp_folder)
|
data/lib/aspera/rest.rb
CHANGED
@@ -11,6 +11,8 @@ require 'net/https'
|
|
11
11
|
require 'json'
|
12
12
|
require 'base64'
|
13
13
|
require 'cgi'
|
14
|
+
require 'singleton'
|
15
|
+
require 'securerandom'
|
14
16
|
|
15
17
|
# Cancel method for HTTP
|
16
18
|
class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
|
@@ -20,22 +22,32 @@ class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModul
|
|
20
22
|
end
|
21
23
|
|
22
24
|
module Aspera
|
25
|
+
# Global settings
|
26
|
+
# @param user_agent [String] HTTP request header: 'User-Agent'
|
27
|
+
# @param download_partial_suffix [String] suffix for partial download
|
28
|
+
# @param session_cb [lambda] lambda called on new HTTP session. Takes the Net::HTTP as arg. Used to change parameters on creation.
|
29
|
+
# @param progress_bar [Object] progress bar object
|
30
|
+
class RestParameters
|
31
|
+
include Singleton
|
32
|
+
|
33
|
+
attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_sleep, :session_cb, :progress_bar
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@user_agent = 'RubyAsperaRest'
|
39
|
+
@download_partial_suffix = '.http_partial'
|
40
|
+
@retry_on_error = 0
|
41
|
+
@retry_sleep = nil
|
42
|
+
@session_cb = nil
|
43
|
+
@progress_bar = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
23
47
|
# a simple class to make HTTP calls, equivalent to rest-client
|
24
48
|
# rest call errors are raised as exception RestCallError
|
25
49
|
# and error are analyzed in RestErrorAnalyzer
|
26
50
|
class Rest
|
27
|
-
# Global settings also valid for any subclass
|
28
|
-
# @param user_agent [String] HTTP request header: 'User-Agent'
|
29
|
-
# @param download_partial_suffix [String] suffix for partial download
|
30
|
-
# @param session_cb [lambda] lambda called on new HTTP session. Takes the Net::HTTP as arg. Used to change parameters on creation.
|
31
|
-
# @param progress_bar [Object] progress bar object
|
32
|
-
@@global = { # rubocop:disable Style/ClassVars
|
33
|
-
user_agent: 'RubyAsperaRest',
|
34
|
-
download_partial_suffix: '.http_partial',
|
35
|
-
session_cb: nil,
|
36
|
-
progress_bar: nil
|
37
|
-
}
|
38
|
-
|
39
51
|
# flag for array parameters prefixed with []
|
40
52
|
ARRAY_PARAMS = '[]'
|
41
53
|
|
@@ -61,37 +73,45 @@ module Aspera
|
|
61
73
|
return values.first.eql?(ARRAY_PARAMS)
|
62
74
|
end
|
63
75
|
|
64
|
-
# Build URI from URL and parameters and check it is http or https
|
65
|
-
|
76
|
+
# Build URI from URL and parameters and check it is http or https
|
77
|
+
# encode array [] parameters
|
78
|
+
# @param query [Hash,Array]
|
79
|
+
def build_uri(url, query=nil)
|
66
80
|
uri = URI.parse(url)
|
67
81
|
Aspera.assert(%w[http https].include?(uri.scheme)){"REST endpoint shall be http/s not #{uri.scheme}"}
|
68
|
-
return uri if
|
69
|
-
Log.log.debug{Log.dump('query',
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
return uri if query.nil? || query.respond_to?(:empty?) && query.empty?
|
83
|
+
Log.log.debug{Log.dump('query', query)}
|
84
|
+
query_array = []
|
85
|
+
case query
|
86
|
+
when Hash
|
87
|
+
query.each do |k, v|
|
88
|
+
case v
|
89
|
+
when Array
|
90
|
+
# support array for query parameter, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
|
91
|
+
suffix = array_params?(v) ? v.shift : ''
|
92
|
+
v.each do |e|
|
93
|
+
query_array.push(["#{k}#{suffix}", e])
|
94
|
+
end
|
95
|
+
else
|
96
|
+
query_array.push([k, v])
|
80
97
|
end
|
81
|
-
else
|
82
|
-
query.push([k, v])
|
83
98
|
end
|
99
|
+
when Array
|
100
|
+
Aspera.assert(query.all?{|i| i.is_a?(Array) && i.length.eql?(2)}) {'Query must be array of arrays or 2 elements'}
|
101
|
+
query_array = query
|
102
|
+
else
|
103
|
+
raise "Query must be Hash or Array, not #{query.class}"
|
84
104
|
end
|
85
105
|
# [] is allowed in url parameters
|
86
|
-
uri.query = URI.encode_www_form(
|
106
|
+
uri.query = URI.encode_www_form(query_array).gsub('%5B%5D=', '[]=')
|
87
107
|
return uri
|
88
108
|
end
|
89
109
|
|
90
|
-
#
|
110
|
+
# Decode query string as Hash
|
91
111
|
# Does not support arrays in query string, no standard, e.g. PHP's way is p[]=1&p[]=2
|
92
|
-
# @param query [String] query string
|
112
|
+
# @param query [String] query string as in URI.query
|
93
113
|
# @return [Hash] decoded query
|
94
|
-
def
|
114
|
+
def query_to_h(query)
|
95
115
|
URI.decode_www_form(query).each_with_object({}) do |pair, h|
|
96
116
|
key = pair.first
|
97
117
|
raise "Array not supported in query string: #{key}" if key.include?('[]') || h.key?(key)
|
@@ -108,7 +128,7 @@ module Aspera
|
|
108
128
|
http_session = Net::HTTP.new(uri.host, uri.port)
|
109
129
|
http_session.use_ssl = uri.scheme.eql?('https')
|
110
130
|
# set http options in callback, such as timeout and cert. verification
|
111
|
-
|
131
|
+
RestParameters.instance.session_cb&.call(http_session)
|
112
132
|
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
113
133
|
http_session.start
|
114
134
|
return http_session
|
@@ -142,19 +162,6 @@ module Aspera
|
|
142
162
|
return result
|
143
163
|
end
|
144
164
|
|
145
|
-
# set global parameters
|
146
|
-
def set_parameters(**options)
|
147
|
-
options.each do |key, value|
|
148
|
-
Aspera.assert(@@global.key?(key)){"Unknown Rest option #{key}"}
|
149
|
-
@@global[key] = value
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# @return [String] HTTP agent name
|
154
|
-
def user_agent
|
155
|
-
return @@global[:user_agent]
|
156
|
-
end
|
157
|
-
|
158
165
|
def parse_header(header)
|
159
166
|
type, *params = header.split(/;\s*/)
|
160
167
|
parameters = params.map do |param|
|
@@ -179,16 +186,17 @@ module Aspera
|
|
179
186
|
|
180
187
|
public
|
181
188
|
|
182
|
-
attr_reader :auth_params
|
183
189
|
attr_reader :base_url
|
190
|
+
attr_reader :auth_params
|
184
191
|
|
192
|
+
# @return creation parameters
|
185
193
|
def params
|
186
194
|
return {
|
187
|
-
base_url: @base_url,
|
188
|
-
auth: @auth_params,
|
189
|
-
not_auth_codes: @not_auth_codes,
|
190
|
-
redirect_max: @redirect_max,
|
191
|
-
headers: @headers
|
195
|
+
base_url: @base_url, # String
|
196
|
+
auth: @auth_params, # Hash
|
197
|
+
not_auth_codes: @not_auth_codes, # Array
|
198
|
+
redirect_max: @redirect_max, # Integer
|
199
|
+
headers: @headers # Hash
|
192
200
|
}
|
193
201
|
end
|
194
202
|
|
@@ -226,9 +234,10 @@ module Aspera
|
|
226
234
|
# OAuth object (created on demand)
|
227
235
|
@oauth = nil
|
228
236
|
@redirect_max = redirect_max
|
237
|
+
Aspera.assert_type(@redirect_max, Integer)
|
229
238
|
@headers = headers.nil? ? {} : headers
|
230
239
|
Aspera.assert_type(@headers, Hash)
|
231
|
-
@headers['User-Agent'] ||=
|
240
|
+
@headers['User-Agent'] ||= RestParameters.instance.user_agent
|
232
241
|
end
|
233
242
|
|
234
243
|
# @return the OAuth object (create, or cached if already created)
|
@@ -263,6 +272,8 @@ module Aspera
|
|
263
272
|
)
|
264
273
|
subpath = subpath.to_s if subpath.is_a?(Symbol)
|
265
274
|
subpath = '' if subpath.nil?
|
275
|
+
Log.log.debug{"#{operation} [#{subpath}]".red.bold.bg_green}
|
276
|
+
Log.log.debug{Log.dump(:body, body)}
|
266
277
|
Aspera.assert_type(subpath, String)
|
267
278
|
if headers.nil?
|
268
279
|
headers = @headers.clone
|
@@ -272,7 +283,6 @@ module Aspera
|
|
272
283
|
headers.merge!(h)
|
273
284
|
end
|
274
285
|
Aspera.assert_type(headers, Hash)
|
275
|
-
Log.log.debug{"#{operation} [#{subpath}]".red.bold.bg_green}
|
276
286
|
case @auth_params[:type]
|
277
287
|
when :none
|
278
288
|
# no auth
|
@@ -322,11 +332,13 @@ module Aspera
|
|
322
332
|
end
|
323
333
|
# :type = :basic
|
324
334
|
req.basic_auth(@auth_params[:username], @auth_params[:password]) if @auth_params[:type].eql?(:basic)
|
325
|
-
Log.log.
|
335
|
+
Log.log.trace1{Log.dump(:req_body, req.body)}
|
326
336
|
# we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
|
327
337
|
oauth_tries ||= 2
|
338
|
+
timeout_tries ||= 5
|
339
|
+
general_tries ||= 1 + RestParameters.instance.retry_on_error
|
328
340
|
# initialize with number of initial retries allowed, nil gives zero
|
329
|
-
tries_remain_redirect = @redirect_max
|
341
|
+
tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
|
330
342
|
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
331
343
|
result_mime = nil
|
332
344
|
file_saved = false
|
@@ -350,38 +362,44 @@ module Aspera
|
|
350
362
|
end
|
351
363
|
end
|
352
364
|
# download with temp filename
|
353
|
-
target_file_tmp = "#{target_file}#{
|
365
|
+
target_file_tmp = "#{target_file}#{RestParameters.instance.download_partial_suffix}"
|
354
366
|
Log.log.debug{"saving to: #{target_file}"}
|
355
367
|
written_size = 0
|
356
|
-
|
357
|
-
|
368
|
+
session_id = SecureRandom.uuid.freeze
|
369
|
+
RestParameters.instance.progress_bar&.event(:session_start, session_id: session_id)
|
370
|
+
RestParameters.instance.progress_bar&.event(:session_size, session_id: session_id, info: total_size)
|
358
371
|
File.open(target_file_tmp, 'wb') do |file|
|
359
372
|
result[:http].read_body do |fragment|
|
360
373
|
file.write(fragment)
|
361
374
|
written_size += fragment.length
|
362
|
-
|
375
|
+
RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size)
|
363
376
|
end
|
364
377
|
end
|
365
|
-
|
378
|
+
RestParameters.instance.progress_bar&.event(:end, session_id: session_id)
|
366
379
|
# rename at the end
|
367
380
|
File.rename(target_file_tmp, target_file)
|
368
381
|
file_saved = true
|
369
382
|
end
|
370
383
|
end
|
384
|
+
Log.log.debug{"result: code=#{result[:http].code} mime=#{result_mime}"}
|
371
385
|
# sometimes there is a UTF8 char (e.g. (c) ), TODO : related to mime type encoding ?
|
372
386
|
# result[:http].body.force_encoding('UTF-8') if result[:http].body.is_a?(String)
|
373
387
|
# Log.log.debug{"result: body=#{result[:http].body}"}
|
374
|
-
|
388
|
+
case result_mime
|
375
389
|
when *JSON_DECODE
|
376
|
-
JSON.parse(result[:http].body) rescue result[:http].body
|
390
|
+
result[:data] = JSON.parse(result[:http].body) rescue result[:http].body
|
391
|
+
Log.log.debug{Log.dump('result_data', result[:data])}
|
377
392
|
else # when 'text/plain'
|
378
|
-
result[:http].body
|
393
|
+
result[:data] = result[:http].body
|
379
394
|
end
|
380
|
-
Log.log.debug{"result: code=#{result[:http].code} mime=#{result_mime}"}
|
381
|
-
Log.log.debug{Log.dump('data', result[:data])}
|
382
395
|
RestErrorAnalyzer.instance.raise_on_error(req, result)
|
383
|
-
File.write(save_to_file, result[:http].body) unless file_saved || save_to_file.nil?
|
396
|
+
File.write(save_to_file, result[:http].body, binmode: true) unless file_saved || save_to_file.nil?
|
384
397
|
rescue RestCallError => e
|
398
|
+
do_retry = false
|
399
|
+
# AoC have some timeout , like Connect to platform.bss.asperasoft.com:443 ...
|
400
|
+
do_retry = true if e.response.body.include?('failed: connect timed out') && (timeout_tries -= 1).positive?
|
401
|
+
# possibility to retry anything if it fails
|
402
|
+
do_retry = true if (general_tries -= 1).positive?
|
385
403
|
# not authorized: oauth token expired
|
386
404
|
if @not_auth_codes.include?(result[:http].code.to_s) && @auth_params[:type].eql?(:oauth2)
|
387
405
|
begin
|
@@ -394,7 +412,11 @@ module Aspera
|
|
394
412
|
req['Authorization'] = oauth.token(refresh: true)
|
395
413
|
end
|
396
414
|
Log.log.debug{"using new token=#{headers['Authorization']}"}
|
397
|
-
|
415
|
+
do_retry = true if (oauth_tries -= 1).positive?
|
416
|
+
end
|
417
|
+
if do_retry
|
418
|
+
sleep(RestParameters.instance.retry_sleep) unless RestParameters.instance.retry_sleep.nil?
|
419
|
+
retry
|
398
420
|
end
|
399
421
|
# redirect ? (any code beginning with 3)
|
400
422
|
if e.response.is_a?(Net::HTTPRedirection) && tries_remain_redirect.positive?
|
@@ -420,27 +442,28 @@ module Aspera
|
|
420
442
|
end
|
421
443
|
|
422
444
|
#
|
423
|
-
# CRUD methods here
|
445
|
+
# CRUD simplified methods here
|
446
|
+
# If specific elements are needed, then use the full `call` method
|
424
447
|
#
|
425
448
|
|
426
449
|
def create(subpath, params)
|
427
|
-
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)
|
450
|
+
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)[:data]
|
428
451
|
end
|
429
452
|
|
430
453
|
def read(subpath, query=nil)
|
431
|
-
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => 'application/json'}, query: query)
|
454
|
+
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => 'application/json'}, query: query)[:data]
|
432
455
|
end
|
433
456
|
|
434
457
|
def update(subpath, params)
|
435
|
-
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)
|
458
|
+
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)[:data]
|
436
459
|
end
|
437
460
|
|
438
461
|
def delete(subpath, params=nil)
|
439
|
-
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}, query: params)
|
462
|
+
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}, query: params)[:data]
|
440
463
|
end
|
441
464
|
|
442
465
|
def cancel(subpath)
|
443
|
-
return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => 'application/json'})
|
466
|
+
return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => 'application/json'})[:data]
|
444
467
|
end
|
445
468
|
|
446
469
|
# Query entity by general search (read with parameter `q`)
|
@@ -449,9 +472,10 @@ module Aspera
|
|
449
472
|
# @param search_name name of searched entity
|
450
473
|
# @param query additional search query parameters
|
451
474
|
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
452
|
-
def lookup_by_name(subpath, search_name, query
|
475
|
+
def lookup_by_name(subpath, search_name, query: nil)
|
476
|
+
query = {} if query.nil?
|
453
477
|
# returns entities matching the query (it matches against several fields in case insensitive way)
|
454
|
-
matching_items = read(subpath, query.merge({'q' => search_name}))
|
478
|
+
matching_items = read(subpath, query.merge({'q' => search_name}))
|
455
479
|
# API style: {totalcount:, ...} cspell: disable-line
|
456
480
|
matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
|
457
481
|
Aspera.assert_type(matching_items, Array)
|
data/lib/aspera/secret_hider.rb
CHANGED
@@ -16,17 +16,18 @@ module Aspera
|
|
16
16
|
KEY_SECRETS = %w[password secret passphrase _key apikey crn token].freeze
|
17
17
|
HTTP_SECRETS = %w[Authorization].freeze
|
18
18
|
ALL_SECRETS = [ASCP_ENV_SECRETS, KEY_SECRETS, HTTP_SECRETS].flatten.freeze
|
19
|
+
ALL_SECRETS2 = [KEY_SECRETS, HTTP_SECRETS].flatten.freeze
|
19
20
|
KEY_FALSE_POSITIVES = [/^access_key$/, /^fallback_private_key$/].freeze
|
20
21
|
# regex that define named captures :begin and :end
|
21
22
|
REGEX_LOG_REPLACES = [
|
22
23
|
# CLI manager get/set options
|
23
24
|
/(?<begin>[sg]et (?:#{KEY_SECRETS.join('|')})=).*(?<end>)/,
|
24
25
|
# env var ascp exec
|
25
|
-
/(?<begin> (?:#{ASCP_ENV_SECRETS.join('|')})=)
|
26
|
+
/(?<begin> (?:#{ASCP_ENV_SECRETS.join('|')})=)[^ ]+(?<end> )/,
|
26
27
|
# rendered JSON or Ruby
|
27
28
|
/(?<begin>(?:(?<quote>["'])|:)[^"':=]*(?:#{ALL_SECRETS.join('|')})[^"':=]*\k<quote>?(?:=>|:) *")[^"]+(?<end>")/,
|
28
29
|
# logged data
|
29
|
-
/(?<begin>(?:#{
|
30
|
+
/(?<begin>(?:#{ALL_SECRETS2.join('|')})[ =:]+).*(?<end>$)/,
|
30
31
|
# private key values
|
31
32
|
/(?<begin>--+BEGIN [^-]+ KEY--+)[[:ascii:]]+?(?<end>--+?END [^-]+ KEY--+)/,
|
32
33
|
# cred in http dump
|
data/lib/aspera/ssh.rb
CHANGED
@@ -14,7 +14,7 @@ if ENV.fetch('ASCLI_ENABLE_ED25519', 'false').eql?('false')
|
|
14
14
|
$VERBOSE = old_verbose
|
15
15
|
end
|
16
16
|
|
17
|
-
if
|
17
|
+
if defined?(JRUBY_VERSION) && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')
|
18
18
|
Net::SSH::Transport::Algorithms::ALGORITHMS.each_value { |a| a.reject! { |a| a =~ /^ecd(sa|h)-sha2/ } }
|
19
19
|
Net::SSH::KnownHosts::SUPPORTED_TYPE.reject! { |t| t =~ /^ecd(sa|h)-sha2/ }
|
20
20
|
end
|
@@ -8,19 +8,21 @@ module Aspera
|
|
8
8
|
PREFIX = 'faux:///'
|
9
9
|
# size suffix
|
10
10
|
SUFFIX = %w[k m g t p e]
|
11
|
+
private_constant :PREFIX, :SUFFIX
|
11
12
|
class << self
|
12
|
-
|
13
|
+
# @return nil if not a faux: scheme, else a FauxFile instance
|
14
|
+
def create(name)
|
13
15
|
return nil unless name.start_with?(PREFIX)
|
14
|
-
|
15
|
-
raise 'Format: #{PREFIX}<file path>?<size>' unless
|
16
|
-
raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m =
|
16
|
+
name_params = name[PREFIX.length..-1].split('?', 2)
|
17
|
+
raise 'Format: #{PREFIX}<file path>?<size>' unless name_params.length.eql?(2)
|
18
|
+
raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = name_params[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
|
17
19
|
size = m[1].to_i
|
18
20
|
suffix = m[2]
|
19
21
|
SUFFIX.each do |s|
|
20
22
|
size *= 1024
|
21
23
|
break if s.eql?(suffix)
|
22
24
|
end
|
23
|
-
return FauxFile.new(
|
25
|
+
return FauxFile.new(name_params[0], size)
|
24
26
|
end
|
25
27
|
end
|
26
28
|
attr_reader :path, :size
|
@@ -21,17 +21,29 @@ module Aspera
|
|
21
21
|
class Parameters
|
22
22
|
# Agents shown in manual for parameters (sub list)
|
23
23
|
SUPPORTED_AGENTS = %i[direct node connect trsdk httpgw].freeze
|
24
|
+
FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
|
24
25
|
# Short names of columns in manual
|
25
26
|
SUPPORTED_AGENTS_SHORT = SUPPORTED_AGENTS.map{|agent_sym|agent_sym.to_s[0].to_sym}
|
26
|
-
|
27
|
+
HTTP_FALLBACK_ACTIVATION_VALUES = ['1', 1, true, 'force'].freeze
|
27
28
|
|
28
29
|
private_constant :SUPPORTED_AGENTS, :FILE_LIST_OPTIONS
|
29
30
|
|
30
31
|
class << self
|
32
|
+
# temp file list files are created here
|
33
|
+
def file_list_folder=(value)
|
34
|
+
@file_list_folder = value
|
35
|
+
return if @file_list_folder.nil?
|
36
|
+
|
37
|
+
FileUtils.mkdir_p(@file_list_folder)
|
38
|
+
TempFileManager.instance.cleanup_expired(@file_list_folder)
|
39
|
+
end
|
40
|
+
|
31
41
|
# Temp folder for file lists, must contain only file lists
|
32
42
|
# because of garbage collection takes any file there
|
33
|
-
# this could be refined, as
|
34
|
-
|
43
|
+
# this could be refined, as, for example, on macos, temp folder is already user specific
|
44
|
+
def file_list_folder
|
45
|
+
@file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
|
46
|
+
end
|
35
47
|
|
36
48
|
# @param formatter [Cli::Formatter] formatter to use
|
37
49
|
# @return a table suitable to display in manual
|
@@ -66,9 +78,7 @@ module Aspera
|
|
66
78
|
else
|
67
79
|
param[:d].eql?(tick_yes) ? '' : 'n/a'
|
68
80
|
end
|
69
|
-
if options.key?(:enum)
|
70
|
-
param[:description] += "\nAllowed values: #{options[:enum].join(', ')}"
|
71
|
-
end
|
81
|
+
param[:description] += "\nAllowed values: #{options[:enum].join(', ')}" if options.key?(:enum)
|
72
82
|
# replace "solidus" HTML entity with its text value
|
73
83
|
param[:description] = param[:description].gsub('/', '\\')
|
74
84
|
result.push(param)
|
@@ -90,37 +100,30 @@ module Aspera
|
|
90
100
|
def ascp_args_file_list?(ascp_args)
|
91
101
|
ascp_args&.any?{|i|FILE_LIST_OPTIONS.include?(i)}
|
92
102
|
end
|
93
|
-
|
94
|
-
# temp file list files are created here
|
95
|
-
def file_list_folder=(value)
|
96
|
-
@file_list_folder = value
|
97
|
-
return if @file_list_folder.nil?
|
98
|
-
FileUtils.mkdir_p(@file_list_folder)
|
99
|
-
TempFileManager.instance.cleanup_expired(@file_list_folder)
|
100
|
-
end
|
101
|
-
|
102
|
-
# static methods
|
103
|
-
attr_reader :file_list_folder
|
104
103
|
end
|
105
104
|
|
106
105
|
# @param options [Hash] key: :wss: bool, :ascp_args: array of strings
|
107
106
|
def initialize(
|
108
107
|
job_spec,
|
109
|
-
ascp_args
|
110
|
-
wss
|
111
|
-
quiet
|
112
|
-
trusted_certs
|
113
|
-
|
108
|
+
ascp_args: nil,
|
109
|
+
wss: true,
|
110
|
+
quiet: true,
|
111
|
+
trusted_certs: nil,
|
112
|
+
client_ssh_key: nil,
|
113
|
+
check_ignore_cb: nil
|
114
114
|
)
|
115
115
|
@job_spec = job_spec
|
116
|
-
@ascp_args = ascp_args
|
116
|
+
@ascp_args = ascp_args.nil? ? [] : ascp_args
|
117
117
|
@wss = wss
|
118
118
|
@quiet = quiet
|
119
|
-
@trusted_certs = trusted_certs
|
119
|
+
@trusted_certs = trusted_certs.nil? ? [] : trusted_certs
|
120
|
+
@client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
|
120
121
|
@check_ignore_cb = check_ignore_cb
|
121
|
-
Aspera.assert_type(job_spec, Hash)
|
122
|
+
Aspera.assert_type(@job_spec, Hash)
|
122
123
|
Aspera.assert_type(@ascp_args, Array){'ascp_args'}
|
123
|
-
Aspera.assert(@ascp_args.all?(String)){'ascp arguments must be
|
124
|
+
Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
|
125
|
+
Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
|
126
|
+
Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
|
124
127
|
@builder = CommandLineBuilder.new(@job_spec, Spec::DESCRIPTION)
|
125
128
|
end
|
126
129
|
|
@@ -162,6 +165,7 @@ module Aspera
|
|
162
165
|
@builder.add_command_line_options(["#{file_list_option}=#{file_list_file}"]) unless file_list_option.nil?
|
163
166
|
end
|
164
167
|
|
168
|
+
# @return the list of certificates to use when token/ssh or wss are used
|
165
169
|
def remote_certificates
|
166
170
|
certificates_to_use = []
|
167
171
|
# use web socket secure for session ?
|
@@ -174,7 +178,7 @@ module Aspera
|
|
174
178
|
@job_spec.delete('fasp_port')
|
175
179
|
@job_spec.delete('sshfp')
|
176
180
|
# set location for CA bundle to be the one of Ruby, see env var SSL_CERT_FILE / SSL_CERT_DIR
|
177
|
-
certificates_to_use.concat(@trusted_certs)
|
181
|
+
certificates_to_use.concat(@trusted_certs)
|
178
182
|
# ignore cert for wss ?
|
179
183
|
if @check_ignore_cb&.call(@job_spec['remote_host'], @job_spec['wss_port'])
|
180
184
|
wss_cert_file = TempFileManager.instance.new_file_path_global('wss_cert')
|
@@ -191,7 +195,7 @@ module Aspera
|
|
191
195
|
# add SSH bypass keys when authentication is token and no auth is provided
|
192
196
|
if @job_spec.key?('token') && !@job_spec.key?('remote_password')
|
193
197
|
# @job_spec['remote_password'] = Ascp::Installation.instance.ssh_cert_uuid # not used: no passphrase
|
194
|
-
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths)
|
198
|
+
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(@client_ssh_key))
|
195
199
|
end
|
196
200
|
end
|
197
201
|
return certificates_to_use
|
@@ -200,9 +204,9 @@ module Aspera
|
|
200
204
|
# translate transfer spec to env vars and command line arguments for ascp
|
201
205
|
def ascp_args
|
202
206
|
env_args = {
|
203
|
-
args:
|
204
|
-
env:
|
205
|
-
|
207
|
+
args: [],
|
208
|
+
env: {},
|
209
|
+
name: :ascp
|
206
210
|
}
|
207
211
|
|
208
212
|
# special cases
|
@@ -213,7 +217,7 @@ module Aspera
|
|
213
217
|
|
214
218
|
# add ssh or wss certificates
|
215
219
|
# (reverse, to keep order, as we unshift)
|
216
|
-
remote_certificates
|
220
|
+
remote_certificates&.reverse_each do |cert|
|
217
221
|
env_args[:args].unshift('-i', cert)
|
218
222
|
end
|
219
223
|
|
@@ -223,9 +227,9 @@ module Aspera
|
|
223
227
|
base64_destination = false
|
224
228
|
# symbol must be index of Ascp::Installation.paths
|
225
229
|
if @builder.read_param('use_ascp4')
|
226
|
-
env_args[:
|
230
|
+
env_args[:name] = :ascp4
|
227
231
|
else
|
228
|
-
env_args[:
|
232
|
+
env_args[:name] = :ascp
|
229
233
|
base64_destination = true
|
230
234
|
end
|
231
235
|
# destination will be base64 encoded, put this before source path arguments
|
@@ -243,10 +247,12 @@ module Aspera
|
|
243
247
|
@builder.add_env_args(env_args)
|
244
248
|
env_args[:args].unshift('-q') if @quiet
|
245
249
|
# add fallback cert and key as arguments if needed
|
246
|
-
if
|
250
|
+
if HTTP_FALLBACK_ACTIVATION_VALUES.include?(@job_spec['http_fallback'])
|
247
251
|
env_args[:args].unshift('-Y', Ascp::Installation.instance.path(:fallback_private_key))
|
248
252
|
env_args[:args].unshift('-I', Ascp::Installation.instance.path(:fallback_certificate))
|
249
253
|
end
|
254
|
+
# disable redis in client
|
255
|
+
env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true'
|
250
256
|
Log.log.debug{"ascp args: #{env_args}"}
|
251
257
|
return env_args
|
252
258
|
end
|
data/lib/aspera/transfer/spec.rb
CHANGED
@@ -10,36 +10,30 @@ module Aspera
|
|
10
10
|
class Spec
|
11
11
|
# default transfer username for access key based transfers
|
12
12
|
ACCESS_KEY_TRANSFER_USER = 'xfer'
|
13
|
+
# default ports for SSH and UDP
|
13
14
|
SSH_PORT = 33_001
|
14
15
|
UDP_PORT = 33_001
|
16
|
+
# base transfer spec for access keys
|
15
17
|
AK_TSPEC_BASE = {
|
16
18
|
'remote_user' => ACCESS_KEY_TRANSFER_USER,
|
17
19
|
'ssh_port' => SSH_PORT,
|
18
20
|
'fasp_port' => UDP_PORT
|
19
21
|
}.freeze
|
20
|
-
# fields for
|
21
|
-
|
22
|
+
# fields for WSS
|
23
|
+
WSS_FIELDS = %w[wss_enabled wss_port].freeze
|
24
|
+
# all fields for transport
|
25
|
+
TRANSPORT_FIELDS = %w[remote_host remote_user ssh_port fasp_port].concat(WSS_FIELDS).freeze
|
22
26
|
# reserved tag for Aspera
|
23
27
|
TAG_RESERVED = 'aspera'
|
24
28
|
class << self
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
when :upload then DIRECTION_SEND
|
29
|
-
when :download then DIRECTION_RECEIVE
|
30
|
-
else Aspera.error_unexpected_value(command.to_sym)
|
31
|
-
end
|
32
|
-
return tspec
|
29
|
+
# translate upload/download to send/receive
|
30
|
+
def transfer_type_to_direction(transfer_type)
|
31
|
+
XFER_TYPE_TO_DIR.fetch(transfer_type)
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
case tspec['direction']
|
39
|
-
when DIRECTION_SEND then :upload
|
40
|
-
when DIRECTION_RECEIVE then :download
|
41
|
-
else Aspera.error_unexpected_value(tspec['direction'])
|
42
|
-
end
|
34
|
+
# translate send/receive to upload/download
|
35
|
+
def direction_to_transfer_type(direction)
|
36
|
+
XFER_DIR_TO_TYPE.fetch(direction)
|
43
37
|
end
|
44
38
|
end
|
45
39
|
DESCRIPTION = CommandLineBuilder.normalize_description(YAML.load_file("#{__FILE__[0..-3]}yaml"))
|
@@ -51,6 +45,10 @@ module Aspera
|
|
51
45
|
const_set("#{name.to_s.upcase}_#{enum.upcase.gsub(/[^A-Z0-9]/, '_')}", enum.freeze)
|
52
46
|
end
|
53
47
|
end
|
48
|
+
# DIRECTION_* are read from yaml
|
49
|
+
XFER_TYPE_TO_DIR = {upload: DIRECTION_SEND, download: DIRECTION_RECEIVE}.freeze
|
50
|
+
XFER_DIR_TO_TYPE = XFER_TYPE_TO_DIR.invert.freeze
|
51
|
+
private_constant :XFER_TYPE_TO_DIR, :XFER_DIR_TO_TYPE
|
54
52
|
end
|
55
53
|
end
|
56
54
|
end
|