aspera-cli 4.16.0 → 4.17.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 (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -19
  4. data/CONTRIBUTING.md +3 -1
  5. data/README.md +965 -793
  6. data/bin/asession +29 -21
  7. data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
  8. data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
  9. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  10. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
  11. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
  12. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
  13. data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
  14. data/lib/aspera/api/aoc.rb +586 -0
  15. data/lib/aspera/api/ats.rb +46 -0
  16. data/lib/aspera/api/cos_node.rb +95 -0
  17. data/lib/aspera/api/node.rb +344 -0
  18. data/lib/aspera/ascmd.rb +46 -10
  19. data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
  20. data/lib/aspera/{fasp → ascp}/management.rb +3 -8
  21. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  22. data/lib/aspera/assert.rb +30 -30
  23. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  24. data/lib/aspera/cli/extended_value.rb +1 -1
  25. data/lib/aspera/cli/formatter.rb +13 -13
  26. data/lib/aspera/cli/hints.rb +5 -5
  27. data/lib/aspera/cli/main.rb +35 -28
  28. data/lib/aspera/cli/manager.rb +25 -24
  29. data/lib/aspera/cli/plugin.rb +22 -15
  30. data/lib/aspera/cli/plugin_factory.rb +61 -0
  31. data/lib/aspera/cli/plugins/alee.rb +7 -7
  32. data/lib/aspera/cli/plugins/aoc.rb +83 -77
  33. data/lib/aspera/cli/plugins/ats.rb +32 -33
  34. data/lib/aspera/cli/plugins/bss.rb +3 -4
  35. data/lib/aspera/cli/plugins/config.rb +169 -186
  36. data/lib/aspera/cli/plugins/console.rb +8 -6
  37. data/lib/aspera/cli/plugins/cos.rb +19 -18
  38. data/lib/aspera/cli/plugins/faspex.rb +61 -54
  39. data/lib/aspera/cli/plugins/faspex5.rb +150 -103
  40. data/lib/aspera/cli/plugins/node.rb +68 -73
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
  42. data/lib/aspera/cli/plugins/preview.rb +31 -31
  43. data/lib/aspera/cli/plugins/server.rb +31 -33
  44. data/lib/aspera/cli/plugins/shares.rb +13 -11
  45. data/lib/aspera/cli/sync_actions.rb +8 -8
  46. data/lib/aspera/cli/transfer_agent.rb +32 -19
  47. data/lib/aspera/cli/transfer_progress.rb +1 -1
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/colors.rb +5 -0
  50. data/lib/aspera/command_line_builder.rb +14 -14
  51. data/lib/aspera/coverage.rb +1 -2
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +2 -3
  54. data/lib/aspera/faspex_gw.rb +5 -6
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/id_generator.rb +2 -2
  57. data/lib/aspera/json_rpc.rb +5 -5
  58. data/lib/aspera/keychain/encrypted_hash.rb +6 -6
  59. data/lib/aspera/keychain/macos_security.rb +27 -22
  60. data/lib/aspera/log.rb +2 -2
  61. data/lib/aspera/nagios.rb +3 -3
  62. data/lib/aspera/node_simulator.rb +5 -6
  63. data/lib/aspera/oauth/base.rb +143 -0
  64. data/lib/aspera/oauth/factory.rb +124 -0
  65. data/lib/aspera/oauth/generic.rb +34 -0
  66. data/lib/aspera/oauth/jwt.rb +51 -0
  67. data/lib/aspera/oauth/url_json.rb +31 -0
  68. data/lib/aspera/oauth/web.rb +50 -0
  69. data/lib/aspera/oauth.rb +5 -331
  70. data/lib/aspera/open_application.rb +7 -7
  71. data/lib/aspera/persistency_action_once.rb +4 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +5 -5
  74. data/lib/aspera/preview/terminal.rb +3 -2
  75. data/lib/aspera/preview/utils.rb +3 -3
  76. data/lib/aspera/proxy_auto_config.rb +4 -4
  77. data/lib/aspera/rest.rb +175 -144
  78. data/lib/aspera/rest_errors_aspera.rb +3 -3
  79. data/lib/aspera/resumer.rb +77 -0
  80. data/lib/aspera/ssh.rb +6 -1
  81. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  82. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  83. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  84. data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
  85. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
  86. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  87. data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
  88. data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
  89. data/lib/aspera/web_server_simple.rb +11 -3
  90. data.tar.gz.sig +0 -0
  91. metadata +36 -63
  92. metadata.gz.sig +0 -0
  93. data/lib/aspera/aoc.rb +0 -601
  94. data/lib/aspera/ats_api.rb +0 -47
  95. data/lib/aspera/cos_node.rb +0 -94
  96. data/lib/aspera/fasp/resume_policy.rb +0 -79
  97. data/lib/aspera/node.rb +0 -339
@@ -15,10 +15,9 @@ require 'tty-spinner'
15
15
  module Aspera
16
16
  module Cli
17
17
  module Plugins
18
- class Faspex5 < Aspera::Cli::BasicAuthPlugin
18
+ class Faspex5 < Cli::BasicAuthPlugin
19
19
  RECIPIENT_TYPES = %w[user workgroup external_user distribution_list shared_inbox].freeze
20
20
  PACKAGE_TERMINATED = %w[completed failed].freeze
21
- API_DETECT = 'api/v5/configuration/ping'
22
21
  # list of supported mailbox types (to list packages)
23
22
  API_LIST_MAILBOX_TYPES = %w[inbox inbox_history inbox_all inbox_all_history outbox outbox_history pending pending_history all].freeze
24
23
  PACKAGE_SEND_FROM_REMOTE_SOURCE = 'remote_source'
@@ -28,34 +27,49 @@ module Aspera
28
27
  accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
29
28
  metadata_profiles email_notifications alternate_addresses
30
29
  ].freeze
30
+ # states for jobs not in final state
31
31
  JOB_RUNNING = %w[queued working].freeze
32
- STANDARD_PATH = '/aspera/faspex'
32
+ PATH_STANDARD_ROOT = '/aspera/faspex'
33
+ PATH_API_V5 = 'api/v5'
34
+ # endpoint for authentication API
35
+ PATH_AUTH = 'auth'
36
+ PATH_HEALTH = 'configuration/ping'
33
37
  PER_PAGE_DEFAULT = 100
34
- private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED API_DETECT API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE PER_PAGE_DEFAULT])
38
+ # OAuth methods supported
39
+ STD_AUTH_TYPES = %i[web jwt boot].freeze
40
+ HEADER_ITERATION_TOKEN = 'X-Aspera-Next-Iteration-Token'
41
+ private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED PATH_HEALTH API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE PER_PAGE_DEFAULT
42
+ STD_AUTH_TYPES])
35
43
  class << self
36
44
  def application_name
37
45
  'Faspex'
38
46
  end
39
47
 
40
48
  def detect(address_or_url)
49
+ # add scheme if missing
41
50
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
42
51
  urls = [address_or_url]
43
- urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
44
-
52
+ urls.push("#{address_or_url}#{PATH_STANDARD_ROOT}") unless address_or_url.end_with?(PATH_STANDARD_ROOT)
53
+ error = nil
45
54
  urls.each do |base_url|
55
+ # Faspex is always HTTPS
46
56
  next unless base_url.start_with?('https://')
47
57
  api = Rest.new(base_url: base_url, redirect_max: 1)
48
- result = api.read(API_DETECT)
58
+ path_api_detect = "#{PATH_API_V5}/#{PATH_HEALTH}"
59
+ result = api.read(path_api_detect)
49
60
  next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
50
- url_length = -2 - API_DETECT.length
61
+ # end is at -1, and substract 1 for "/"
62
+ url_length = -2 - path_api_detect.length
51
63
  # take redirect if any
52
64
  return {
53
65
  version: result[:http]['x-ibm-aspera'] || '5',
54
66
  url: result[:http].uri.to_s[0..url_length]
55
67
  }
56
68
  rescue StandardError => e
69
+ error = e
57
70
  Log.log.debug{"detect error: #{e}"}
58
71
  end
72
+ raise error if error
59
73
  return nil
60
74
  end
61
75
 
@@ -91,31 +105,25 @@ module Aspera
91
105
  }
92
106
  end
93
107
 
108
+ # @return true if the URL is a public link
94
109
  def public_link?(url)
95
- url.include?('/public/')
110
+ url.include?('?context=')
96
111
  end
97
112
  end
98
113
 
99
- def initialize(env)
100
- super(env)
114
+ def initialize(**env)
115
+ super
101
116
  options.declare(:client_id, 'OAuth client identifier')
102
117
  options.declare(:client_secret, 'OAuth client secret')
103
118
  options.declare(:redirect_uri, 'OAuth redirect URI for web authentication')
104
- options.declare(:auth, 'OAuth type of authentication', values: %i[boot].concat(Oauth::STD_AUTH_TYPES), default: :jwt)
119
+ options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
105
120
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
106
121
  options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
107
122
  options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{ExtendedValue::ALL}", default: 'inbox')
108
123
  options.declare(:shared_folder, 'Send package with files from shared folder')
109
124
  options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
110
125
  options.parse_options!
111
- end
112
-
113
- def api_url
114
- return "#{@faspex5_api_base_url}/api/v5"
115
- end
116
-
117
- def auth_api_url
118
- return "#{@faspex5_api_base_url}/auth"
126
+ @pub_link_context = nil
119
127
  end
120
128
 
121
129
  def set_api
@@ -124,60 +132,63 @@ module Aspera
124
132
  auth_type = self.class.public_link?(@faspex5_api_base_url) ? :public_link : options.get_option(:auth, mandatory: true)
125
133
  case auth_type
126
134
  when :public_link
135
+ # resolve any redirect
136
+ @faspex5_api_base_url = Rest.new(base_url: @faspex5_api_base_url, redirect_max: 3).read('')[:http].uri.to_s
127
137
  encoded_context = Rest.decode_query(URI.parse(@faspex5_api_base_url).query)['context']
128
138
  raise 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
139
+ # public link information (allowed usage)
129
140
  @pub_link_context = JSON.parse(Base64.decode64(encoded_context))
130
141
  Log.log.trace1{Log.dump(:@pub_link_context, @pub_link_context)}
131
142
  # ok, we have the additional parameters, get the base url
132
143
  @faspex5_api_base_url = @faspex5_api_base_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
133
- @api_v5 = Rest.new({
134
- base_url: api_url,
144
+ @api_v5 = Rest.new(
145
+ base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
135
146
  headers: {'Passcode' => @pub_link_context['passcode']}
136
- })
147
+ )
137
148
  when :boot
138
149
  # the password here is the token copied directly from browser in developer mode
139
- @api_v5 = Rest.new({
140
- base_url: api_url,
150
+ @api_v5 = Rest.new(
151
+ base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
141
152
  headers: {'Authorization' => options.get_option(:password, mandatory: true)}
142
- })
153
+ )
143
154
  when :web
144
155
  # opens a browser and ask user to auth using web
145
- @api_v5 = Rest.new({
146
- base_url: api_url,
156
+ @api_v5 = Rest.new(
157
+ base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
147
158
  auth: {
148
159
  type: :oauth2,
149
- base_url: auth_api_url,
160
+ base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
150
161
  grant_method: :web,
151
162
  client_id: options.get_option(:client_id, mandatory: true),
152
- web: {redirect_uri: options.get_option(:redirect_uri, mandatory: true)}
153
- }})
163
+ redirect_uri: options.get_option(:redirect_uri, mandatory: true)
164
+ })
154
165
  when :jwt
155
166
  app_client_id = options.get_option(:client_id, mandatory: true)
156
- @api_v5 = Rest.new({
157
- base_url: api_url,
167
+ @api_v5 = Rest.new(
168
+ base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
158
169
  auth: {
159
- type: :oauth2,
160
- base_url: auth_api_url,
161
- grant_method: :jwt,
162
- client_id: app_client_id,
163
- jwt: {
164
- payload: {
165
- iss: app_client_id, # issuer
166
- aud: app_client_id, # audience (this field is not clear...)
167
- sub: "user:#{options.get_option(:username, mandatory: true)}" # subject is a user
168
- },
169
- private_key_obj: OpenSSL::PKey::RSA.new(options.get_option(:private_key, mandatory: true), options.get_option(:passphrase)),
170
- headers: {typ: 'JWT'}
171
- }
172
- }})
173
- else error_unexpected_value(auth_type)
170
+ type: :oauth2,
171
+ grant_method: :jwt,
172
+ base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
173
+ client_id: app_client_id,
174
+ payload: {
175
+ iss: app_client_id, # issuer
176
+ aud: app_client_id, # audience (this field is not clear...)
177
+ sub: "user:#{options.get_option(:username, mandatory: true)}" # subject is a user
178
+ },
179
+ private_key_obj: OpenSSL::PKey::RSA.new(options.get_option(:private_key, mandatory: true), options.get_option(:passphrase)),
180
+ headers: {typ: 'JWT'}
181
+ })
182
+ else Aspera.error_unexpected_value(auth_type)
174
183
  end
184
+ # in case user wants to use HTTPGW tell transfer agent how to get address
185
+ transfer.httpgw_url_cb = lambda { @api_v5.read('account')[:data]['gateway_url'] }
175
186
  end
176
187
 
177
188
  # if recipient is just an email, then convert to expected API hash : name and type
178
189
  def normalize_recipients(parameters)
179
190
  return unless parameters.key?('recipients')
180
- assert_type(parameters['recipients'], Array){'recipients'}
191
+ Aspera.assert_type(parameters['recipients'], Array){'recipients'}
181
192
  recipient_types = RECIPIENT_TYPES
182
193
  if parameters.key?('recipient_types')
183
194
  recipient_types = parameters['recipient_types']
@@ -185,7 +196,7 @@ module Aspera
185
196
  recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
186
197
  end
187
198
  parameters['recipients'].map! do |recipient_data|
188
- # if just a string, assume it is the name
199
+ # if just a string, make a general lookup and build expected name/type hash
189
200
  if recipient_data.is_a?(String)
190
201
  matched = @api_v5.lookup_by_name('contacts', recipient_data, {context: 'packages', type: Rest.array_params(recipient_types)})
191
202
  recipient_data = {
@@ -238,7 +249,7 @@ module Aspera
238
249
  spinner.spin
239
250
  sleep(0.5)
240
251
  end
241
- error_unreachable_line
252
+ Aspera.error_unreachable_line
242
253
  end
243
254
 
244
255
  # Get a (full or partial) list of all entities of a given type
@@ -248,7 +259,7 @@ module Aspera
248
259
  # @param item_list_key [String] key in the result to get the list of items
249
260
  def list_entities(type:, real_path: nil, query: {}, item_list_key: nil)
250
261
  type = type.to_s if type.is_a?(Symbol)
251
- assert_type(type, String)
262
+ Aspera.assert_type(type, String)
252
263
  item_list_key = type if item_list_key.nil?
253
264
  full_path = real_path.nil? ? type : real_path
254
265
  result = []
@@ -277,7 +288,7 @@ module Aspera
277
288
  # lookup an entity id from its name
278
289
  def lookup_entity_by_field(type:, value:, field: 'name', query: :default, real_path: nil, item_list_key: nil)
279
290
  if query.eql?(:default)
280
- assert(field.eql?('name')){'Default query is on name only'}
291
+ Aspera.assert(field.eql?('name')){'Default query is on name only'}
281
292
  query = {'q'=> value}
282
293
  end
283
294
  found = list_entities(type: type, real_path: real_path, query: query, item_list_key: item_list_key).select{|i|i[field].eql?(value)}
@@ -313,7 +324,7 @@ module Aspera
313
324
  if options.get_option(:once_only, mandatory: true)
314
325
  # read ids from persistency
315
326
  skip_ids_persistency = PersistencyActionOnce.new(
316
- manager: @agents[:persistency],
327
+ manager: persistency,
317
328
  data: [],
318
329
  id: IdGenerator.from_list([
319
330
  'faspex_recv',
@@ -325,7 +336,7 @@ module Aspera
325
336
  packages = []
326
337
  case package_ids
327
338
  when ExtendedValue::INIT
328
- assert(skip_ids_persistency){'Only with option once_only'}
339
+ Aspera.assert(skip_ids_persistency){'Only with option once_only'}
329
340
  skip_ids_persistency.data.clear.concat(list_packages_with_filter.map{|p|p['id']})
330
341
  skip_ids_persistency.save
331
342
  return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
@@ -339,8 +350,8 @@ module Aspera
339
350
  else
340
351
  # a single id was provided, or a list of ids
341
352
  package_ids = [package_ids] unless package_ids.is_a?(Array)
342
- assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
343
- assert(package_ids.all?(String)){'Package id shall be String'}
353
+ Aspera.assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
354
+ Aspera.assert(package_ids.all?(String)){'Package id shall be String'}
344
355
  # packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")[:data]}
345
356
  packages = package_ids.map{|pkg_id|{'id'=>pkg_id}}
346
357
  end
@@ -386,6 +397,45 @@ module Aspera
386
397
  return Main.result_transfer_multiple(result_transfer)
387
398
  end
388
399
 
400
+ # browse a folder
401
+ # @param browse_endpoint [String] the endpoint to browse
402
+ def browse_folder(browse_endpoint)
403
+ path = options.get_next_argument('folder path', mandatory: false, default: '/')
404
+ query = query_read_delete(default: {})
405
+ query['filters'] = {} unless query.key?('filters')
406
+ filters = query.delete('filters')
407
+ filters['basenames'] = [] unless filters.key?('basenames')
408
+ Aspera.assert_type(filters, Hash){'filters'}
409
+ max_items = query.delete('max')
410
+ recursive = query.delete('recursive')
411
+ all_items = []
412
+ folders_to_process = [path]
413
+ until folders_to_process.empty?
414
+ path = folders_to_process.shift
415
+ query.delete('iteration_token')
416
+ loop do
417
+ response = @api_v5.call(
418
+ operation: 'POST',
419
+ subpath: browse_endpoint,
420
+ headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
421
+ url_params: query,
422
+ json_params: {'path' => path, 'filters' => filters})
423
+ all_items.concat(response[:data]['items'])
424
+ if recursive
425
+ folders_to_process.concat(response[:data]['items'].select{|i|i['type'].eql?('directory')}.map{|i|i['path']})
426
+ end
427
+ if !max_items.nil? && (all_items.count >= max_items)
428
+ all_items = all_items.slice(0, max_items) if all_items.count > max_items
429
+ break
430
+ end
431
+ iteration_token = response[:http][HEADER_ITERATION_TOKEN]
432
+ break if iteration_token.nil? || iteration_token.empty?
433
+ query['iteration_token'] = iteration_token
434
+ end
435
+ end
436
+ return {type: :object_list, data: all_items}
437
+ end
438
+
389
439
  def package_action
390
440
  command = options.get_next_command(%i[show browse status delete receive send list])
391
441
  package_id =
@@ -396,36 +446,34 @@ module Aspera
396
446
  when :show
397
447
  return {type: :single_object, data: @api_v5.read("packages/#{package_id}")[:data]}
398
448
  when :browse
399
- path = options.get_next_argument('path', expected: :single, mandatory: false) || '/'
400
- # TODO: support multi-page listing ?
401
- params = {
402
- # recipient_user_id: 25,
403
- # offset: 0,
404
- # limit: 25
405
- }
406
- result = @api_v5.call({
407
- operation: 'POST',
408
- subpath: "packages/#{package_id}/files/received",
409
- headers: {'Accept' => 'application/json'},
410
- url_params: params,
411
- json_params: {'path' => path, 'filters' => {'basenames'=>[]}}})[:data]
412
- formatter.display_item_count(result['item_count'], result['total_count'])
413
- return {type: :object_list, data: result['items']}
449
+ location = case options.get_option(:box)
450
+ when 'inbox' then 'received'
451
+ when 'outbox' then 'sent'
452
+ else raise 'Browse only available for inbox and outbox'
453
+ end
454
+ return browse_folder("packages/#{package_id}/files/#{location}")
414
455
  when :status
415
456
  status = wait_package_status(package_id, status_list: nil)
416
457
  return {type: :single_object, data: status}
417
458
  when :delete
418
459
  ids = package_id
419
460
  ids = [ids] unless ids.is_a?(Array)
420
- assert_type(ids, Array){'Package identifier'}
421
- assert(ids.all?(String)){'Package id shall be String'}
461
+ Aspera.assert_type(ids, Array){'Package identifier'}
462
+ Aspera.assert(ids.all?(String)){'Package id shall be String'}
422
463
  # API returns 204, empty on success
423
- @api_v5.call({operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids}})
464
+ @api_v5.call(operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids})
424
465
  return Main.result_status('Package(s) deleted')
425
466
  when :receive
426
467
  return package_receive(package_id)
427
468
  when :send
428
469
  parameters = value_create_modify(command: command)
470
+ # autofill recipient for public url
471
+ if @pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
472
+ parameters['recipients'] = [{
473
+ name: @pub_link_context['name'],
474
+ recipient_type: @pub_link_context['recipient_type']
475
+ }]
476
+ end
429
477
  normalize_recipients(parameters)
430
478
  package = @api_v5.create('packages', parameters)[:data]
431
479
  shared_folder = options.get_option(:shared_folder)
@@ -465,7 +513,7 @@ module Aspera
465
513
  end # case package
466
514
  end
467
515
 
468
- ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing].freeze
516
+ ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing invitations].freeze
469
517
 
470
518
  def execute_action
471
519
  command = options.get_next_command(ACTIONS)
@@ -485,7 +533,9 @@ module Aspera
485
533
  end
486
534
  return nagios.result
487
535
  when :user
488
- case options.get_next_command(%i[profile])
536
+ case options.get_next_command(%i[account profile])
537
+ when :account
538
+ return { type: :single_object, data: @api_v5.read('account')[:data] }
489
539
  when :profile
490
540
  case options.get_next_command(%i[show modify])
491
541
  when :show
@@ -511,21 +561,9 @@ module Aspera
511
561
  raise "multiple matches for #{field} = #{value}" if matches.length > 1
512
562
  matches.first['id']
513
563
  end
514
- path = options.get_next_argument('folder path', mandatory: false) || '/'
515
564
  node = all_shared_folders.find{|i|i['id'].eql?(shared_folder_id)}
516
565
  raise "No such shared folder id #{shared_folder_id}" if node.nil?
517
- result = @api_v5.call({
518
- operation: 'POST',
519
- subpath: "nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse",
520
- headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
521
- json_params: {'path': path, 'filters': {'basenames': []}},
522
- url_params: {offset: 0, limit: 100}
523
- })[:data]
524
- if result.key?('items')
525
- return {type: :object_list, data: result['items']}
526
- else
527
- return {type: :single_object, data: result['self']}
528
- end
566
+ return browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse")
529
567
  end
530
568
  when :admin
531
569
  case options.get_next_command(%i[resource smtp].freeze)
@@ -550,12 +588,12 @@ module Aspera
550
588
  display_fields = Formatter.all_but('user_profile_data_attributes')
551
589
  when :oauth_clients
552
590
  display_fields = Formatter.all_but('public_key')
553
- adm_api = Rest.new(@api_v5.params.merge({base_url: auth_api_url}))
591
+ adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
554
592
  when :shared_inboxes, :workgroups
555
593
  available_commands.push(:members, :saml_groups, :invite_external_collaborator)
556
594
  special_query = {'all': true}
557
595
  when :nodes
558
- available_commands.push(:shared_folders)
596
+ available_commands.push(:shared_folders, :browse)
559
597
  end
560
598
  res_command = options.get_next_command(available_commands)
561
599
  case res_command
@@ -573,6 +611,8 @@ module Aspera
573
611
  lookup_entity_by_field(
574
612
  type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
575
613
  end
614
+ when :browse
615
+ return browse_folder("#{res_path}/#{instance_identifier}/browse")
576
616
  when :invite_external_collaborator
577
617
  shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
578
618
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
@@ -637,34 +677,41 @@ module Aspera
637
677
  return { type: :single_object, data: result }
638
678
  end
639
679
  end
680
+ when :invitations
681
+ invitation_endpoint = 'invitations'
682
+ invitation_command = options.get_next_command(%i[resend].concat(Plugin::ALL_OPS))
683
+ case invitation_command
684
+ when :create
685
+ return do_bulk_operation(command: invitation_command, descr: 'data') do |params|
686
+ invitation_endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
687
+ @api_v5.create(invitation_endpoint, params)[:data]
688
+ end
689
+ when :resend
690
+ @api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend")
691
+ return Main.result_status('Invitation resent')
692
+ else
693
+ return entity_command(
694
+ invitation_command, @api_v5, invitation_endpoint, item_list_key: invitation_endpoint,
695
+ display_fields: %w[id public recipient_type recipient_name email_address])
696
+ end
640
697
  when :gateway
641
698
  require 'aspera/faspex_gw'
642
699
  url = value_create_modify(command: command, description: 'listening url (e.g. https://localhost:12345)', type: String)
643
700
  uri = URI.parse(url)
644
701
  server = WebServerSimple.new(uri)
645
702
  server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
646
- # on ctrl-c, tell server main loop to exit
647
- trap('INT') { server.shutdown }
648
- formatter.display_status("Gateway for Faspex 4-style API listening on #{url}")
649
- Log.log.info("Listening on #{url}")
650
- # this is blocking until server exits
651
703
  server.start
652
704
  return Main.result_status('Gateway terminated')
653
705
  when :postprocessing
654
706
  require 'aspera/faspex_postproc' # cspell:disable-line
655
707
  parameters = value_create_modify(command: command)
656
708
  parameters = parameters.symbolize_keys
657
- assert(parameters.key?(:url)){'Missing key: url'}
709
+ Aspera.assert(parameters.key?(:url)){'Missing key: url'}
658
710
  uri = URI.parse(parameters[:url])
659
711
  parameters[:processing] ||= {}
660
712
  parameters[:processing][:root] = uri.path
661
713
  server = WebServerSimple.new(uri, certificate: parameters[:certificate])
662
714
  server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
663
- # on ctrl-c, tell server main loop to exit
664
- trap('INT') { server.shutdown }
665
- formatter.display_status("Web-hook for Faspex 4-style post processing listening on #{uri.port}")
666
- Log.log.info("Listening on #{uri.port}")
667
- # this is blocking until server exits
668
715
  server.start
669
716
  return Main.result_status('Gateway terminated')
670
717
  end # case command