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.
- 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
|