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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +50 -19
- data/CONTRIBUTING.md +3 -1
- data/README.md +965 -793
- data/bin/asession +29 -21
- data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
- data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
- data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
- data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
- data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
- data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
- data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
- data/lib/aspera/api/aoc.rb +586 -0
- data/lib/aspera/api/ats.rb +46 -0
- data/lib/aspera/api/cos_node.rb +95 -0
- data/lib/aspera/api/node.rb +344 -0
- data/lib/aspera/ascmd.rb +46 -10
- data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
- data/lib/aspera/{fasp → ascp}/management.rb +3 -8
- data/lib/aspera/{fasp → ascp}/products.rb +1 -1
- data/lib/aspera/assert.rb +30 -30
- data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
- data/lib/aspera/cli/extended_value.rb +1 -1
- data/lib/aspera/cli/formatter.rb +13 -13
- data/lib/aspera/cli/hints.rb +5 -5
- data/lib/aspera/cli/main.rb +35 -28
- data/lib/aspera/cli/manager.rb +25 -24
- data/lib/aspera/cli/plugin.rb +22 -15
- data/lib/aspera/cli/plugin_factory.rb +61 -0
- data/lib/aspera/cli/plugins/alee.rb +7 -7
- data/lib/aspera/cli/plugins/aoc.rb +83 -77
- data/lib/aspera/cli/plugins/ats.rb +32 -33
- data/lib/aspera/cli/plugins/bss.rb +3 -4
- data/lib/aspera/cli/plugins/config.rb +169 -186
- data/lib/aspera/cli/plugins/console.rb +8 -6
- data/lib/aspera/cli/plugins/cos.rb +19 -18
- data/lib/aspera/cli/plugins/faspex.rb +61 -54
- data/lib/aspera/cli/plugins/faspex5.rb +150 -103
- data/lib/aspera/cli/plugins/node.rb +68 -73
- data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
- data/lib/aspera/cli/plugins/preview.rb +31 -31
- data/lib/aspera/cli/plugins/server.rb +31 -33
- data/lib/aspera/cli/plugins/shares.rb +13 -11
- data/lib/aspera/cli/sync_actions.rb +8 -8
- data/lib/aspera/cli/transfer_agent.rb +32 -19
- data/lib/aspera/cli/transfer_progress.rb +1 -1
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +5 -0
- data/lib/aspera/command_line_builder.rb +14 -14
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +2 -3
- data/lib/aspera/faspex_gw.rb +5 -6
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/id_generator.rb +2 -2
- data/lib/aspera/json_rpc.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +6 -6
- data/lib/aspera/keychain/macos_security.rb +27 -22
- data/lib/aspera/log.rb +2 -2
- data/lib/aspera/nagios.rb +3 -3
- data/lib/aspera/node_simulator.rb +5 -6
- data/lib/aspera/oauth/base.rb +143 -0
- data/lib/aspera/oauth/factory.rb +124 -0
- data/lib/aspera/oauth/generic.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +51 -0
- data/lib/aspera/oauth/url_json.rb +31 -0
- data/lib/aspera/oauth/web.rb +50 -0
- data/lib/aspera/oauth.rb +5 -331
- data/lib/aspera/open_application.rb +7 -7
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/generator.rb +5 -5
- data/lib/aspera/preview/terminal.rb +3 -2
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +4 -4
- data/lib/aspera/rest.rb +175 -144
- data/lib/aspera/rest_errors_aspera.rb +3 -3
- data/lib/aspera/resumer.rb +77 -0
- data/lib/aspera/ssh.rb +6 -1
- data/lib/aspera/{fasp → transfer}/error.rb +3 -3
- data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
- data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
- data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
- data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
- data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
- data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
- data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
- data/lib/aspera/web_server_simple.rb +11 -3
- data.tar.gz.sig +0 -0
- metadata +36 -63
- metadata.gz.sig +0 -0
- data/lib/aspera/aoc.rb +0 -601
- data/lib/aspera/ats_api.rb +0 -47
- data/lib/aspera/cos_node.rb +0 -94
- data/lib/aspera/fasp/resume_policy.rb +0 -79
- 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/
|
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
|
-
|
13
|
+
PARAM_LOG_LEVEL = 'loglevel'
|
13
14
|
# by default go to /tmp/username.filelist
|
14
|
-
|
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 <
|
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(' <
|
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 "#{
|
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(%
|
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
|
-
|
56
|
-
|
59
|
+
session_argument = ARGV.pop
|
60
|
+
session_spec = Aspera::Cli::ExtendedValue.instance.evaluate(session_argument)
|
57
61
|
rescue
|
58
|
-
assert_usage(false, "Cannot
|
62
|
+
assert_usage(false, "Cannot parse argument: #{session_argument}")
|
59
63
|
end
|
60
64
|
# ensure right type
|
61
|
-
assert_usage(
|
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
|
64
|
-
Aspera::Log.instance.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
|
69
|
-
Aspera::
|
70
|
-
|
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::
|
81
|
+
client = Aspera::Agent::Direct.new(session_spec['agent'])
|
74
82
|
# start transfer (asynchronous)
|
75
|
-
job_id = client.start_transfer(
|
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/
|
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
|
12
|
-
class
|
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
|
16
|
-
SLEEP_SEC_BETWEEN_RETRY =
|
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
|
32
|
+
rescue Errno::ECONNREFUSED => e
|
38
33
|
start_url = START_URIS[method_index]
|
39
34
|
method_index += 1
|
40
|
-
raise StandardError, "Unable to start
|
41
|
-
Log.log.warn{"
|
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://
|
44
|
-
raise StandardError,
|
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 =
|
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
|
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
|
100
|
+
raise Transfer::Error, transfer['error_desc']
|
100
101
|
when 'cancelled'
|
101
102
|
notify_progress(type: :end, session_id: @xfer_id)
|
102
|
-
raise
|
103
|
+
raise Transfer::Error, 'Transfer cancelled by user'
|
103
104
|
else
|
104
105
|
notify_progress(type: :end, session_id: @xfer_id)
|
105
|
-
raise
|
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
|
6
|
+
module Agent
|
7
7
|
# Base class for transfer agents
|
8
|
-
class
|
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.
|
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.
|
29
|
-
end.map{|file|file
|
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
|
-
|
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.
|
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/
|
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
|
10
|
-
class
|
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 =
|
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(
|
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://
|
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'][
|
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
|
110
|
+
raise Transfer::Error, transfer['error_desc']
|
109
111
|
when 'cancelled'
|
110
112
|
notify_progress(type: :end, session_id: session_id)
|
111
|
-
raise
|
113
|
+
raise Transfer::Error, 'Transfer cancelled by user'
|
112
114
|
else
|
113
115
|
notify_progress(type: :end, session_id: session_id)
|
114
|
-
raise
|
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/
|
4
|
-
require 'aspera/
|
5
|
-
require 'aspera/
|
6
|
-
require 'aspera/
|
7
|
-
require 'aspera/
|
8
|
-
require 'aspera/
|
9
|
-
require 'aspera/
|
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
|
18
|
+
module Agent
|
19
19
|
# executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
|
20
|
-
class
|
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
|
-
|
31
|
+
check_ignore_cb: nil, # callback with host,port
|
32
|
+
management_cb: nil # callback for management events
|
32
33
|
}.freeze
|
33
|
-
|
34
|
-
|
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, :
|
37
|
+
private_constant :DEFAULT_OPTIONS, :LISTEN_LOCAL_ADDRESS, :ANY_AVAILABLE_PORT
|
37
38
|
|
38
|
-
# method of
|
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
|
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'][
|
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'][
|
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'] :
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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 =
|
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:
|
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
|
-
|
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
|
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
|
267
|
+
# Process.spawn failed, or socket error
|
268
|
+
raise Transfer::Error, e.message
|
274
269
|
rescue Interrupt
|
275
|
-
raise
|
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
|
286
|
-
|
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
|
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 =
|
331
|
+
@options = Base.options(default: DEFAULT_OPTIONS, options: options)
|
336
332
|
Log.log.debug{Log.dump(:agent_options, @options)}
|
337
|
-
@resume_policy =
|
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
|