aspera-cli 4.16.0 → 4.17.0

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