aspera-cli 4.17.0 → 4.18.1
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 +3 -4
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +15 -1
- data/README.md +711 -432
- data/bin/ascli +5 -0
- data/bin/asession +2 -2
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +10 -8
- data/lib/aspera/agent/base.rb +9 -6
- data/lib/aspera/agent/connect.rb +7 -8
- data/lib/aspera/agent/direct.rb +56 -37
- data/lib/aspera/agent/httpgw.rb +23 -324
- data/lib/aspera/agent/node.rb +19 -20
- data/lib/aspera/agent/trsdk.rb +19 -20
- data/lib/aspera/api/aoc.rb +17 -14
- data/lib/aspera/api/cos_node.rb +4 -4
- data/lib/aspera/api/httpgw.rb +342 -0
- data/lib/aspera/api/node.rb +135 -89
- data/lib/aspera/ascmd.rb +4 -3
- data/lib/aspera/ascp/installation.rb +15 -7
- data/lib/aspera/ascp/management.rb +2 -2
- data/lib/aspera/ascp/products.rb +1 -1
- data/lib/aspera/cli/basic_auth_plugin.rb +5 -9
- data/lib/aspera/cli/extended_value.rb +35 -16
- data/lib/aspera/cli/formatter.rb +161 -70
- data/lib/aspera/cli/hints.rb +18 -0
- data/lib/aspera/cli/main.rb +32 -39
- data/lib/aspera/cli/manager.rb +151 -119
- data/lib/aspera/cli/plugin.rb +27 -21
- data/lib/aspera/cli/plugin_factory.rb +31 -20
- data/lib/aspera/cli/plugins/alee.rb +14 -2
- data/lib/aspera/cli/plugins/aoc.rb +152 -141
- data/lib/aspera/cli/plugins/ats.rb +1 -1
- data/lib/aspera/cli/plugins/config.rb +72 -65
- data/lib/aspera/cli/plugins/console.rb +8 -5
- data/lib/aspera/cli/plugins/faspex.rb +32 -23
- data/lib/aspera/cli/plugins/faspex5.rb +232 -156
- data/lib/aspera/cli/plugins/faspio.rb +85 -0
- data/lib/aspera/cli/plugins/httpgw.rb +55 -0
- data/lib/aspera/cli/plugins/node.rb +129 -64
- data/lib/aspera/cli/plugins/orchestrator.rb +33 -30
- data/lib/aspera/cli/plugins/preview.rb +7 -3
- data/lib/aspera/cli/plugins/server.rb +6 -6
- data/lib/aspera/cli/plugins/shares.rb +16 -14
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +10 -10
- data/lib/aspera/cli/transfer_agent.rb +7 -6
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +70 -9
- data/lib/aspera/faspex_gw.rb +5 -4
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/log.rb +6 -3
- data/lib/aspera/node_simulator.rb +2 -2
- data/lib/aspera/oauth/base.rb +31 -19
- data/lib/aspera/oauth/factory.rb +12 -13
- data/lib/aspera/oauth/generic.rb +1 -0
- data/lib/aspera/oauth/jwt.rb +18 -15
- data/lib/aspera/oauth/url_json.rb +8 -6
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/options.rb +3 -3
- data/lib/aspera/preview/terminal.rb +4 -4
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +5 -1
- data/lib/aspera/rest.rb +105 -88
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +2 -2
- data/lib/aspera/rest_errors_aspera.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +2 -4
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +39 -36
- data/lib/aspera/transfer/spec.rb +2 -0
- data/lib/aspera/transfer/sync.rb +2 -1
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +5 -4
- data/lib/aspera/web_auth.rb +1 -1
- data/lib/aspera/web_server_simple.rb +4 -3
- data.tar.gz.sig +0 -0
- metadata +7 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/bss.rb +0 -71
- data/lib/aspera/open_application.rb +0 -71
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
require 'aspera/cli/basic_auth_plugin'
|
|
6
6
|
require 'aspera/cli/extended_value'
|
|
7
|
+
require 'aspera/cli/special_values'
|
|
7
8
|
require 'aspera/persistency_action_once'
|
|
8
9
|
require 'aspera/id_generator'
|
|
9
10
|
require 'aspera/nagios'
|
|
10
11
|
require 'aspera/environment'
|
|
11
12
|
require 'aspera/assert'
|
|
12
13
|
require 'securerandom'
|
|
13
|
-
require 'tty-spinner'
|
|
14
14
|
|
|
15
15
|
module Aspera
|
|
16
16
|
module Cli
|
|
@@ -24,8 +24,8 @@ module Aspera
|
|
|
24
24
|
# Faspex API v5: get transfer spec for connect
|
|
25
25
|
TRANSFER_CONNECT = 'connect'
|
|
26
26
|
ADMIN_RESOURCES = %i[
|
|
27
|
-
accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
|
|
28
|
-
metadata_profiles email_notifications alternate_addresses
|
|
27
|
+
accounts distribution_lists contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
|
|
28
|
+
metadata_profiles email_notifications alternate_addresses webhooks
|
|
29
29
|
].freeze
|
|
30
30
|
# states for jobs not in final state
|
|
31
31
|
JOB_RUNNING = %w[queued working].freeze
|
|
@@ -58,7 +58,7 @@ module Aspera
|
|
|
58
58
|
path_api_detect = "#{PATH_API_V5}/#{PATH_HEALTH}"
|
|
59
59
|
result = api.read(path_api_detect)
|
|
60
60
|
next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
|
|
61
|
-
# end is at -1, and
|
|
61
|
+
# end is at -1, and subtract 1 for "/"
|
|
62
62
|
url_length = -2 - path_api_detect.length
|
|
63
63
|
# take redirect if any
|
|
64
64
|
return {
|
|
@@ -82,7 +82,7 @@ module Aspera
|
|
|
82
82
|
if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
|
|
83
83
|
formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
|
|
84
84
|
formatter.display_status("Admin should login to: #{instance_url}")
|
|
85
|
-
|
|
85
|
+
Environment.instance.open_uri(instance_url)
|
|
86
86
|
formatter.display_status('Navigate to: 𓃑 → Admin → Configurations → API clients')
|
|
87
87
|
formatter.display_status('Create an API client with:')
|
|
88
88
|
formatter.display_status('- name: ascli')
|
|
@@ -119,7 +119,7 @@ module Aspera
|
|
|
119
119
|
options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
|
|
120
120
|
options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
|
|
121
121
|
options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
|
|
122
|
-
options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{
|
|
122
|
+
options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox')
|
|
123
123
|
options.declare(:shared_folder, 'Send package with files from shared folder')
|
|
124
124
|
options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
|
|
125
125
|
options.parse_options!
|
|
@@ -237,31 +237,27 @@ module Aspera
|
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
def wait_for_job(job_id)
|
|
240
|
-
spinner = nil
|
|
241
240
|
loop do
|
|
242
241
|
status = @api_v5.read("jobs/#{job_id}", {type: :formatted})[:data]
|
|
243
242
|
return status unless JOB_RUNNING.include?(status['status'])
|
|
244
|
-
|
|
245
|
-
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
|
246
|
-
spinner.start
|
|
247
|
-
end
|
|
248
|
-
spinner.update(title: status['status'])
|
|
249
|
-
spinner.spin
|
|
243
|
+
formatter.long_operation_running(status['status'])
|
|
250
244
|
sleep(0.5)
|
|
251
245
|
end
|
|
252
246
|
Aspera.error_unreachable_line
|
|
253
247
|
end
|
|
254
248
|
|
|
255
|
-
# Get a (full or partial) list of all entities of a given type
|
|
249
|
+
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
256
250
|
# @param type [String] the type of entity to list (just a name)
|
|
257
251
|
# @param query [Hash,nil] additional query parameters
|
|
258
252
|
# @param real_path [String] real path if it's n ot just the type
|
|
259
253
|
# @param item_list_key [String] key in the result to get the list of items
|
|
260
|
-
def list_entities(type:, real_path: nil,
|
|
254
|
+
def list_entities(type:, real_path: nil, item_list_key: nil, query: {})
|
|
255
|
+
Log.log.trace1{"list_entities t=#{type} p=#{real_path} k=#{item_list_key} q=#{query}"}
|
|
261
256
|
type = type.to_s if type.is_a?(Symbol)
|
|
262
257
|
Aspera.assert_type(type, String)
|
|
258
|
+
Aspera.assert_type(query, Hash)
|
|
263
259
|
item_list_key = type if item_list_key.nil?
|
|
264
|
-
|
|
260
|
+
real_path = type if real_path.nil?
|
|
265
261
|
result = []
|
|
266
262
|
offset = 0
|
|
267
263
|
max_items = query.delete(MAX_ITEMS)
|
|
@@ -270,7 +266,8 @@ module Aspera
|
|
|
270
266
|
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
271
267
|
loop do
|
|
272
268
|
query['offset'] = offset
|
|
273
|
-
page_result = @api_v5.read(
|
|
269
|
+
page_result = @api_v5.read(real_path, query)[:data]
|
|
270
|
+
Aspera.assert_type(page_result[item_list_key], Array)
|
|
274
271
|
result.concat(page_result[item_list_key])
|
|
275
272
|
# reach the limit set by user ?
|
|
276
273
|
if !max_items.nil? && (result.length >= max_items)
|
|
@@ -281,12 +278,19 @@ module Aspera
|
|
|
281
278
|
remain_pages -= 1 unless remain_pages.nil?
|
|
282
279
|
break if remain_pages == 0
|
|
283
280
|
offset += page_result[item_list_key].length
|
|
281
|
+
formatter.long_operation_running
|
|
284
282
|
end
|
|
285
283
|
return result
|
|
286
284
|
end
|
|
287
285
|
|
|
288
286
|
# lookup an entity id from its name
|
|
289
|
-
|
|
287
|
+
# @param type [String] the type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
288
|
+
# @param value [String] the value to lookup
|
|
289
|
+
# @param field [String] the field to match, by default it is 'name'
|
|
290
|
+
# @param real_path [String] real path if it's not just the type (override type)
|
|
291
|
+
# @param item_list_key [String] key in the result to get the list of items (override type)
|
|
292
|
+
# @param query [Hash] additional query parameters
|
|
293
|
+
def lookup_entity_by_field(type:, value:, field: 'name', real_path: nil, item_list_key: nil, query: :default)
|
|
290
294
|
if query.eql?(:default)
|
|
291
295
|
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
292
296
|
query = {'q'=> value}
|
|
@@ -301,12 +305,12 @@ module Aspera
|
|
|
301
305
|
|
|
302
306
|
# list all packages with optional filter
|
|
303
307
|
def list_packages_with_filter(query: {})
|
|
304
|
-
filter = options.get_next_argument('filter', mandatory: false,
|
|
308
|
+
filter = options.get_next_argument('filter', mandatory: false, validation: Proc, default: ->(_x){true})
|
|
305
309
|
# translate box name to API prefix (with ending slash)
|
|
306
310
|
box = options.get_option(:box)
|
|
307
311
|
real_path =
|
|
308
312
|
case box
|
|
309
|
-
when
|
|
313
|
+
when SpecialValues::ALL then 'packages' # only admin can list all packages globally
|
|
310
314
|
when *API_LIST_MAILBOX_TYPES then "#{box}/packages"
|
|
311
315
|
else
|
|
312
316
|
group_type = options.get_option(:group_type)
|
|
@@ -335,12 +339,12 @@ module Aspera
|
|
|
335
339
|
end
|
|
336
340
|
packages = []
|
|
337
341
|
case package_ids
|
|
338
|
-
when
|
|
342
|
+
when SpecialValues::INIT
|
|
339
343
|
Aspera.assert(skip_ids_persistency){'Only with option once_only'}
|
|
340
344
|
skip_ids_persistency.data.clear.concat(list_packages_with_filter.map{|p|p['id']})
|
|
341
345
|
skip_ids_persistency.save
|
|
342
346
|
return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
|
343
|
-
when
|
|
347
|
+
when SpecialValues::ALL
|
|
344
348
|
# TODO: if packages have same name, they will overwrite ?
|
|
345
349
|
packages = list_packages_with_filter(query: {'status' => 'completed'})
|
|
346
350
|
Log.log.trace1{Log.dump(:package_ids, packages.map{|p|p['id']})}
|
|
@@ -381,8 +385,9 @@ module Aspera
|
|
|
381
385
|
operation: 'POST',
|
|
382
386
|
subpath: "packages/#{pkg_id}/transfer_spec/download",
|
|
383
387
|
headers: {'Accept' => 'application/json'},
|
|
384
|
-
|
|
385
|
-
|
|
388
|
+
query: download_params,
|
|
389
|
+
body: param_file_list,
|
|
390
|
+
body_type: :json
|
|
386
391
|
)[:data]
|
|
387
392
|
# delete flag for Connect Client
|
|
388
393
|
transfer_spec.delete('authentication')
|
|
@@ -400,7 +405,7 @@ module Aspera
|
|
|
400
405
|
# browse a folder
|
|
401
406
|
# @param browse_endpoint [String] the endpoint to browse
|
|
402
407
|
def browse_folder(browse_endpoint)
|
|
403
|
-
|
|
408
|
+
folders_to_process = [options.get_next_argument('folder path', mandatory: false, default: '/')]
|
|
404
409
|
query = query_read_delete(default: {})
|
|
405
410
|
query['filters'] = {} unless query.key?('filters')
|
|
406
411
|
filters = query.delete('filters')
|
|
@@ -409,17 +414,16 @@ module Aspera
|
|
|
409
414
|
max_items = query.delete('max')
|
|
410
415
|
recursive = query.delete('recursive')
|
|
411
416
|
all_items = []
|
|
412
|
-
folders_to_process = [path]
|
|
413
417
|
until folders_to_process.empty?
|
|
414
418
|
path = folders_to_process.shift
|
|
415
|
-
query.delete('iteration_token')
|
|
416
419
|
loop do
|
|
417
420
|
response = @api_v5.call(
|
|
418
421
|
operation: 'POST',
|
|
419
422
|
subpath: browse_endpoint,
|
|
420
|
-
headers: {'Accept' => 'application/json'
|
|
421
|
-
|
|
422
|
-
|
|
423
|
+
headers: {'Accept' => 'application/json'},
|
|
424
|
+
query: query,
|
|
425
|
+
body: {'path' => path, 'filters' => filters},
|
|
426
|
+
body_type: :json)
|
|
423
427
|
all_items.concat(response[:data]['items'])
|
|
424
428
|
if recursive
|
|
425
429
|
folders_to_process.concat(response[:data]['items'].select{|i|i['type'].eql?('directory')}.map{|i|i['path']})
|
|
@@ -431,7 +435,9 @@ module Aspera
|
|
|
431
435
|
iteration_token = response[:http][HEADER_ITERATION_TOKEN]
|
|
432
436
|
break if iteration_token.nil? || iteration_token.empty?
|
|
433
437
|
query['iteration_token'] = iteration_token
|
|
438
|
+
formatter.long_operation_running(all_items.count)
|
|
434
439
|
end
|
|
440
|
+
query.delete('iteration_token')
|
|
435
441
|
end
|
|
436
442
|
return {type: :object_list, data: all_items}
|
|
437
443
|
end
|
|
@@ -459,9 +465,14 @@ module Aspera
|
|
|
459
465
|
ids = package_id
|
|
460
466
|
ids = [ids] unless ids.is_a?(Array)
|
|
461
467
|
Aspera.assert_type(ids, Array){'Package identifier'}
|
|
462
|
-
Aspera.assert(ids.all?(String)){
|
|
468
|
+
Aspera.assert(ids.all?(String)){"Package id(s) shall be String, but have: #{ids.map(&:class).uniq.join(', ')}"}
|
|
463
469
|
# API returns 204, empty on success
|
|
464
|
-
@api_v5.call(
|
|
470
|
+
@api_v5.call(
|
|
471
|
+
operation: 'DELETE',
|
|
472
|
+
subpath: 'packages',
|
|
473
|
+
headers: {'Accept' => 'application/json'},
|
|
474
|
+
body: {ids: ids},
|
|
475
|
+
body_type: :json)
|
|
465
476
|
return Main.result_status('Package(s) deleted')
|
|
466
477
|
when :receive
|
|
467
478
|
return package_receive(package_id)
|
|
@@ -483,8 +494,9 @@ module Aspera
|
|
|
483
494
|
operation: 'POST',
|
|
484
495
|
subpath: "packages/#{package['id']}/transfer_spec/upload",
|
|
485
496
|
headers: {'Accept' => 'application/json'},
|
|
486
|
-
|
|
487
|
-
|
|
497
|
+
query: {transfer_type: TRANSFER_CONNECT},
|
|
498
|
+
body: {paths: transfer.source_list},
|
|
499
|
+
body_type: :json
|
|
488
500
|
)[:data]
|
|
489
501
|
# well, we asked a TS for connect, but we actually want a generic one
|
|
490
502
|
transfer_spec.delete('authentication')
|
|
@@ -492,7 +504,10 @@ module Aspera
|
|
|
492
504
|
else
|
|
493
505
|
# send from remote shared folder
|
|
494
506
|
if (m = shared_folder.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
495
|
-
shared_folder = lookup_entity_by_field(
|
|
507
|
+
shared_folder = lookup_entity_by_field(
|
|
508
|
+
type: 'shared_folders',
|
|
509
|
+
field: m[1],
|
|
510
|
+
value: ExtendedValue.instance.evaluate(m[2]))['id']
|
|
496
511
|
end
|
|
497
512
|
transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
|
|
498
513
|
# start remote transfer and get first status
|
|
@@ -510,7 +525,176 @@ module Aspera
|
|
|
510
525
|
data: list_packages_with_filter,
|
|
511
526
|
fields: %w[id title release_date total_bytes total_files created_time state]
|
|
512
527
|
}
|
|
513
|
-
end
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def execute_resource(res_type)
|
|
532
|
+
list_key = res_path = res_type.to_s
|
|
533
|
+
id_as_arg = false
|
|
534
|
+
display_fields = nil
|
|
535
|
+
adm_api = @api_v5
|
|
536
|
+
res_id_query = :default
|
|
537
|
+
delete_style = nil
|
|
538
|
+
available_commands = [].concat(Plugin::ALL_OPS)
|
|
539
|
+
case res_type
|
|
540
|
+
when :metadata_profiles
|
|
541
|
+
res_path = 'configuration/metadata_profiles'
|
|
542
|
+
list_key = 'profiles'
|
|
543
|
+
when :alternate_addresses
|
|
544
|
+
res_path = 'configuration/alternate_addresses'
|
|
545
|
+
when :distribution_lists
|
|
546
|
+
res_path = 'account/distribution_lists'
|
|
547
|
+
list_key = 'distribution_lists'
|
|
548
|
+
delete_style = 'ids'
|
|
549
|
+
when :email_notifications
|
|
550
|
+
list_key = false
|
|
551
|
+
id_as_arg = 'type'
|
|
552
|
+
when :accounts
|
|
553
|
+
display_fields = Formatter.all_but('user_profile_data_attributes')
|
|
554
|
+
when :oauth_clients
|
|
555
|
+
display_fields = Formatter.all_but('public_key')
|
|
556
|
+
adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
|
|
557
|
+
when :shared_inboxes, :workgroups
|
|
558
|
+
available_commands.push(:members, :saml_groups, :invite_external_collaborator)
|
|
559
|
+
res_id_query = {'all': true}
|
|
560
|
+
when :nodes
|
|
561
|
+
available_commands.push(:shared_folders, :browse)
|
|
562
|
+
end
|
|
563
|
+
res_command = options.get_next_command(available_commands)
|
|
564
|
+
case res_command
|
|
565
|
+
when *Plugin::ALL_OPS
|
|
566
|
+
return entity_command(
|
|
567
|
+
res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg,
|
|
568
|
+
delete_style: delete_style) do |field, value|
|
|
569
|
+
lookup_entity_by_field(
|
|
570
|
+
type: res_type, value: value, field: field, real_path: res_path, item_list_key: list_key, query: res_id_query)['id']
|
|
571
|
+
end
|
|
572
|
+
when :shared_folders
|
|
573
|
+
node_id = instance_identifier do |field, value|
|
|
574
|
+
lookup_entity_by_field(type: res_type, field: field, value: value)['id']
|
|
575
|
+
end
|
|
576
|
+
sh_path = "#{res_path}/#{node_id}/shared_folders"
|
|
577
|
+
sh_command = options.get_next_command([:user].concat(Plugin::ALL_OPS))
|
|
578
|
+
case sh_command
|
|
579
|
+
when *Plugin::ALL_OPS
|
|
580
|
+
return entity_command(sh_command, adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
|
|
581
|
+
lookup_entity_by_field(type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
|
|
582
|
+
end
|
|
583
|
+
when :user
|
|
584
|
+
sh_id = instance_identifier do |field, value|
|
|
585
|
+
lookup_entity_by_field(type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
|
|
586
|
+
end
|
|
587
|
+
user_path = "#{sh_path}/#{sh_id}/custom_access_users"
|
|
588
|
+
return entity_action(adm_api, user_path, item_list_key: 'users') do |field, value|
|
|
589
|
+
lookup_entity_by_field(type: 'users', real_path: user_path, field: field, value: value)['id']
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
end
|
|
593
|
+
when :browse
|
|
594
|
+
return browse_folder("#{res_path}/#{instance_identifier}/browse")
|
|
595
|
+
when :invite_external_collaborator
|
|
596
|
+
shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
|
|
597
|
+
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
|
598
|
+
creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
|
|
599
|
+
res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
|
|
600
|
+
result = adm_api.create(res_path, creation_payload)[:data]
|
|
601
|
+
formatter.display_status(result['message'])
|
|
602
|
+
result = lookup_entity_by_field(
|
|
603
|
+
type: 'members',
|
|
604
|
+
real_path: "#{res_type}/#{shared_inbox_id}/members",
|
|
605
|
+
value: creation_payload['email_address'],
|
|
606
|
+
query: {})
|
|
607
|
+
return {type: :single_object, data: result}
|
|
608
|
+
when :members, :saml_groups
|
|
609
|
+
res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
|
|
610
|
+
res_prefix = "#{res_type}/#{res_id}"
|
|
611
|
+
res_path = "#{res_prefix}/#{res_command}"
|
|
612
|
+
list_key = res_command.to_s
|
|
613
|
+
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
614
|
+
sub_command = options.get_next_command(%i[create list modify delete])
|
|
615
|
+
if sub_command.eql?(:create) && options.get_option(:value).nil?
|
|
616
|
+
raise "use option 'value' to provide saml group_id and access (refer to API)" unless res_command.eql?(:members)
|
|
617
|
+
# first arg is one user name or list of users
|
|
618
|
+
users = options.get_next_argument('user id, %name:, or Array')
|
|
619
|
+
users = [users] unless users.is_a?(Array)
|
|
620
|
+
users = users.map do |user|
|
|
621
|
+
if (m = user.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
622
|
+
lookup_entity_by_field(
|
|
623
|
+
type: 'accounts',
|
|
624
|
+
field: m[1],
|
|
625
|
+
value: ExtendedValue.instance.evaluate(m[2]),
|
|
626
|
+
query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
|
|
627
|
+
else
|
|
628
|
+
# it's the user id (not member id...)
|
|
629
|
+
user
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
access = options.get_next_argument('level', mandatory: false, accept_list: %i[submit_only standard shared_inbox_admin], default: :standard)
|
|
633
|
+
# TODO: unshift to command line parameters instead of using deprecated option "value"
|
|
634
|
+
options.set_option(:value, {user: users.map{|u|{id: u, access: access}}})
|
|
635
|
+
end
|
|
636
|
+
return entity_command(sub_command, adm_api, res_path, item_list_key: list_key) do |field, value|
|
|
637
|
+
lookup_entity_by_field(
|
|
638
|
+
type: 'accounts',
|
|
639
|
+
field: field,
|
|
640
|
+
value: value,
|
|
641
|
+
query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def execute_admin
|
|
647
|
+
command = options.get_next_command(%i[configuration smtp resource events clean_deleted].concat(ADMIN_RESOURCES).freeze)
|
|
648
|
+
case command
|
|
649
|
+
when :resource
|
|
650
|
+
# resource will be deprecated
|
|
651
|
+
Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
|
|
652
|
+
return execute_resource(options.get_next_command(ADMIN_RESOURCES))
|
|
653
|
+
when *ADMIN_RESOURCES
|
|
654
|
+
return execute_resource(command)
|
|
655
|
+
when :clean_deleted
|
|
656
|
+
delete_data = value_create_modify(command: command, default: {days_before_deleting_package_records: 365})
|
|
657
|
+
res = @api_v5.create('internal/packages/clean_deleted', delete_data)
|
|
658
|
+
return {type: :single_object, data: res[:data]}
|
|
659
|
+
when :events
|
|
660
|
+
event_type = options.get_next_command(%i[application webhook])
|
|
661
|
+
case event_type
|
|
662
|
+
when :application
|
|
663
|
+
return {type: :object_list, data: list_entities(type: 'application_events'), fields: %w[event_type created_at application user.name]}
|
|
664
|
+
when :webhook
|
|
665
|
+
return {type: :object_list, data: list_entities(type: 'all_webhooks_events', item_list_key: 'events')}
|
|
666
|
+
end
|
|
667
|
+
when :configuration
|
|
668
|
+
conf_path = 'configuration'
|
|
669
|
+
conf_cmd = options.get_next_command(%i[show modify])
|
|
670
|
+
case conf_cmd
|
|
671
|
+
when :show
|
|
672
|
+
return { type: :single_object, data: @api_v5.read(conf_path)[:data] }
|
|
673
|
+
when :modify
|
|
674
|
+
return { type: :single_object, data: @api_v5.update(conf_path, value_create_modify(command: conf_cmd))[:data] }
|
|
675
|
+
end
|
|
676
|
+
when :smtp
|
|
677
|
+
smtp_path = 'configuration/smtp'
|
|
678
|
+
smtp_cmd = options.get_next_command(%i[show create modify delete test])
|
|
679
|
+
case smtp_cmd
|
|
680
|
+
when :show
|
|
681
|
+
return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
|
|
682
|
+
when :create
|
|
683
|
+
return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
|
684
|
+
when :modify
|
|
685
|
+
return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
|
686
|
+
when :delete
|
|
687
|
+
@api_v5.delete(smtp_path)[:data]
|
|
688
|
+
return Main.result_status('SMTP configuration deleted')
|
|
689
|
+
when :test
|
|
690
|
+
test_data = options.get_next_argument('Email or test data, see API')
|
|
691
|
+
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
|
692
|
+
creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
|
|
693
|
+
result = wait_for_job(creation['job_id'])
|
|
694
|
+
result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
|
|
695
|
+
return { type: :single_object, data: result }
|
|
696
|
+
end
|
|
697
|
+
end
|
|
514
698
|
end
|
|
515
699
|
|
|
516
700
|
ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing invitations].freeze
|
|
@@ -541,12 +725,12 @@ module Aspera
|
|
|
541
725
|
when :show
|
|
542
726
|
return { type: :single_object, data: @api_v5.read('account/preferences')[:data] }
|
|
543
727
|
when :modify
|
|
544
|
-
@api_v5.update('account/preferences', options.get_next_argument('modified parameters',
|
|
728
|
+
@api_v5.update('account/preferences', options.get_next_argument('modified parameters', validation: Hash))
|
|
545
729
|
return Main.result_status('modified')
|
|
546
730
|
end
|
|
547
731
|
end
|
|
548
732
|
when :bearer_token
|
|
549
|
-
return {type: :text, data: @api_v5.
|
|
733
|
+
return {type: :text, data: @api_v5.oauth.token}
|
|
550
734
|
when :packages
|
|
551
735
|
return package_action
|
|
552
736
|
when :shared_folders
|
|
@@ -566,117 +750,7 @@ module Aspera
|
|
|
566
750
|
return browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse")
|
|
567
751
|
end
|
|
568
752
|
when :admin
|
|
569
|
-
|
|
570
|
-
when :resource
|
|
571
|
-
res_type = options.get_next_command(ADMIN_RESOURCES)
|
|
572
|
-
res_path = list_key = res_type.to_s
|
|
573
|
-
id_as_arg = false
|
|
574
|
-
display_fields = nil
|
|
575
|
-
adm_api = @api_v5
|
|
576
|
-
special_query = :default
|
|
577
|
-
available_commands = [].concat(Plugin::ALL_OPS)
|
|
578
|
-
case res_type
|
|
579
|
-
when :metadata_profiles
|
|
580
|
-
res_path = 'configuration/metadata_profiles'
|
|
581
|
-
list_key = 'profiles'
|
|
582
|
-
when :alternate_addresses
|
|
583
|
-
res_path = 'configuration/alternate_addresses'
|
|
584
|
-
when :email_notifications
|
|
585
|
-
list_key = false
|
|
586
|
-
id_as_arg = 'type'
|
|
587
|
-
when :accounts
|
|
588
|
-
display_fields = Formatter.all_but('user_profile_data_attributes')
|
|
589
|
-
when :oauth_clients
|
|
590
|
-
display_fields = Formatter.all_but('public_key')
|
|
591
|
-
adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
|
|
592
|
-
when :shared_inboxes, :workgroups
|
|
593
|
-
available_commands.push(:members, :saml_groups, :invite_external_collaborator)
|
|
594
|
-
special_query = {'all': true}
|
|
595
|
-
when :nodes
|
|
596
|
-
available_commands.push(:shared_folders, :browse)
|
|
597
|
-
end
|
|
598
|
-
res_command = options.get_next_command(available_commands)
|
|
599
|
-
case res_command
|
|
600
|
-
when *Plugin::ALL_OPS
|
|
601
|
-
return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg) do |field, value|
|
|
602
|
-
lookup_entity_by_field(
|
|
603
|
-
type: res_type, real_path: res_path, field: field, value: value, query: special_query)['id']
|
|
604
|
-
end
|
|
605
|
-
when :shared_folders
|
|
606
|
-
node_id = instance_identifier do |field, value|
|
|
607
|
-
lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']
|
|
608
|
-
end
|
|
609
|
-
sh_path = "#{res_path}/#{node_id}/shared_folders"
|
|
610
|
-
return entity_action(adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
|
|
611
|
-
lookup_entity_by_field(
|
|
612
|
-
type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
|
|
613
|
-
end
|
|
614
|
-
when :browse
|
|
615
|
-
return browse_folder("#{res_path}/#{instance_identifier}/browse")
|
|
616
|
-
when :invite_external_collaborator
|
|
617
|
-
shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
|
618
|
-
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
|
619
|
-
creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
|
|
620
|
-
res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
|
|
621
|
-
result = adm_api.create(res_path, creation_payload)[:data]
|
|
622
|
-
formatter.display_status(result['message'])
|
|
623
|
-
result = lookup_entity_by_field(
|
|
624
|
-
type: 'members', real_path: "#{res_type}/#{shared_inbox_id}/members", value: creation_payload['email_address'],
|
|
625
|
-
query: {})
|
|
626
|
-
return {type: :single_object, data: result}
|
|
627
|
-
when :members, :saml_groups
|
|
628
|
-
res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
|
629
|
-
res_prefix = "#{res_type}/#{res_id}"
|
|
630
|
-
res_path = "#{res_prefix}/#{res_command}"
|
|
631
|
-
list_key = res_command.to_s
|
|
632
|
-
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
633
|
-
sub_command = options.get_next_command(%i[create list modify delete])
|
|
634
|
-
if sub_command.eql?(:create) && options.get_option(:value).nil?
|
|
635
|
-
raise "use option 'value' to provide saml group_id and access (refer to API)" unless res_command.eql?(:members)
|
|
636
|
-
# first arg is one user name or list of users
|
|
637
|
-
users = options.get_next_argument('user id, or email, or list of')
|
|
638
|
-
users = [users] unless users.is_a?(Array)
|
|
639
|
-
users = users.map do |user|
|
|
640
|
-
if (m = user.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
641
|
-
lookup_entity_by_field(
|
|
642
|
-
type: 'accounts', field: m[1], value: m[2],
|
|
643
|
-
query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
|
|
644
|
-
else
|
|
645
|
-
# it's the user id (not member id...)
|
|
646
|
-
user
|
|
647
|
-
end
|
|
648
|
-
end
|
|
649
|
-
access = options.get_next_argument('level', mandatory: false, expected: %i[submit_only standard shared_inbox_admin], default: :standard)
|
|
650
|
-
# TODO: unshift to command line parameters instead of using deprecated option "value"
|
|
651
|
-
options.set_option(:value, {user: users.map{|u|{id: u, access: access}}})
|
|
652
|
-
end
|
|
653
|
-
return entity_command(sub_command, adm_api, res_path, item_list_key: list_key) do |field, value|
|
|
654
|
-
lookup_entity_by_field(
|
|
655
|
-
type: 'accounts', field: field, value: value,
|
|
656
|
-
query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
|
|
657
|
-
end
|
|
658
|
-
end
|
|
659
|
-
when :smtp
|
|
660
|
-
smtp_path = 'configuration/smtp'
|
|
661
|
-
smtp_cmd = options.get_next_command(%i[show create modify delete test])
|
|
662
|
-
case smtp_cmd
|
|
663
|
-
when :show
|
|
664
|
-
return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
|
|
665
|
-
when :create
|
|
666
|
-
return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
|
667
|
-
when :modify
|
|
668
|
-
return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
|
669
|
-
when :delete
|
|
670
|
-
return { type: :single_object, data: @api_v5.delete(smtp_path)[:data] }
|
|
671
|
-
when :test
|
|
672
|
-
test_data = options.get_next_argument('Email or test data, see API')
|
|
673
|
-
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
|
674
|
-
creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
|
|
675
|
-
result = wait_for_job(creation['job_id'])
|
|
676
|
-
result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
|
|
677
|
-
return { type: :single_object, data: result }
|
|
678
|
-
end
|
|
679
|
-
end
|
|
753
|
+
return execute_admin
|
|
680
754
|
when :invitations
|
|
681
755
|
invitation_endpoint = 'invitations'
|
|
682
756
|
invitation_command = options.get_next_command(%i[resend].concat(Plugin::ALL_OPS))
|
|
@@ -692,7 +766,9 @@ module Aspera
|
|
|
692
766
|
else
|
|
693
767
|
return entity_command(
|
|
694
768
|
invitation_command, @api_v5, invitation_endpoint, item_list_key: invitation_endpoint,
|
|
695
|
-
display_fields: %w[id public recipient_type recipient_name email_address])
|
|
769
|
+
display_fields: %w[id public recipient_type recipient_name email_address]) do |field, value|
|
|
770
|
+
lookup_entity_by_field(type: invitation_endpoint, field: field, value: value, query: {})['id']
|
|
771
|
+
end
|
|
696
772
|
end
|
|
697
773
|
when :gateway
|
|
698
774
|
require 'aspera/faspex_gw'
|
|
@@ -714,9 +790,9 @@ module Aspera
|
|
|
714
790
|
server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
|
|
715
791
|
server.start
|
|
716
792
|
return Main.result_status('Gateway terminated')
|
|
717
|
-
end
|
|
718
|
-
end
|
|
719
|
-
end
|
|
720
|
-
end
|
|
721
|
-
end
|
|
722
|
-
end
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/rest'
|
|
4
|
+
require 'aspera/nagios'
|
|
5
|
+
require 'aspera/cli/basic_auth_plugin'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
module Cli
|
|
9
|
+
module Plugins
|
|
10
|
+
class Faspio < BasicAuthPlugin
|
|
11
|
+
class << self
|
|
12
|
+
def application_name
|
|
13
|
+
'faspio Gateway'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def detect(base_url)
|
|
17
|
+
api = Rest.new(base_url: base_url)
|
|
18
|
+
ping_result = api.read('ping')
|
|
19
|
+
server_type = ping_result[:http]['Server']
|
|
20
|
+
return nil unless ping_result[:data].is_a?(Hash) && ping_result[:data].empty?
|
|
21
|
+
return nil unless server_type.is_a?(String) && server_type.include?('faspio')
|
|
22
|
+
return {
|
|
23
|
+
version: server_type.gsub(%r{^.*/}, ''),
|
|
24
|
+
url: base_url
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
ACTIONS = %i[health bridges].freeze
|
|
29
|
+
|
|
30
|
+
def initialize(**env)
|
|
31
|
+
super
|
|
32
|
+
options.declare(:auth, 'OAuth type of authentication', values: %i[jwt basic])
|
|
33
|
+
options.declare(:client_id, 'OAuth client identifier')
|
|
34
|
+
options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
|
|
35
|
+
options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
|
|
36
|
+
options.parse_options!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute_action
|
|
40
|
+
base_url = options.get_option(:url, mandatory: true)
|
|
41
|
+
api =
|
|
42
|
+
case options.get_option(:auth, mandatory: true)
|
|
43
|
+
when :basic
|
|
44
|
+
basic_auth_api
|
|
45
|
+
when :jwt
|
|
46
|
+
app_client_id = options.get_option(:client_id, mandatory: true)
|
|
47
|
+
Rest.new(
|
|
48
|
+
base_url: base_url,
|
|
49
|
+
auth: {
|
|
50
|
+
type: :oauth2,
|
|
51
|
+
grant_method: :jwt,
|
|
52
|
+
base_url: "#{base_url}/auth",
|
|
53
|
+
client_id: app_client_id,
|
|
54
|
+
use_query: true,
|
|
55
|
+
payload: {
|
|
56
|
+
iss: app_client_id, # issuer
|
|
57
|
+
sub: app_client_id # subject
|
|
58
|
+
},
|
|
59
|
+
private_key_obj: OpenSSL::PKey::RSA.new(options.get_option(:private_key, mandatory: true), options.get_option(:passphrase)),
|
|
60
|
+
headers: {typ: 'JWT'}
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
command = options.get_next_command(ACTIONS)
|
|
64
|
+
case command
|
|
65
|
+
when :health
|
|
66
|
+
nagios = Nagios.new
|
|
67
|
+
begin
|
|
68
|
+
result = api.read('ping')[:data]
|
|
69
|
+
if result.is_a?(Hash) && result.empty?
|
|
70
|
+
nagios.add_ok('api', 'answered ok')
|
|
71
|
+
else
|
|
72
|
+
nagios.add_critical('api', 'not expected answer')
|
|
73
|
+
end
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
nagios.add_critical('api', e.to_s)
|
|
76
|
+
end
|
|
77
|
+
return nagios.result
|
|
78
|
+
when :bridges
|
|
79
|
+
return entity_action(api, 'bridges')
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|