aspera-cli 4.7.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 +844 -861
- data/bin/ascli +20 -1
- data/bin/asession +37 -34
- data/docs/test_env.conf +11 -3
- data/examples/aoc.rb +13 -12
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +34 -29
- data/examples/transfer.rb +30 -29
- data/lib/aspera/aoc.rb +151 -143
- data/lib/aspera/ascmd.rb +56 -45
- data/lib/aspera/ats_api.rb +6 -5
- data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
- data/lib/aspera/cli/extended_value.rb +32 -30
- data/lib/aspera/cli/formater.rb +103 -111
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/listener/line_dump.rb +1 -0
- data/lib/aspera/cli/listener/logger.rb +1 -0
- data/lib/aspera/cli/listener/progress.rb +13 -12
- data/lib/aspera/cli/listener/progress_multi.rb +21 -20
- data/lib/aspera/cli/main.rb +106 -89
- data/lib/aspera/cli/manager.rb +96 -85
- data/lib/aspera/cli/plugin.rb +50 -32
- data/lib/aspera/cli/plugins/alee.rb +6 -5
- data/lib/aspera/cli/plugins/aoc.rb +521 -426
- data/lib/aspera/cli/plugins/ats.rb +84 -83
- data/lib/aspera/cli/plugins/bss.rb +30 -27
- data/lib/aspera/cli/plugins/config.rb +483 -397
- data/lib/aspera/cli/plugins/console.rb +17 -15
- data/lib/aspera/cli/plugins/cos.rb +26 -35
- data/lib/aspera/cli/plugins/faspex.rb +201 -168
- data/lib/aspera/cli/plugins/faspex5.rb +109 -74
- data/lib/aspera/cli/plugins/node.rb +378 -189
- data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
- data/lib/aspera/cli/plugins/preview.rb +131 -122
- data/lib/aspera/cli/plugins/server.rb +94 -93
- data/lib/aspera/cli/plugins/shares.rb +42 -28
- data/lib/aspera/cli/plugins/sync.rb +15 -14
- data/lib/aspera/cli/transfer_agent.rb +56 -52
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +29 -28
- data/lib/aspera/command_line_builder.rb +50 -43
- data/lib/aspera/cos_node.rb +64 -38
- data/lib/aspera/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +18 -8
- data/lib/aspera/fasp/agent_base.rb +26 -23
- data/lib/aspera/fasp/agent_connect.rb +35 -30
- data/lib/aspera/fasp/agent_direct.rb +68 -60
- data/lib/aspera/fasp/agent_httpgw.rb +71 -64
- data/lib/aspera/fasp/agent_node.rb +24 -23
- data/lib/aspera/fasp/agent_trsdk.rb +19 -20
- data/lib/aspera/fasp/error.rb +2 -1
- data/lib/aspera/fasp/error_info.rb +79 -68
- data/lib/aspera/fasp/installation.rb +122 -114
- data/lib/aspera/fasp/listener.rb +1 -0
- data/lib/aspera/fasp/parameters.rb +44 -41
- data/lib/aspera/fasp/resume_policy.rb +14 -11
- data/lib/aspera/fasp/transfer_spec.rb +6 -5
- data/lib/aspera/fasp/uri.rb +25 -24
- data/lib/aspera/faspex_gw.rb +83 -72
- data/lib/aspera/hash_ext.rb +10 -12
- data/lib/aspera/id_generator.rb +8 -7
- data/lib/aspera/keychain/encrypted_hash.rb +60 -45
- data/lib/aspera/keychain/macos_security.rb +26 -24
- data/lib/aspera/log.rb +34 -38
- data/lib/aspera/nagios.rb +14 -13
- data/lib/aspera/node.rb +19 -19
- data/lib/aspera/oauth.rb +121 -101
- data/lib/aspera/open_application.rb +6 -5
- data/lib/aspera/persistency_action_once.rb +9 -8
- data/lib/aspera/persistency_folder.rb +10 -9
- data/lib/aspera/preview/file_types.rb +261 -266
- data/lib/aspera/preview/generator.rb +74 -73
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +7 -6
- data/lib/aspera/preview/utils.rb +30 -33
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.rb +25 -23
- data/lib/aspera/rest.rb +73 -74
- data/lib/aspera/rest_call_error.rb +1 -0
- data/lib/aspera/rest_error_analyzer.rb +11 -9
- data/lib/aspera/rest_errors_aspera.rb +5 -4
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +12 -10
- data/lib/aspera/sync.rb +49 -47
- data/lib/aspera/temp_file_manager.rb +7 -5
- data/lib/aspera/timer_limiter.rb +9 -8
- data/lib/aspera/uri_reader.rb +11 -14
- data/lib/aspera/web_auth.rb +17 -15
- data.tar.gz.sig +0 -0
- metadata +117 -34
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
data/lib/aspera/aoc.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'aspera/log'
|
3
4
|
require 'aspera/rest'
|
4
5
|
require 'aspera/hash_ext'
|
@@ -7,49 +8,54 @@ require 'aspera/fasp/transfer_spec'
|
|
7
8
|
require 'base64'
|
8
9
|
|
9
10
|
Aspera::Oauth.register_token_creator(:aoc_pub_link,lambda{|o|
|
10
|
-
o.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
o.api.call({
|
12
|
+
operation: 'POST',
|
13
|
+
subpath: o.gparams[:path_token],
|
14
|
+
headers: {'Accept' => 'application/json'},
|
15
|
+
json_params: o.sparams[:json],
|
16
|
+
url_params: o.sparams[:url].merge(scope: o.gparams[:scope]) # scope is here because it changes over time (node)
|
16
17
|
})
|
18
|
+
},lambda { |oauth|
|
19
|
+
return [oauth.sparams.dig(:json,:url_token)]
|
17
20
|
})
|
18
21
|
|
19
22
|
module Aspera
|
20
23
|
class AoC < Rest
|
21
|
-
PRODUCT_NAME='Aspera on Cloud'
|
24
|
+
PRODUCT_NAME = 'Aspera on Cloud'
|
22
25
|
# Production domain of AoC
|
23
|
-
PROD_DOMAIN='ibmaspera.com'
|
26
|
+
PROD_DOMAIN = 'ibmaspera.com'
|
24
27
|
# to avoid infinite loop in pub link redirection
|
25
|
-
MAX_REDIRECT=10
|
28
|
+
MAX_REDIRECT = 10
|
26
29
|
# Well-known AoC globals client apps
|
27
|
-
GLOBAL_CLIENT_APPS=[
|
30
|
+
GLOBAL_CLIENT_APPS = %w[aspera.global-cli-client aspera.drive].freeze
|
28
31
|
# index offset in data repository of client app
|
29
32
|
DATA_REPO_INDEX_START = 4
|
30
33
|
# cookie prefix so that console can decode identity
|
31
|
-
COOKIE_PREFIX='aspera.aoc'
|
34
|
+
COOKIE_PREFIX = 'aspera.aoc'
|
32
35
|
# path in URL of public links
|
33
|
-
PUBLIC_LINK_PATHS=[
|
34
|
-
JWT_AUDIENCE='https://api.asperafiles.com/api/v1/oauth2/token'
|
35
|
-
OAUTH_API_SUBPATH='api/v1/oauth2'
|
36
|
+
PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public].freeze
|
37
|
+
JWT_AUDIENCE = 'https://api.asperafiles.com/api/v1/oauth2/token'
|
38
|
+
OAUTH_API_SUBPATH = 'api/v1/oauth2'
|
36
39
|
# minimum fields for user info if retrieval fails
|
37
|
-
USER_INFO_FIELDS_MIN=[
|
40
|
+
USER_INFO_FIELDS_MIN = %w[name email id default_workspace_id organization_id].freeze
|
38
41
|
|
39
|
-
private_constant :MAX_REDIRECT,:GLOBAL_CLIENT_APPS,:DATA_REPO_INDEX_START,:COOKIE_PREFIX,:PUBLIC_LINK_PATHS,:JWT_AUDIENCE
|
42
|
+
private_constant :MAX_REDIRECT,:GLOBAL_CLIENT_APPS,:DATA_REPO_INDEX_START,:COOKIE_PREFIX,:PUBLIC_LINK_PATHS,:JWT_AUDIENCE,
|
43
|
+
:OAUTH_API_SUBPATH,:USER_INFO_FIELDS_MIN
|
40
44
|
|
41
45
|
# various API scopes supported
|
42
|
-
SCOPE_FILES_SELF='self'
|
43
|
-
SCOPE_FILES_USER='user:all'
|
44
|
-
SCOPE_FILES_ADMIN='admin:all'
|
45
|
-
SCOPE_FILES_ADMIN_USER='admin-user:all'
|
46
|
-
SCOPE_FILES_ADMIN_USER_USER=SCOPE_FILES_ADMIN_USER+'+'+SCOPE_FILES_USER
|
47
|
-
SCOPE_NODE_USER='user:all'
|
48
|
-
SCOPE_NODE_ADMIN='admin:all'
|
49
|
-
PATH_SEPARATOR='/'
|
50
|
-
FILES_APP='files'
|
51
|
-
PACKAGES_APP='packages'
|
52
|
-
API_V1='api/v1'
|
46
|
+
SCOPE_FILES_SELF = 'self'
|
47
|
+
SCOPE_FILES_USER = 'user:all'
|
48
|
+
SCOPE_FILES_ADMIN = 'admin:all'
|
49
|
+
SCOPE_FILES_ADMIN_USER = 'admin-user:all'
|
50
|
+
SCOPE_FILES_ADMIN_USER_USER = SCOPE_FILES_ADMIN_USER + '+' + SCOPE_FILES_USER
|
51
|
+
SCOPE_NODE_USER = 'user:all'
|
52
|
+
SCOPE_NODE_ADMIN = 'admin:all'
|
53
|
+
PATH_SEPARATOR = '/'
|
54
|
+
FILES_APP = 'files'
|
55
|
+
PACKAGES_APP = 'packages'
|
56
|
+
API_V1 = 'api/v1'
|
57
|
+
# error message when entity not found
|
58
|
+
ENTITY_NOT_FOUND = 'No such'
|
53
59
|
|
54
60
|
# class instance variable, access with accessors on class
|
55
61
|
@use_standard_ports = true
|
@@ -59,19 +65,19 @@ module Aspera
|
|
59
65
|
attr_accessor :use_standard_ports
|
60
66
|
# strings /Applications/Aspera\ Drive.app/Contents/MacOS/AsperaDrive|grep -E '.{100}==$'|base64 --decode
|
61
67
|
def get_client_info(client_name=GLOBAL_CLIENT_APPS.first)
|
62
|
-
client_index=GLOBAL_CLIENT_APPS.index(client_name)
|
68
|
+
client_index = GLOBAL_CLIENT_APPS.index(client_name)
|
63
69
|
raise "no such pre-defined client: #{client_name}" if client_index.nil?
|
64
|
-
return client_name,Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START+client_index))
|
70
|
+
return client_name,Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START + client_index))
|
65
71
|
end
|
66
72
|
|
67
73
|
# @param url of AoC instance
|
68
74
|
# @return organization id in url and AoC domain: ibmaspera.com, asperafiles.com or qa.asperafiles.com, etc...
|
69
75
|
def parse_url(aoc_org_url)
|
70
|
-
uri=URI.parse(aoc_org_url.gsub(/\/+$/,''))
|
71
|
-
instance_fqdn=uri.host
|
76
|
+
uri = URI.parse(aoc_org_url.gsub(/\/+$/,''))
|
77
|
+
instance_fqdn = uri.host
|
72
78
|
Log.log.debug("instance_fqdn=#{instance_fqdn}")
|
73
79
|
raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if instance_fqdn.nil?
|
74
|
-
organization,instance_domain=instance_fqdn.split('.',2)
|
80
|
+
organization,instance_domain = instance_fqdn.split('.',2)
|
75
81
|
Log.log.debug("instance_domain=#{instance_domain}")
|
76
82
|
Log.log.debug("organization=#{organization}")
|
77
83
|
raise "expecting a public FQDN for #{PRODUCT_NAME}" if instance_domain.nil?
|
@@ -79,14 +85,14 @@ module Aspera
|
|
79
85
|
end
|
80
86
|
|
81
87
|
# base API url depends on domain, which could be "qa.xxx"
|
82
|
-
def api_base_url(api_domain
|
83
|
-
return "https
|
88
|
+
def api_base_url(organization: 'api', api_domain: PROD_DOMAIN)
|
89
|
+
return "https://#{organization}.#{api_domain}"
|
84
90
|
end
|
85
91
|
|
86
92
|
def metering_api(entitlement_id,customer_id,api_domain=PROD_DOMAIN)
|
87
93
|
return Rest.new({
|
88
|
-
base_url:
|
89
|
-
headers:
|
94
|
+
base_url: "#{api_base_url(api_domain: api_domain)}/metering/v1",
|
95
|
+
headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_creds(entitlement_id,customer_id)}
|
90
96
|
})
|
91
97
|
end
|
92
98
|
|
@@ -99,25 +105,25 @@ module Aspera
|
|
99
105
|
# if present try to get token value (resolve redirection if short links used)
|
100
106
|
# then set options url/token/auth
|
101
107
|
def resolve_pub_link(a_auth,a_opt)
|
102
|
-
public_link_url=a_opt[:link]
|
108
|
+
public_link_url = a_opt[:link]
|
103
109
|
return if public_link_url.nil?
|
104
110
|
raise 'do not use both link and url options' unless a_opt[:url].nil?
|
105
|
-
redirect_count=0
|
111
|
+
redirect_count = 0
|
106
112
|
while redirect_count <= MAX_REDIRECT
|
107
|
-
uri=URI.parse(public_link_url)
|
113
|
+
uri = URI.parse(public_link_url)
|
108
114
|
# detect if it's an expected format
|
109
115
|
if PUBLIC_LINK_PATHS.include?(uri.path)
|
110
|
-
url_param_token_pair=URI.decode_www_form(uri.query).
|
116
|
+
url_param_token_pair = URI.decode_www_form(uri.query).find{|e|e.first.eql?('token')}
|
111
117
|
raise ArgumentError,'link option must be URL with "token" parameter' if url_param_token_pair.nil?
|
112
118
|
# ok we get it !
|
113
|
-
a_opt[:url]='https://'+uri.host
|
114
|
-
a_auth[:crtype]
|
115
|
-
a_auth[:aoc_pub_link]={
|
119
|
+
a_opt[:url] = 'https://' + uri.host
|
120
|
+
a_auth[:crtype] = :aoc_pub_link
|
121
|
+
a_auth[:aoc_pub_link] = {
|
116
122
|
url: {grant_type: 'url_token'}, # URL args
|
117
123
|
json: {url_token: url_param_token_pair.last} # JSON body
|
118
124
|
}
|
119
125
|
# password protection of link
|
120
|
-
a_auth[:aoc_pub_link][:json][:password]=a_opt[:password] unless a_opt[:password].nil?
|
126
|
+
a_auth[:aoc_pub_link][:json][:password] = a_opt[:password] unless a_opt[:password].nil?
|
121
127
|
return # SUCCESS
|
122
128
|
end
|
123
129
|
Log.log.debug("no expected format: #{public_link_url}")
|
@@ -133,52 +139,52 @@ module Aspera
|
|
133
139
|
|
134
140
|
# additional transfer spec (tags) for package information
|
135
141
|
def package_tags(package_info,operation)
|
136
|
-
return {'tags'=>{'aspera'=>{'files'=>{
|
142
|
+
return {'tags' => {'aspera' => {'files' => {
|
137
143
|
'package_id' => package_info['id'],
|
138
144
|
'package_name' => package_info['name'],
|
139
145
|
'package_operation' => operation
|
140
|
-
|
146
|
+
}}}}
|
141
147
|
end
|
142
148
|
|
143
149
|
# add details to show in analytics
|
144
150
|
def analytics_ts(app,direction,ws_id,ws_name)
|
145
151
|
# translate transfer to operation
|
146
|
-
operation=
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
+
operation =
|
153
|
+
case direction
|
154
|
+
when Fasp::TransferSpec::DIRECTION_SEND then 'upload'
|
155
|
+
when Fasp::TransferSpec::DIRECTION_RECEIVE then 'download'
|
156
|
+
else raise "ERROR: unexpected value: #{direction}"
|
157
|
+
end
|
152
158
|
|
153
159
|
return {
|
154
|
-
'tags'
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
160
|
+
'tags' => {
|
161
|
+
'aspera' => {
|
162
|
+
'usage_id' => "aspera.files.workspace.#{ws_id}", # activity tracking
|
163
|
+
'files' => {
|
164
|
+
'files_transfer_action' => "#{operation}_#{app.gsub(/s$/,'')}",
|
165
|
+
'workspace_name' => ws_name, # activity tracking
|
166
|
+
'workspace_id' => ws_id
|
167
|
+
}
|
168
|
+
}
|
163
169
|
}
|
164
170
|
}
|
165
171
|
end
|
166
172
|
end # static methods
|
167
173
|
|
168
|
-
# @param :link,:url,:auth,:client_id,:client_secret,:scope,:redirect_uri,:private_key,:username,:subpath,:password (for pub link)
|
174
|
+
# @param :link,:url,:auth,:client_id,:client_secret,:scope,:redirect_uri,:private_key,:passphrase,:username,:subpath,:password (for pub link)
|
169
175
|
def initialize(opt)
|
170
176
|
raise ArgumentError,'Missing mandatory option: scope' if opt[:scope].nil?
|
171
177
|
|
172
178
|
# access key secrets are provided out of band to get node api access
|
173
179
|
# key: access key
|
174
180
|
# value: associated secret
|
175
|
-
@key_chain=nil
|
176
|
-
@user_info=nil
|
181
|
+
@key_chain = nil
|
182
|
+
@user_info = nil
|
177
183
|
|
178
184
|
# init rest params
|
179
|
-
aoc_rest_p={auth: {type: :oauth2}}
|
185
|
+
aoc_rest_p = {auth: {type: :oauth2}}
|
180
186
|
# shortcut to auth section
|
181
|
-
aoc_auth_p=aoc_rest_p[:auth]
|
187
|
+
aoc_auth_p = aoc_rest_p[:auth]
|
182
188
|
|
183
189
|
# sets opt[:url], aoc_rest_p[:auth][:crtype], [:auth][:aoc_pub_link] if there is a link
|
184
190
|
self.class.resolve_pub_link(aoc_auth_p,opt)
|
@@ -187,14 +193,14 @@ module Aspera
|
|
187
193
|
raise ArgumentError,'Missing mandatory option: url' if opt[:url].nil?
|
188
194
|
|
189
195
|
# get org name and domain from url
|
190
|
-
organization,instance_domain=self.class.parse_url(opt[:url])
|
196
|
+
organization,instance_domain = self.class.parse_url(opt[:url])
|
191
197
|
# this is the base API url
|
192
|
-
api_url_base=self.class.api_base_url(instance_domain)
|
198
|
+
api_url_base = self.class.api_base_url(api_domain: instance_domain)
|
193
199
|
# API URL, including subpath (version ...)
|
194
|
-
aoc_rest_p[:base_url]="#{api_url_base}/#{opt[:subpath]}"
|
200
|
+
aoc_rest_p[:base_url] = "#{api_url_base}/#{opt[:subpath]}"
|
195
201
|
# base auth URL
|
196
202
|
aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{organization}"
|
197
|
-
aoc_auth_p[:client_id]=opt[:client_id]
|
203
|
+
aoc_auth_p[:client_id] = opt[:client_id]
|
198
204
|
aoc_auth_p[:client_secret] = opt[:client_secret]
|
199
205
|
aoc_auth_p[:scope] = opt[:scope]
|
200
206
|
|
@@ -205,30 +211,30 @@ module Aspera
|
|
205
211
|
end
|
206
212
|
|
207
213
|
if aoc_auth_p[:client_id].nil?
|
208
|
-
aoc_auth_p[:client_id],aoc_auth_p[:client_secret] = self.class.get_client_info
|
214
|
+
aoc_auth_p[:client_id],aoc_auth_p[:client_secret] = self.class.get_client_info
|
209
215
|
end
|
210
216
|
|
211
217
|
# fill other auth parameters based on Oauth method
|
212
218
|
case aoc_auth_p[:crtype]
|
213
219
|
when :web
|
214
220
|
raise ArgumentError,'Missing mandatory option: redirect_uri' if opt[:redirect_uri].nil?
|
215
|
-
aoc_auth_p[:web]={redirect_uri: opt[:redirect_uri]}
|
221
|
+
aoc_auth_p[:web] = {redirect_uri: opt[:redirect_uri]}
|
216
222
|
when :jwt
|
217
223
|
raise ArgumentError,'Missing mandatory option: private_key' if opt[:private_key].nil?
|
218
224
|
raise ArgumentError,'Missing mandatory option: username' if opt[:username].nil?
|
219
|
-
aoc_auth_p[:jwt]={
|
220
|
-
private_key_obj: OpenSSL::PKey::RSA.new(opt[:private_key]),
|
221
|
-
payload:
|
225
|
+
aoc_auth_p[:jwt] = {
|
226
|
+
private_key_obj: OpenSSL::PKey::RSA.new(opt[:private_key],opt[:passphrase]),
|
227
|
+
payload: {
|
222
228
|
iss: aoc_auth_p[:client_id], # issuer
|
223
229
|
sub: opt[:username], # subject
|
224
230
|
aud: JWT_AUDIENCE
|
225
231
|
}
|
226
232
|
}
|
227
233
|
# add jwt payload for global ids
|
228
|
-
aoc_auth_p[:jwt][:payload][:org]=organization if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
|
234
|
+
aoc_auth_p[:jwt][:payload][:org] = organization if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
|
229
235
|
when :aoc_pub_link
|
230
236
|
# basic auth required for /token
|
231
|
-
aoc_auth_p[:auth]={type: :basic, username: aoc_auth_p[:client_id],password: aoc_auth_p[:client_secret]}
|
237
|
+
aoc_auth_p[:auth] = {type: :basic, username: aoc_auth_p[:client_id],password: aoc_auth_p[:client_secret]}
|
232
238
|
else raise "ERROR: unsupported auth method: #{aoc_auth_p[:crtype]}"
|
233
239
|
end
|
234
240
|
super(aoc_rest_p)
|
@@ -243,21 +249,21 @@ module Aspera
|
|
243
249
|
def key_chain=(keychain)
|
244
250
|
raise 'keychain already set' unless @key_chain.nil?
|
245
251
|
raise 'keychain must have get_secret' unless keychain.respond_to?(:get_secret)
|
246
|
-
@key_chain=keychain
|
252
|
+
@key_chain = keychain
|
247
253
|
end
|
248
254
|
|
249
255
|
# cached user information
|
250
256
|
def user_info
|
251
257
|
if @user_info.nil?
|
252
258
|
# get our user's default information
|
253
|
-
@user_info=
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
USER_INFO_FIELDS_MIN.each{|f|@user_info[f]='unknown' if @user_info[f].nil?}
|
259
|
+
@user_info =
|
260
|
+
begin
|
261
|
+
read('self')[:data]
|
262
|
+
rescue StandardError => e
|
263
|
+
Log.log.debug("ignoring error: #{e}")
|
264
|
+
{}
|
265
|
+
end
|
266
|
+
USER_INFO_FIELDS_MIN.each{|f|@user_info[f] = 'unknown' if @user_info[f].nil?}
|
261
267
|
end
|
262
268
|
return @user_info
|
263
269
|
end
|
@@ -265,9 +271,9 @@ module Aspera
|
|
265
271
|
# build ts addon for IBM Aspera Console (cookie)
|
266
272
|
def console_ts(app)
|
267
273
|
# we are sure that fields are not nil
|
268
|
-
elements=[app,user_info['name'],user_info['email']].map{|e|Base64.strict_encode64(e)}
|
274
|
+
elements = [app,user_info['name'],user_info['email']].map{|e|Base64.strict_encode64(e)}
|
269
275
|
elements.unshift(COOKIE_PREFIX)
|
270
|
-
return {'cookie'=>elements.join(':')}
|
276
|
+
return {'cookie' => elements.join(':')}
|
271
277
|
end
|
272
278
|
|
273
279
|
# build "transfer info", 2 elements array with:
|
@@ -275,26 +281,26 @@ module Aspera
|
|
275
281
|
# - source and token regeneration method
|
276
282
|
def tr_spec(app,direction,node_file,ts_add)
|
277
283
|
# get node api
|
278
|
-
node_api=get_node_api(node_file[:node_info])
|
284
|
+
node_api = get_node_api(node_file[:node_info])
|
279
285
|
# this lambda returns the bearer token for node, if
|
280
|
-
token_generation_lambda=lambda{|do_refresh|node_api.oauth_token(force_refresh: do_refresh)}
|
286
|
+
token_generation_lambda = lambda{|do_refresh|node_api.oauth_token(force_refresh: do_refresh)}
|
281
287
|
# prepare transfer specification
|
282
288
|
# note xfer_id and xfer_retry are set by the transfer agent itself
|
283
|
-
transfer_spec={
|
284
|
-
'direction'
|
285
|
-
'token'
|
286
|
-
'tags'
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
289
|
+
transfer_spec = {
|
290
|
+
'direction' => direction,
|
291
|
+
'token' => token_generation_lambda.call(false), # first time, use cache
|
292
|
+
'tags' => {
|
293
|
+
'aspera' => {
|
294
|
+
'app' => app,
|
295
|
+
'files' => {
|
296
|
+
'node_id' => node_file[:node_info]['id']
|
297
|
+
}, # files
|
298
|
+
'node' => {
|
299
|
+
'access_key' => node_file[:node_info]['access_key'],
|
300
|
+
#'file_id' => ts_add['source_root_id']
|
301
|
+
'file_id' => node_file[:file_id]
|
302
|
+
} # node
|
303
|
+
} # aspera
|
298
304
|
} # tags
|
299
305
|
}
|
300
306
|
# add remote host info
|
@@ -302,22 +308,24 @@ module Aspera
|
|
302
308
|
# get default TCP/UDP ports and transfer user
|
303
309
|
transfer_spec.merge!(Fasp::TransferSpec::AK_TSPEC_BASE)
|
304
310
|
# by default: same address as node API
|
305
|
-
transfer_spec['remote_host']=node_file[:node_info]['host']
|
311
|
+
transfer_spec['remote_host'] = node_file[:node_info]['host']
|
306
312
|
# 30 it's necessarily https scheme: webui does not allow anything else
|
307
313
|
if node_file[:node_info]['transfer_url'].is_a?(String) && !node_file[:node_info]['transfer_url'].empty?
|
308
|
-
transfer_spec['remote_host']=URI.parse(node_file[:node_info]['transfer_url']).host
|
314
|
+
transfer_spec['remote_host'] = URI.parse(node_file[:node_info]['transfer_url']).host
|
309
315
|
end
|
310
316
|
else
|
311
317
|
# retrieve values from API
|
312
|
-
std_t_spec=node_api.create('files/download_setup',
|
313
|
-
|
318
|
+
std_t_spec = node_api.create('files/download_setup',
|
319
|
+
{transfer_requests: [{ transfer_request: {paths: [{'source' => '/'}] } }] }
|
320
|
+
)[:data]['transfer_specs'].first['transfer_spec']
|
321
|
+
%w[remote_host remote_user ssh_port fasp_port].each {|i| transfer_spec[i] = std_t_spec[i]}
|
314
322
|
end
|
315
323
|
# add caller provided transfer spec
|
316
324
|
transfer_spec.deep_merge!(ts_add)
|
317
325
|
# additional information for transfer agent
|
318
|
-
source_and_token_generator={
|
319
|
-
src:
|
320
|
-
regenerate_token:
|
326
|
+
source_and_token_generator = {
|
327
|
+
src: :node_gen4,
|
328
|
+
regenerate_token: token_generation_lambda
|
321
329
|
}
|
322
330
|
return transfer_spec,source_and_token_generator
|
323
331
|
end
|
@@ -330,21 +338,21 @@ module Aspera
|
|
330
338
|
def get_node_api(node_info, scope: SCOPE_NODE_USER, use_secret: true)
|
331
339
|
raise 'internal error' unless node_info.is_a?(Hash) && node_info.has_key?('url') && node_info.has_key?('access_key')
|
332
340
|
# get optional secret unless :use_secret is false
|
333
|
-
ak_secret
|
341
|
+
ak_secret = @key_chain.get_secret(url: node_info['url'], username: node_info['access_key'], mandatory: false) if use_secret && !@key_chain.nil?
|
334
342
|
raise "There must be at least one of: 'secret' or 'scope' for access key #{node_info['access_key']}" if ak_secret.nil? && scope.nil?
|
335
|
-
node_rest_params={base_url: node_info['url']}
|
343
|
+
node_rest_params = {base_url: node_info['url']}
|
336
344
|
# if secret is available
|
337
345
|
if !ak_secret.nil?
|
338
|
-
node_rest_params[:auth]={
|
346
|
+
node_rest_params[:auth] = {
|
339
347
|
type: :basic,
|
340
348
|
username: node_info['access_key'],
|
341
349
|
password: ak_secret
|
342
350
|
}
|
343
351
|
else
|
344
352
|
# X-Aspera-AccessKey required for bearer token only
|
345
|
-
node_rest_params[:headers]= {'X-Aspera-AccessKey'=>node_info['access_key']}
|
346
|
-
node_rest_params[:auth]=params[:auth].clone
|
347
|
-
node_rest_params[:auth][:scope]=self.class.node_scope(node_info['access_key'],scope)
|
353
|
+
node_rest_params[:headers] = {'X-Aspera-AccessKey' => node_info['access_key']}
|
354
|
+
node_rest_params[:auth] = params[:auth].clone
|
355
|
+
node_rest_params[:auth][:scope] = self.class.node_scope(node_info['access_key'],scope)
|
348
356
|
end
|
349
357
|
return Node.new(node_rest_params)
|
350
358
|
end
|
@@ -353,9 +361,9 @@ module Aspera
|
|
353
361
|
# @return split values
|
354
362
|
def check_get_node_file(node_file)
|
355
363
|
raise "node_file must be Hash (got #{node_file.class})" unless node_file.is_a?(Hash)
|
356
|
-
raise 'node_file must have 2 keys: :file_id and :node_info' unless node_file.keys.sort.eql?([
|
357
|
-
node_info=node_file[:node_info]
|
358
|
-
file_id=node_file[:file_id]
|
364
|
+
raise 'node_file must have 2 keys: :file_id and :node_info' unless node_file.keys.sort.eql?(%i[file_id node_info])
|
365
|
+
node_info = node_file[:node_info]
|
366
|
+
file_id = node_file[:file_id]
|
359
367
|
raise "node_info must be Hash (got #{node_info.class}: #{node_info})" unless node_info.is_a?(Hash)
|
360
368
|
raise 'node_info must have id' unless node_info.has_key?('id')
|
361
369
|
raise 'file_id is empty' if file_id.to_s.empty?
|
@@ -366,11 +374,11 @@ module Aspera
|
|
366
374
|
def process_find_files(entry,path)
|
367
375
|
begin
|
368
376
|
# add to result if match filter
|
369
|
-
@find_state[:found].push(entry.merge({'path'=>path})) if @find_state[:test_block].call(entry)
|
377
|
+
@find_state[:found].push(entry.merge({'path' => path})) if @find_state[:test_block].call(entry)
|
370
378
|
# process link
|
371
379
|
if entry[:type].eql?('link')
|
372
|
-
sub_node_info=read("nodes/#{entry['target_node_id']}")[:data]
|
373
|
-
sub_opt={method: process_find_files, top_file_id: entry['target_id'], top_file_path: path}
|
380
|
+
sub_node_info = read("nodes/#{entry['target_node_id']}")[:data]
|
381
|
+
sub_opt = {method: process_find_files, top_file_id: entry['target_id'], top_file_path: path}
|
374
382
|
get_node_api(sub_node_info).crawl(self,sub_opt)
|
375
383
|
end
|
376
384
|
rescue StandardError => e
|
@@ -381,12 +389,12 @@ module Aspera
|
|
381
389
|
end
|
382
390
|
|
383
391
|
def find_files(top_node_file, test_block)
|
384
|
-
top_node_info,top_file_id=check_get_node_file(top_node_file)
|
392
|
+
top_node_info,top_file_id = check_get_node_file(top_node_file)
|
385
393
|
Log.log.debug("find_files: node_info=#{top_node_info}, fileid=#{top_file_id}")
|
386
|
-
@find_state={found: [], test_block: test_block}
|
394
|
+
@find_state = {found: [], test_block: test_block}
|
387
395
|
get_node_api(top_node_info).crawl(self,{method: :process_find_files, top_file_id: top_file_id})
|
388
|
-
result
|
389
|
-
@find_state=nil
|
396
|
+
result = @find_state[:found]
|
397
|
+
@find_state = nil
|
390
398
|
return result
|
391
399
|
end
|
392
400
|
|
@@ -399,18 +407,18 @@ module Aspera
|
|
399
407
|
when 'file'
|
400
408
|
# file must be terminal
|
401
409
|
raise "#{entry['name']} is a file, expecting folder to find: #{@resolve_state[:path]}" unless @resolve_state[:path].empty?
|
402
|
-
@resolve_state[:result][:file_id]=entry['id']
|
410
|
+
@resolve_state[:result][:file_id] = entry['id']
|
403
411
|
when 'link'
|
404
|
-
@resolve_state[:result][:node_info]=read("nodes/#{entry['target_node_id']}")[:data]
|
412
|
+
@resolve_state[:result][:node_info] = read("nodes/#{entry['target_node_id']}")[:data]
|
405
413
|
if @resolve_state[:path].empty?
|
406
|
-
@resolve_state[:result][:file_id]=entry['target_id']
|
414
|
+
@resolve_state[:result][:file_id] = entry['target_id']
|
407
415
|
else
|
408
416
|
get_node_api(@resolve_state[:result][:node_info]).crawl(self,{method: :process_resolve_node_file, top_file_id: entry['target_id']})
|
409
417
|
end
|
410
418
|
when 'folder'
|
411
419
|
if @resolve_state[:path].empty?
|
412
420
|
# found: store
|
413
|
-
@resolve_state[:result][:file_id]=entry['id']
|
421
|
+
@resolve_state[:result][:file_id] = entry['id']
|
414
422
|
return false
|
415
423
|
end
|
416
424
|
else
|
@@ -425,16 +433,16 @@ module Aspera
|
|
425
433
|
# @param element_path_string String path of element
|
426
434
|
# supports links to secondary nodes
|
427
435
|
def resolve_node_file(top_node_file, element_path_string)
|
428
|
-
top_node_info,top_file_id=check_get_node_file(top_node_file)
|
429
|
-
path_elements=element_path_string.split(PATH_SEPARATOR).reject(&:empty?)
|
430
|
-
result={node_info: top_node_info, file_id: nil}
|
436
|
+
top_node_info,top_file_id = check_get_node_file(top_node_file)
|
437
|
+
path_elements = element_path_string.split(PATH_SEPARATOR).reject(&:empty?)
|
438
|
+
result = {node_info: top_node_info, file_id: nil}
|
431
439
|
if path_elements.empty?
|
432
|
-
result[:file_id]=top_file_id
|
440
|
+
result[:file_id] = top_file_id
|
433
441
|
else
|
434
|
-
@resolve_state={path: path_elements, result: result}
|
442
|
+
@resolve_state = {path: path_elements, result: result}
|
435
443
|
get_node_api(top_node_info).crawl(self,{method: :process_resolve_node_file, top_file_id: top_file_id})
|
436
|
-
not_found
|
437
|
-
@resolve_state=nil
|
444
|
+
not_found = @resolve_state[:path]
|
445
|
+
@resolve_state = nil
|
438
446
|
raise "entry not found: #{not_found}" if result[:file_id].nil?
|
439
447
|
end
|
440
448
|
return result
|
@@ -445,17 +453,17 @@ module Aspera
|
|
445
453
|
# @param options additional search options
|
446
454
|
def lookup_entity_by_name(entity_type,entity_name,options={})
|
447
455
|
# returns entities whose name contains value (case insensitive)
|
448
|
-
matching_items=read(entity_type,options.merge({'q'=>entity_name}))[:data]
|
456
|
+
matching_items = read(entity_type,options.merge({'q' => entity_name}))[:data]
|
449
457
|
case matching_items.length
|
450
458
|
when 1 then return matching_items.first
|
451
|
-
when 0 then raise
|
459
|
+
when 0 then raise %Q{#{ENTITY_NOT_FOUND} #{entity_type}: "#{entity_name}"}
|
452
460
|
else
|
453
461
|
# multiple case insensitive partial matches, try case insensitive full match
|
454
462
|
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
455
|
-
icase_matches=matching_items.select{|i|i['name'].casecmp?(entity_name)}
|
463
|
+
icase_matches = matching_items.select{|i|i['name'].casecmp?(entity_name)}
|
456
464
|
case icase_matches.length
|
457
465
|
when 1 then return icase_matches.first
|
458
|
-
when 0 then raise %Q(#{entity_type}: multiple case insensitive partial match for: "#{entity_name}": #{matching_items.map{|i|i['name']}} but no case insensitive full match. Please be more specific or give exact name.)
|
466
|
+
when 0 then raise %Q(#{entity_type}: multiple case insensitive partial match for: "#{entity_name}": #{matching_items.map{|i|i['name']}} but no case insensitive full match. Please be more specific or give exact name.) # rubocop:disable Layout/LineLength
|
459
467
|
else raise "Two entities cannot have the same case insensitive name: #{icase_matches.map{|i|i['name']}}"
|
460
468
|
end
|
461
469
|
end
|