aspera-cli 4.11.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
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']