aspera-cli 4.14.0 → 4.15.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 (90) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +54 -3
  4. data/CONTRIBUTING.md +7 -7
  5. data/README.md +1457 -880
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/proxy.pac +1 -1
  9. data/lib/aspera/aoc.rb +198 -127
  10. data/lib/aspera/ascmd.rb +24 -14
  11. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  12. data/lib/aspera/cli/error.rb +17 -0
  13. data/lib/aspera/cli/extended_value.rb +47 -12
  14. data/lib/aspera/cli/formatter.rb +260 -171
  15. data/lib/aspera/cli/hints.rb +80 -0
  16. data/lib/aspera/cli/main.rb +101 -147
  17. data/lib/aspera/cli/manager.rb +160 -124
  18. data/lib/aspera/cli/plugin.rb +70 -59
  19. data/lib/aspera/cli/plugins/alee.rb +0 -1
  20. data/lib/aspera/cli/plugins/aoc.rb +239 -273
  21. data/lib/aspera/cli/plugins/ats.rb +8 -5
  22. data/lib/aspera/cli/plugins/bss.rb +2 -2
  23. data/lib/aspera/cli/plugins/config.rb +516 -375
  24. data/lib/aspera/cli/plugins/console.rb +40 -0
  25. data/lib/aspera/cli/plugins/cos.rb +4 -5
  26. data/lib/aspera/cli/plugins/faspex.rb +99 -84
  27. data/lib/aspera/cli/plugins/faspex5.rb +179 -148
  28. data/lib/aspera/cli/plugins/node.rb +219 -153
  29. data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
  30. data/lib/aspera/cli/plugins/preview.rb +46 -32
  31. data/lib/aspera/cli/plugins/server.rb +57 -17
  32. data/lib/aspera/cli/plugins/shares.rb +34 -12
  33. data/lib/aspera/cli/sync_actions.rb +68 -0
  34. data/lib/aspera/cli/transfer_agent.rb +45 -55
  35. data/lib/aspera/cli/transfer_progress.rb +74 -0
  36. data/lib/aspera/cli/version.rb +1 -1
  37. data/lib/aspera/colors.rb +3 -1
  38. data/lib/aspera/command_line_builder.rb +14 -11
  39. data/lib/aspera/cos_node.rb +3 -2
  40. data/lib/aspera/environment.rb +17 -6
  41. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  42. data/lib/aspera/fasp/agent_base.rb +31 -77
  43. data/lib/aspera/fasp/agent_connect.rb +21 -22
  44. data/lib/aspera/fasp/agent_direct.rb +88 -102
  45. data/lib/aspera/fasp/agent_httpgw.rb +196 -192
  46. data/lib/aspera/fasp/agent_node.rb +41 -34
  47. data/lib/aspera/fasp/agent_trsdk.rb +75 -34
  48. data/lib/aspera/fasp/error_info.rb +2 -2
  49. data/lib/aspera/fasp/faux_file.rb +52 -0
  50. data/lib/aspera/fasp/installation.rb +43 -184
  51. data/lib/aspera/fasp/management.rb +244 -0
  52. data/lib/aspera/fasp/parameters.rb +59 -26
  53. data/lib/aspera/fasp/parameters.yaml +75 -8
  54. data/lib/aspera/fasp/products.rb +162 -0
  55. data/lib/aspera/fasp/transfer_spec.rb +1 -1
  56. data/lib/aspera/fasp/uri.rb +4 -4
  57. data/lib/aspera/faspex_gw.rb +2 -2
  58. data/lib/aspera/faspex_postproc.rb +2 -2
  59. data/lib/aspera/hash_ext.rb +2 -2
  60. data/lib/aspera/json_rpc.rb +49 -0
  61. data/lib/aspera/line_logger.rb +23 -0
  62. data/lib/aspera/log.rb +57 -16
  63. data/lib/aspera/node.rb +97 -14
  64. data/lib/aspera/oauth.rb +36 -18
  65. data/lib/aspera/open_application.rb +4 -4
  66. data/lib/aspera/persistency_folder.rb +2 -2
  67. data/lib/aspera/preview/file_types.rb +4 -2
  68. data/lib/aspera/preview/generator.rb +22 -35
  69. data/lib/aspera/preview/options.rb +2 -0
  70. data/lib/aspera/preview/terminal.rb +24 -13
  71. data/lib/aspera/preview/utils.rb +19 -26
  72. data/lib/aspera/rest.rb +103 -72
  73. data/lib/aspera/rest_call_error.rb +1 -1
  74. data/lib/aspera/rest_error_analyzer.rb +15 -14
  75. data/lib/aspera/rest_errors_aspera.rb +37 -34
  76. data/lib/aspera/secret_hider.rb +14 -16
  77. data/lib/aspera/ssh.rb +4 -1
  78. data/lib/aspera/sync.rb +128 -122
  79. data/lib/aspera/temp_file_manager.rb +10 -3
  80. data/lib/aspera/web_auth.rb +10 -7
  81. data/lib/aspera/web_server_simple.rb +9 -4
  82. data.tar.gz.sig +0 -0
  83. metadata +33 -15
  84. metadata.gz.sig +0 -0
  85. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  86. data/lib/aspera/cli/listener/logger.rb +0 -22
  87. data/lib/aspera/cli/listener/progress.rb +0 -50
  88. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  89. data/lib/aspera/cli/plugins/sync.rb +0 -44
  90. data/lib/aspera/fasp/listener.rb +0 -13
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # spellchecker: ignore workgroups,mypackages
3
+ # spellchecker: ignore workgroups mypackages passcode
4
4
 
5
5
  require 'aspera/cli/basic_auth_plugin'
6
6
  require 'aspera/persistency_action_once'
@@ -8,7 +8,6 @@ require 'aspera/id_generator'
8
8
  require 'aspera/nagios'
9
9
  require 'aspera/environment'
10
10
  require 'securerandom'
11
- require 'ruby-progressbar'
12
11
  require 'tty-spinner'
13
12
 
14
13
  module Aspera
@@ -22,78 +21,130 @@ module Aspera
22
21
  API_LIST_MAILBOX_TYPES = %w[inbox inbox_history inbox_all inbox_all_history outbox outbox_history pending pending_history all].freeze
23
22
  PACKAGE_ALL_INIT = 'INIT'
24
23
  PACKAGE_SEND_FROM_REMOTE_SOURCE = 'remote_source'
25
- ADMIN_RESOURCES = %i[accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs metadata_profiles
26
- email_notifications].freeze
27
- private_constant(*%i[RECIPIENT_TYPES PACKAGE_TERMINATED API_DETECT API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE])
24
+ # Faspex API v5: get transfer spec for connect
25
+ TRANSFER_CONNECT = 'connect'
26
+ ADMIN_RESOURCES = %i[
27
+ accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
28
+ metadata_profiles email_notifications
29
+ ].freeze
30
+ JOB_RUNNING = %w[queued working].freeze
31
+ STANDARD_PATH = '/aspera/faspex'
32
+ private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED API_DETECT API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE])
28
33
  class << self
29
- def detect(base_url)
30
- api = Rest.new(base_url: base_url, redirect_max: 1)
31
- result = api.read(API_DETECT)
32
- if result[:http].code.start_with?('2') && result[:http].body.strip.empty?
33
- suffix_length = -2 - API_DETECT.length
34
+ def application_name
35
+ 'Faspex'
36
+ end
37
+
38
+ def detect(address_or_url)
39
+ address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
40
+ urls = [address_or_url]
41
+ urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
42
+
43
+ urls.each do |base_url|
44
+ next unless base_url.start_with?('https://')
45
+ api = Rest.new(base_url: base_url, redirect_max: 1)
46
+ result = api.read(API_DETECT)
47
+ next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
48
+ url_length = -2 - API_DETECT.length
49
+ # take redirect if any
34
50
  return {
35
51
  version: result[:http]['x-ibm-aspera'] || '5',
36
- url: result[:http].uri.to_s[0..suffix_length],
37
- name: 'Faspex 5'
52
+ url: result[:http].uri.to_s[0..url_length]
38
53
  }
54
+ rescue StandardError => e
55
+ Log.log.debug{"detect error: #{e}"}
39
56
  end
40
57
  return nil
41
58
  end
42
- end
43
59
 
44
- # Faspex API v5: get transfer spec for connect
45
- TRANSFER_CONNECT = 'connect'
60
+ def wizard(object:, private_key_path:, pub_key_pem:)
61
+ options = object.options
62
+ formatter = object.formatter
63
+ instance_url = options.get_option(:url, mandatory: true)
64
+ wiz_username = options.get_option(:username, mandatory: true)
65
+ raise "Username shall be an email in Faspex: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
66
+ if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
67
+ formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
68
+ formatter.display_status("Admin should login to: #{instance_url}")
69
+ OpenApplication.instance.uri(instance_url)
70
+ formatter.display_status('Navigate to: 𓃑 → Admin → Configurations → API clients')
71
+ formatter.display_status('Create an API client with:')
72
+ formatter.display_status('- name: ascli')
73
+ formatter.display_status('- JWT: enabled')
74
+ formatter.display_status("Then, logged in as #{wiz_username.red} go to your profile:")
75
+ formatter.display_status('👤 → Account Settings → Preferences -> Public Key in PEM:')
76
+ formatter.display_status(pub_key_pem)
77
+ formatter.display_status('Once set, fill in the parameters:')
78
+ end
79
+ return {
80
+ preset_value: {
81
+ url: instance_url,
82
+ username: wiz_username,
83
+ auth: :jwt.to_s,
84
+ private_key: "@file:#{private_key_path}",
85
+ client_id: options.get_option(:client_id, mandatory: true),
86
+ client_secret: options.get_option(:client_secret, mandatory: true)
87
+ },
88
+ test_args: 'user profile show'
89
+ }
90
+ end
91
+
92
+ def public_link?(url)
93
+ url.include?('/public/')
94
+ end
95
+ end
46
96
 
47
97
  def initialize(env)
48
98
  super(env)
49
99
  options.declare(:client_id, 'OAuth client identifier')
50
100
  options.declare(:client_secret, 'OAuth client secret')
51
101
  options.declare(:redirect_uri, 'OAuth redirect URI for web authentication')
52
- options.declare(:auth, 'OAuth type of authentication', values: %i[boot link].concat(Oauth::STD_AUTH_TYPES), default: :jwt)
102
+ options.declare(:auth, 'OAuth type of authentication', values: %i[boot].concat(Oauth::STD_AUTH_TYPES), default: :jwt)
53
103
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
54
104
  options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
55
- options.declare(:link, 'Public link authorization (specific operations)')
56
- options.declare(:box, "Package inbox, either shared inbox name or one of #{API_LIST_MAILBOX_TYPES} or #{VAL_ALL}", default: 'inbox')
105
+ options.declare(:box, "Package inbox, either shared inbox name or one of #{API_LIST_MAILBOX_TYPES} or #{ExtendedValue::ALL}", default: 'inbox')
57
106
  options.declare(:shared_folder, 'Send package with files from shared folder')
58
- options.declare(:group_type, 'Shared inbox or workgroup', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
107
+ options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
59
108
  options.parse_options!
60
109
  end
61
110
 
111
+ def api_url
112
+ return "#{@faspex5_api_base_url}/api/v5"
113
+ end
114
+
115
+ def auth_api_url
116
+ return "#{@faspex5_api_base_url}/auth"
117
+ end
118
+
62
119
  def set_api
63
- public_link = options.get_option(:link)
64
- unless public_link.nil?
65
- @faspex5_api_base_url = public_link.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
66
- options.set_option(:auth, :link)
67
- end
68
- @faspex5_api_base_url ||= options.get_option(:url, mandatory: true).gsub(%r{/+$}, '')
69
- @faspex5_api_auth_url = "#{@faspex5_api_base_url}/auth"
70
- faspex5_api_v5_url = "#{@faspex5_api_base_url}/api/v5"
71
- case options.get_option(:auth, mandatory: true)
72
- when :link
73
- uri = URI.parse(public_link)
74
- args = URI.decode_www_form(uri.query).each_with_object({}){|v, h|h[v.first] = v.last; }
75
- Log.dump(:args, args)
76
- context = args['context']
77
- raise 'missing context' if context.nil?
78
- @pub_link_context = JSON.parse(Base64.decode64(context))
79
- Log.dump(:@pub_link_context, @pub_link_context)
120
+ # get endpoint, remove unnecessary trailing slashes
121
+ @faspex5_api_base_url = options.get_option(:url, mandatory: true).gsub(%r{/+$}, '')
122
+ auth_type = self.class.public_link?(@faspex5_api_base_url) ? :public_link : options.get_option(:auth, mandatory: true)
123
+ case auth_type
124
+ when :public_link
125
+ encoded_context = Rest.decode_query(URI.parse(@faspex5_api_base_url).query)['context']
126
+ raise 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
127
+ @pub_link_context = JSON.parse(Base64.decode64(encoded_context))
128
+ Log.log.trace1{Log.dump(:@pub_link_context, @pub_link_context)}
129
+ # ok, we have the additional parameters, get the base url
130
+ @faspex5_api_base_url = @faspex5_api_base_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
80
131
  @api_v5 = Rest.new({
81
- base_url: faspex5_api_v5_url,
132
+ base_url: api_url,
82
133
  headers: {'Passcode' => @pub_link_context['passcode']}
83
134
  })
84
135
  when :boot
85
136
  # the password here is the token copied directly from browser in developer mode
86
137
  @api_v5 = Rest.new({
87
- base_url: faspex5_api_v5_url,
138
+ base_url: api_url,
88
139
  headers: {'Authorization' => options.get_option(:password, mandatory: true)}
89
140
  })
90
141
  when :web
91
142
  # opens a browser and ask user to auth using web
92
143
  @api_v5 = Rest.new({
93
- base_url: faspex5_api_v5_url,
144
+ base_url: api_url,
94
145
  auth: {
95
146
  type: :oauth2,
96
- base_url: @faspex5_api_auth_url,
147
+ base_url: auth_api_url,
97
148
  grant_method: :web,
98
149
  client_id: options.get_option(:client_id, mandatory: true),
99
150
  web: {redirect_uri: options.get_option(:redirect_uri, mandatory: true)}
@@ -101,10 +152,10 @@ module Aspera
101
152
  when :jwt
102
153
  app_client_id = options.get_option(:client_id, mandatory: true)
103
154
  @api_v5 = Rest.new({
104
- base_url: faspex5_api_v5_url,
155
+ base_url: api_url,
105
156
  auth: {
106
157
  type: :oauth2,
107
- base_url: @faspex5_api_auth_url,
158
+ base_url: auth_api_url,
108
159
  grant_method: :jwt,
109
160
  client_id: app_client_id,
110
161
  jwt: {
@@ -147,32 +198,45 @@ module Aspera
147
198
 
148
199
  # wait for package status to be in provided list
149
200
  def wait_package_status(id, status_list: PACKAGE_TERMINATED)
150
- spinner = nil
151
- progress = nil
152
- while true
201
+ total_sent = false
202
+ loop do
153
203
  status = @api_v5.read("packages/#{id}/upload_details")[:data]
204
+ status['id'] = id
154
205
  # user asked to not follow
155
- break if status_list.nil? || status_list.include?(status['upload_status'])
206
+ return status if status_list.nil?
156
207
  if status['upload_status'].eql?('submitted')
157
- if spinner.nil?
158
- spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
159
- spinner.start
160
- end
161
- spinner.update(title: status['upload_status'])
162
- spinner.spin
163
- elsif progress.nil?
164
- progress = ProgressBar.create(
165
- format: '%a %B %p%% %r Mbps %e',
166
- rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
167
- title: 'progress',
168
- total: status['bytes_total'].to_i)
208
+ config.progress_bar&.event(session_id: nil, type: :pre_start, info: status['upload_status'])
209
+ elsif !total_sent
210
+ config.progress_bar&.event(session_id: id, type: :session_start)
211
+ config.progress_bar&.event(session_id: id, type: :session_size, info: status['bytes_total'].to_i)
212
+ total_sent = true
169
213
  else
170
- progress.progress = status['bytes_written'].to_i
214
+ config.progress_bar&.event(session_id: id, type: :transfer, info: status['bytes_written'].to_i)
215
+ end
216
+ if status_list.include?(status['upload_status'])
217
+ # if status['upload_status'].eql?('completed')
218
+ config.progress_bar&.event(session_id: id, type: :end)
219
+ return status
220
+ # end
221
+ end
222
+ sleep(1.0)
223
+ end
224
+ end
225
+
226
+ def wait_for_job(job_id)
227
+ spinner = nil
228
+ loop do
229
+ status = @api_v5.read("jobs/#{job_id}", {type: :formatted})[:data]
230
+ return status unless JOB_RUNNING.include?(status['status'])
231
+ if spinner.nil?
232
+ spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
233
+ spinner.start
171
234
  end
235
+ spinner.update(title: status['status'])
236
+ spinner.spin
172
237
  sleep(0.5)
173
238
  end
174
- status['id'] = id
175
- return status
239
+ raise 'internal error'
176
240
  end
177
241
 
178
242
  # get a (full or partial) list of all entities of a given type
@@ -228,7 +292,7 @@ module Aspera
228
292
  box = options.get_option(:box)
229
293
  api_path =
230
294
  case box
231
- when VAL_ALL then '' # only admin can list all packages globally
295
+ when ExtendedValue::ALL then '' # only admin can list all packages globally
232
296
  when *API_LIST_MAILBOX_TYPES then box
233
297
  else
234
298
  group_type = options.get_option(:group_type)
@@ -240,7 +304,7 @@ module Aspera
240
304
  path: api_path).select(&filter)
241
305
  end
242
306
 
243
- def package_receive
307
+ def package_receive(package_ids)
244
308
  # prepare persistency if needed
245
309
  skip_ids_persistency = nil
246
310
  if options.get_option(:once_only, mandatory: true)
@@ -253,50 +317,43 @@ module Aspera
253
317
  options.get_option(:url, mandatory: true),
254
318
  options.get_option(:username, mandatory: true)]))
255
319
  end
256
- package_ids =
257
- if @pub_link_context&.key?('package_id')
258
- @pub_link_context['package_id']
259
- else
260
- # one or several packages
261
- instance_identifier
262
- end
263
320
  case package_ids
264
321
  when PACKAGE_ALL_INIT
265
322
  raise 'Only with option once_only' unless skip_ids_persistency
266
323
  skip_ids_persistency.data.clear.concat(list_packages_with_filter.map{|p|p['id']})
267
324
  skip_ids_persistency.save
268
325
  return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
269
- when VAL_ALL
326
+ when ExtendedValue::ALL
270
327
  # TODO: if packages have same name, they will overwrite ?
271
328
  package_ids = list_packages_with_filter.map{|p|p['id']}
272
- Log.dump(:package_ids, package_ids)
273
- Log.dump(:package_ids, skip_ids_persistency.data)
329
+ Log.log.debug{Log.dump(:package_ids, package_ids)}
330
+ Log.log.debug{Log.dump(:skip_ids, skip_ids_persistency.data)}
274
331
  package_ids.reject!{|i|skip_ids_persistency.data.include?(i)} if skip_ids_persistency
275
- Log.dump(:package_ids, package_ids)
332
+ Log.log.debug{Log.dump(:package_ids, package_ids)}
276
333
  end
277
334
  # a single id was provided
278
335
  # TODO: check package_ids is a list of strings
279
336
  package_ids = [package_ids] if package_ids.is_a?(String)
280
337
  result_transfer = []
338
+ param_file_list = {}
339
+ begin
340
+ param_file_list['paths'] = transfer.source_list.map{|source|{'path'=>source}}
341
+ rescue Cli::BadArgument
342
+ # paths is optional
343
+ end
344
+ download_params = {
345
+ type: 'received',
346
+ transfer_type: TRANSFER_CONNECT
347
+ }
348
+ box = options.get_option(:box)
349
+ case box
350
+ when /outbox/ then download_params[:type] = 'sent'
351
+ when *API_LIST_MAILBOX_TYPES then nil # nothing to do
352
+ else # shared inbox / workgroup
353
+ download_params[:recipient_workgroup_id] = lookup_entity_by_field(type: options.get_option(:group_type), value: box)['id']
354
+ end
281
355
  package_ids.each do |pkg_id|
282
356
  formatter.display_status("Receiving package #{pkg_id}")
283
- param_file_list = {}
284
- begin
285
- param_file_list['paths'] = transfer.source_list.map{|source|{'path'=>source}}
286
- rescue Aspera::Cli::CliBadArgument
287
- # paths is optional
288
- end
289
- download_params = {
290
- type: 'received',
291
- transfer_type: TRANSFER_CONNECT
292
- }
293
- box = options.get_option(:box)
294
- case box
295
- when /outbox/ then download_params[:type] = 'sent'
296
- when *API_LIST_MAILBOX_TYPES then nil # nothing to do
297
- else # shared inbox / workgroup
298
- download_params[:recipient_workgroup_id] = lookup_entity_by_field(type: options.get_option(:group_type), value: box)['id']
299
- end
300
357
  # TODO: allow from sent as well ?
301
358
  transfer_spec = @api_v5.call(
302
359
  operation: 'POST',
@@ -319,21 +376,15 @@ module Aspera
319
376
  end
320
377
 
321
378
  def package_action
322
- command = options.get_next_command(%i[list show browse status delete send receive])
379
+ command = options.get_next_command(%i[show browse status delete receive send list])
380
+ package_id =
381
+ if %i[receive show browse status delete].include?(command)
382
+ @pub_link_context&.key?('package_id') ? @pub_link_context['package_id'] : instance_identifier
383
+ end
323
384
  case command
324
- when :list
325
- return {
326
- type: :object_list,
327
- data: list_packages_with_filter,
328
- fields: %w[id title release_date total_bytes total_files created_time state]
329
- }
330
385
  when :show
331
- id = @pub_link_context['package_id'] if @pub_link_context&.key?('package_id')
332
- id ||= instance_identifier
333
- return {type: :single_object, data: @api_v5.read("packages/#{id}")[:data]}
386
+ return {type: :single_object, data: @api_v5.read("packages/#{package_id}")[:data]}
334
387
  when :browse
335
- id = @pub_link_context['package_id'] if @pub_link_context&.key?('package_id')
336
- id ||= instance_identifier
337
388
  path = options.get_next_argument('path', expected: :single, mandatory: false) || '/'
338
389
  # TODO: support multi-page listing ?
339
390
  params = {
@@ -343,29 +394,31 @@ module Aspera
343
394
  }
344
395
  result = @api_v5.call({
345
396
  operation: 'POST',
346
- subpath: "packages/#{id}/files/received",
397
+ subpath: "packages/#{package_id}/files/received",
347
398
  headers: {'Accept' => 'application/json'},
348
399
  url_params: params,
349
400
  json_params: {'path' => path, 'filters' => {'basenames'=>[]}}})[:data]
350
401
  formatter.display_item_count(result['item_count'], result['total_count'])
351
402
  return {type: :object_list, data: result['items']}
352
403
  when :status
353
- status = wait_package_status(instance_identifier, status_list: nil)
404
+ status = wait_package_status(package_id, status_list: nil)
354
405
  return {type: :single_object, data: status}
355
406
  when :delete
356
- ids = instance_identifier
407
+ ids = package_id
357
408
  ids = [ids] unless ids.is_a?(Array)
358
409
  raise 'Package identifier must be a single id or an Array' unless ids.is_a?(Array) && ids.all?(String)
359
410
  # API returns 204, empty on success
360
411
  @api_v5.call({operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids}})
361
412
  return Main.result_status('Package(s) deleted')
413
+ when :receive
414
+ return package_receive(package_id)
362
415
  when :send
363
- parameters = value_create_modify(command: command, type: Hash)
416
+ parameters = value_create_modify(command: command)
364
417
  normalize_recipients(parameters)
365
418
  package = @api_v5.create('packages', parameters)[:data]
366
419
  shared_folder = options.get_option(:shared_folder)
367
420
  if shared_folder.nil?
368
- # TODO: option to send from remote source or httpgw
421
+ # send from local files
369
422
  transfer_spec = @api_v5.call(
370
423
  operation: 'POST',
371
424
  subpath: "packages/#{package['id']}/transfer_spec/upload",
@@ -377,6 +430,7 @@ module Aspera
377
430
  transfer_spec.delete('authentication')
378
431
  return Main.result_transfer(transfer.start(transfer_spec))
379
432
  else
433
+ # send from remote shared folder
380
434
  if (m = shared_folder.match(REGEX_LOOKUP_ID_BY_FIELD))
381
435
  shared_folder = lookup_entity_by_field(type: 'shared_folders', value: m[2])['id']
382
436
  end
@@ -390,8 +444,12 @@ module Aspera
390
444
  end
391
445
  return {type: :single_object, data: result}
392
446
  end
393
- when :receive
394
- return package_receive
447
+ when :list
448
+ return {
449
+ type: :object_list,
450
+ data: list_packages_with_filter,
451
+ fields: %w[id title release_date total_bytes total_files created_time state]
452
+ }
395
453
  end # case package
396
454
  end
397
455
 
@@ -474,10 +532,10 @@ module Aspera
474
532
  list_key = false
475
533
  id_as_arg = 'type'
476
534
  when :accounts
477
- display_fields = [:all_but, 'user_profile_data_attributes']
535
+ display_fields = Formatter.all_but('user_profile_data_attributes')
478
536
  when :oauth_clients
479
- display_fields = [:all_but, 'public_key']
480
- adm_api = Rest.new(@api_v5.params.merge({base_url: @faspex5_api_auth_url}))
537
+ display_fields = Formatter.all_but('public_key')
538
+ adm_api = Rest.new(@api_v5.params.merge({base_url: auth_api_url}))
481
539
  when :shared_inboxes, :workgroups
482
540
  available_commands.push(:members, :saml_groups, :invite_external_collaborator)
483
541
  end
@@ -533,20 +591,23 @@ module Aspera
533
591
  when :show
534
592
  return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
535
593
  when :create
536
- return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd, type: Hash))[:data] }
594
+ return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
537
595
  when :modify
538
- return { type: :single_object, data: @api_v5.modify(smtp_path, value_create_modify(command: smtp_cmd, type: Hash))[:data] }
596
+ return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
539
597
  when :delete
540
598
  return { type: :single_object, data: @api_v5.delete(smtp_path)[:data] }
541
599
  when :test
542
600
  test_data = options.get_next_argument('Email or test data, see API')
543
601
  test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
544
- return { type: :single_object, data: @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data] }
602
+ creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
603
+ result = wait_for_job(creation['job_id'])
604
+ result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
605
+ return { type: :single_object, data: result }
545
606
  end
546
607
  end
547
608
  when :gateway
548
609
  require 'aspera/faspex_gw'
549
- url = value_create_modify(type: String)
610
+ url = value_create_modify(command: command, type: String)
550
611
  uri = URI.parse(url)
551
612
  server = WebServerSimple.new(uri)
552
613
  server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
@@ -559,7 +620,7 @@ module Aspera
559
620
  return Main.result_status('Gateway terminated')
560
621
  when :postprocessing
561
622
  require 'aspera/faspex_postproc' # cspell:disable-line
562
- parameters = value_create_modify(type: Hash)
623
+ parameters = value_create_modify(command: command)
563
624
  parameters = parameters.symbolize_keys
564
625
  raise 'Missing key: url' unless parameters.key?(:url)
565
626
  uri = URI.parse(parameters[:url])
@@ -576,36 +637,6 @@ module Aspera
576
637
  return Main.result_status('Gateway terminated')
577
638
  end # case command
578
639
  end # action
579
-
580
- def wizard(params)
581
- if params[:prepare]
582
- # if not defined by user, generate unique name
583
- params[:preset_name] ||= [params[:plugin_sym]].concat(URI.parse(params[:instance_url]).host.gsub(/[^a-z0-9.]/, '').split('.')).join('_')
584
- params[:need_private_key] = true
585
- return
586
- end
587
- formatter.display_status('Ask the ascli client id and secret to your Administrator, or ask them to go to:'.red)
588
- OpenApplication.instance.uri(params[:instance_url])
589
- formatter.display_status('Then: 𓃑 → Admin → Configurations → API clients')
590
- formatter.display_status('Create an API client with:')
591
- formatter.display_status('- name: ascli')
592
- formatter.display_status('- JWT: enabled')
593
- formatter.display_status('Then, logged in as user go to your profile:')
594
- formatter.display_status('👤 → Account Settings → Preferences -> Public Key in PEM:')
595
- formatter.display_status(params[:pub_key_pem])
596
- formatter.display_status('Once set, fill in the parameters:')
597
- return {
598
- preset_value: {
599
- url: params[:instance_url],
600
- username: options.get_option(:username, mandatory: true),
601
- auth: :jwt.to_s,
602
- private_key: '@file:' + params[:private_key_path],
603
- client_id: options.get_option(:client_id, mandatory: true),
604
- client_secret: options.get_option(:client_secret, mandatory: true)
605
- },
606
- test_args: "#{params[:plugin_sym]} user profile show"
607
- }
608
- end
609
640
  end # Faspex5
610
641
  end # Plugins
611
642
  end # Cli