aspera-cli 4.16.0 → 4.17.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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -19
  4. data/CONTRIBUTING.md +3 -1
  5. data/README.md +965 -793
  6. data/bin/asession +29 -21
  7. data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
  8. data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
  9. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  10. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
  11. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
  12. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
  13. data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
  14. data/lib/aspera/api/aoc.rb +586 -0
  15. data/lib/aspera/api/ats.rb +46 -0
  16. data/lib/aspera/api/cos_node.rb +95 -0
  17. data/lib/aspera/api/node.rb +344 -0
  18. data/lib/aspera/ascmd.rb +46 -10
  19. data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
  20. data/lib/aspera/{fasp → ascp}/management.rb +3 -8
  21. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  22. data/lib/aspera/assert.rb +30 -30
  23. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  24. data/lib/aspera/cli/extended_value.rb +1 -1
  25. data/lib/aspera/cli/formatter.rb +13 -13
  26. data/lib/aspera/cli/hints.rb +5 -5
  27. data/lib/aspera/cli/main.rb +35 -28
  28. data/lib/aspera/cli/manager.rb +25 -24
  29. data/lib/aspera/cli/plugin.rb +22 -15
  30. data/lib/aspera/cli/plugin_factory.rb +61 -0
  31. data/lib/aspera/cli/plugins/alee.rb +7 -7
  32. data/lib/aspera/cli/plugins/aoc.rb +83 -77
  33. data/lib/aspera/cli/plugins/ats.rb +32 -33
  34. data/lib/aspera/cli/plugins/bss.rb +3 -4
  35. data/lib/aspera/cli/plugins/config.rb +169 -186
  36. data/lib/aspera/cli/plugins/console.rb +8 -6
  37. data/lib/aspera/cli/plugins/cos.rb +19 -18
  38. data/lib/aspera/cli/plugins/faspex.rb +61 -54
  39. data/lib/aspera/cli/plugins/faspex5.rb +150 -103
  40. data/lib/aspera/cli/plugins/node.rb +68 -73
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
  42. data/lib/aspera/cli/plugins/preview.rb +31 -31
  43. data/lib/aspera/cli/plugins/server.rb +31 -33
  44. data/lib/aspera/cli/plugins/shares.rb +13 -11
  45. data/lib/aspera/cli/sync_actions.rb +8 -8
  46. data/lib/aspera/cli/transfer_agent.rb +32 -19
  47. data/lib/aspera/cli/transfer_progress.rb +1 -1
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/colors.rb +5 -0
  50. data/lib/aspera/command_line_builder.rb +14 -14
  51. data/lib/aspera/coverage.rb +1 -2
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +2 -3
  54. data/lib/aspera/faspex_gw.rb +5 -6
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/id_generator.rb +2 -2
  57. data/lib/aspera/json_rpc.rb +5 -5
  58. data/lib/aspera/keychain/encrypted_hash.rb +6 -6
  59. data/lib/aspera/keychain/macos_security.rb +27 -22
  60. data/lib/aspera/log.rb +2 -2
  61. data/lib/aspera/nagios.rb +3 -3
  62. data/lib/aspera/node_simulator.rb +5 -6
  63. data/lib/aspera/oauth/base.rb +143 -0
  64. data/lib/aspera/oauth/factory.rb +124 -0
  65. data/lib/aspera/oauth/generic.rb +34 -0
  66. data/lib/aspera/oauth/jwt.rb +51 -0
  67. data/lib/aspera/oauth/url_json.rb +31 -0
  68. data/lib/aspera/oauth/web.rb +50 -0
  69. data/lib/aspera/oauth.rb +5 -331
  70. data/lib/aspera/open_application.rb +7 -7
  71. data/lib/aspera/persistency_action_once.rb +4 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +5 -5
  74. data/lib/aspera/preview/terminal.rb +3 -2
  75. data/lib/aspera/preview/utils.rb +3 -3
  76. data/lib/aspera/proxy_auto_config.rb +4 -4
  77. data/lib/aspera/rest.rb +175 -144
  78. data/lib/aspera/rest_errors_aspera.rb +3 -3
  79. data/lib/aspera/resumer.rb +77 -0
  80. data/lib/aspera/ssh.rb +6 -1
  81. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  82. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  83. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  84. data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
  85. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
  86. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  87. data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
  88. data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
  89. data/lib/aspera/web_server_simple.rb +11 -3
  90. data.tar.gz.sig +0 -0
  91. metadata +36 -63
  92. metadata.gz.sig +0 -0
  93. data/lib/aspera/aoc.rb +0 -601
  94. data/lib/aspera/ats_api.rb +0 -47
  95. data/lib/aspera/cos_node.rb +0 -94
  96. data/lib/aspera/fasp/resume_policy.rb +0 -79
  97. data/lib/aspera/node.rb +0 -339
data/bin/asession CHANGED
@@ -3,16 +3,18 @@
3
3
 
4
4
  # Laurent Martin/2017
5
5
  $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
6
- require 'aspera/fasp/agent_direct'
6
+ require 'aspera/agent/direct'
7
7
  require 'aspera/cli/extended_value'
8
+ require 'aspera/ascp/installation'
8
9
  require 'aspera/log'
9
10
  require 'json'
10
11
  # extended transfer spec parameter (only used in asession)
11
12
  # Change log level
12
- TS_LOG_LEVEL = 'EX_loglevel'
13
+ PARAM_LOG_LEVEL = 'loglevel'
13
14
  # by default go to /tmp/username.filelist
14
- TS_TMP_FILE_LIST_FOLDER = 'EX_file_list_folder'
15
-
15
+ PARAM_TMP_FILE_LIST_FOLDER = 'file_list_folder'
16
+ # place transfer spec in that
17
+ PARAM_SPEC = 'spec'
16
18
  SAMPLE_DEMO = '"remote_host":"demo.asperasoft.com","remote_user":"asperaweb","ssh_port":33001,"remote_password":"demoaspera"'
17
19
  SAMPLE_DEMO2 = '"direction":"receive","destination_root":"./test.dir"'
18
20
  def assert_usage(assertion, error_message)
@@ -21,25 +23,27 @@ def assert_usage(assertion, error_message)
21
23
  $stderr.puts('USAGE')
22
24
  $stderr.puts(' asession')
23
25
  $stderr.puts(' asession -h|--help')
24
- $stderr.puts(' asession <transfer spec extended value>')
26
+ $stderr.puts(' asession <session spec extended value>')
25
27
  $stderr.puts(' ')
26
28
  $stderr.puts(' If no argument is provided, default will be used: @json:@stdin')
27
29
  $stderr.puts(' -h, --help display this message')
28
- $stderr.puts(' <transfer spec extended value> a JSON value for transfer_spec, using the prefix: @json:')
30
+ $stderr.puts(' <session spec extended value> a dictionary value (Hash)')
29
31
  $stderr.puts(' The value can be either:')
30
32
  $stderr.puts(" the JSON description itself, e.g. @json:'{\"xx\":\"yy\",...}'")
31
33
  $stderr.puts(' @json:@stdin, if the JSON is provided from stdin')
32
34
  $stderr.puts(' @json:@file:<path>, if the JSON is provided from a file')
35
+ $stderr.puts(" Parameter #{PARAM_SPEC} is mandatory, it contains the transfer spec")
33
36
  $stderr.puts(' Asynchronous commands can be provided on STDIN, examples:')
34
37
  $stderr.puts(' {"type":"START","source":"/aspera-test-dir-tiny/200KB.2"}')
35
38
  $stderr.puts(' {"type":"START","source":"xx","destination":"yy"}')
36
39
  $stderr.puts(' {"type":"DONE"}')
37
- $stderr.puts(%Q(Note: debug information can be placed on STDERR, using the "#{TS_LOG_LEVEL}" parameter in transfer spec (debug=0)))
40
+ $stderr.puts(%Q(Note: debug information can be placed on STDERR, using the "#{PARAM_LOG_LEVEL}" parameter in session spec (debug=0)))
38
41
  $stderr.puts('EXAMPLES')
39
- $stderr.puts(%Q( asession @json:'{#{SAMPLE_DEMO},#{SAMPLE_DEMO2},"paths":[{"source":"/aspera-test-dir-tiny/200KB.1"}]}'))
40
- $stderr.puts(%q( echo '{"remote_host":...}'|asession @json:@stdin))
42
+ $stderr.puts(%Q( asession @json:'{"#{PARAM_SPEC}":{#{SAMPLE_DEMO},#{SAMPLE_DEMO2},"paths":[{"source":"/aspera-test-dir-tiny/200KB.1"}]}}'))
43
+ $stderr.puts(%Q( echo '{"#{PARAM_SPEC}":{"remote_host":...}}'|asession @json:@stdin))
41
44
  Process.exit(1)
42
45
  end
46
+ Aspera::Ascp::Installation.instance.sdk_folder = File.join(Dir.home, '.aspera', 'sdk')
43
47
 
44
48
  parameter_source_err_msg = ' (argument), did you specify: "@json:" ?'
45
49
  # by default assume JSON input on stdin if no argument
@@ -52,27 +56,31 @@ assert_usage(ARGV.length.eql?(1), 'exactly one argument is expected')
52
56
  assert_usage(!['-h', '--help'].include?(ARGV.first), nil)
53
57
  # parse transfer spec
54
58
  begin
55
- transfer_spec_arg = ARGV.pop
56
- transfer_spec = Aspera::Cli::ExtendedValue.instance.evaluate(transfer_spec_arg)
59
+ session_argument = ARGV.pop
60
+ session_spec = Aspera::Cli::ExtendedValue.instance.evaluate(session_argument)
57
61
  rescue
58
- assert_usage(false, "Cannot extract transfer spec from: #{transfer_spec_arg}")
62
+ assert_usage(false, "Cannot parse argument: #{session_argument}")
59
63
  end
60
64
  # ensure right type
61
- assert_usage(transfer_spec.is_a?(Hash), "the value must be a hash table#{parameter_source_err_msg}")
65
+ assert_usage(session_spec.is_a?(Hash), "The value must be a hash table#{parameter_source_err_msg}")
66
+ assert_usage(session_spec[PARAM_SPEC].is_a?(Hash), "the value must contain key #{PARAM_SPEC}")
62
67
  # additional debug capability
63
- if transfer_spec.key?(TS_LOG_LEVEL)
64
- Aspera::Log.instance.level = transfer_spec[TS_LOG_LEVEL]
65
- transfer_spec.delete(TS_LOG_LEVEL)
68
+ if session_spec.key?(PARAM_LOG_LEVEL)
69
+ Aspera::Log.instance.level = session_spec[PARAM_LOG_LEVEL]
66
70
  end
67
71
  # possibly override temp folder
68
- if transfer_spec.key?(TS_TMP_FILE_LIST_FOLDER)
69
- Aspera::Fasp::Parameters.file_list_folder = transfer_spec[TS_TMP_FILE_LIST_FOLDER]
70
- transfer_spec.delete(TS_TMP_FILE_LIST_FOLDER)
72
+ if session_spec.key?(PARAM_TMP_FILE_LIST_FOLDER)
73
+ Aspera::Transfer::Parameters.file_list_folder = session_spec[PARAM_TMP_FILE_LIST_FOLDER]
74
+ end
75
+ session_spec['agent'] = {} unless session_spec.key?('agent')
76
+ session_spec['agent']['quiet'] = true
77
+ session_spec['agent']['management_cb'] = ->(event) do
78
+ puts JSON.generate(Aspera::Ascp::Management.enhanced_event_format(event))
71
79
  end
72
80
  # get local agent (ascp), disable ascp output on stdout to not mix with JSON events
73
- client = Aspera::Fasp::AgentDirect.new({quiet: true})
81
+ client = Aspera::Agent::Direct.new(session_spec['agent'])
74
82
  # start transfer (asynchronous)
75
- job_id = client.start_transfer(transfer_spec)
83
+ job_id = client.start_transfer(session_spec[PARAM_SPEC])
76
84
  # async commands
77
85
  Thread.new do
78
86
  # we assume here a single session
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/fasp/agent_base'
3
+ require 'aspera/agent/base'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/log'
6
6
  require 'aspera/json_rpc'
@@ -8,12 +8,14 @@ require 'aspera/open_application'
8
8
  require 'securerandom'
9
9
 
10
10
  module Aspera
11
- module Fasp
12
- class AgentAlpha < Aspera::Fasp::AgentBase
11
+ module Agent
12
+ class Alpha < Base
13
13
  # try twice the main init url in sequence
14
- START_URIS = ['aspera://']
15
- # delay between each try to start connect
16
- SLEEP_SEC_BETWEEN_RETRY = 3
14
+ START_URIS = ['aspera://', 'aspera://', 'aspera://']
15
+ # delay between each try to start the app
16
+ SLEEP_SEC_BETWEEN_RETRY = 5
17
+ APP_IDENTIFIER = 'com.ibm.software.aspera.desktop'
18
+ APP_NAME = 'Aspera Desktop Alpha Client'
17
19
  private_constant :START_URIS, :SLEEP_SEC_BETWEEN_RETRY
18
20
  def initialize(options)
19
21
  @application_id = SecureRandom.uuid
@@ -21,35 +23,32 @@ module Aspera
21
23
  raise 'Using client requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
22
24
  method_index = 0
23
25
  begin
26
+ # curl 'http://127.0.0.1:33024/' -X POST -H 'content-type: application/json' --data-raw '{"jsonrpc":"2.0","params":[],"id":999999,"method":"rpc.discover"}'
27
+ # https://playground.open-rpc.org/?schemaUrl=http://127.0.0.1:33024
24
28
  @client_app_api = Aspera::JsonRpcClient.new(Aspera::Rest.new(base_url: aspera_client_api_url))
25
29
  client_info = @client_app_api.get_info
26
30
  Log.log.debug{Log.dump(:client_version, client_info)}
27
- # my_transfer_id = '0513fe85-65cf-465b-ad5f-18fd40d8c69f'
28
- # @client_app_api.get_all_transfers({app_id: @application_id})
29
- # @client_app_api.get_transfer(app_id: @application_id, transfer_id: my_transfer_id)
30
- # @client_app_api.start_transfer(app_id: @application_id,transfer_spec: {})
31
- # @client_app_api.remove_transfer
32
- # @client_app_api.stop_transfer
33
- # @client_app_api.modify_transfer
34
- # @client_app_api.show_directory({app_id: @application_id, transfer_id: my_transfer_id})
35
- # @client_app_api.get_files_list({app_id: @application_id, transfer_id: my_transfer_id})
36
31
  Log.log.info('Client was reached') if method_index > 0
37
- rescue StandardError => e # Errno::ECONNREFUSED
32
+ rescue Errno::ECONNREFUSED => e
38
33
  start_url = START_URIS[method_index]
39
34
  method_index += 1
40
- raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
41
- Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
35
+ raise StandardError, "Unable to start #{APP_NAME} #{method_index} times" if start_url.nil?
36
+ Log.log.warn{"#{APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
42
37
  if !OpenApplication.uri_graphical(start_url)
43
- OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
44
- raise StandardError, 'Connect is not installed'
38
+ OpenApplication.uri_graphical('https://www.ibm.com/aspera/connect/')
39
+ raise StandardError, "#{APP_NAME} is not installed"
45
40
  end
46
41
  sleep(SLEEP_SEC_BETWEEN_RETRY)
47
42
  retry
48
43
  end
49
44
  end
50
45
 
46
+ def sdk_log_file
47
+ File.join(Dir.home, 'Library', 'Logs', APP_IDENTIFIER, 'ibm-aspera-desktop.log')
48
+ end
49
+
51
50
  def aspera_client_api_url
52
- log_file = File.join(Dir.home, 'Library', 'Logs', 'IBM Aspera', 'ibm-aspera-desktop.log')
51
+ log_file = sdk_log_file
53
52
  url = nil
54
53
  File.open(log_file, 'r') do |file|
55
54
  file.each_line do |line|
@@ -59,12 +58,14 @@ module Aspera
59
58
  end
60
59
  end
61
60
  end
61
+ url = 'http://127.0.0.1:33024' if url.nil?
62
+ raise StandardError, "Unable to find the JSON-RPC server URL in #{log_file}" if url.nil?
62
63
  return url
63
64
  end
64
65
 
65
66
  def start_transfer(transfer_spec, token_regenerator: nil)
66
67
  @request_id = SecureRandom.uuid
67
- # if there is a token, we ask connect client to use well known ssh private keys
68
+ # if there is a token, we ask the client app to use well known ssh private keys
68
69
  # instead of asking password
69
70
  transfer_spec['authentication'] = 'token' if transfer_spec.key?('token')
70
71
  result = @client_app_api.start_transfer(app_id: @application_id, desktop_spec: {}, transfer_spec: transfer_spec)
@@ -96,13 +97,13 @@ module Aspera
96
97
  break
97
98
  when 'failed'
98
99
  notify_progress(type: :end, session_id: @xfer_id)
99
- raise Fasp::Error, transfer['error_desc']
100
+ raise Transfer::Error, transfer['error_desc']
100
101
  when 'cancelled'
101
102
  notify_progress(type: :end, session_id: @xfer_id)
102
- raise Fasp::Error, 'Transfer cancelled by user'
103
+ raise Transfer::Error, 'Transfer cancelled by user'
103
104
  else
104
105
  notify_progress(type: :end, session_id: @xfer_id)
105
- raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
106
+ raise Transfer::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
106
107
  end
107
108
  sleep(1)
108
109
  end
@@ -3,16 +3,17 @@
3
3
  require 'aspera/log'
4
4
  require 'aspera/assert'
5
5
  module Aspera
6
- module Fasp
6
+ module Agent
7
7
  # Base class for transfer agents
8
- class AgentBase
8
+ class Base
9
+ RUBY_EXT = '.rb'
9
10
  class << self
10
11
  # compute options from user provided and default options
11
12
  def options(default:, options:)
12
13
  result = options.symbolize_keys
13
14
  available = default.map{|k, v|"#{k}(#{v})"}.join(', ')
14
- result.each do |k, _v|
15
- assert_values(k, default.keys){"transfer agent parameter: #{k}"}
15
+ result.each_key do |k|
16
+ Aspera.assert_values(k, default.keys){"transfer agent parameter: #{k}"}
16
17
  # check it is the expected type: too limiting, as we can have an Integer or Float, or symbol and string
17
18
  # raise "Invalid value for transfer agent parameter: #{k}, expect #{default[k].class.name}" unless default[k].nil? || v.is_a?(default[k].class)
18
19
  end
@@ -23,18 +24,20 @@ module Aspera
23
24
  return result
24
25
  end
25
26
 
27
+ # discover available agents
26
28
  def agent_list
29
+ base_class = File.basename(__FILE__)
27
30
  Dir.entries(File.dirname(File.expand_path(__FILE__))).select do |file|
28
- file.start_with?('agent_') && !file.eql?('agent_base.rb')
29
- end.map{|file|file.sub(/^agent_/, '').sub(/\.rb$/, '').to_sym}
31
+ file.end_with?(RUBY_EXT) && !file.eql?(base_class)
32
+ end.map{|file|file[0..(-1 - RUBY_EXT.length)].to_sym}
30
33
  end
31
- end
34
+ end
32
35
  def wait_for_completion
33
36
  # list of: :success or "error message string"
34
37
  statuses = wait_for_transfers_completion
35
38
  @progress&.reset
36
- assert_type(statuses, Array)
37
- assert(statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?){"bad statuses content: #{statuses}"}
39
+ Aspera.assert_type(statuses, Array)
40
+ Aspera.assert(statuses.none?{|i|!i.eql?(:success) && !i.is_a?(StandardError)}){"bad statuses content: #{statuses}"}
38
41
  return statuses
39
42
  end
40
43
 
@@ -42,9 +45,9 @@ module Aspera
42
45
 
43
46
  def initialize(options)
44
47
  # method `shutdown` is optional
45
- assert(respond_to?(:start_transfer))
46
- assert(respond_to?(:wait_for_transfers_completion))
47
- assert_type(options, Hash){'transfer agent options'}
48
+ Aspera.assert(respond_to?(:start_transfer))
49
+ Aspera.assert(respond_to?(:wait_for_transfers_completion))
50
+ Aspera.assert_type(options, Hash){'transfer agent options'}
48
51
  Log.log.debug{Log.dump(:agent_options, options)}
49
52
  @progress = options[:progress]
50
53
  options.delete(:progress)
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/fasp/agent_base'
3
+ require 'aspera/agent/base'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/open_application'
6
6
  require 'securerandom'
7
7
 
8
8
  module Aspera
9
- module Fasp
10
- class AgentConnect < Aspera::Fasp::AgentBase
9
+ module Agent
10
+ class Connect < Base
11
11
  # try twice the main init url in sequence
12
12
  CONNECT_START_URIS = ['fasp://initialize', 'fasp://initialize', 'aspera-drive://initialize', 'https://test-connect.ibmaspera.com/']
13
13
  # delay between each try to start connect
14
- SLEEP_SEC_BETWEEN_RETRY = 3
14
+ SLEEP_SEC_BETWEEN_RETRY = 5
15
15
  private_constant :CONNECT_START_URIS, :SLEEP_SEC_BETWEEN_RETRY
16
16
  def initialize(options)
17
17
  super(options)
@@ -21,9 +21,11 @@ module Aspera
21
21
  raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
22
22
  method_index = 0
23
23
  begin
24
- connect_url = Products.connect_uri
24
+ connect_url = Ascp::Products.connect_uri
25
25
  Log.log.debug{"found: #{connect_url}"}
26
- @connect_api = Rest.new({base_url: "#{connect_url}/v5/connect", headers: {'Origin' => Rest.user_agent}}) # could use v6 also now
26
+ @connect_api = Rest.new(
27
+ base_url: "#{connect_url}/v5/connect", # could use v6 also now
28
+ headers: {'Origin' => Rest.user_agent})
27
29
  connect_info = @connect_api.read('info/version')[:data]
28
30
  Log.log.info('Connect was reached') if method_index > 0
29
31
  Log.log.debug{Log.dump(:connect_version, connect_info)}
@@ -33,7 +35,7 @@ module Aspera
33
35
  raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
34
36
  Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
35
37
  if !OpenApplication.uri_graphical(start_url)
36
- OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
38
+ OpenApplication.uri_graphical('https://www.ibm.com/aspera/connect/')
37
39
  raise StandardError, 'Connect is not installed'
38
40
  end
39
41
  sleep(SLEEP_SEC_BETWEEN_RETRY)
@@ -67,7 +69,7 @@ module Aspera
67
69
  }]}
68
70
  # asynchronous anyway
69
71
  res = @connect_api.create('transfers/start', connect_transfer_args)[:data]
70
- @xfer_id = res['transfer_specs'].first['transfer_spec']['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_id']
72
+ @xfer_id = res['transfer_specs'].first['transfer_spec']['tags'][Transfer::Spec::TAG_RESERVED]['xfer_id']
71
73
  end
72
74
 
73
75
  def wait_for_transfers_completion
@@ -105,13 +107,13 @@ module Aspera
105
107
  break
106
108
  when 'failed'
107
109
  notify_progress(type: :end, session_id: session_id)
108
- raise Fasp::Error, transfer['error_desc']
110
+ raise Transfer::Error, transfer['error_desc']
109
111
  when 'cancelled'
110
112
  notify_progress(type: :end, session_id: session_id)
111
- raise Fasp::Error, 'Transfer cancelled by user'
113
+ raise Transfer::Error, 'Transfer cancelled by user'
112
114
  else
113
115
  notify_progress(type: :end, session_id: session_id)
114
- raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
116
+ raise Transfer::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
115
117
  end
116
118
  end
117
119
  sleep(1)
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/fasp/agent_base'
4
- require 'aspera/fasp/error'
5
- require 'aspera/fasp/parameters'
6
- require 'aspera/fasp/installation'
7
- require 'aspera/fasp/resume_policy'
8
- require 'aspera/fasp/transfer_spec'
9
- require 'aspera/fasp/management'
3
+ require 'aspera/agent/base'
4
+ require 'aspera/ascp/installation'
5
+ require 'aspera/ascp/management'
6
+ require 'aspera/transfer/parameters'
7
+ require 'aspera/transfer/error'
8
+ require 'aspera/transfer/spec'
9
+ require 'aspera/resumer'
10
10
  require 'aspera/log'
11
11
  require 'aspera/assert'
12
12
  require 'socket'
@@ -15,27 +15,28 @@ require 'shellwords'
15
15
  require 'English'
16
16
 
17
17
  module Aspera
18
- module Fasp
18
+ module Agent
19
19
  # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
20
- class AgentDirect < Aspera::Fasp::AgentBase
20
+ class Direct < Base
21
21
  # options for initialize (same as values in option transfer_info)
22
22
  DEFAULT_OPTIONS = {
23
+ wss: true, # true: if both SSH and wss in ts: prefer wss
24
+ ascp_args: [],
23
25
  spawn_timeout_sec: 2,
24
26
  spawn_delay_sec: 2, # optional delay to start between sessions
25
- wss: true, # true: if both SSH and wss in ts: prefer wss
26
27
  multi_incr_udp: true,
28
+ trusted_certs: [], # list of files with trusted certificates (stores)
27
29
  resume: {},
28
- ascp_args: [],
29
- check_ignore: nil, # callback with host,port
30
30
  quiet: true, # by default no native ascp progress bar
31
- trusted_certs: [] # list of files with trusted certificates (stores)
31
+ check_ignore_cb: nil, # callback with host,port
32
+ management_cb: nil # callback for management events
32
33
  }.freeze
33
- LISTEN_ADDRESS = '127.0.0.1'
34
- LISTEN_AVAILABLE_PORT = 0 # 0 means any available port
34
+ LISTEN_LOCAL_ADDRESS = '127.0.0.1'
35
+ ANY_AVAILABLE_PORT = 0 # 0 means any available port
35
36
  # spellchecker: enable
36
- private_constant :DEFAULT_OPTIONS, :LISTEN_ADDRESS
37
+ private_constant :DEFAULT_OPTIONS, :LISTEN_LOCAL_ADDRESS, :ANY_AVAILABLE_PORT
37
38
 
38
- # method of Aspera::Fasp::AgentBase
39
+ # method of Base
39
40
  # start ascp transfer(s) (non blocking), single or multi-session
40
41
  # session information added to @sessions
41
42
  # @param transfer_spec [Hash] aspera transfer specification
@@ -44,15 +45,15 @@ module Aspera
44
45
  # clone transfer spec because we modify it (first level keys)
45
46
  transfer_spec = transfer_spec.clone
46
47
  # if there are aspera tags
47
- if transfer_spec['tags'].is_a?(Hash) && transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED].is_a?(Hash)
48
+ if transfer_spec.dig('tags', Transfer::Spec::TAG_RESERVED).is_a?(Hash)
48
49
  # TODO: what is this for ? only on local ascp ?
49
50
  # NOTE: important: transfer id must be unique: generate random id
50
51
  # using a non unique id results in discard of tags in AoC, and a package is never finalized
51
52
  # all sessions in a multi-session transfer must have the same xfer_id (see admin manual)
52
- transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_id'] ||= SecureRandom.uuid
53
+ transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['xfer_id'] ||= SecureRandom.uuid
53
54
  Log.log.debug{"xfer id=#{transfer_spec['xfer_id']}"}
54
55
  # TODO: useful ? node only ? seems to be a timeout for retry in node
55
- transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_retry'] ||= 3600
56
+ transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['xfer_retry'] ||= 3600
56
57
  end
57
58
  Log.log.debug{Log.dump('ts', transfer_spec)}
58
59
  # Compute this before using transfer spec because it potentially modifies the transfer spec
@@ -72,7 +73,7 @@ module Aspera
72
73
  multi_session_info = nil
73
74
  elsif @options[:multi_incr_udp] # multi_session_info[:count] > 0
74
75
  # if option not true: keep default udp port for all sessions
75
- multi_session_info[:udp_base] = transfer_spec.key?('fasp_port') ? transfer_spec['fasp_port'] : TransferSpec::UDP_PORT
76
+ multi_session_info[:udp_base] = transfer_spec.key?('fasp_port') ? transfer_spec['fasp_port'] : Transfer::Spec::UDP_PORT
76
77
  # delete from original transfer spec, as we will increment values
77
78
  transfer_spec.delete('fasp_port')
78
79
  # override if specified, else use default value
@@ -89,7 +90,7 @@ module Aspera
89
90
  io: nil, # management port server socket
90
91
  token_regenerator: token_regenerator, # regenerate bearer token with oauth
91
92
  # env vars and args to ascp (from transfer spec)
92
- env_args: Parameters.new(transfer_spec, @options).ascp_args
93
+ env_args: Transfer::Parameters.new(transfer_spec, @options).ascp_args
93
94
  }
94
95
 
95
96
  if multi_session_info.nil?
@@ -141,12 +142,6 @@ module Aspera
141
142
  Log.log.debug('fasp local shutdown')
142
143
  end
143
144
 
144
- # cspell:disable
145
- # begin 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size
146
- # progress 'Type' => 'STATS', 'Bytescont' => size
147
- # end 'Type' => 'DONE'
148
- # cspell:enable
149
-
150
145
  # @param event management port event
151
146
  def process_progress(event)
152
147
  session_id = event['SessionId']
@@ -194,25 +189,25 @@ module Aspera
194
189
  # @param session this session information
195
190
  # could be private method
196
191
  def start_transfer_with_args_env(env_args, session)
197
- assert_type(env_args, Hash)
198
- assert_type(session, Hash)
192
+ Aspera.assert_type(env_args, Hash)
193
+ Aspera.assert_type(session, Hash)
199
194
  Log.log.debug{"env_args=#{env_args.inspect}"}
200
195
  notify_progress(session_id: nil, type: :pre_start, info: 'starting')
201
196
  begin
202
- # we use Socket directly, instead of TCPServer, s it gives access to lower level options
203
- mgt_server_socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
204
- # open an available (0) local TCP port as ascp management
205
- # Socket.pack_sockaddr_in(LISTEN_AVAILABLE_PORT, LISTEN_ADDRESS)
206
- mgt_server_socket.bind(Addrinfo.tcp(LISTEN_ADDRESS, LISTEN_AVAILABLE_PORT))
207
- # clone arguments and add mgt port
197
+ ascp_pid = nil
198
+ # we use Socket directly, instead of TCPServer, as it gives access to lower level options
199
+ socket_class = RUBY_ENGINE.eql?('jruby') ? ServerSocket : Socket
200
+ mgt_server_socket = socket_class.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
201
+ # open any available (0) local TCP port for use as ascp management port
202
+ mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS, ANY_AVAILABLE_PORT))
203
+ # build arguments and add mgt port
208
204
  ascp_arguments = ['-M', mgt_server_socket.local_address.ip_port.to_s].concat(env_args[:args])
209
- # mgt_server_socket.addr[1]
210
205
  # get location of ascp executable
211
- ascp_path = Fasp::Installation.instance.path(env_args[:ascp_version])
206
+ ascp_path = Ascp::Installation.instance.path(env_args[:ascp_version])
212
207
  # display ascp command line
213
208
  Log.log.debug do
214
209
  [
215
- 'execute:',
210
+ 'execute:'.red,
216
211
  env_args[:env].map{|k, v| "#{k}=#{Shellwords.shellescape(v)}"},
217
212
  Shellwords.shellescape(ascp_path),
218
213
  ascp_arguments.map{|a|Shellwords.shellescape(a)}
@@ -220,14 +215,14 @@ module Aspera
220
215
  end
221
216
  # start ascp in separate process
222
217
  ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments, close_others: true)
223
- Log.log.debug{"spawned pid #{ascp_pid}"}
218
+ Log.log.debug{"spawned ascp pid #{ascp_pid}"}
224
219
  notify_progress(session_id: nil, type: :pre_start, info: 'waiting for ascp')
225
220
  mgt_server_socket.listen(1)
226
221
  # TODO: timeout does not work when Process.spawn is used... until process exits, then it works
227
222
  Log.log.debug{"before select, timeout: #{@options[:spawn_timeout_sec]}"}
228
223
  readable, _, _ = IO.select([mgt_server_socket], nil, nil, @options[:spawn_timeout_sec])
229
224
  Log.log.debug('after select, before accept')
230
- assert(readable, exception_class: Fasp::Error){'timeout waiting mgt port connect (select not readable)'}
225
+ Aspera.assert(readable, exception_class: Transfer::Error){'timeout waiting mgt port connect (select not readable)'}
231
226
  # There is a connection to accept
232
227
  client_socket, _client_addrinfo = mgt_server_socket.accept
233
228
  Log.log.debug('after accept')
@@ -237,14 +232,14 @@ module Aspera
237
232
  # TODO: use same value as Encoding.default_external
238
233
  ascp_mgt_io.set_encoding(Encoding::UTF_8)
239
234
  session[:io] = ascp_mgt_io
240
- processor = Management.new
235
+ processor = Ascp::Management.new
241
236
  # read management port, until socket is closed (gets returns nil)
242
237
  while (line = ascp_mgt_io.gets)
243
238
  event = processor.process_line(line.chomp)
244
239
  next unless event
245
240
  # event is ready
246
241
  Log.log.trace1{Log.dump(:management_port, event)}
247
- # Log.log.trace1{"event: #{JSON.generate(Management.enhanced_event_format(event))}"}
242
+ @options[:management_cb]&.call(event)
248
243
  process_progress(event)
249
244
  Log.log.error((event['Description']).to_s) if event['Type'].eql?('FILEERROR') # cspell:disable-line
250
245
  end
@@ -261,7 +256,7 @@ module Aspera
261
256
  Log.log.warn('Regenerating token for transfer')
262
257
  env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
263
258
  end
264
- raise Fasp::Error.new(last_event['Description'], last_event['Code'].to_i)
259
+ raise Transfer::Error.new(last_event['Description'], last_event['Code'].to_i)
265
260
  when 'DONE'
266
261
  nil
267
262
  else
@@ -269,23 +264,24 @@ module Aspera
269
264
  end # case
270
265
  end
271
266
  rescue SystemCallError => e
272
- # Process.spawn
273
- raise Fasp::Error, e.message
267
+ # Process.spawn failed, or socket error
268
+ raise Transfer::Error, e.message
274
269
  rescue Interrupt
275
- raise Fasp::Error, 'transfer interrupted by user'
270
+ raise Transfer::Error, 'transfer interrupted by user'
276
271
  ensure
277
272
  mgt_server_socket.close
278
- # if ascp was successfully started
273
+ # if ascp was successfully started, check its status
279
274
  unless ascp_pid.nil?
280
275
  # "wait" for process to avoid zombie
281
276
  Process.wait(ascp_pid)
282
277
  status = $CHILD_STATUS
283
278
  ascp_pid = nil
284
279
  session.delete(:io)
285
- if !status.success?
286
- message = "ascp failed: #{status}"
280
+ # status is nil if an exception occurred before starting ascp
281
+ if !status&.success?
282
+ message = status.nil? ? 'ascp not started' : "ascp failed (#{status})"
287
283
  # raise error only if there was not already an exception (ERROR_INFO)
288
- raise Fasp::Error, message unless $ERROR_INFO
284
+ raise Transfer::Error, message unless $ERROR_INFO
289
285
  # else display this message also, as main exception is already here
290
286
  Log.log.error(message)
291
287
  end
@@ -332,9 +328,9 @@ module Aspera
332
328
  def initialize(options={})
333
329
  super(options)
334
330
  # set default options and override if specified
335
- @options = AgentBase.options(default: DEFAULT_OPTIONS, options: options)
331
+ @options = Base.options(default: DEFAULT_OPTIONS, options: options)
336
332
  Log.log.debug{Log.dump(:agent_options, @options)}
337
- @resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
333
+ @resume_policy = Resumer.new(@options[:resume].symbolize_keys)
338
334
  # all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
339
335
  @sessions = []
340
336
  # mutex protects global data accessed by threads