aspera-cli 4.24.2 → 4.25.0.pre2
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 +1067 -758
- data/CONTRIBUTING.md +93 -120
- data/README.md +817 -510
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/transferd.rb +4 -4
- data/lib/aspera/api/aoc.rb +71 -43
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +6 -5
- data/lib/aspera/api/node.rb +10 -12
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +55 -41
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +28 -6
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +94 -62
- data/lib/aspera/cli/formatter.rb +55 -22
- data/lib/aspera/cli/main.rb +21 -14
- data/lib/aspera/cli/manager.rb +349 -248
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +94 -51
- data/lib/aspera/cli/plugins/base.rb +62 -49
- data/lib/aspera/cli/plugins/config.rb +85 -96
- data/lib/aspera/cli/plugins/console.rb +15 -9
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +34 -27
- data/lib/aspera/cli/plugins/faspex5.rb +47 -44
- data/lib/aspera/cli/plugins/faspio.rb +7 -6
- data/lib/aspera/cli/plugins/httpgw.rb +3 -2
- data/lib/aspera/cli/plugins/node.rb +132 -120
- data/lib/aspera/cli/plugins/oauth.rb +1 -1
- data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
- data/lib/aspera/cli/plugins/preview.rb +26 -46
- data/lib/aspera/cli/plugins/server.rb +9 -10
- data/lib/aspera/cli/plugins/shares.rb +77 -43
- data/lib/aspera/cli/sync_actions.rb +49 -38
- data/lib/aspera/cli/transfer_agent.rb +16 -34
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +8 -5
- data/lib/aspera/command_line_builder.rb +20 -17
- data/lib/aspera/coverage.rb +6 -2
- data/lib/aspera/environment.rb +71 -84
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +6 -5
- data/lib/aspera/oauth/base.rb +17 -27
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/products/connect.rb +3 -3
- data/lib/aspera/rest.rb +51 -39
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/conf.schema.yaml +182 -34
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +128 -72
- data/lib/aspera/transfer/parameters.rb +3 -4
- data/lib/aspera/transfer/spec.rb +2 -3
- data/lib/aspera/transfer/spec.schema.yaml +49 -19
- data/lib/aspera/transfer/spec_doc.rb +14 -14
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +33 -6
- metadata.gz.sig +0 -0
data/lib/aspera/rest.rb
CHANGED
|
@@ -50,13 +50,13 @@ module Aspera
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
class EntityNotFound < Error
|
|
54
|
+
end
|
|
55
|
+
|
|
53
56
|
# a simple class to make HTTP calls, equivalent to rest-client
|
|
54
57
|
# rest call errors are raised as exception RestCallError
|
|
55
58
|
# and error are analyzed in RestErrorAnalyzer
|
|
56
59
|
class Rest
|
|
57
|
-
# Error message when entity not found (TODO: use specific exception)
|
|
58
|
-
ENTITY_NOT_FOUND = 'No such'
|
|
59
|
-
|
|
60
60
|
MIME_JSON = 'application/json'
|
|
61
61
|
MIME_WWW = 'application/x-www-form-urlencoded'
|
|
62
62
|
MIME_TEXT = 'text/plain'
|
|
@@ -71,7 +71,7 @@ module Aspera
|
|
|
71
71
|
def basic_authorization(user, pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"; end
|
|
72
72
|
|
|
73
73
|
# Indicate that the given Hash query uses php style for array parameters
|
|
74
|
-
# a[]=1&a[]=2
|
|
74
|
+
# @param query [Hash] A key can have Array value and result will use PHP format: a[]=1&a[]=2
|
|
75
75
|
def php_style(query)
|
|
76
76
|
Aspera.assert_type(query, Hash){'query'}
|
|
77
77
|
query[:x_array_php_style] = true
|
|
@@ -300,14 +300,16 @@ module Aspera
|
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
# HTTP/S REST call
|
|
303
|
-
# @param operation
|
|
304
|
-
# @param subpath
|
|
305
|
-
# @param query
|
|
306
|
-
# @param content_type [String,nil] Type of body parameters (one of MIME_*) and serialization, else use headers
|
|
307
|
-
# @param body
|
|
308
|
-
# @param headers
|
|
309
|
-
# @param save_to_file (filepath)
|
|
310
|
-
# @param exception
|
|
303
|
+
# @param operation [String] HTTP operation (GET, POST, PUT, DELETE)
|
|
304
|
+
# @param subpath [String] subpath of REST API
|
|
305
|
+
# @param query [Hash] URL parameters
|
|
306
|
+
# @param content_type [String, nil] Type of body parameters (one of MIME_*) and serialization, else use headers
|
|
307
|
+
# @param body [Hash, String] body parameters
|
|
308
|
+
# @param headers [Hash] additional headers (override Content-Type)
|
|
309
|
+
# @param save_to_file [String, nil](filepath)
|
|
310
|
+
# @param exception [Bool] `true`, error raise exception
|
|
311
|
+
# @param ret [:data, :resp, :both] Tell to return only data, only http response, or both
|
|
312
|
+
# @return [Object, Array] only data, only http response, or both
|
|
311
313
|
def call(
|
|
312
314
|
operation:,
|
|
313
315
|
subpath: nil,
|
|
@@ -316,7 +318,8 @@ module Aspera
|
|
|
316
318
|
body: nil,
|
|
317
319
|
headers: nil,
|
|
318
320
|
save_to_file: nil,
|
|
319
|
-
exception: true
|
|
321
|
+
exception: true,
|
|
322
|
+
ret: :data
|
|
320
323
|
)
|
|
321
324
|
subpath = subpath.to_s if subpath.is_a?(Symbol)
|
|
322
325
|
subpath = '' if subpath.nil?
|
|
@@ -325,6 +328,8 @@ module Aspera
|
|
|
325
328
|
Log.dump(:query, query, level: :trace1)
|
|
326
329
|
Log.dump(:headers, headers, level: :trace1)
|
|
327
330
|
Aspera.assert_type(subpath, String)
|
|
331
|
+
# We must have a way to check return code
|
|
332
|
+
Aspera.assert(exception || !ret.eql?(:data))
|
|
328
333
|
if headers.nil?
|
|
329
334
|
headers = @headers.clone
|
|
330
335
|
else
|
|
@@ -348,7 +353,8 @@ module Aspera
|
|
|
348
353
|
end
|
|
349
354
|
else Aspera.error_unexpected_value(@auth_params[:type])
|
|
350
355
|
end
|
|
351
|
-
|
|
356
|
+
result_http = nil
|
|
357
|
+
result_data = nil
|
|
352
358
|
# start a block to be able to retry the actual HTTP request in case of OAuth token expiration
|
|
353
359
|
begin
|
|
354
360
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
|
@@ -391,14 +397,14 @@ module Aspera
|
|
|
391
397
|
file_saved = false
|
|
392
398
|
# make http request (pipelined)
|
|
393
399
|
http_session.request(req) do |response|
|
|
394
|
-
|
|
395
|
-
result_mime = self.class.parse_header(
|
|
396
|
-
Log.log.debug{"response: code=#{
|
|
400
|
+
result_http = response
|
|
401
|
+
result_mime = self.class.parse_header(result_http['Content-Type'] || MIME_TEXT)[:type]
|
|
402
|
+
Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime}, mime2= #{response['Content-Type']}"}
|
|
397
403
|
# JSON data needs to be parsed, in case it contains an error code
|
|
398
404
|
if !save_to_file.nil? &&
|
|
399
|
-
|
|
405
|
+
result_http.code.to_s.start_with?('2') &&
|
|
400
406
|
!JSON_DECODE.include?(result_mime)
|
|
401
|
-
total_size =
|
|
407
|
+
total_size = result_http['Content-Length']&.to_i
|
|
402
408
|
Log.log.debug('before write file')
|
|
403
409
|
target_file = save_to_file
|
|
404
410
|
# override user's path to path in header
|
|
@@ -416,7 +422,7 @@ module Aspera
|
|
|
416
422
|
FileUtils.mkdir_p(File.dirname(target_file_tmp))
|
|
417
423
|
limiter = TimerLimiter.new(0.5)
|
|
418
424
|
File.open(target_file_tmp, 'wb') do |file|
|
|
419
|
-
|
|
425
|
+
result_http.read_body do |fragment|
|
|
420
426
|
file.write(fragment)
|
|
421
427
|
written_size += fragment.length
|
|
422
428
|
RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
|
@@ -429,32 +435,32 @@ module Aspera
|
|
|
429
435
|
file_saved = true
|
|
430
436
|
end
|
|
431
437
|
end
|
|
432
|
-
Log.log.debug{"result: code=#{
|
|
438
|
+
Log.log.debug{"result: code=#{result_http.code} mime=#{result_mime}"}
|
|
433
439
|
# sometimes there is a UTF8 char (e.g. (c) ), TODO : related to mime type encoding ?
|
|
434
|
-
#
|
|
435
|
-
# Log.log.debug{"result: body=#{
|
|
440
|
+
# result_http.body.force_encoding('UTF-8') if result_http.body.is_a?(String)
|
|
441
|
+
# Log.log.debug{"result: body=#{result_http.body}"}
|
|
436
442
|
case result_mime
|
|
437
443
|
when *JSON_DECODE
|
|
438
|
-
|
|
439
|
-
Log.dump(:result_data,
|
|
444
|
+
result_data = JSON.parse(result_http.body) rescue result_http.body
|
|
445
|
+
Log.dump(:result_data, result_data)
|
|
440
446
|
else # when MIME_TEXT
|
|
441
|
-
|
|
447
|
+
result_data = result_http.body
|
|
442
448
|
end
|
|
443
|
-
RestErrorAnalyzer.instance.raise_on_error(req,
|
|
449
|
+
RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
|
|
444
450
|
unless file_saved || save_to_file.nil?
|
|
445
451
|
FileUtils.mkdir_p(File.dirname(save_to_file))
|
|
446
|
-
File.write(save_to_file,
|
|
452
|
+
File.write(save_to_file, result_http.body, binmode: true)
|
|
447
453
|
end
|
|
448
454
|
rescue RestCallError => e
|
|
449
455
|
do_retry = false
|
|
450
456
|
# AoC have some timeout , like Connect to platform.bss.asperasoft.com:443 ...
|
|
451
457
|
do_retry ||= true if e.response.body.include?('failed: connect timed out') && RestParameters.instance.retry_on_timeout
|
|
452
458
|
# AoC sometimes not available
|
|
453
|
-
do_retry ||= true if RestParameters.instance.retry_on_unavailable && UNAVAILABLE_CODES.include?(
|
|
459
|
+
do_retry ||= true if RestParameters.instance.retry_on_unavailable && UNAVAILABLE_CODES.include?(result_http.code.to_s)
|
|
454
460
|
# possibility to retry anything if it fails
|
|
455
461
|
do_retry ||= true if RestParameters.instance.retry_on_error
|
|
456
462
|
# not authorized: oauth token expired
|
|
457
|
-
if @not_auth_codes.include?(
|
|
463
|
+
if @not_auth_codes.include?(result_http.code.to_s) && @auth_params[:type].eql?(:oauth2)
|
|
458
464
|
begin
|
|
459
465
|
# try to use refresh token
|
|
460
466
|
req['Authorization'] = oauth.authorization(refresh: true)
|
|
@@ -494,14 +500,20 @@ module Aspera
|
|
|
494
500
|
content_type: content_type,
|
|
495
501
|
save_to_file: save_to_file,
|
|
496
502
|
exception: exception,
|
|
497
|
-
headers: headers
|
|
503
|
+
headers: headers,
|
|
504
|
+
ret: ret
|
|
498
505
|
)
|
|
499
506
|
end
|
|
500
507
|
# raise exception if could not retry and not return error in result
|
|
501
508
|
raise e if exception
|
|
502
509
|
end
|
|
503
|
-
Log.log.debug{"result=http:#{
|
|
504
|
-
return
|
|
510
|
+
Log.log.debug{"result=http:#{result_http}, data:#{result_data.class}"}
|
|
511
|
+
return case ret
|
|
512
|
+
when :data then result_data
|
|
513
|
+
when :resp then result_http
|
|
514
|
+
when :both then [result_data, result_http]
|
|
515
|
+
else Aspera.error_unexpected_value(ret){'Type of result for REST'}
|
|
516
|
+
end
|
|
505
517
|
end
|
|
506
518
|
|
|
507
519
|
#
|
|
@@ -511,27 +523,27 @@ module Aspera
|
|
|
511
523
|
|
|
512
524
|
# Create: `POST`
|
|
513
525
|
def create(subpath, params, **kwargs)
|
|
514
|
-
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
526
|
+
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
515
527
|
end
|
|
516
528
|
|
|
517
529
|
# Read: `GET`
|
|
518
530
|
def read(subpath, query = nil, **kwargs)
|
|
519
|
-
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query, **kwargs)
|
|
531
|
+
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query, **kwargs)
|
|
520
532
|
end
|
|
521
533
|
|
|
522
534
|
# Update: `PUT`
|
|
523
535
|
def update(subpath, params, **kwargs)
|
|
524
|
-
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
536
|
+
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
525
537
|
end
|
|
526
538
|
|
|
527
539
|
# Delete: `DELETE`
|
|
528
540
|
def delete(subpath, params = nil, **kwargs)
|
|
529
|
-
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params, **kwargs)
|
|
541
|
+
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params, **kwargs)
|
|
530
542
|
end
|
|
531
543
|
|
|
532
544
|
# Cancel: `CANCEL`
|
|
533
545
|
def cancel(subpath, **kwargs)
|
|
534
|
-
return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => MIME_JSON}, **kwargs)
|
|
546
|
+
return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => MIME_JSON}, **kwargs)
|
|
535
547
|
end
|
|
536
548
|
|
|
537
549
|
# Query entity by general search (read with parameter `q`)
|
|
@@ -549,7 +561,7 @@ module Aspera
|
|
|
549
561
|
Aspera.assert_type(matching_items, Array)
|
|
550
562
|
case matching_items.length
|
|
551
563
|
when 1 then return matching_items.first
|
|
552
|
-
when 0 then raise %Q{
|
|
564
|
+
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
553
565
|
else
|
|
554
566
|
# multiple case insensitive partial matches, try case insensitive full match
|
|
555
567
|
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
@@ -27,13 +27,13 @@ module Aspera
|
|
|
27
27
|
# Use this method to analyze a EST result and raise an exception
|
|
28
28
|
# Analyzes REST call response and raises a RestCallError exception
|
|
29
29
|
# if HTTP result code is not 2XX
|
|
30
|
-
def raise_on_error(req,
|
|
31
|
-
Log.log.debug{"raise_on_error #{req.method} #{req.path} #{
|
|
30
|
+
def raise_on_error(req, data, http)
|
|
31
|
+
Log.log.debug{"raise_on_error #{req.method} #{req.path} #{http.code}"}
|
|
32
32
|
call_context = {
|
|
33
33
|
messages: [],
|
|
34
34
|
request: req,
|
|
35
|
-
response:
|
|
36
|
-
data:
|
|
35
|
+
response: http,
|
|
36
|
+
data: data
|
|
37
37
|
}
|
|
38
38
|
# multiple error messages can be found
|
|
39
39
|
# analyze errors from provided handlers
|
data/lib/aspera/ssh.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Aspera
|
|
|
11
11
|
class Error < Aspera::Error
|
|
12
12
|
end
|
|
13
13
|
class << self
|
|
14
|
+
# HACK: disable some key type
|
|
14
15
|
def disable_ed25519_keys
|
|
15
16
|
Log.log.debug('Disabling SSH ed25519 user keys')
|
|
16
17
|
old_verbose = $VERBOSE
|
|
@@ -24,6 +25,7 @@ module Aspera
|
|
|
24
25
|
$VERBOSE = old_verbose
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# HACK: disable some algorithms
|
|
27
29
|
def disable_ecd_sha2_algorithms
|
|
28
30
|
Log.log.debug('Disabling SSH ecdsa')
|
|
29
31
|
Net::SSH::Transport::Algorithms::ALGORITHMS.each_value{ |a| a.reject!{ |a| a =~ /^ecd(sa|h)-sha2/}}
|
|
@@ -38,6 +40,7 @@ module Aspera
|
|
|
38
40
|
Aspera.assert_type(host, String)
|
|
39
41
|
Aspera.assert_type(username, String)
|
|
40
42
|
Aspera.assert_type(ssh_options, Hash)
|
|
43
|
+
ssh_options[:use_agent] = false unless ssh_options.key?(:use_agent)
|
|
41
44
|
@host = host
|
|
42
45
|
@username = username
|
|
43
46
|
@ssh_options = ssh_options
|
|
@@ -62,7 +65,7 @@ module Aspera
|
|
|
62
65
|
exit_code = data.read_long
|
|
63
66
|
next if exit_code.zero?
|
|
64
67
|
error_message = "#{cmd}: exit #{exit_code}, #{error.join.chomp}"
|
|
65
|
-
raise Error, error_message if
|
|
68
|
+
raise Error, error_message if exception
|
|
66
69
|
# Happens when windows user hasn't logged in and created home account.
|
|
67
70
|
error_message += "\nHint: home not created in Windows?" if data.include?('Could not chdir to home directory')
|
|
68
71
|
Log.log.debug(error_message)
|
|
@@ -82,5 +85,5 @@ module Aspera
|
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
# Deactivate ed25519 and ecdsa private keys from SSH identities, as it usually causes problems
|
|
85
|
-
Aspera::Ssh.disable_ed25519_keys if Gem::Specification.find_all_by_name('ed25519').none?
|
|
88
|
+
Aspera::Ssh.disable_ed25519_keys if Gem::Specification.find_all_by_name('ed25519').none? || ENV.fetch('ASCLI_ENABLE_ED25519', 'true').eql?('false')
|
|
86
89
|
Aspera::Ssh.disable_ecd_sha2_algorithms if defined?(JRUBY_VERSION) && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')
|
data/lib/aspera/ssl.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'aspera/assert'
|
|
5
|
+
require 'aspera/log'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
# Give possibility to globally override SSL options
|
|
9
|
+
module SSL
|
|
10
|
+
@extra_options = 0
|
|
11
|
+
class << self
|
|
12
|
+
@extra_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
|
13
|
+
attr_reader :extra_options
|
|
14
|
+
|
|
15
|
+
def option_list=(v)
|
|
16
|
+
Aspera.assert_type(v, Array){'ssl_options'}
|
|
17
|
+
v.each do |opt|
|
|
18
|
+
Aspera.assert_type(opt, String, Integer){'Expected String or Integer in ssl_options'}
|
|
19
|
+
case opt
|
|
20
|
+
when Integer
|
|
21
|
+
@extra_options = opt
|
|
22
|
+
when String
|
|
23
|
+
name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
|
|
24
|
+
raise Cli::BadArgument, "Unknown ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
|
|
25
|
+
if opt.start_with?('-')
|
|
26
|
+
@extra_options &= ~OpenSSL::SSL.const_get(name)
|
|
27
|
+
else
|
|
28
|
+
@extra_options |= OpenSSL::SSL.const_get(name)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
def set_params(params = {})
|
|
35
|
+
super(params)
|
|
36
|
+
self.options = Aspera::SSL.extra_options unless Aspera::SSL.extra_options.nil?
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
OpenSSL::SSL::SSLContext.prepend(Aspera::SSL)
|