aspera-cli 4.25.2 → 4.25.4
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +430 -406
- data/CONTRIBUTING.md +104 -93
- data/README.md +6 -3
- data/lib/aspera/api/aoc.rb +15 -11
- data/lib/aspera/api/cos_node.rb +4 -0
- data/lib/aspera/api/faspex.rb +35 -7
- data/lib/aspera/api/node.rb +81 -27
- data/lib/aspera/assert.rb +1 -1
- data/lib/aspera/cli/formatter.rb +27 -11
- data/lib/aspera/cli/hints.rb +7 -0
- data/lib/aspera/cli/manager.rb +51 -30
- data/lib/aspera/cli/plugins/aoc.rb +152 -123
- data/lib/aspera/cli/plugins/base.rb +16 -19
- data/lib/aspera/cli/plugins/config.rb +6 -44
- data/lib/aspera/cli/plugins/faspex.rb +4 -4
- data/lib/aspera/cli/plugins/faspex5.rb +92 -83
- data/lib/aspera/cli/plugins/node.rb +10 -15
- data/lib/aspera/cli/plugins/oauth.rb +26 -25
- data/lib/aspera/cli/plugins/preview.rb +3 -3
- data/lib/aspera/cli/plugins/shares.rb +15 -7
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +2 -1
- data/lib/aspera/colors.rb +7 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +4 -4
- data/lib/aspera/log.rb +12 -11
- data/lib/aspera/markdown.rb +5 -0
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/base.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -2
- data/lib/aspera/rest.rb +68 -48
- data/lib/aspera/rest_error_analyzer.rb +38 -36
- data/lib/aspera/rest_errors_aspera.rb +19 -18
- data/lib/aspera/transfer/spec.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
|
@@ -94,7 +94,7 @@ module Aspera
|
|
|
94
94
|
|
|
95
95
|
def initialize(**_)
|
|
96
96
|
super
|
|
97
|
-
options.declare(:box, "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: '
|
|
97
|
+
options.declare(:box, "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox_all')
|
|
98
98
|
options.declare(:shared_folder, 'Send package with files from shared folder')
|
|
99
99
|
options.declare(:group_type, 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes)
|
|
100
100
|
options.parse_options!
|
|
@@ -102,7 +102,7 @@ module Aspera
|
|
|
102
102
|
|
|
103
103
|
def set_api
|
|
104
104
|
# create an API object with the same options, but with a different subpath
|
|
105
|
-
@api_v5 =
|
|
105
|
+
@api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
|
|
106
106
|
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
107
107
|
transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
|
|
108
108
|
end
|
|
@@ -118,7 +118,7 @@ module Aspera
|
|
|
118
118
|
recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
|
|
119
119
|
end
|
|
120
120
|
parameters['recipients'].map! do |recipient_data|
|
|
121
|
-
#
|
|
121
|
+
# If just a string, make a general lookup and build expected name/type hash
|
|
122
122
|
if recipient_data.is_a?(String)
|
|
123
123
|
matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
|
|
124
124
|
recipient_data = {
|
|
@@ -164,18 +164,18 @@ module Aspera
|
|
|
164
164
|
loop do
|
|
165
165
|
result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
|
|
166
166
|
break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
|
|
167
|
-
|
|
167
|
+
RestParameters.instance.spinner_cb.call(result['status'])
|
|
168
168
|
sleep(0.5)
|
|
169
169
|
end
|
|
170
|
-
|
|
170
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
171
171
|
return result
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
# list all packages with optional filter
|
|
175
175
|
def list_packages_with_filter(query: {})
|
|
176
176
|
filter = options.get_next_argument('filter', mandatory: false, validation: Proc, default: ->(_x){true})
|
|
177
|
-
# translate box name to API prefix (with ending slash)
|
|
178
177
|
box = options.get_option(:box)
|
|
178
|
+
# Translate box name to API prefix (with ending slash)
|
|
179
179
|
entity =
|
|
180
180
|
case box
|
|
181
181
|
when SpecialValues::ALL then 'packages' # only admin can list all packages globally
|
|
@@ -192,6 +192,16 @@ module Aspera
|
|
|
192
192
|
return list.select(&filter), total
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
# Build query to get package recipients based on package info in case of shared inbox or workgroup recipient
|
|
196
|
+
# @param package_id [String] the package id to get info from
|
|
197
|
+
def recipient_query(package_id)
|
|
198
|
+
package_info = @api_v5.read("packages/#{package_id}")
|
|
199
|
+
base_query = {}
|
|
200
|
+
base_query['recipient_workgroup_id'] = package_info['recipients'].first['id'] if WORKGROUP_TYPES.include?(package_info['recipients'].first['recipient_type'])
|
|
201
|
+
base_query['recipient_user_id'] = package_info['recipients'].first['id'] if package_info['recipients'].first['recipient_type'].eql?('user')
|
|
202
|
+
base_query
|
|
203
|
+
end
|
|
204
|
+
|
|
195
205
|
def package_receive(package_ids)
|
|
196
206
|
# prepare persistency if needed
|
|
197
207
|
skip_ids_persistency = nil
|
|
@@ -236,17 +246,12 @@ module Aspera
|
|
|
236
246
|
rescue Cli::BadArgument
|
|
237
247
|
# paths is optional
|
|
238
248
|
end
|
|
249
|
+
box = options.get_option(:box)
|
|
239
250
|
download_params = {
|
|
240
|
-
type:
|
|
251
|
+
type: Api::Faspex.box_type(box),
|
|
241
252
|
transfer_type: Api::Faspex::TRANSFER_CONNECT
|
|
242
253
|
}
|
|
243
|
-
|
|
244
|
-
case box
|
|
245
|
-
when /outbox/ then download_params[:type] = 'sent'
|
|
246
|
-
when *Api::Faspex::API_LIST_MAILBOX_TYPES then nil # nothing to do
|
|
247
|
-
else # shared inbox / workgroup
|
|
248
|
-
download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id']
|
|
249
|
-
end
|
|
254
|
+
# download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
|
|
250
255
|
packages.each do |package|
|
|
251
256
|
pkg_id = package['id']
|
|
252
257
|
formatter.display_status("Receiving package #{pkg_id}")
|
|
@@ -254,10 +259,10 @@ module Aspera
|
|
|
254
259
|
transfer_spec = @api_v5.call(
|
|
255
260
|
operation: 'POST',
|
|
256
261
|
subpath: "packages/#{pkg_id}/transfer_spec/download",
|
|
257
|
-
query: download_params,
|
|
258
|
-
content_type:
|
|
262
|
+
query: download_params.merge(recipient_query(pkg_id)),
|
|
263
|
+
content_type: Mime::JSON,
|
|
259
264
|
body: param_file_list,
|
|
260
|
-
headers: {'Accept' =>
|
|
265
|
+
headers: {'Accept' => Mime::JSON}
|
|
261
266
|
)
|
|
262
267
|
# delete flag for Connect Client
|
|
263
268
|
transfer_spec.delete('authentication')
|
|
@@ -272,11 +277,61 @@ module Aspera
|
|
|
272
277
|
return Main.result_transfer_multiple(result_transfer)
|
|
273
278
|
end
|
|
274
279
|
|
|
280
|
+
def package_send
|
|
281
|
+
parameters = value_create_modify(command: :send)
|
|
282
|
+
# autofill recipient for public url
|
|
283
|
+
if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
|
|
284
|
+
parameters['recipients'] = [{
|
|
285
|
+
name: @api_v5.pub_link_context['name'],
|
|
286
|
+
recipient_type: @api_v5.pub_link_context['recipient_type']
|
|
287
|
+
}]
|
|
288
|
+
end
|
|
289
|
+
normalize_recipients(parameters)
|
|
290
|
+
# User specified content prot in tspec, but faspex requires in package creation
|
|
291
|
+
# `transfer_spec/upload` will set `content_protection`
|
|
292
|
+
if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
|
|
293
|
+
transfer.user_transfer_spec.delete('content_protection')
|
|
294
|
+
parameters['ear_enabled'] = true
|
|
295
|
+
end
|
|
296
|
+
package = @api_v5.create('packages', parameters)
|
|
297
|
+
shared_folder = options.get_option(:shared_folder)
|
|
298
|
+
if shared_folder.nil?
|
|
299
|
+
# send from local files
|
|
300
|
+
transfer_spec = @api_v5.create(
|
|
301
|
+
"packages/#{package['id']}/transfer_spec/upload",
|
|
302
|
+
{paths: transfer.source_list},
|
|
303
|
+
query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
|
|
304
|
+
)
|
|
305
|
+
# well, we asked a TS for connect, but we actually want a generic one
|
|
306
|
+
transfer_spec.delete('authentication')
|
|
307
|
+
return Main.result_transfer(transfer.start(transfer_spec))
|
|
308
|
+
else
|
|
309
|
+
# send from remote shared folder
|
|
310
|
+
if (m = Base.percent_selector(shared_folder))
|
|
311
|
+
shared_folder = lookup_entity_by_field(
|
|
312
|
+
api: @api_v5,
|
|
313
|
+
entity: 'shared_folders',
|
|
314
|
+
field: m[:field],
|
|
315
|
+
value: m[:value]
|
|
316
|
+
)['id']
|
|
317
|
+
end
|
|
318
|
+
transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
|
|
319
|
+
# start remote transfer and get first status
|
|
320
|
+
result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
|
|
321
|
+
result['id'] = package['id']
|
|
322
|
+
unless result['status'].eql?('completed')
|
|
323
|
+
formatter.display_status("Package #{package['id']}")
|
|
324
|
+
result = wait_package_status(package['id'])
|
|
325
|
+
end
|
|
326
|
+
return Main.result_single_object(result)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
275
330
|
# Browse a folder
|
|
276
331
|
# @param browse_endpoint [String] the endpoint to browse
|
|
277
|
-
def browse_folder(browse_endpoint)
|
|
332
|
+
def browse_folder(browse_endpoint, base_query = {})
|
|
278
333
|
folders_to_process = [options.get_next_argument('folder path', default: '/')]
|
|
279
|
-
query = query_read_delete(default: {})
|
|
334
|
+
query = base_query.merge(query_read_delete(default: {}))
|
|
280
335
|
filters = query.delete('filters'){{}}
|
|
281
336
|
Aspera.assert_type(filters, Hash)
|
|
282
337
|
filters['basenames'] ||= []
|
|
@@ -300,9 +355,9 @@ module Aspera
|
|
|
300
355
|
operation: 'POST',
|
|
301
356
|
subpath: browse_endpoint,
|
|
302
357
|
query: query,
|
|
303
|
-
content_type:
|
|
358
|
+
content_type: Mime::JSON,
|
|
304
359
|
body: {'path' => path, 'filters' => filters},
|
|
305
|
-
headers: {'Accept' =>
|
|
360
|
+
headers: {'Accept' => Mime::JSON},
|
|
306
361
|
ret: :both
|
|
307
362
|
)
|
|
308
363
|
all_items.concat(data['items'])
|
|
@@ -312,7 +367,7 @@ module Aspera
|
|
|
312
367
|
end
|
|
313
368
|
folders_to_process.concat(data['items'].select{ |i| i['type'].eql?('directory')}.map{ |i| i['path']}) if recursive
|
|
314
369
|
if use_paging
|
|
315
|
-
iteration_token = http[Api::Faspex::
|
|
370
|
+
iteration_token = http[Api::Faspex::HEADER_X_NEXT_ITER_TOKEN]
|
|
316
371
|
break if iteration_token.nil? || iteration_token.empty?
|
|
317
372
|
query['iteration_token'] = iteration_token
|
|
318
373
|
else
|
|
@@ -320,12 +375,11 @@ module Aspera
|
|
|
320
375
|
break if data['item_count'].eql?(0)
|
|
321
376
|
query['offset'] += data['item_count']
|
|
322
377
|
end
|
|
323
|
-
|
|
378
|
+
RestParameters.instance.spinner_cb.call(all_items.count)
|
|
324
379
|
end
|
|
325
380
|
query.delete('iteration_token')
|
|
326
381
|
end
|
|
327
|
-
|
|
328
|
-
|
|
382
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
329
383
|
return Main.result_object_list(all_items, total: total_count)
|
|
330
384
|
end
|
|
331
385
|
|
|
@@ -339,12 +393,7 @@ module Aspera
|
|
|
339
393
|
when :show
|
|
340
394
|
return Main.result_single_object(@api_v5.read("packages/#{package_id}"))
|
|
341
395
|
when :browse
|
|
342
|
-
|
|
343
|
-
when 'inbox' then 'received'
|
|
344
|
-
when 'outbox' then 'sent'
|
|
345
|
-
else raise BadArgument, 'Browse only available for inbox and outbox'
|
|
346
|
-
end
|
|
347
|
-
return browse_folder("packages/#{package_id}/files/#{location}")
|
|
396
|
+
return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}", recipient_query(package_id))
|
|
348
397
|
when :status
|
|
349
398
|
status_list = options.get_next_argument('list of states, or nothing', mandatory: false, validation: Array)
|
|
350
399
|
status = wait_package_status(package_id, status_list: status_list)
|
|
@@ -357,64 +406,21 @@ module Aspera
|
|
|
357
406
|
@api_v5.call(
|
|
358
407
|
operation: 'DELETE',
|
|
359
408
|
subpath: 'packages',
|
|
360
|
-
content_type:
|
|
409
|
+
content_type: Mime::JSON,
|
|
361
410
|
body: {ids: ids},
|
|
362
|
-
headers: {'Accept' =>
|
|
411
|
+
headers: {'Accept' => Mime::JSON}
|
|
363
412
|
)
|
|
364
413
|
return Main.result_status('Package(s) deleted')
|
|
365
414
|
when :receive
|
|
366
415
|
return package_receive(package_id)
|
|
367
416
|
when :send
|
|
368
|
-
|
|
369
|
-
# autofill recipient for public url
|
|
370
|
-
if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
|
|
371
|
-
parameters['recipients'] = [{
|
|
372
|
-
name: @api_v5.pub_link_context['name'],
|
|
373
|
-
recipient_type: @api_v5.pub_link_context['recipient_type']
|
|
374
|
-
}]
|
|
375
|
-
end
|
|
376
|
-
normalize_recipients(parameters)
|
|
377
|
-
# User specified content prot in tspec, but faspex requires in package creation
|
|
378
|
-
# `transfer_spec/upload` will set `content_protection`
|
|
379
|
-
if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
|
|
380
|
-
transfer.user_transfer_spec.delete('content_protection')
|
|
381
|
-
parameters['ear_enabled'] = true
|
|
382
|
-
end
|
|
383
|
-
package = @api_v5.create('packages', parameters)
|
|
384
|
-
shared_folder = options.get_option(:shared_folder)
|
|
385
|
-
if shared_folder.nil?
|
|
386
|
-
# send from local files
|
|
387
|
-
transfer_spec = @api_v5.create(
|
|
388
|
-
"packages/#{package['id']}/transfer_spec/upload",
|
|
389
|
-
{paths: transfer.source_list},
|
|
390
|
-
query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
|
|
391
|
-
)
|
|
392
|
-
# well, we asked a TS for connect, but we actually want a generic one
|
|
393
|
-
transfer_spec.delete('authentication')
|
|
394
|
-
return Main.result_transfer(transfer.start(transfer_spec))
|
|
395
|
-
else
|
|
396
|
-
# send from remote shared folder
|
|
397
|
-
if (m = Base.percent_selector(shared_folder))
|
|
398
|
-
shared_folder = lookup_entity_by_field(
|
|
399
|
-
api: @api_v5,
|
|
400
|
-
entity: 'shared_folders',
|
|
401
|
-
field: m[:field],
|
|
402
|
-
value: m[:value]
|
|
403
|
-
)['id']
|
|
404
|
-
end
|
|
405
|
-
transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
|
|
406
|
-
# start remote transfer and get first status
|
|
407
|
-
result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
|
|
408
|
-
result['id'] = package['id']
|
|
409
|
-
unless result['status'].eql?('completed')
|
|
410
|
-
formatter.display_status("Package #{package['id']}")
|
|
411
|
-
result = wait_package_status(package['id'])
|
|
412
|
-
end
|
|
413
|
-
return Main.result_single_object(result)
|
|
414
|
-
end
|
|
417
|
+
return package_send
|
|
415
418
|
when :list
|
|
416
419
|
list, total = list_packages_with_filter
|
|
417
|
-
|
|
420
|
+
fields = %w[id title status sender.name recipients.0.name release_date total_bytes total_files]
|
|
421
|
+
fields.delete('recipients.0.name') if %w[inbox inbox_history].include?(options.get_option(:box))
|
|
422
|
+
fields.delete('sender.name') if %w[outbox outbox_history].include?(options.get_option(:box))
|
|
423
|
+
return Main.result_object_list(list, total: total, fields: fields)
|
|
418
424
|
end
|
|
419
425
|
end
|
|
420
426
|
|
|
@@ -450,6 +456,8 @@ module Aspera
|
|
|
450
456
|
res_id_query = {'all': true}
|
|
451
457
|
when :nodes
|
|
452
458
|
available_commands += %i[shared_folders browse]
|
|
459
|
+
when :jobs
|
|
460
|
+
exec_args[:display_fields] = %w[id job_name job_type status]
|
|
453
461
|
end
|
|
454
462
|
res_command = options.get_next_command(available_commands)
|
|
455
463
|
return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
|
|
@@ -687,7 +695,7 @@ module Aspera
|
|
|
687
695
|
@api_v5.create(invitation_endpoint, params)
|
|
688
696
|
end
|
|
689
697
|
when :resend
|
|
690
|
-
@api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend")
|
|
698
|
+
@api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend", nil)
|
|
691
699
|
return Main.result_status('Invitation resent')
|
|
692
700
|
else
|
|
693
701
|
return entity_execute(
|
|
@@ -722,7 +730,8 @@ module Aspera
|
|
|
722
730
|
end
|
|
723
731
|
SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
|
|
724
732
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
725
|
-
|
|
733
|
+
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
734
|
+
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
726
735
|
private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES
|
|
727
736
|
end
|
|
728
737
|
end
|
|
@@ -93,12 +93,9 @@ module Aspera
|
|
|
93
93
|
options.declare(:validator, 'Identifier of validator (optional for central)')
|
|
94
94
|
options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
|
|
95
95
|
options.declare(
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
options.declare(
|
|
100
|
-
:node_cache, 'Gen4: Set to no to force actual file system read', allowed: Allowed::TYPES_BOOLEAN,
|
|
101
|
-
handler: {o: Api::Node, m: :use_node_cache}
|
|
96
|
+
:node_api, 'Gen4: standard_ports: Use standard FASP ports (true) or get from node API (false). cache: Set to false to force actual file system read',
|
|
97
|
+
allowed: Hash,
|
|
98
|
+
handler: {o: Api::Node, m: :api_options}
|
|
102
99
|
)
|
|
103
100
|
options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
|
|
104
101
|
options.declare(:dynamic_key, 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key})
|
|
@@ -242,14 +239,14 @@ module Aspera
|
|
|
242
239
|
break if all_items.count >= total_count
|
|
243
240
|
offset += items.count
|
|
244
241
|
query['skip'] = offset
|
|
245
|
-
|
|
242
|
+
RestParameters.instance.spinner_cb.call(all_items.count)
|
|
246
243
|
end
|
|
247
244
|
query.delete('skip')
|
|
248
245
|
end
|
|
249
246
|
@prefixer&.remove_in_object_list!(all_items)
|
|
250
247
|
return Main.result_object_list(all_items)
|
|
251
248
|
ensure
|
|
252
|
-
|
|
249
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
253
250
|
end
|
|
254
251
|
|
|
255
252
|
# Create async transfer spec request from direction and folders
|
|
@@ -388,7 +385,7 @@ module Aspera
|
|
|
388
385
|
when :transport
|
|
389
386
|
return Main.result_single_object(@api_node.transport_params)
|
|
390
387
|
when :spec
|
|
391
|
-
return Main.result_single_object(@api_node.base_spec)
|
|
388
|
+
return Main.result_single_object(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC))
|
|
392
389
|
end
|
|
393
390
|
Aspera.error_unreachable_line
|
|
394
391
|
end
|
|
@@ -450,7 +447,7 @@ module Aspera
|
|
|
450
447
|
@api_node.call(
|
|
451
448
|
operation: 'POST',
|
|
452
449
|
subpath: 'services/soap/Transfer-201210',
|
|
453
|
-
content_type:
|
|
450
|
+
content_type: Mime::TEXT,
|
|
454
451
|
body: CENTRAL_SOAP_API_TEST,
|
|
455
452
|
headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
|
|
456
453
|
ret: :resp
|
|
@@ -471,9 +468,7 @@ module Aspera
|
|
|
471
468
|
return Main.result_single_object(nd_info)
|
|
472
469
|
when :license
|
|
473
470
|
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
|
474
|
-
|
|
475
|
-
Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal') if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
|
|
476
|
-
return Main.result_single_object(node_license)
|
|
471
|
+
return Main.result_single_object(@api_node.read('license'))
|
|
477
472
|
when :api_details
|
|
478
473
|
return Main.result_single_object({base_url: @api_node.base_url}.merge(@api_node.params))
|
|
479
474
|
end
|
|
@@ -525,7 +520,7 @@ module Aspera
|
|
|
525
520
|
return Main.result_text(result[:password])
|
|
526
521
|
when :browse
|
|
527
522
|
apifid = apifid_from_next_arg(top_file_id)
|
|
528
|
-
file_info = apifid[:api].read("files/#{apifid[:file_id]}",
|
|
523
|
+
file_info = apifid[:api].read("files/#{apifid[:file_id]}", headers: Api::Node.add_cache_control)
|
|
529
524
|
unless file_info['type'].eql?('folder')
|
|
530
525
|
# a single file
|
|
531
526
|
return Main.result_object_list([file_info], fields: GEN4_LS_FIELDS)
|
|
@@ -837,7 +832,7 @@ module Aspera
|
|
|
837
832
|
@api_node.call(
|
|
838
833
|
operation: 'POST',
|
|
839
834
|
subpath: "asyncs/#{asyncs_id}/#{sync_command}",
|
|
840
|
-
content_type:
|
|
835
|
+
content_type: Mime::TEXT,
|
|
841
836
|
body: '',
|
|
842
837
|
ret: :resp
|
|
843
838
|
).body
|
|
@@ -7,9 +7,33 @@ module Aspera
|
|
|
7
7
|
module Plugins
|
|
8
8
|
# base class for applications supporting OAuth 2.0 authentication
|
|
9
9
|
class Oauth < BasicAuth
|
|
10
|
-
|
|
10
|
+
class << self
|
|
11
|
+
# Get command line options specified by `AUTH_OPTIONS` and `defaults.keys` (value is default).
|
|
12
|
+
# Adds those not nil to the `kwargs`.
|
|
13
|
+
# Instantiate the provided `klass` with those `kwargs`.
|
|
14
|
+
# `defaults` can specify a default value (not `nil`)
|
|
15
|
+
# @param options [Cli::Manager] Object to get command line options.
|
|
16
|
+
# @param kwargs [Hash] Object creation arguments
|
|
17
|
+
# @param defaults [Hash] Additional options, key=symbol, value=default value or nil
|
|
18
|
+
# @return [Object] instance of `klass`
|
|
19
|
+
# @raise [Cli::Error] if a required option is missing
|
|
20
|
+
def args_from_options(options, defaults: nil, **kwargs)
|
|
21
|
+
defaults ||= {}
|
|
22
|
+
(AUTH_OPTIONS + defaults.keys).each_with_object(kwargs) do |i, m|
|
|
23
|
+
v = options.get_option(i)
|
|
24
|
+
m[i] = v unless v.nil?
|
|
25
|
+
m[i] = defaults[i] if m[i].nil? && !defaults[i].nil?
|
|
26
|
+
end
|
|
27
|
+
rescue ::ArgumentError => e
|
|
28
|
+
if (m = e.message.match(/missing keyword: :(.*)$/))
|
|
29
|
+
raise Cli::Error, "Missing option: #{m[1]}"
|
|
30
|
+
end
|
|
31
|
+
raise
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
# OAuth methods supported (web, jwt)
|
|
11
35
|
AUTH_TYPES = %i[web jwt boot].freeze
|
|
12
|
-
# Options used for authentication
|
|
36
|
+
# Options used for authentication (url, auth, client_id, etc...)
|
|
13
37
|
AUTH_OPTIONS = %i[url auth client_id client_secret redirect_uri private_key passphrase username password].freeze
|
|
14
38
|
def initialize(**_)
|
|
15
39
|
super
|
|
@@ -20,29 +44,6 @@ module Aspera
|
|
|
20
44
|
options.declare(:private_key, 'OAuth (JWT) RSA private key PEM value (prefix file path with @file:)')
|
|
21
45
|
options.declare(:passphrase, 'OAuth (JWT) RSA private key passphrase')
|
|
22
46
|
end
|
|
23
|
-
|
|
24
|
-
# Get command line options specified by `AUTH_OPTIONS` and `option.keys` (value is default).
|
|
25
|
-
# Adds those not nil to the `kwargs`.
|
|
26
|
-
# Instantiate the provided `klass` with those kwargs.
|
|
27
|
-
# `option` can specify a default value (not `nil`)
|
|
28
|
-
# @param klass [Class] API object to create
|
|
29
|
-
# @param kwargs [Hash] The fixed keyword arguments for creation
|
|
30
|
-
# @param option [Hash] Additional options, key=symbol, value:default value or nil
|
|
31
|
-
# @return [Object] instance of `klass`
|
|
32
|
-
# @raise [Cli::Error] if a required option is missing
|
|
33
|
-
def new_with_options(klass, kwargs: {}, option: {})
|
|
34
|
-
klass.new(**
|
|
35
|
-
(AUTH_OPTIONS + option.keys).each_with_object(kwargs) do |i, m|
|
|
36
|
-
v = options.get_option(i)
|
|
37
|
-
m[i] = v unless v.nil?
|
|
38
|
-
m[i] = option[i] unless !m[i].nil? || option[i].nil?
|
|
39
|
-
end)
|
|
40
|
-
rescue ::ArgumentError => e
|
|
41
|
-
if (m = e.message.match(/missing keyword: :(.*)$/))
|
|
42
|
-
raise Cli::Error, "Missing option: #{m[1]}"
|
|
43
|
-
end
|
|
44
|
-
raise
|
|
45
|
-
end
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
end
|
|
@@ -90,8 +90,8 @@ module Aspera
|
|
|
90
90
|
Aspera::Preview::Options::DESCRIPTIONS.each do |opt|
|
|
91
91
|
values = if opt.key?(:values)
|
|
92
92
|
opt[:values]
|
|
93
|
-
elsif
|
|
94
|
-
|
|
93
|
+
elsif BoolValue.symbol?(opt[:default])
|
|
94
|
+
BoolValue::TYPES
|
|
95
95
|
end
|
|
96
96
|
options.declare(opt[:name], opt[:description].capitalize, allowed: values, handler: {o: @gen_options, m: opt[:name]}, default: opt[:default])
|
|
97
97
|
end
|
|
@@ -109,7 +109,7 @@ module Aspera
|
|
|
109
109
|
# /files/id/files is normally cached in Redis, but we can discard the cache
|
|
110
110
|
# but /files/id is not cached
|
|
111
111
|
def get_folder_entries(file_id, request_args = nil)
|
|
112
|
-
headers = {'Accept' =>
|
|
112
|
+
headers = {'Accept' => Mime::JSON}
|
|
113
113
|
headers['X-Aspera-Cache-Control'] = 'no-cache' if @option_folder_reset_cache.eql?(:header)
|
|
114
114
|
return @api_node.read("files/#{file_id}/files", request_args, headers: headers)
|
|
115
115
|
end
|
|
@@ -17,20 +17,21 @@ module Aspera
|
|
|
17
17
|
# @return [Hash] with version, ping, api
|
|
18
18
|
def health_check(url)
|
|
19
19
|
result = {}
|
|
20
|
+
# Get version from main page
|
|
20
21
|
result[:version] =
|
|
21
22
|
begin
|
|
22
23
|
version = nil
|
|
23
24
|
login_page = Rest
|
|
24
25
|
.new(base_url: url, redirect_max: 2)
|
|
25
26
|
.read('', headers: {'Accept'=>'text/html'})
|
|
27
|
+
raise 'not Shares' unless login_page.include?('aspera-Shares')
|
|
26
28
|
if (m = login_page.match(/\(v([0-9a-f\.]+)\)/))
|
|
27
29
|
version = m[1]
|
|
28
30
|
if (m = login_page.match(/Patch level ([0-9]+)/))
|
|
29
|
-
version = "#{
|
|
31
|
+
version = "#{version} #{m[0]}"
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
|
-
|
|
33
|
-
version
|
|
34
|
+
version.nil? ? 'no version' : version
|
|
34
35
|
rescue => e
|
|
35
36
|
e
|
|
36
37
|
end
|
|
@@ -45,10 +46,15 @@ module Aspera
|
|
|
45
46
|
end
|
|
46
47
|
result[:api] =
|
|
47
48
|
begin
|
|
48
|
-
resp = Rest
|
|
49
|
+
data, resp = Rest
|
|
50
|
+
.new(base_url: "#{url}/#{NODE_API_PATH}", redirect_max: 1)
|
|
51
|
+
.read('info', exception: false, ret: :both)
|
|
49
52
|
# shall fail: shares requires auth, but we check error message
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if resp.code.to_s.eql?('401') && data&.dig('error', 'user_message')&.include?('authentication failed')
|
|
54
|
+
'available'
|
|
55
|
+
else
|
|
56
|
+
raise "not found (#{resp.code})"
|
|
57
|
+
end
|
|
52
58
|
rescue => e
|
|
53
59
|
e
|
|
54
60
|
end
|
|
@@ -88,7 +94,7 @@ module Aspera
|
|
|
88
94
|
SAML_IMPORT_MANDATORY = %w[id name_id].freeze
|
|
89
95
|
SAML_IMPORT_ALLOWED = %w[email given_name surname].concat(SAML_IMPORT_MANDATORY).freeze
|
|
90
96
|
|
|
91
|
-
ACTIONS = %i[health files admin].freeze
|
|
97
|
+
ACTIONS = %i[health info files admin].freeze
|
|
92
98
|
# common to users and groups
|
|
93
99
|
USR_GRP_SETTINGS = %i[transfer_settings app_authorizations share_permissions].freeze
|
|
94
100
|
|
|
@@ -111,6 +117,8 @@ module Aspera
|
|
|
111
117
|
nagios.add_critical('API', health[:api].to_s)
|
|
112
118
|
end
|
|
113
119
|
Main.result_object_list(nagios.status_list)
|
|
120
|
+
when :info
|
|
121
|
+
return Main.result_single_object(basic_auth_api(NODE_API_PATH).read('info', headers: {'Content-Type'=>'application/json'}))
|
|
114
122
|
when :files
|
|
115
123
|
api_shares_node = basic_auth_api(NODE_API_PATH)
|
|
116
124
|
repo_command = options.get_next_command(Node::COMMANDS_SHARES)
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Aspera
|
|
|
62
62
|
detection_info = nil
|
|
63
63
|
begin
|
|
64
64
|
Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
|
|
65
|
-
|
|
65
|
+
RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
|
|
66
66
|
detection_info = plugin_klass.detect(app_url)
|
|
67
67
|
rescue OpenSSL::SSL::SSLError => e
|
|
68
68
|
Log.log.warn(e.message)
|
|
@@ -78,6 +78,7 @@ module Aspera
|
|
|
78
78
|
# If there is a redirect, then the detector can override the url.
|
|
79
79
|
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
80
80
|
end
|
|
81
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
81
82
|
raise "No known application found at #{app_url}" if found_apps.empty?
|
|
82
83
|
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
|
|
83
84
|
return found_apps
|
data/lib/aspera/colors.rb
CHANGED
|
@@ -58,6 +58,13 @@ class String
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# Applies the provided list of string decoration (colors).
|
|
62
|
+
# @param colors [Array<Symbol>] List of decorations.
|
|
63
|
+
# @return [String] Enhanced String.
|
|
64
|
+
def apply(*colors)
|
|
65
|
+
colors.reduce(self){ |s, c| s.public_send(c)}
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
# Transform capitalized to snake case
|
|
62
69
|
def capital_to_snake
|
|
63
70
|
return gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -52,9 +52,9 @@ module Aspera
|
|
|
52
52
|
operation: 'POST',
|
|
53
53
|
subpath: "packages/#{package['id']}/transfer_spec/upload",
|
|
54
54
|
query: {transfer_type: Api::Faspex::TRANSFER_CONNECT},
|
|
55
|
-
content_type:
|
|
55
|
+
content_type: Mime::JSON,
|
|
56
56
|
body: {paths: [{'destination'=>'/'}]},
|
|
57
|
-
headers: {'Accept' =>
|
|
57
|
+
headers: {'Accept' => Mime::JSON}
|
|
58
58
|
)
|
|
59
59
|
transfer_spec.delete('authentication')
|
|
60
60
|
# but we place it in a Faspex package creation response
|
|
@@ -82,18 +82,18 @@ module Aspera
|
|
|
82
82
|
end
|
|
83
83
|
Log.log.debug{"faspex_package_create_result=#{faspex_package_create_result}"}
|
|
84
84
|
response.status = 200
|
|
85
|
-
response.content_type =
|
|
85
|
+
response.content_type = Mime::JSON
|
|
86
86
|
response.body = JSON.generate(faspex_package_create_result)
|
|
87
87
|
rescue => e
|
|
88
88
|
response.status = 500
|
|
89
|
-
response['Content-Type'] =
|
|
89
|
+
response['Content-Type'] = Mime::JSON
|
|
90
90
|
response.body = {error: e.message, stacktrace: e.backtrace}.to_json
|
|
91
91
|
Log.log.error(e.message)
|
|
92
92
|
Log.log.debug{e.backtrace.join("\n")}
|
|
93
93
|
end
|
|
94
94
|
else
|
|
95
95
|
response.status = 400
|
|
96
|
-
response['Content-Type'] =
|
|
96
|
+
response['Content-Type'] = Mime::JSON
|
|
97
97
|
response.body = {error: 'Unsupported endpoint'}.to_json
|
|
98
98
|
end
|
|
99
99
|
end
|
|
@@ -32,13 +32,13 @@ module Aspera
|
|
|
32
32
|
# Only accept requests on the root
|
|
33
33
|
if !request.path.start_with?(@parameters[:root])
|
|
34
34
|
response.status = 400
|
|
35
|
-
response['Content-Type'] =
|
|
35
|
+
response['Content-Type'] = Mime::JSON
|
|
36
36
|
response.body = {status: 'error', message: 'Request outside domain'}.to_json
|
|
37
37
|
return
|
|
38
38
|
end
|
|
39
39
|
if request.body.nil?
|
|
40
40
|
response.status = 400
|
|
41
|
-
response['Content-Type'] =
|
|
41
|
+
response['Content-Type'] = Mime::JSON
|
|
42
42
|
response.body = {status: 'error', message: 'Empty request'}.to_json
|
|
43
43
|
return
|
|
44
44
|
end
|
|
@@ -65,7 +65,7 @@ module Aspera
|
|
|
65
65
|
raise "script #{script_path} failed with code #{process_status.exitstatus}" if !process_status.success? && @parameters[:fail_on_error]
|
|
66
66
|
end
|
|
67
67
|
response.status = 200
|
|
68
|
-
response.content_type =
|
|
68
|
+
response.content_type = Mime::JSON
|
|
69
69
|
response.body = JSON.generate({status: 'success', script: script_path, exit_code: process_status.exitstatus})
|
|
70
70
|
Log.log.debug{'Script executed successfully'}
|
|
71
71
|
rescue => e
|
|
@@ -76,7 +76,7 @@ module Aspera
|
|
|
76
76
|
Log.log.error("Killed process: #{post_proc_pid}")
|
|
77
77
|
end
|
|
78
78
|
response.status = 500
|
|
79
|
-
response['Content-Type'] =
|
|
79
|
+
response['Content-Type'] = Mime::JSON
|
|
80
80
|
response.body = {status: 'error', script: script_path, message: e.message}.to_json
|
|
81
81
|
end
|
|
82
82
|
end
|