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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data/README.md +844 -861
  4. data/bin/ascli +20 -1
  5. data/bin/asession +37 -34
  6. data/docs/test_env.conf +11 -3
  7. data/examples/aoc.rb +13 -12
  8. data/examples/dascli +26 -0
  9. data/examples/faspex4.rb +34 -29
  10. data/examples/transfer.rb +30 -29
  11. data/lib/aspera/aoc.rb +151 -143
  12. data/lib/aspera/ascmd.rb +56 -45
  13. data/lib/aspera/ats_api.rb +6 -5
  14. data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
  15. data/lib/aspera/cli/extended_value.rb +32 -30
  16. data/lib/aspera/cli/formater.rb +103 -111
  17. data/lib/aspera/cli/info.rb +2 -1
  18. data/lib/aspera/cli/listener/line_dump.rb +1 -0
  19. data/lib/aspera/cli/listener/logger.rb +1 -0
  20. data/lib/aspera/cli/listener/progress.rb +13 -12
  21. data/lib/aspera/cli/listener/progress_multi.rb +21 -20
  22. data/lib/aspera/cli/main.rb +106 -89
  23. data/lib/aspera/cli/manager.rb +96 -85
  24. data/lib/aspera/cli/plugin.rb +50 -32
  25. data/lib/aspera/cli/plugins/alee.rb +6 -5
  26. data/lib/aspera/cli/plugins/aoc.rb +521 -426
  27. data/lib/aspera/cli/plugins/ats.rb +84 -83
  28. data/lib/aspera/cli/plugins/bss.rb +30 -27
  29. data/lib/aspera/cli/plugins/config.rb +483 -397
  30. data/lib/aspera/cli/plugins/console.rb +17 -15
  31. data/lib/aspera/cli/plugins/cos.rb +26 -35
  32. data/lib/aspera/cli/plugins/faspex.rb +201 -168
  33. data/lib/aspera/cli/plugins/faspex5.rb +109 -74
  34. data/lib/aspera/cli/plugins/node.rb +378 -189
  35. data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
  36. data/lib/aspera/cli/plugins/preview.rb +131 -122
  37. data/lib/aspera/cli/plugins/server.rb +94 -93
  38. data/lib/aspera/cli/plugins/shares.rb +42 -28
  39. data/lib/aspera/cli/plugins/sync.rb +15 -14
  40. data/lib/aspera/cli/transfer_agent.rb +56 -52
  41. data/lib/aspera/cli/version.rb +2 -1
  42. data/lib/aspera/colors.rb +29 -28
  43. data/lib/aspera/command_line_builder.rb +50 -43
  44. data/lib/aspera/cos_node.rb +64 -38
  45. data/lib/aspera/data_repository.rb +1 -0
  46. data/lib/aspera/environment.rb +18 -8
  47. data/lib/aspera/fasp/agent_base.rb +26 -23
  48. data/lib/aspera/fasp/agent_connect.rb +35 -30
  49. data/lib/aspera/fasp/agent_direct.rb +68 -60
  50. data/lib/aspera/fasp/agent_httpgw.rb +71 -64
  51. data/lib/aspera/fasp/agent_node.rb +24 -23
  52. data/lib/aspera/fasp/agent_trsdk.rb +19 -20
  53. data/lib/aspera/fasp/error.rb +2 -1
  54. data/lib/aspera/fasp/error_info.rb +79 -68
  55. data/lib/aspera/fasp/installation.rb +122 -114
  56. data/lib/aspera/fasp/listener.rb +1 -0
  57. data/lib/aspera/fasp/parameters.rb +44 -41
  58. data/lib/aspera/fasp/resume_policy.rb +14 -11
  59. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  60. data/lib/aspera/fasp/uri.rb +25 -24
  61. data/lib/aspera/faspex_gw.rb +83 -72
  62. data/lib/aspera/hash_ext.rb +10 -12
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +60 -45
  65. data/lib/aspera/keychain/macos_security.rb +26 -24
  66. data/lib/aspera/log.rb +34 -38
  67. data/lib/aspera/nagios.rb +14 -13
  68. data/lib/aspera/node.rb +19 -19
  69. data/lib/aspera/oauth.rb +121 -101
  70. data/lib/aspera/open_application.rb +6 -5
  71. data/lib/aspera/persistency_action_once.rb +9 -8
  72. data/lib/aspera/persistency_folder.rb +10 -9
  73. data/lib/aspera/preview/file_types.rb +261 -266
  74. data/lib/aspera/preview/generator.rb +74 -73
  75. data/lib/aspera/preview/image_error.png +0 -0
  76. data/lib/aspera/preview/options.rb +7 -6
  77. data/lib/aspera/preview/utils.rb +30 -33
  78. data/lib/aspera/preview/video_error.png +0 -0
  79. data/lib/aspera/proxy_auto_config.rb +25 -23
  80. data/lib/aspera/rest.rb +73 -74
  81. data/lib/aspera/rest_call_error.rb +1 -0
  82. data/lib/aspera/rest_error_analyzer.rb +11 -9
  83. data/lib/aspera/rest_errors_aspera.rb +5 -4
  84. data/lib/aspera/secret_hider.rb +68 -0
  85. data/lib/aspera/ssh.rb +12 -10
  86. data/lib/aspera/sync.rb +49 -47
  87. data/lib/aspera/temp_file_manager.rb +7 -5
  88. data/lib/aspera/timer_limiter.rb +9 -8
  89. data/lib/aspera/uri_reader.rb +11 -14
  90. data/lib/aspera/web_auth.rb +17 -15
  91. data.tar.gz.sig +0 -0
  92. metadata +117 -34
  93. metadata.gz.sig +2 -0
  94. 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.token_auth_api.call({
11
- operation: 'POST',
12
- subpath: o.params[:path_token],
13
- headers: {'Accept'=>'application/json'},
14
- json_params: o.params[:aoc_pub_link][:json],
15
- url_params: o.params[:aoc_pub_link][:url].merge(scope: o.params[:scope]) # scope is here because it changes over time (node)
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=['aspera.global-cli-client','aspera.drive']
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=['/packages/public/receive','/packages/public/send','/files/public']
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=['name','email','id','default_workspace_id','organization_id']
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,:OAUTH_API_SUBPATH,:USER_INFO_FIELDS_MIN
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=PROD_DOMAIN)
83
- return "https://api.#{api_domain}"
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: "#{api_base_url(api_domain)}/metering/v1",
89
- headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_creds(entitlement_id,customer_id)}
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).select{|e|e.first.eql?('token')}.first
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]=:aoc_pub_link
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
- case direction
148
- when Fasp::TransferSpec::DIRECTION_SEND then 'upload'
149
- when Fasp::TransferSpec::DIRECTION_RECEIVE then 'download'
150
- else raise "ERROR: unexpected value: #{direction}"
151
- end
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
- 'aspera' => {
156
- 'usage_id' => "aspera.files.workspace.#{ws_id}", # activity tracking
157
- 'files' => {
158
- 'files_transfer_action' => "#{operation}_#{app.gsub(/s$/,'')}",
159
- 'workspace_name' => ws_name, # activity tracking
160
- 'workspace_id' => ws_id
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
- begin
255
- read('self')[:data]
256
- rescue StandardError => e
257
- Log.log.debug("ignoring error: #{e}")
258
- {}
259
- end
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' => direction,
285
- 'token' => token_generation_lambda.call(false), # first time, use cache
286
- 'tags' => {
287
- 'aspera' => {
288
- 'app' => app,
289
- 'files' => {
290
- 'node_id' => node_file[:node_info]['id']
291
- }, # files
292
- 'node' => {
293
- 'access_key' => node_file[:node_info]['access_key'],
294
- #'file_id' => ts_add['source_root_id']
295
- 'file_id' => node_file[:file_id]
296
- } # node
297
- } # aspera
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',{transfer_requests: [{ transfer_request: {paths: [{'source'=>'/'}] } }] })[:data]['transfer_specs'].first['transfer_spec']
313
- ['remote_host','remote_user','ssh_port','fasp_port'].each {|i| transfer_spec[i]=std_t_spec[i]}
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: :node_gen4,
320
- regenerate_token: token_generation_lambda
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=@key_chain.get_secret(url: node_info['url'], username: node_info['access_key'], mandatory: false) if use_secret && !@key_chain.nil?
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?([:file_id,:node_info])
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=@find_state[:found]
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=@resolve_state[:path]
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 'not found'
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