aspera-cli 4.5.0 → 4.8.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 +1 -0
- data/README.md +1894 -1574
- data/bin/ascli +21 -1
- data/bin/asession +38 -34
- data/docs/test_env.conf +14 -3
- data/examples/aoc.rb +17 -15
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +42 -35
- data/examples/proxy.pac +1 -1
- data/examples/transfer.rb +38 -37
- data/lib/aspera/aoc.rb +245 -205
- data/lib/aspera/ascmd.rb +111 -90
- data/lib/aspera/ats_api.rb +16 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +19 -18
- data/lib/aspera/cli/extended_value.rb +50 -39
- data/lib/aspera/cli/formater.rb +161 -135
- data/lib/aspera/cli/info.rb +18 -0
- data/lib/aspera/cli/listener/line_dump.rb +4 -2
- data/lib/aspera/cli/listener/logger.rb +3 -1
- data/lib/aspera/cli/listener/progress.rb +20 -21
- data/lib/aspera/cli/listener/progress_multi.rb +29 -31
- data/lib/aspera/cli/main.rb +194 -183
- data/lib/aspera/cli/manager.rb +213 -206
- data/lib/aspera/cli/plugin.rb +71 -49
- data/lib/aspera/cli/plugins/alee.rb +8 -7
- data/lib/aspera/cli/plugins/aoc.rb +675 -558
- data/lib/aspera/cli/plugins/ats.rb +116 -109
- data/lib/aspera/cli/plugins/bss.rb +35 -34
- data/lib/aspera/cli/plugins/config.rb +722 -542
- data/lib/aspera/cli/plugins/console.rb +28 -22
- data/lib/aspera/cli/plugins/cos.rb +28 -37
- data/lib/aspera/cli/plugins/faspex.rb +281 -227
- data/lib/aspera/cli/plugins/faspex5.rb +129 -84
- data/lib/aspera/cli/plugins/node.rb +426 -232
- data/lib/aspera/cli/plugins/orchestrator.rb +106 -98
- data/lib/aspera/cli/plugins/preview.rb +196 -191
- data/lib/aspera/cli/plugins/server.rb +131 -126
- data/lib/aspera/cli/plugins/shares.rb +49 -36
- data/lib/aspera/cli/plugins/sync.rb +27 -28
- data/lib/aspera/cli/transfer_agent.rb +84 -79
- data/lib/aspera/cli/version.rb +3 -1
- data/lib/aspera/colors.rb +37 -28
- data/lib/aspera/command_line_builder.rb +84 -63
- data/lib/aspera/cos_node.rb +68 -34
- data/lib/aspera/data_repository.rb +4 -2
- data/lib/aspera/environment.rb +61 -46
- data/lib/aspera/fasp/agent_base.rb +36 -31
- data/lib/aspera/fasp/agent_connect.rb +44 -37
- data/lib/aspera/fasp/agent_direct.rb +101 -104
- data/lib/aspera/fasp/agent_httpgw.rb +91 -90
- data/lib/aspera/fasp/agent_node.rb +36 -33
- data/lib/aspera/fasp/agent_trsdk.rb +28 -31
- data/lib/aspera/fasp/error.rb +3 -1
- data/lib/aspera/fasp/error_info.rb +81 -54
- data/lib/aspera/fasp/installation.rb +171 -151
- data/lib/aspera/fasp/listener.rb +2 -0
- data/lib/aspera/fasp/parameters.rb +105 -111
- data/lib/aspera/fasp/parameters.yaml +305 -249
- data/lib/aspera/fasp/resume_policy.rb +20 -20
- data/lib/aspera/fasp/transfer_spec.rb +27 -0
- data/lib/aspera/fasp/uri.rb +31 -29
- data/lib/aspera/faspex_gw.rb +95 -118
- data/lib/aspera/hash_ext.rb +12 -13
- data/lib/aspera/id_generator.rb +11 -9
- data/lib/aspera/keychain/encrypted_hash.rb +73 -57
- data/lib/aspera/keychain/macos_security.rb +27 -29
- data/lib/aspera/log.rb +40 -39
- data/lib/aspera/nagios.rb +24 -22
- data/lib/aspera/node.rb +38 -30
- data/lib/aspera/oauth.rb +217 -248
- data/lib/aspera/open_application.rb +9 -7
- data/lib/aspera/persistency_action_once.rb +15 -14
- data/lib/aspera/persistency_folder.rb +15 -18
- data/lib/aspera/preview/file_types.rb +266 -270
- data/lib/aspera/preview/generator.rb +94 -92
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +20 -17
- data/lib/aspera/preview/utils.rb +99 -102
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
- data/lib/aspera/proxy_auto_config.rb +114 -21
- data/lib/aspera/rest.rb +144 -142
- data/lib/aspera/rest_call_error.rb +3 -2
- data/lib/aspera/rest_error_analyzer.rb +31 -31
- data/lib/aspera/rest_errors_aspera.rb +18 -16
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +20 -16
- data/lib/aspera/sync.rb +57 -54
- data/lib/aspera/temp_file_manager.rb +20 -14
- data/lib/aspera/timer_limiter.rb +10 -8
- data/lib/aspera/uri_reader.rb +14 -15
- data/lib/aspera/web_auth.rb +85 -80
- data.tar.gz.sig +0 -0
- metadata +169 -40
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
- data/docs/Makefile +0 -63
- data/docs/README.erb.md +0 -4221
- data/docs/README.md +0 -13
- data/docs/diagrams.txt +0 -49
- data/docs/doc_tools.rb +0 -58
- data/lib/aspera/cli/plugins/shares2.rb +0 -114
- data/lib/aspera/fasp/default.rb +0 -17
data/lib/aspera/cos_node.rb
CHANGED
@@ -1,56 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/log'
|
2
4
|
require 'aspera/rest'
|
3
5
|
require 'xmlsimple'
|
4
6
|
|
5
7
|
module Aspera
|
6
8
|
class CosNode < Rest
|
9
|
+
class << self
|
10
|
+
def parameters_from_svc_creds(service_credentials,bucket_region)
|
11
|
+
# check necessary contents
|
12
|
+
raise 'service_credentials must be a Hash' unless service_credentials.is_a?(Hash)
|
13
|
+
%w[apikey resource_instance_id endpoints].each do |field|
|
14
|
+
raise "service_credentials must have a field: #{field}" unless service_credentials.has_key?(field)
|
15
|
+
end
|
16
|
+
Aspera::Log.dump('service_credentials',service_credentials)
|
17
|
+
# read endpoints from service provided in service credentials
|
18
|
+
endpoints = Aspera::Rest.new({base_url: service_credentials['endpoints']}).read('')[:data]
|
19
|
+
Aspera::Log.dump('endpoints',endpoints)
|
20
|
+
storage_endpoint = endpoints.dig('service-endpoints','regional',bucket_region,'public',bucket_region)
|
21
|
+
raise "no such region: #{bucket_region}" if storage_endpoint.nil?
|
22
|
+
return {
|
23
|
+
instance_id: service_credentials['resource_instance_id'],
|
24
|
+
service_api_key: service_credentials['apikey'],
|
25
|
+
storage_endpoint: "https://#{storage_endpoint}"
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
IBM_CLOUD_TOKEN_URL = 'https://iam.cloud.ibm.com/identity'
|
30
|
+
TOKEN_FIELD = 'delegated_refresh_token'
|
7
31
|
attr_reader :add_ts
|
8
|
-
IBM_CLOUD_TOKEN_URL='https://iam.cloud.ibm.com/identity'
|
9
|
-
TOKEN_FIELD='delegated_refresh_token'
|
10
32
|
def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url=IBM_CLOUD_TOKEN_URL)
|
11
|
-
@auth_url=auth_url
|
12
|
-
@api_key=api_key
|
13
|
-
s3_api=Aspera::Rest.new({
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
33
|
+
@auth_url = auth_url
|
34
|
+
@api_key = api_key
|
35
|
+
s3_api = Aspera::Rest.new({
|
36
|
+
base_url: storage_endpoint,
|
37
|
+
not_auth_codes: %w[401 403], # error codes when not authorized
|
38
|
+
headers: {'ibm-service-instance-id' => instance_id},
|
39
|
+
auth: {
|
40
|
+
type: :oauth2,
|
41
|
+
base_url: @auth_url,
|
42
|
+
crtype: :generic,
|
43
|
+
generic: {
|
44
|
+
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
45
|
+
response_type: 'cloud_iam',
|
46
|
+
apikey: @api_key
|
47
|
+
}}})
|
23
48
|
# read FASP connection information for bucket
|
24
|
-
xml_result_text=s3_api.call(
|
25
|
-
|
49
|
+
xml_result_text = s3_api.call(
|
50
|
+
operation: 'GET',
|
51
|
+
subpath: bucket_name,
|
52
|
+
headers: {'Accept' => 'application/xml'},
|
53
|
+
url_params: {'faspConnectionInfo' => nil}
|
54
|
+
)[:http].body
|
55
|
+
ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
|
26
56
|
Aspera::Log.dump('ats_info',ats_info)
|
27
57
|
super({
|
28
|
-
:
|
29
|
-
:
|
30
|
-
|
31
|
-
|
32
|
-
|
58
|
+
base_url: ats_info['ATSEndpoint'],
|
59
|
+
auth: {
|
60
|
+
type: :basic,
|
61
|
+
username: ats_info['AccessKey']['Id'],
|
62
|
+
password: ats_info['AccessKey']['Secret']}})
|
33
63
|
# prepare transfer spec addition
|
34
|
-
@add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>{
|
64
|
+
@add_ts = {'tags' => {'aspera' => {'node' => {'storage_credentials' => {
|
35
65
|
'type' => 'token',
|
36
|
-
'token' => {TOKEN_FIELD=>nil}
|
37
|
-
|
66
|
+
'token' => {TOKEN_FIELD => nil}
|
67
|
+
}}}}}
|
38
68
|
generate_token
|
39
69
|
end
|
40
70
|
|
41
71
|
# potentially call this if delegated token is expired
|
42
72
|
def generate_token
|
43
73
|
# OAuth API to get delegated token
|
44
|
-
delegated_oauth=Oauth.new({
|
45
|
-
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
74
|
+
delegated_oauth = Oauth.new({
|
75
|
+
type: :oauth2,
|
76
|
+
base_url: @auth_url,
|
77
|
+
token_field: TOKEN_FIELD,
|
78
|
+
crtype: :generic,
|
79
|
+
generic: {
|
80
|
+
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
81
|
+
response_type: 'delegated_refresh_token',
|
82
|
+
apikey: @api_key,
|
83
|
+
receiver_client_ids: 'aspera_ats'
|
84
|
+
}})
|
85
|
+
# get delegated token to be placed in rest call header and in transfer tags
|
86
|
+
@add_ts['tags']['aspera']['node']['storage_credentials']['token'][TOKEN_FIELD] = delegated_oauth.get_authorization.gsub(/^Bearer /,'')
|
87
|
+
@params[:headers] = {'X-Aspera-Storage-Credentials' => JSON.generate(@add_ts['tags']['aspera']['node']['storage_credentials'])}
|
54
88
|
end
|
55
89
|
end
|
56
90
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/log'
|
2
4
|
require 'singleton'
|
3
5
|
|
@@ -6,8 +8,8 @@ module Aspera
|
|
6
8
|
class DataRepository
|
7
9
|
include Singleton
|
8
10
|
# get binary value from data repository
|
9
|
-
def
|
10
|
-
File.read(File.join(
|
11
|
+
def data(id)
|
12
|
+
File.read(File.join(__dir__,'data',id.to_s),mode: 'rb')
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/aspera/environment.rb
CHANGED
@@ -1,65 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/log'
|
2
4
|
require 'rbconfig'
|
3
5
|
|
4
6
|
module Aspera
|
5
|
-
# detect OS, architecture, and
|
7
|
+
# detect OS, architecture, and specific stuff
|
6
8
|
class Environment
|
7
9
|
OS_WINDOWS = :windows
|
8
10
|
OS_X = :osx
|
9
11
|
OS_LINUX = :linux
|
10
12
|
OS_AIX = :aix
|
11
|
-
OS_LIST=[OS_WINDOWS,OS_X,OS_LINUX,OS_AIX]
|
13
|
+
OS_LIST = [OS_WINDOWS,OS_X,OS_LINUX,OS_AIX].freeze
|
14
|
+
CPU_X86_64 = :x86_64
|
15
|
+
CPU_PPC64 = :ppc64
|
16
|
+
CPU_PPC64LE = :ppc64le
|
17
|
+
CPU_S390 = :s390
|
18
|
+
CPU_LIST = [CPU_X86_64,CPU_PPC64,CPU_PPC64LE,CPU_S390].freeze
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
return OS_WINDOWS
|
17
|
-
when /darwin/,/mac os/
|
18
|
-
return OS_X
|
19
|
-
when /linux/
|
20
|
-
return OS_LINUX
|
21
|
-
when /aix/
|
22
|
-
return OS_AIX
|
23
|
-
else
|
24
|
-
raise "Unknown OS: #{RbConfig::CONFIG['host_os']}"
|
20
|
+
class << self
|
21
|
+
def ruby_version
|
22
|
+
return RbConfig::CONFIG['RUBY_PROGRAM_VERSION']
|
25
23
|
end
|
26
|
-
end
|
27
|
-
CPU_X86_64=:x86_64
|
28
|
-
CPU_PPC64=:ppc64
|
29
|
-
CPU_PPC64LE=:ppc64le
|
30
|
-
CPU_S390=:s390
|
31
|
-
CPU_LIST=[CPU_X86_64,CPU_PPC64,CPU_PPC64LE,CPU_S390]
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
25
|
+
def os
|
26
|
+
case RbConfig::CONFIG['host_os']
|
27
|
+
when /mswin/,/msys/,/mingw/,/cygwin/,/bccwin/,/wince/,/emc/
|
28
|
+
return OS_WINDOWS
|
29
|
+
when /darwin/,/mac os/
|
30
|
+
return OS_X
|
31
|
+
when /linux/
|
32
|
+
return OS_LINUX
|
33
|
+
when /aix/
|
34
|
+
return OS_AIX
|
35
|
+
else
|
36
|
+
raise "Unknown OS: #{RbConfig::CONFIG['host_os']}"
|
37
|
+
end
|
44
38
|
end
|
45
|
-
end
|
46
39
|
|
47
|
-
|
48
|
-
|
49
|
-
|
40
|
+
def cpu
|
41
|
+
case RbConfig::CONFIG['host_cpu']
|
42
|
+
when /x86_64/,/x64/
|
43
|
+
return CPU_X86_64
|
44
|
+
when /powerpc/,/ppc64/
|
45
|
+
return CPU_PPC64LE if os.eql?(OS_LINUX)
|
46
|
+
return CPU_PPC64
|
47
|
+
when /s390/
|
48
|
+
return CPU_S390
|
49
|
+
else # other
|
50
|
+
raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
|
51
|
+
end
|
52
|
+
end
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
54
|
+
def architecture
|
55
|
+
return "#{os}-#{cpu}"
|
56
|
+
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
def exe_extension
|
59
|
+
return '.exe' if os.eql?(OS_WINDOWS)
|
60
|
+
return ''
|
61
|
+
end
|
62
|
+
|
63
|
+
# on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
|
64
|
+
# so, tell Ruby the right way
|
65
|
+
def fix_home
|
66
|
+
return unless os.eql?(OS_WINDOWS) && ENV.has_key?('USERPROFILE') && Dir.exist?(ENV['USERPROFILE'])
|
67
|
+
ENV['HOME'] = ENV['USERPROFILE']
|
68
|
+
Log.log.debug("Windows: set home to USERPROFILE: #{ENV['HOME']}")
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty_binding
|
72
|
+
return Kernel.binding
|
73
|
+
end
|
74
|
+
|
75
|
+
# secure execution of Ruby code
|
76
|
+
def secure_eval(code)
|
77
|
+
Kernel.send('lave'.reverse,code,empty_binding, __FILE__, __LINE__)
|
63
78
|
end
|
64
79
|
end
|
65
80
|
end
|
@@ -1,69 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aspera
|
2
4
|
module Fasp
|
3
5
|
# Base class for FASP transfer agents
|
4
6
|
# sub classes shall implement start_transfer and shutdown
|
5
7
|
class AgentBase
|
8
|
+
# fields description for JSON generation
|
9
|
+
INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
|
10
|
+
DatagramSize XoptFlags VLinkVersion PeerVLinkVersion DSPipelineDepth PeerDSPipelineDepth ReadBlockSize WriteBlockSize
|
11
|
+
ClusterNumNodes ClusterNodeId Size Written Loss FileBytes PreTransferBytes TransferBytes PMTU Elapsedusec ArgScansAttempted
|
12
|
+
ArgScansCompleted PathScansAttempted FileScansCompleted TransfersAttempted TransfersPassed Delay].freeze
|
13
|
+
BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
|
14
|
+
MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
|
15
|
+
EXPECTED_METHODS = %i[text struct enhanced].freeze
|
16
|
+
private_constant :INTEGER_FIELDS,:BOOLEAN_FIELDS,:EXPECTED_METHODS
|
6
17
|
|
7
18
|
private
|
8
19
|
|
9
|
-
# fields description for JSON generation
|
10
|
-
IntegerFields=['Bytescont','FaspFileArgIndex','StartByte','Rate','MinRate','Port','Priority','RateCap','MinRateCap','TCPPort','CreatePolicy','TimePolicy','DatagramSize','XoptFlags','VLinkVersion','PeerVLinkVersion','DSPipelineDepth','PeerDSPipelineDepth','ReadBlockSize','WriteBlockSize','ClusterNumNodes','ClusterNodeId','Size','Written','Loss','FileBytes','PreTransferBytes','TransferBytes','PMTU','Elapsedusec','ArgScansAttempted','ArgScansCompleted','PathScansAttempted','FileScansCompleted','TransfersAttempted','TransfersPassed','Delay']
|
11
|
-
BooleanFields=['Encryption','Remote','RateLock','MinRateLock','PolicyLock','FilesEncrypt','FilesDecrypt','VLinkLocalEnabled','VLinkRemoteEnabled','MoveRange','Keepalive','TestLogin','UseProxy','Precalc','RTTAutocorrect']
|
12
|
-
ExpectedMethod=[:text,:struct,:enhanced]
|
13
|
-
|
14
20
|
# translates legacy event into enhanced (JSON) event
|
15
21
|
def enhanced_event_format(event)
|
16
|
-
return event.keys.
|
22
|
+
return event.keys.each_with_object({}) do |e,h|
|
17
23
|
# capital_to_snake_case
|
18
|
-
new_name=e.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
value=event[e]
|
24
|
-
value=value.to_i if
|
25
|
-
value=value.eql?('Yes')
|
26
|
-
h[new_name]=value
|
27
|
-
h
|
24
|
+
new_name = e.
|
25
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
26
|
+
gsub(/([a-z\d])(usec)$/,'\1_\2').
|
27
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
28
|
+
downcase
|
29
|
+
value = event[e]
|
30
|
+
value = value.to_i if INTEGER_FIELDS.include?(e)
|
31
|
+
value = value.eql?('Yes') if BOOLEAN_FIELDS.include?(e)
|
32
|
+
h[new_name] = value
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
31
36
|
def initialize
|
32
|
-
@listeners=[]
|
37
|
+
@listeners = []
|
33
38
|
end
|
34
39
|
|
35
40
|
def notify_listeners(current_event_text,current_event_data)
|
36
|
-
Log.log.debug(
|
37
|
-
enhanced_event=nil
|
41
|
+
Log.log.debug('send event to listeners')
|
42
|
+
enhanced_event = nil
|
38
43
|
@listeners.each do |listener|
|
39
|
-
listener.
|
40
|
-
listener.
|
44
|
+
listener.event_text(current_event_text) if listener.respond_to?(:event_text)
|
45
|
+
listener.event_struct(current_event_data) if listener.respond_to?(:event_struct)
|
41
46
|
if listener.respond_to?(:event_enhanced)
|
42
|
-
enhanced_event=enhanced_event_format(current_event_data) if enhanced_event.nil?
|
43
|
-
listener.
|
47
|
+
enhanced_event = enhanced_event_format(current_event_data) if enhanced_event.nil?
|
48
|
+
listener.event_enhanced(enhanced_event)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end # notify_listeners
|
47
52
|
|
48
53
|
def notify_begin(id,size)
|
49
|
-
notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'NOTIFICATION','PreTransferBytes'=>size})
|
54
|
+
notify_listeners('emulated',{LISTENER_SESSION_ID_B => id,'Type' => 'NOTIFICATION','PreTransferBytes' => size})
|
50
55
|
end
|
51
56
|
|
52
57
|
def notify_progress(id,size)
|
53
|
-
notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'STATS','Bytescont'=>size})
|
58
|
+
notify_listeners('emulated',{LISTENER_SESSION_ID_B => id,'Type' => 'STATS','Bytescont' => size})
|
54
59
|
end
|
55
60
|
|
56
61
|
def notify_end(id)
|
57
|
-
notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'DONE'})
|
62
|
+
notify_listeners('emulated',{LISTENER_SESSION_ID_B => id,'Type' => 'DONE'})
|
58
63
|
end
|
59
64
|
|
60
65
|
public
|
61
|
-
|
62
|
-
|
66
|
+
|
67
|
+
LISTENER_SESSION_ID_B = 'ListenerSessionId'
|
68
|
+
LISTENER_SESSION_ID_S = 'listener_session_id'
|
63
69
|
|
64
70
|
# listener receives events
|
65
71
|
def add_listener(listener)
|
66
|
-
raise "expect one of #{
|
72
|
+
raise "expect one of #{EXPECTED_METHODS}" if EXPECTED_METHODS.inject(0){|m,e|m += listener.respond_to?("event_#{e}") ? 1 : 0;m}.eql?(0)
|
67
73
|
@listeners.push(listener)
|
68
74
|
self
|
69
75
|
end
|
@@ -72,14 +78,13 @@ module Aspera
|
|
72
78
|
# it must be a list of :success or exception
|
73
79
|
def self.validate_status_list(statuses)
|
74
80
|
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
75
|
-
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success)
|
81
|
+
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?
|
76
82
|
end
|
77
83
|
|
78
84
|
# the following methods must be implemented by subclass:
|
79
85
|
# start_transfer(transfer_spec,options) : start and wait for completion
|
80
86
|
# wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
|
81
87
|
# optional: shutdown
|
82
|
-
|
83
88
|
end
|
84
89
|
end
|
85
90
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aspera/fasp/agent_base'
|
2
4
|
require 'aspera/rest'
|
3
5
|
require 'aspera/open_application'
|
@@ -7,26 +9,26 @@ require 'tty-spinner'
|
|
7
9
|
module Aspera
|
8
10
|
module Fasp
|
9
11
|
class AgentConnect < AgentBase
|
10
|
-
MAX_CONNECT_START_RETRY=3
|
11
|
-
SLEEP_SEC_BETWEEN_RETRY=2
|
12
|
+
MAX_CONNECT_START_RETRY = 3
|
13
|
+
SLEEP_SEC_BETWEEN_RETRY = 2
|
12
14
|
private_constant :MAX_CONNECT_START_RETRY,:SLEEP_SEC_BETWEEN_RETRY
|
13
|
-
def initialize(
|
15
|
+
def initialize(_options)
|
14
16
|
super()
|
15
|
-
@connect_settings={
|
17
|
+
@connect_settings = {
|
16
18
|
'app_id' => SecureRandom.uuid
|
17
19
|
}
|
18
20
|
raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
|
19
|
-
trynumber=0
|
21
|
+
trynumber = 0
|
20
22
|
begin
|
21
|
-
connect_url=Installation.instance.connect_uri
|
23
|
+
connect_url = Installation.instance.connect_uri
|
22
24
|
Log.log.debug("found: #{connect_url}")
|
23
|
-
@connect_api=Rest.new({base_url: "#{connect_url}/v5/connect",headers: {'Origin'=>Rest.user_agent}}) # could use v6 also now
|
24
|
-
cinfo
|
25
|
+
@connect_api = Rest.new({base_url: "#{connect_url}/v5/connect",headers: {'Origin' => Rest.user_agent}}) # could use v6 also now
|
26
|
+
cinfo = @connect_api.read('info/version')[:data]
|
25
27
|
Log.dump(:connect_version,cinfo)
|
26
|
-
rescue => e # Errno::ECONNREFUSED
|
28
|
+
rescue StandardError => e # Errno::ECONNREFUSED
|
27
29
|
raise StandardError,"Unable to start connect after #{trynumber} try" if trynumber >= MAX_CONNECT_START_RETRY
|
28
30
|
Log.log.warn("connect is not started. Retry ##{trynumber}, err=#{e}")
|
29
|
-
trynumber+=1
|
31
|
+
trynumber += 1
|
30
32
|
if !OpenApplication.uri_graphical('fasp://initialize')
|
31
33
|
OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
|
32
34
|
raise StandardError,'Connect is not installed'
|
@@ -36,41 +38,46 @@ module Aspera
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
def start_transfer(transfer_spec,
|
41
|
+
def start_transfer(transfer_spec,_options=nil)
|
40
42
|
if transfer_spec['direction'] == 'send'
|
41
43
|
Log.log.warn("Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red)
|
42
44
|
transfer_spec.delete('paths')
|
43
|
-
resdata
|
44
|
-
|
45
|
+
resdata = @connect_api.create('windows/select-open-file-dialog/',{
|
46
|
+
'aspera_connect_settings' => @connect_settings,
|
47
|
+
'title' => 'Select Files',
|
48
|
+
'suggestedName' => '',
|
49
|
+
'allowMultipleSelection' => true,
|
50
|
+
'allowedFileTypes' => ''})[:data]
|
51
|
+
transfer_spec['paths'] = resdata['dataTransfer']['files'].map { |i| {'source' => i['name']}}
|
45
52
|
end
|
46
|
-
@request_id=SecureRandom.uuid
|
53
|
+
@request_id = SecureRandom.uuid
|
47
54
|
# if there is a token, we ask connect client to use well known ssh private keys
|
48
55
|
# instead of asking password
|
49
|
-
transfer_spec['authentication']='token' if transfer_spec.has_key?('token')
|
50
|
-
connect_transfer_args={
|
51
|
-
'aspera_connect_settings'
|
52
|
-
|
53
|
-
|
56
|
+
transfer_spec['authentication'] = 'token' if transfer_spec.has_key?('token')
|
57
|
+
connect_transfer_args = {
|
58
|
+
'aspera_connect_settings' => @connect_settings.merge({
|
59
|
+
'request_id' => @request_id,
|
60
|
+
'allow_dialogs' => true
|
54
61
|
}),
|
55
|
-
'transfer_specs'
|
56
|
-
|
62
|
+
'transfer_specs' => [{
|
63
|
+
'transfer_spec' => transfer_spec
|
57
64
|
}]}
|
58
65
|
# asynchronous anyway
|
59
|
-
res
|
60
|
-
@xfer_id=res['transfer_specs'].first['transfer_spec']['tags']['aspera']['xfer_id']
|
66
|
+
res = @connect_api.create('transfers/start',connect_transfer_args)[:data]
|
67
|
+
@xfer_id = res['transfer_specs'].first['transfer_spec']['tags']['aspera']['xfer_id']
|
61
68
|
end
|
62
69
|
|
63
70
|
def wait_for_transfers_completion
|
64
|
-
connect_activity_args={'aspera_connect_settings'
|
65
|
-
started=false
|
66
|
-
spinner=nil
|
71
|
+
connect_activity_args = {'aspera_connect_settings' => @connect_settings}
|
72
|
+
started = false
|
73
|
+
spinner = nil
|
67
74
|
begin
|
68
75
|
loop do
|
69
|
-
tr_info
|
76
|
+
tr_info = @connect_api.create("transfers/info/#{@xfer_id}",connect_activity_args)[:data]
|
70
77
|
if tr_info['transfer_info'].is_a?(Hash)
|
71
|
-
trdata=tr_info['transfer_info']
|
78
|
+
trdata = tr_info['transfer_info']
|
72
79
|
if trdata.nil?
|
73
|
-
Log.log.warn(
|
80
|
+
Log.log.warn('no session in Connect')
|
74
81
|
break
|
75
82
|
end
|
76
83
|
# TODO: get session id
|
@@ -87,23 +94,23 @@ module Aspera
|
|
87
94
|
spinner.spin
|
88
95
|
when 'running'
|
89
96
|
#puts "running: sessions:#{trdata['sessions'].length}, #{trdata['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
|
90
|
-
if !started
|
91
|
-
spinner
|
97
|
+
if !started && (trdata['bytes_expected'] != 0)
|
98
|
+
spinner&.success
|
92
99
|
notify_begin(@connect_settings['app_id'],trdata['bytes_expected'])
|
93
|
-
started=true
|
100
|
+
started = true
|
94
101
|
else
|
95
102
|
notify_progress(@connect_settings['app_id'],trdata['bytes_written'])
|
96
103
|
end
|
97
104
|
when 'failed'
|
98
|
-
spinner
|
99
|
-
raise Fasp::Error
|
105
|
+
spinner&.error
|
106
|
+
raise Fasp::Error, trdata['error_desc']
|
100
107
|
else
|
101
|
-
raise Fasp::Error
|
108
|
+
raise Fasp::Error, "unknown status: #{trdata['status']}: #{trdata['error_desc']}"
|
102
109
|
end
|
103
110
|
end
|
104
|
-
sleep
|
111
|
+
sleep(1)
|
105
112
|
end
|
106
|
-
rescue => e
|
113
|
+
rescue StandardError => e
|
107
114
|
return [e]
|
108
115
|
end
|
109
116
|
return [:success]
|