aspera-cli 4.11.0 → 4.12.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 (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +0 -1
  4. data/CHANGELOG.md +71 -52
  5. data/CONTRIBUTING.md +31 -6
  6. data/README.md +404 -259
  7. data/bin/asession +2 -2
  8. data/docs/test_env.conf +1 -0
  9. data/examples/aoc.rb +2 -2
  10. data/examples/dascli +11 -11
  11. data/examples/faspex4.rb +7 -7
  12. data/examples/node.rb +1 -1
  13. data/examples/proxy.pac +2 -2
  14. data/examples/server.rb +3 -3
  15. data/lib/aspera/aoc.rb +105 -40
  16. data/lib/aspera/cli/extended_value.rb +4 -4
  17. data/lib/aspera/cli/{formater.rb → formatter.rb} +7 -7
  18. data/lib/aspera/cli/listener/progress.rb +1 -1
  19. data/lib/aspera/cli/listener/progress_multi.rb +2 -2
  20. data/lib/aspera/cli/main.rb +18 -18
  21. data/lib/aspera/cli/manager.rb +5 -5
  22. data/lib/aspera/cli/plugin.rb +23 -20
  23. data/lib/aspera/cli/plugins/aoc.rb +75 -112
  24. data/lib/aspera/cli/plugins/ats.rb +6 -6
  25. data/lib/aspera/cli/plugins/config.rb +84 -83
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +38 -38
  28. data/lib/aspera/cli/plugins/faspex5.rb +187 -43
  29. data/lib/aspera/cli/plugins/node.rb +30 -37
  30. data/lib/aspera/cli/plugins/orchestrator.rb +7 -4
  31. data/lib/aspera/cli/plugins/preview.rb +10 -9
  32. data/lib/aspera/cli/plugins/server.rb +1 -1
  33. data/lib/aspera/cli/plugins/shares.rb +67 -43
  34. data/lib/aspera/cli/transfer_agent.rb +16 -16
  35. data/lib/aspera/cli/version.rb +2 -1
  36. data/lib/aspera/command_line_builder.rb +70 -66
  37. data/lib/aspera/cos_node.rb +9 -9
  38. data/lib/aspera/fasp/agent_base.rb +3 -1
  39. data/lib/aspera/fasp/agent_connect.rb +23 -23
  40. data/lib/aspera/fasp/agent_direct.rb +13 -14
  41. data/lib/aspera/fasp/agent_httpgw.rb +20 -19
  42. data/lib/aspera/fasp/agent_node.rb +13 -15
  43. data/lib/aspera/fasp/agent_trsdk.rb +1 -1
  44. data/lib/aspera/fasp/installation.rb +5 -5
  45. data/lib/aspera/fasp/listener.rb +1 -1
  46. data/lib/aspera/fasp/parameters.rb +49 -41
  47. data/lib/aspera/fasp/parameters.yaml +311 -212
  48. data/lib/aspera/fasp/resume_policy.rb +2 -2
  49. data/lib/aspera/fasp/transfer_spec.rb +0 -13
  50. data/lib/aspera/faspex_gw.rb +80 -161
  51. data/lib/aspera/faspex_postproc.rb +77 -0
  52. data/lib/aspera/log.rb +7 -7
  53. data/lib/aspera/nagios.rb +6 -6
  54. data/lib/aspera/node.rb +24 -19
  55. data/lib/aspera/oauth.rb +50 -47
  56. data/lib/aspera/proxy_auto_config.js +22 -22
  57. data/lib/aspera/proxy_auto_config.rb +3 -3
  58. data/lib/aspera/rest.rb +12 -10
  59. data/lib/aspera/rest_error_analyzer.rb +5 -5
  60. data/lib/aspera/secret_hider.rb +4 -3
  61. data/lib/aspera/ssh.rb +4 -4
  62. data/lib/aspera/sync.rb +37 -36
  63. data/lib/aspera/web_auth.rb +7 -59
  64. data/lib/aspera/web_server_simple.rb +76 -0
  65. data.tar.gz.sig +0 -0
  66. metadata +6 -4
  67. metadata.gz.sig +0 -0
@@ -1,28 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # spellchecker: ignore workgroups,mypackages
4
+
3
5
  require 'aspera/cli/basic_auth_plugin'
4
6
  require 'aspera/persistency_action_once'
5
7
  require 'aspera/id_generator'
6
8
  require 'aspera/nagios'
9
+ require 'aspera/environment'
7
10
  require 'securerandom'
11
+ require 'ruby-progressbar'
12
+ require 'tty-spinner'
8
13
 
9
14
  module Aspera
10
15
  module Cli
11
16
  module Plugins
12
17
  class Faspex5 < Aspera::Cli::BasicAuthPlugin
18
+ RECIPIENT_TYPES = %w[user workgroup external_user distribution_list shared_inbox].freeze
19
+ PACKAGE_TERMINATED = %w[completed failed].freeze
20
+ API_DETECT = 'api/v5/configuration/ping'
13
21
  class << self
14
22
  def detect(base_url)
15
23
  api = Rest.new(base_url: base_url, redirect_max: 1)
16
- result = api.read('api/v5/configuration/ping')
24
+ result = api.read(API_DETECT)
17
25
  if result[:http].code.start_with?('2') && result[:http].body.strip.empty?
18
- return {version: '5'}
26
+ return {version: '5', url: result[:http].uri.to_s[0..-(API_DETECT.length + 2)]}
19
27
  end
20
28
  return nil
21
29
  end
22
30
  end
23
31
 
24
32
  TRANSFER_CONNECT = 'connect'
25
- private_constant :TRANSFER_CONNECT
26
33
 
27
34
  def initialize(env)
28
35
  super(env)
@@ -32,42 +39,43 @@ module Aspera
32
39
  options.add_opt_list(:auth, [:boot].concat(Oauth::STD_AUTH_TYPES), 'OAuth type of authentication')
33
40
  options.add_opt_simple(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
34
41
  options.add_opt_simple(:passphrase, 'RSA private key passphrase')
42
+ options.add_opt_simple(:shared_folder, 'Shared folder source for package files')
35
43
  options.set_option(:auth, :jwt)
36
44
  options.parse_options!
37
45
  end
38
46
 
39
47
  def set_api
40
- @faxpex5_api_base_url = options.get_option(:url, is_type: :mandatory).gsub(%r{/+$}, '')
41
- @faxpex5_api_auth_url = "#{@faxpex5_api_base_url}/auth"
42
- faxpex5_api_v5_url = "#{@faxpex5_api_base_url}/api/v5"
48
+ @faspex5_api_base_url = options.get_option(:url, is_type: :mandatory).gsub(%r{/+$}, '')
49
+ @faspex5_api_auth_url = "#{@faspex5_api_base_url}/auth"
50
+ faspex5_api_v5_url = "#{@faspex5_api_base_url}/api/v5"
43
51
  case options.get_option(:auth, is_type: :mandatory)
44
52
  when :boot
45
53
  # the password here is the token copied directly from browser in developer mode
46
54
  @api_v5 = Rest.new({
47
- base_url: faxpex5_api_v5_url,
55
+ base_url: faspex5_api_v5_url,
48
56
  headers: {'Authorization' => options.get_option(:password, is_type: :mandatory)}
49
57
  })
50
58
  when :web
51
59
  # opens a browser and ask user to auth using web
52
60
  @api_v5 = Rest.new({
53
- base_url: faxpex5_api_v5_url,
61
+ base_url: faspex5_api_v5_url,
54
62
  auth: {
55
- type: :oauth2,
56
- base_url: @faxpex5_api_auth_url,
57
- crtype: :web,
58
- client_id: options.get_option(:client_id, is_type: :mandatory),
59
- web: {redirect_uri: options.get_option(:redirect_uri, is_type: :mandatory)}
63
+ type: :oauth2,
64
+ base_url: @faspex5_api_auth_url,
65
+ grant_method: :web,
66
+ client_id: options.get_option(:client_id, is_type: :mandatory),
67
+ web: {redirect_uri: options.get_option(:redirect_uri, is_type: :mandatory)}
60
68
  }})
61
69
  when :jwt
62
70
  app_client_id = options.get_option(:client_id, is_type: :mandatory)
63
71
  @api_v5 = Rest.new({
64
- base_url: faxpex5_api_v5_url,
72
+ base_url: faspex5_api_v5_url,
65
73
  auth: {
66
- type: :oauth2,
67
- base_url: @faxpex5_api_auth_url,
68
- crtype: :jwt,
69
- client_id: app_client_id,
70
- jwt: {
74
+ type: :oauth2,
75
+ base_url: @faspex5_api_auth_url,
76
+ grant_method: :jwt,
77
+ client_id: app_client_id,
78
+ jwt: {
71
79
  payload: {
72
80
  iss: app_client_id, # issuer
73
81
  aud: app_client_id, # audience TODO: ???
@@ -81,18 +89,77 @@ module Aspera
81
89
  end
82
90
  end
83
91
 
84
- ACTIONS = %i[health version user bearer_token package admin].freeze
92
+ def normalize_recipients(parameters)
93
+ return unless parameters.key?('recipients')
94
+ raise 'Field recipients must be an Array' unless parameters['recipients'].is_a?(Array)
95
+ parameters['recipients'] = parameters['recipients'].map do |recipient_data|
96
+ # if just a string, assume it is the name
97
+ if recipient_data.is_a?(String)
98
+ result = @api_v5.read('contacts', {q: recipient_data, context: 'packages', type: [Rest::ARRAY_PARAMS, *RECIPIENT_TYPES]})[:data]
99
+ raise "No matching contact for #{recipient_data}" if result.empty?
100
+ raise "Multiple matching contact for #{recipient_data} : #{result['contacts'].map{|i|i['name']}.join(', ')}" unless 1.eql?(result['total_count'])
101
+ matched = result['contacts'].first
102
+ recipient_data = {
103
+ name: matched['name'],
104
+ recipient_type: matched['type']
105
+ }
106
+ end
107
+ # result for mapping
108
+ recipient_data
109
+ end
110
+ end
111
+
112
+ def wait_for_complete_upload(id)
113
+ parameters = options.get_option(:value)
114
+ spinner = nil
115
+ progress = nil
116
+ while true
117
+ status = @api_v5.read("packages/#{id}/upload_details")[:data]
118
+ # user asked to not follow
119
+ break unless parameters
120
+ if status['upload_status'].eql?('submitted')
121
+ if spinner.nil?
122
+ spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
123
+ spinner.start
124
+ end
125
+ spinner.update(title: status['upload_status'])
126
+ spinner.spin
127
+ elsif progress.nil?
128
+ progress = ProgressBar.create(
129
+ format: '%a %B %p%% %r Mbps %e',
130
+ rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
131
+ title: 'progress',
132
+ total: status['bytes_total'].to_i)
133
+ else
134
+ progress.progress = status['bytes_written'].to_i
135
+ end
136
+ break if PACKAGE_TERMINATED.include?(status['upload_status'])
137
+ sleep(0.5)
138
+ end
139
+ status['id'] = id
140
+ return status
141
+ end
142
+
143
+ def lookup_entity(entity_type, property, value)
144
+ # TODO: what if too many, use paging ?
145
+ all = @api_v5.read(entity_type)[:data][entity_type]
146
+ found = all.find{|i|i[property].eql?(value)}
147
+ raise "No #{entity_type} with #{property} = #{value}" if found.nil?
148
+ return found
149
+ end
150
+
151
+ ACTIONS = %i[health version user bearer_token package shared_folders admin gateway postprocessing].freeze
85
152
 
86
153
  def execute_action
87
- set_api
88
154
  command = options.get_next_command(ACTIONS)
155
+ set_api unless command.eql?(:postprocessing)
89
156
  case command
90
157
  when :version
91
158
  return { type: :single_object, data: @api_v5.read('version')[:data] }
92
159
  when :health
93
160
  nagios = Nagios.new
94
161
  begin
95
- result = Rest.new(base_url: @faxpex5_api_base_url).read('health')[:data]
162
+ result = Rest.new(base_url: @faspex5_api_base_url).read('health')[:data]
96
163
  result.each do |k, v|
97
164
  nagios.add_ok(k, v.to_s)
98
165
  end
@@ -114,7 +181,7 @@ module Aspera
114
181
  when :bearer_token
115
182
  return {type: :text, data: @api_v5.oauth_token}
116
183
  when :package
117
- command = options.get_next_command(%i[list show send receive])
184
+ command = options.get_next_command(%i[list show send receive status])
118
185
  case command
119
186
  when :list
120
187
  parameters = options.get_option(:value)
@@ -126,20 +193,40 @@ module Aspera
126
193
  when :show
127
194
  id = instance_identifier
128
195
  return {type: :single_object, data: @api_v5.read("packages/#{id}")[:data]}
196
+ when :status
197
+ status = wait_for_complete_upload(instance_identifier)
198
+ return {type: :single_object, data: status}
129
199
  when :send
130
200
  parameters = options.get_option(:value, is_type: :mandatory)
131
- raise CliBadArgument, 'value must be hash, refer to API' unless parameters.is_a?(Hash)
201
+ raise CliBadArgument, 'Value must be Hash, refer to API' unless parameters.is_a?(Hash)
202
+ normalize_recipients(parameters)
132
203
  package = @api_v5.create('packages', parameters)[:data]
133
- # TODO: option to send from remote source or httpgw
134
- transfer_spec = @api_v5.call(
135
- operation: 'POST',
136
- subpath: "packages/#{package['id']}/transfer_spec/upload",
137
- headers: {'Accept' => 'application/json'},
138
- url_params: {transfer_type: TRANSFER_CONNECT},
139
- json_params: {paths: transfer.ts_source_paths.map{|i|i['source']}}
140
- )[:data]
141
- transfer_spec.delete('authentication')
142
- return Main.result_transfer(transfer.start(transfer_spec))
204
+ shared_folder = options.get_option(:shared_folder)
205
+ if shared_folder.nil?
206
+ # TODO: option to send from remote source or httpgw
207
+ transfer_spec = @api_v5.call(
208
+ operation: 'POST',
209
+ subpath: "packages/#{package['id']}/transfer_spec/upload",
210
+ headers: {'Accept' => 'application/json'},
211
+ url_params: {transfer_type: TRANSFER_CONNECT},
212
+ json_params: {paths: transfer.source_list}
213
+ )[:data]
214
+ transfer_spec.delete('authentication')
215
+ return Main.result_transfer(transfer.start(transfer_spec))
216
+ else
217
+ if !shared_folder.to_i.to_s.eql?(shared_folder)
218
+ shared_folder = lookup_entity('shared_folders', 'name', shared_folder)['id']
219
+ end
220
+ transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
221
+ # start remote transfer and get first status
222
+ result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)[:data]
223
+ result['id'] = package['id']
224
+ unless result['status'].eql?('completed')
225
+ formatter.display_status("Package #{package['id']}")
226
+ result = wait_for_complete_upload(package['id'])
227
+ end
228
+ return {type: :single_object, data: result}
229
+ end
143
230
  when :receive
144
231
  pkg_type = 'received'
145
232
  pack_id = instance_identifier
@@ -166,39 +253,67 @@ module Aspera
166
253
  package_ids.reject!{|i|skip_ids_data.include?(i)}
167
254
  end
168
255
  result_transfer = []
169
- package_ids.each do |pkgid|
256
+ package_ids.each do |pkg_id|
170
257
  param_file_list = {}
171
258
  begin
172
- param_file_list['paths'] = transfer.ts_source_paths.map{|i|i['source']}
259
+ param_file_list['paths'] = transfer.source_list
173
260
  rescue Aspera::Cli::CliBadArgument
174
261
  # paths is optional
175
262
  end
176
263
  # TODO: allow from sent as well ?
177
264
  transfer_spec = @api_v5.call(
178
265
  operation: 'POST',
179
- subpath: "packages/#{pkgid}/transfer_spec/download",
266
+ subpath: "packages/#{pkg_id}/transfer_spec/download",
180
267
  headers: {'Accept' => 'application/json'},
181
268
  url_params: {transfer_type: TRANSFER_CONNECT, type: pkg_type},
182
269
  json_params: param_file_list
183
270
  )[:data]
184
271
  transfer_spec.delete('authentication')
185
272
  statuses = transfer.start(transfer_spec)
186
- result_transfer.push({'package' => pkgid, Main::STATUS_FIELD => statuses})
273
+ result_transfer.push({'package' => pkg_id, Main::STATUS_FIELD => statuses})
187
274
  # skip only if all sessions completed
188
- skip_ids_data.push(pkgid) if TransferAgent.session_status(statuses).eql?(:success)
275
+ skip_ids_data.push(pkg_id) if TransferAgent.session_status(statuses).eql?(:success)
189
276
  end
190
277
  skip_ids_persistency&.save
191
278
  return Main.result_transfer_multiple(result_transfer)
192
279
  end # case package
280
+ when :shared_folders
281
+ all_shared_folders = @api_v5.read('shared_folders')[:data]['shared_folders']
282
+ case options.get_next_command(%i[list browse])
283
+ when :list
284
+ return {type: :object_list, data: all_shared_folders}
285
+ when :browse
286
+ shared_folder_id = instance_identifier
287
+ path = options.get_next_argument('folder path', mandatory: false) || '/'
288
+ node = all_shared_folders.find{|i|i['id'].eql?(shared_folder_id)}
289
+ raise "No such shared folder id #{shared_folder_id}" if node.nil?
290
+ result = @api_v5.call({
291
+ operation: 'POST',
292
+ subpath: "nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse",
293
+ headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
294
+ json_params: {'path': path, 'filters': {'basenames': []}},
295
+ url_params: {offset: 0, limit: 100}
296
+ })[:data]
297
+ if result.key?('items')
298
+ return {type: :object_list, data: result['items']}
299
+ else
300
+ return {type: :single_object, data: result['self']}
301
+ end
302
+ end
193
303
  when :admin
194
- case options.get_next_command([:resource])
304
+ case options.get_next_command(%i[resource])
195
305
  when :resource
196
- res_type = options.get_next_command(%i[accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs metadata_profiles])
306
+ res_type = options.get_next_command(%i[accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs metadata_profiles
307
+ email_notifications])
197
308
  res_path = list_key = res_type.to_s
309
+ id_as_arg = false
198
310
  case res_type
199
311
  when :metadata_profiles
200
312
  res_path = 'configuration/metadata_profiles'
201
313
  list_key = 'profiles'
314
+ when :email_notifications
315
+ list_key = false
316
+ id_as_arg = 'type'
202
317
  end
203
318
  display_fields =
204
319
  case res_type
@@ -207,10 +322,39 @@ module Aspera
207
322
  end
208
323
  adm_api = @api_v5
209
324
  if res_type.eql?(:oauth_clients)
210
- adm_api = Rest.new(@api_v5.params.merge({base_url: @faxpex5_api_auth_url}))
325
+ adm_api = Rest.new(@api_v5.params.merge({base_url: @faspex5_api_auth_url}))
211
326
  end
212
- return entity_action(adm_api, res_path, item_list_key: list_key, display_fields: display_fields)
327
+ return entity_action(adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg)
213
328
  end
329
+ when :gateway
330
+ require 'aspera/faspex_gw'
331
+ url = options.get_option(:value, is_type: :mandatory)
332
+ uri = URI.parse(url)
333
+ server = WebServerSimple.new(uri)
334
+ server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
335
+ trap('INT') { server.shutdown }
336
+ formatter.display_status("Faspex 4 gateway listening on #{url}")
337
+ Log.log.info("Listening on #{url}")
338
+ # this is blocking until server exits
339
+ server.start
340
+ return Main.result_status('Gateway terminated')
341
+ when :postprocessing
342
+ require 'aspera/faspex_postproc'
343
+ parameters = options.get_option(:value, is_type: :mandatory)
344
+ raise 'parameters must be Hash' unless parameters.is_a?(Hash)
345
+ parameters = parameters.symbolize_keys
346
+ raise 'Missing key: url' unless parameters.key?(:url)
347
+ uri = URI.parse(parameters[:url])
348
+ parameters[:processing] ||= {}
349
+ parameters[:processing][:root] = uri.path
350
+ server = WebServerSimple.new(uri, certificate: parameters[:certificate])
351
+ server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
352
+ trap('INT') { server.shutdown }
353
+ formatter.display_status("Faspex 4 post processing listening on #{uri.port}")
354
+ Log.log.info("Listening on #{uri.port}")
355
+ # this is blocking until server exits
356
+ server.start
357
+ return Main.result_status('Gateway terminated')
214
358
  end # case command
215
359
  end # action
216
360
  end # Faspex5
@@ -40,12 +40,14 @@ module Aspera
40
40
  end
41
41
  end
42
42
 
43
+ # spellchecker: disable
43
44
  # SOAP API call to test central API
44
45
  CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>'\
45
46
  '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">'\
46
47
  '<soapenv:Header></soapenv:Header>'\
47
48
  '<soapenv:Body><typ:GetSessionInfoRequest><SessionFilter><SessionStatus>running</SessionStatus></SessionFilter></typ:GetSessionInfoRequest></soapenv:Body>'\
48
49
  '</soapenv:Envelope>'
50
+ # spellchecker: enable
49
51
 
50
52
  # fields removed in result of search
51
53
  SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
@@ -88,8 +90,8 @@ module Aspera
88
90
  Aspera::Node.new(params: {
89
91
  base_url: options.get_option(:url, is_type: :mandatory),
90
92
  headers: {
91
- Aspera::Node::X_ASPERA_ACCESSKEY => options.get_option(:username, is_type: :mandatory),
92
- 'Authorization' => options.get_option(:password, is_type: :mandatory)
93
+ Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, is_type: :mandatory),
94
+ 'Authorization' => options.get_option(:password, is_type: :mandatory)
93
95
  }
94
96
  })
95
97
  else
@@ -141,10 +143,10 @@ module Aspera
141
143
  end
142
144
 
143
145
  # translates paths results into CLI result, and removes prefix
144
- def c_result_translate_rem_prefix(resp, type, success_msg, path_prefix)
146
+ def c_result_translate_rem_prefix(response, type, success_msg, path_prefix)
145
147
  errors = []
146
148
  resres = { data: [], type: :object_list, fields: [type, 'result']}
147
- JSON.parse(resp[:http].body)['paths'].each do |p|
149
+ JSON.parse(response[:http].body)['paths'].each do |p|
148
150
  result = success_msg
149
151
  if p.key?('error')
150
152
  Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
@@ -162,10 +164,10 @@ module Aspera
162
164
 
163
165
  # get path arguments from command line, and add prefix
164
166
  def get_next_arg_add_prefix(path_prefix, name, number=:single)
165
- thepath = options.get_next_argument(name, expected: number)
166
- return thepath if path_prefix.nil?
167
- return File.join(path_prefix, thepath) if thepath.is_a?(String)
168
- return thepath.map {|p| File.join(path_prefix, p)} if thepath.is_a?(Array)
167
+ path_or_list = options.get_next_argument(name, expected: number)
168
+ return path_or_list if path_prefix.nil?
169
+ return File.join(path_prefix, path_or_list) if path_or_list.is_a?(String)
170
+ return path_or_list.map {|p| File.join(path_prefix, p)} if path_or_list.is_a?(Array)
169
171
  raise StandardError, 'expect: nil, String or Array'
170
172
  end
171
173
 
@@ -185,8 +187,8 @@ module Aspera
185
187
  result = { type: :object_list, data: resp[:data]['items']}
186
188
  return Main.result_empty if result[:data].empty?
187
189
  result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
188
- self.format.display_status("Items: #{resp[:data]['item_count']}/#{resp[:data]['total_count']}")
189
- self.format.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
190
+ formatter.display_status("Items: #{resp[:data]['item_count']}/#{resp[:data]['total_count']}")
191
+ formatter.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
190
192
  return c_result_remove_prefix_path(result, 'path', prefix_path)
191
193
  when :space
192
194
  path_list = get_next_arg_add_prefix(prefix_path, 'folder path or ext.val. list')
@@ -217,8 +219,7 @@ module Aspera
217
219
  resp = @api_node.create('files/rename', { 'paths' => [{ 'path' => path_base, 'source' => path_src, 'destination' => path_dst }] })
218
220
  return c_result_translate_rem_prefix(resp, 'entry', 'moved', prefix_path)
219
221
  when :browse
220
- thepath = get_next_arg_add_prefix(prefix_path, 'path')
221
- query = { path: thepath}
222
+ query = { path: get_next_arg_add_prefix(prefix_path, 'path')}
222
223
  additional_query = options.get_option(:query)
223
224
  query.merge!(additional_query) unless additional_query.nil?
224
225
  send_result = @api_node.create('files/browse', query)[:data]
@@ -227,7 +228,7 @@ module Aspera
227
228
  case send_result['self']['type']
228
229
  when 'directory', 'container' # directory: node, container: shares
229
230
  result = { data: send_result['items'], type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
230
- self.format.display_status("Items: #{send_result['item_count']}/#{send_result['total_count']}")
231
+ formatter.display_status("Items: #{send_result['item_count']}/#{send_result['total_count']}")
231
232
  else # 'file','symbolic_link'
232
233
  result = { data: send_result['self'], type: :single_object}
233
234
  # result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
@@ -351,7 +352,7 @@ module Aspera
351
352
  case command_perm
352
353
  when :list
353
354
  # generic options : TODO: as arg ? option_url_query
354
- list_options ||= {'include' => ['[]', 'access_level', 'permission_count']}
355
+ list_options ||= {'include' => [Rest::ARRAY_PARAMS, 'access_level', 'permission_count']}
355
356
  # add which one to get
356
357
  list_options['file_id'] = apifid[:file_id]
357
358
  list_options['inherited'] ||= false
@@ -392,8 +393,7 @@ module Aspera
392
393
  apifid = @api_node.resolve_api_fid(top_file_id, '')
393
394
  return Node.new(@agents.merge(skip_basic_auth_options: true, skip_node_options: true, node_api: apifid[:api])).execute_action(command_legacy)
394
395
  when :node_info, :bearer_token_node
395
- thepath = options.get_next_argument('path')
396
- apifid = @api_node.resolve_api_fid(top_file_id, thepath)
396
+ apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
397
397
  result = {
398
398
  url: apifid[:api].params[:base_url],
399
399
  root_id: apifid[:file_id]
@@ -404,7 +404,7 @@ module Aspera
404
404
  result[:username] = apifid[:api].params[:auth][:username]
405
405
  result[:password] = apifid[:api].params[:auth][:password]
406
406
  when :oauth2
407
- result[:username] = apifid[:api].params[:headers][Aspera::Node::X_ASPERA_ACCESSKEY]
407
+ result[:username] = apifid[:api].params[:headers][Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY]
408
408
  result[:password] = apifid[:api].oauth_token
409
409
  else raise 'unknown'
410
410
  end
@@ -412,38 +412,34 @@ module Aspera
412
412
  raise 'not bearer token' unless result[:password].start_with?('Bearer ')
413
413
  return Main.result_status(result[:password])
414
414
  when :browse
415
- thepath = options.get_next_argument('path')
416
- apifid = @api_node.resolve_api_fid(top_file_id, thepath)
415
+ apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
417
416
  file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
418
417
  if file_info['type'].eql?('folder')
419
418
  result = apifid[:api].read("files/#{apifid[:file_id]}/files", options.get_option(:value))
420
419
  items = result[:data]
421
- self.format.display_status("Items: #{result[:data].length}/#{result[:http]['X-Total-Count']}")
420
+ formatter.display_status("Items: #{result[:data].length}/#{result[:http]['X-Total-Count']}")
422
421
  else
423
422
  items = [file_info]
424
423
  end
425
424
  return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
426
425
  when :find
427
- thepath = options.get_next_argument('path')
428
- apifid = @api_node.resolve_api_fid(top_file_id, thepath)
426
+ apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
429
427
  test_block = Aspera::Node.file_matcher(options.get_option(:value))
430
428
  return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
431
429
  when :mkdir
432
- thepath = options.get_next_argument('path')
433
- containing_folder_path = thepath.split(Aspera::Node::PATH_SEPARATOR)
430
+ containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
434
431
  new_folder = containing_folder_path.pop
435
432
  apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Aspera::Node::PATH_SEPARATOR))
436
433
  result = apifid[:api].create("files/#{apifid[:file_id]}/files", {name: new_folder, type: :folder})[:data]
437
434
  return Main.result_status("created: #{result['name']} (id=#{result['id']})")
438
435
  when :rename
439
- thepath = options.get_next_argument('source path')
436
+ file_path = options.get_next_argument('source path')
440
437
  newname = options.get_next_argument('new name')
441
- apifid = @api_node.resolve_api_fid(top_file_id, thepath)
438
+ apifid = @api_node.resolve_api_fid(top_file_id, file_path)
442
439
  result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
443
- return Main.result_status("renamed #{thepath} to #{newname}")
440
+ return Main.result_status("renamed #{file_path} to #{newname}")
444
441
  when :delete
445
- thepath = options.get_next_argument('path')
446
- return do_bulk_operation(thepath, 'deleted', id_result: 'path') do |l_path|
442
+ return do_bulk_operation(options.get_next_argument('path'), 'deleted', id_result: 'path') do |l_path|
447
443
  raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
448
444
  apifid = @api_node.resolve_api_fid(top_file_id, l_path)
449
445
  result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
@@ -517,7 +513,7 @@ module Aspera
517
513
  asyncname = options.get_option(:sync_name)
518
514
  if asyncname.nil?
519
515
  asyncid = instance_identifier
520
- if asyncid.eql?('ALL') && %i[show delete].include?(command)
516
+ if asyncid.eql?(VAL_ALL) && %i[show delete].include?(command)
521
517
  asyncids = @api_node.read('async/list')[:data]['sync_ids']
522
518
  else
523
519
  Integer(asyncid) # must be integer
@@ -540,7 +536,7 @@ module Aspera
540
536
  when :show
541
537
  resp = @api_node.create('async/summary', pdata)[:data]['sync_summaries']
542
538
  return Main.result_empty if resp.empty?
543
- return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if asyncid.eql?('ALL')
539
+ return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if asyncid.eql?(VAL_ALL)
544
540
  return { type: :single_object, data: resp.first }
545
541
  when :delete
546
542
  resp = @api_node.create('async/delete', pdata)[:data]
@@ -628,16 +624,13 @@ module Aspera
628
624
  resp = @api_node.create('streams', options.get_option(:value, is_type: :mandatory))
629
625
  return { type: :single_object, data: resp[:data] }
630
626
  when :show
631
- trid = options.get_next_argument('transfer id')
632
- resp = @api_node.read('ops/transfers/' + trid)
627
+ resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
633
628
  return { type: :other_struct, data: resp[:data] }
634
629
  when :modify
635
- trid = options.get_next_argument('transfer id')
636
- resp = @api_node.update('streams/' + trid, options.get_option(:value, is_type: :mandatory))
630
+ resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", options.get_option(:value, is_type: :mandatory))
637
631
  return { type: :other_struct, data: resp[:data] }
638
632
  when :cancel
639
- trid = options.get_next_argument('transfer id')
640
- resp = @api_node.cancel('streams/' + trid)
633
+ resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
641
634
  return { type: :other_struct, data: resp[:data] }
642
635
  else
643
636
  raise 'error'
@@ -123,13 +123,16 @@ module Aspera
123
123
  case command
124
124
  when :status
125
125
  options = {}
126
- options[:id] = wf_id unless wf_id.eql?('ALL')
126
+ options[:id] = wf_id unless wf_id.eql?(VAL_ALL)
127
127
  result = call_ao('workflows_status', options)[:data]
128
128
  return {type: :object_list, data: result['workflows']['workflow']}
129
129
  when :list
130
130
  result = call_ao('workflows_list', id: 0)[:data]
131
- return {type: :object_list, data: result['workflows']['workflow'],
132
- fields: %w[id portable_id name published_status published_revision_id latest_revision_id last_modification]}
131
+ return {
132
+ type: :object_list,
133
+ data: result['workflows']['workflow'],
134
+ fields: %w[id portable_id name published_status published_revision_id latest_revision_id last_modification]
135
+ }
133
136
  when :details
134
137
  result = call_ao('workflow_details', id: wf_id)[:data]
135
138
  return {type: :object_list, data: result['workflows']['workflow']['statuses']}
@@ -160,7 +163,7 @@ fields: %w[id portable_id name published_status published_revision_id latest_rev
160
163
  raise "Expects: work_step:result_name format, but got #{expected}" if fields.length != 2
161
164
  call_params['explicit_output_step'] = fields[0]
162
165
  call_params['explicit_output_variable'] = fields[1]
163
- # implicitely, call is synchronous
166
+ # implicitly, call is synchronous
164
167
  call_params['synchronous'] = true
165
168
  end
166
169
  if call_params['synchronous']