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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1067 -758
  4. data/CONTRIBUTING.md +93 -120
  5. data/README.md +817 -510
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/transferd.rb +4 -4
  8. data/lib/aspera/api/aoc.rb +71 -43
  9. data/lib/aspera/api/cos_node.rb +3 -2
  10. data/lib/aspera/api/faspex.rb +6 -5
  11. data/lib/aspera/api/node.rb +10 -12
  12. data/lib/aspera/ascmd.rb +1 -2
  13. data/lib/aspera/ascp/installation.rb +55 -41
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +28 -6
  16. data/lib/aspera/cli/error.rb +4 -2
  17. data/lib/aspera/cli/extended_value.rb +94 -62
  18. data/lib/aspera/cli/formatter.rb +55 -22
  19. data/lib/aspera/cli/main.rb +21 -14
  20. data/lib/aspera/cli/manager.rb +349 -248
  21. data/lib/aspera/cli/plugins/alee.rb +3 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +94 -51
  23. data/lib/aspera/cli/plugins/base.rb +62 -49
  24. data/lib/aspera/cli/plugins/config.rb +85 -96
  25. data/lib/aspera/cli/plugins/console.rb +15 -9
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +34 -27
  28. data/lib/aspera/cli/plugins/faspex5.rb +47 -44
  29. data/lib/aspera/cli/plugins/faspio.rb +7 -6
  30. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  31. data/lib/aspera/cli/plugins/node.rb +132 -120
  32. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  33. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  34. data/lib/aspera/cli/plugins/preview.rb +26 -46
  35. data/lib/aspera/cli/plugins/server.rb +9 -10
  36. data/lib/aspera/cli/plugins/shares.rb +77 -43
  37. data/lib/aspera/cli/sync_actions.rb +49 -38
  38. data/lib/aspera/cli/transfer_agent.rb +16 -34
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/cli/wizard.rb +8 -5
  41. data/lib/aspera/command_line_builder.rb +20 -17
  42. data/lib/aspera/coverage.rb +6 -2
  43. data/lib/aspera/environment.rb +71 -84
  44. data/lib/aspera/faspex_gw.rb +1 -1
  45. data/lib/aspera/faspex_postproc.rb +1 -1
  46. data/lib/aspera/keychain/factory.rb +1 -2
  47. data/lib/aspera/keychain/macos_security.rb +2 -2
  48. data/lib/aspera/log.rb +2 -1
  49. data/lib/aspera/markdown.rb +31 -0
  50. data/lib/aspera/nagios.rb +6 -5
  51. data/lib/aspera/oauth/base.rb +17 -27
  52. data/lib/aspera/oauth/factory.rb +1 -1
  53. data/lib/aspera/oauth/url_json.rb +2 -1
  54. data/lib/aspera/preview/file_types.rb +23 -37
  55. data/lib/aspera/preview/terminal.rb +95 -29
  56. data/lib/aspera/preview/utils.rb +6 -5
  57. data/lib/aspera/products/connect.rb +3 -3
  58. data/lib/aspera/rest.rb +51 -39
  59. data/lib/aspera/rest_error_analyzer.rb +4 -4
  60. data/lib/aspera/ssh.rb +5 -2
  61. data/lib/aspera/ssl.rb +41 -0
  62. data/lib/aspera/sync/conf.schema.yaml +182 -34
  63. data/lib/aspera/sync/database.rb +2 -1
  64. data/lib/aspera/sync/operations.rb +128 -72
  65. data/lib/aspera/transfer/parameters.rb +3 -4
  66. data/lib/aspera/transfer/spec.rb +2 -3
  67. data/lib/aspera/transfer/spec.schema.yaml +49 -19
  68. data/lib/aspera/transfer/spec_doc.rb +14 -14
  69. data/lib/aspera/uri_reader.rb +1 -1
  70. data/lib/transferd_pb.rb +2 -2
  71. data.tar.gz.sig +0 -0
  72. metadata +33 -6
  73. 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 [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 (filepath)
310
- # @param exception (bool) true, error raise 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
- result = {http: nil}
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
- result[:http] = response
395
- result_mime = self.class.parse_header(result[:http]['Content-Type'] || MIME_TEXT)[:type]
396
- Log.log.debug{"response: code=#{result[:http].code}, mime=#{result_mime}, mime2= #{response['Content-Type']}"}
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
- result[:http].code.to_s.start_with?('2') &&
405
+ result_http.code.to_s.start_with?('2') &&
400
406
  !JSON_DECODE.include?(result_mime)
401
- total_size = result[:http]['Content-Length']&.to_i
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
- result[:http].read_body do |fragment|
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=#{result[:http].code} mime=#{result_mime}"}
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
- # result[:http].body.force_encoding('UTF-8') if result[:http].body.is_a?(String)
435
- # Log.log.debug{"result: body=#{result[:http].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
- result[:data] = JSON.parse(result[:http].body) rescue result[:http].body
439
- Log.dump(:result_data, 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
- result[:data] = result[:http].body
447
+ result_data = result_http.body
442
448
  end
443
- RestErrorAnalyzer.instance.raise_on_error(req, result)
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, result[:http].body, binmode: true)
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?(result[:http].code.to_s)
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?(result[:http].code.to_s) && @auth_params[:type].eql?(:oauth2)
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:#{result[:http]}, data:#{result[:data].class}"}
504
- return result
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)[:data]
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)[:data]
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)[:data]
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)[:data]
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)[:data]
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{#{ENTITY_NOT_FOUND} #{subpath}: "#{search_name}"}
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, res)
31
- Log.log.debug{"raise_on_error #{req.method} #{req.path} #{res[:http].code}"}
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: res[:http],
36
- data: res[: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 exception
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)