aspera-cli 4.26.2 → 4.27.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +281 -21
- data/CONTRIBUTING.md +98 -7
- data/bin/ascli +3 -3
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +26 -24
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/agent/node.rb +32 -0
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -16
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +15 -14
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +26 -23
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +21 -19
- data/lib/aspera/ascp/installation.rb +37 -38
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +16 -3
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +2 -1
- data/lib/aspera/cli/formatter.rb +41 -27
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +70 -19
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1166 -853
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +457 -100
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +428 -754
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +294 -257
- data/lib/aspera/cli/plugins/faspex5.rb +585 -357
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +998 -794
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
- data/lib/aspera/cli/plugins/preview.rb +180 -139
- data/lib/aspera/cli/plugins/server.rb +124 -79
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +13 -12
- data/lib/aspera/cli/result.rb +61 -11
- data/lib/aspera/cli/runner.rb +275 -141
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +83 -59
- data/lib/aspera/cli/terminal_formatter.rb +1 -1
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +3 -3
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +21 -20
- data/lib/aspera/command_line_builder.rb +22 -22
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +4 -4
- data/lib/aspera/environment.rb +18 -11
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +3 -3
- data/lib/aspera/keychain/macos_security.rb +3 -3
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +17 -3
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +5 -5
- data/lib/aspera/oauth/boot.rb +2 -2
- data/lib/aspera/oauth/factory.rb +7 -5
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_action_once.rb +8 -8
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +3 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +7 -6
- data/lib/aspera/rest.rb +10 -36
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +3 -3
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +59 -10
- data/lib/aspera/schema/registry.rb +39 -6
- data/lib/aspera/secret_hider.rb +7 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/operations.rb +10 -13
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +23 -21
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +6 -6
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +25 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/json_rpc.rb +0 -52
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
require 'aspera/cli/plugins/oauth'
|
|
6
6
|
require 'aspera/cli/extended_value'
|
|
7
|
+
require 'aspera/transfer/result'
|
|
7
8
|
require 'aspera/cli/special_values'
|
|
8
9
|
require 'aspera/cli/wizard'
|
|
9
10
|
require 'aspera/api/faspex'
|
|
@@ -18,11 +19,11 @@ module Aspera
|
|
|
18
19
|
module Cli
|
|
19
20
|
module Plugins
|
|
20
21
|
class Faspex5 < Oauth
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
22
|
+
application_name 'Faspex v5'
|
|
23
|
+
# Commands that may carry a query_schema annotation
|
|
24
|
+
QUERY_SCHEMA_COMMANDS = %i[list delete].freeze
|
|
25
25
|
|
|
26
|
+
class << self
|
|
26
27
|
# @return [Hash,NilClass]
|
|
27
28
|
def detect(address_or_url)
|
|
28
29
|
# add scheme if missing
|
|
@@ -92,11 +93,12 @@ module Aspera
|
|
|
92
93
|
}
|
|
93
94
|
end
|
|
94
95
|
|
|
96
|
+
option :box, description: "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox_all'
|
|
97
|
+
option :shared_folder, description: 'Send package with files from shared folder'
|
|
98
|
+
option :group_type, description: 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes
|
|
99
|
+
|
|
95
100
|
def initialize(**_)
|
|
96
101
|
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: 'inbox_all')
|
|
98
|
-
options.declare(:shared_folder, 'Send package with files from shared folder')
|
|
99
|
-
options.declare(:group_type, 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes)
|
|
100
102
|
options.parse_options!
|
|
101
103
|
# [Aspera::Api::Faspex]
|
|
102
104
|
@api_v5 = nil
|
|
@@ -136,24 +138,24 @@ module Aspera
|
|
|
136
138
|
# user asked to not follow
|
|
137
139
|
return status if status_list.nil?
|
|
138
140
|
if status['upload_status'].eql?('submitted')
|
|
139
|
-
|
|
141
|
+
progress_bar&.event(:sessions_init, session_id: nil, info: status['upload_status'])
|
|
140
142
|
elsif !total_sent
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
progress_bar&.event(:session_start, session_id: id)
|
|
144
|
+
progress_bar&.event(:session_size, session_id: id, info: status['bytes_total'].to_i)
|
|
143
145
|
total_sent = true
|
|
144
146
|
else
|
|
145
|
-
|
|
147
|
+
progress_bar&.event(:transfer, session_id: id, info: status['bytes_written'].to_i)
|
|
146
148
|
end
|
|
147
149
|
if status_list.include?(status['upload_status'])
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
progress_bar&.event(:session_end, session_id: id)
|
|
151
|
+
progress_bar&.event(:end)
|
|
150
152
|
return status
|
|
151
153
|
end
|
|
152
154
|
sleep(1.0)
|
|
153
155
|
end
|
|
154
156
|
end
|
|
155
157
|
|
|
156
|
-
# @param [String] job identifier
|
|
158
|
+
# @param job_id [String] job identifier
|
|
157
159
|
# @return [Hash] result of API call for job status
|
|
158
160
|
def wait_for_job(job_id)
|
|
159
161
|
result = nil
|
|
@@ -261,7 +263,7 @@ module Aspera
|
|
|
261
263
|
statuses = transfer.start(transfer_spec)
|
|
262
264
|
result_transfer.push({'package' => pkg_id, Runner::STATUS_FIELD => statuses})
|
|
263
265
|
# skip only if all sessions completed
|
|
264
|
-
if
|
|
266
|
+
if statuses.is_a?(Transfer::Result::Success) && skip_ids_persistency
|
|
265
267
|
skip_ids_persistency.data.push(pkg_id)
|
|
266
268
|
skip_ids_persistency.save
|
|
267
269
|
end
|
|
@@ -269,8 +271,7 @@ module Aspera
|
|
|
269
271
|
return Runner.result_transfer_multiple(result_transfer)
|
|
270
272
|
end
|
|
271
273
|
|
|
272
|
-
def package_send
|
|
273
|
-
parameters = value_create_modify(command: :send, schema: Schema::Registry.req_body(Schema::Registry::FASPEX, 'packages.post'))
|
|
274
|
+
def package_send(parameters)
|
|
274
275
|
# autofill recipient for public url
|
|
275
276
|
if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
|
|
276
277
|
parameters['recipients'] = [{
|
|
@@ -299,7 +300,7 @@ module Aspera
|
|
|
299
300
|
return Runner.result_transfer(transfer.start(transfer_spec))
|
|
300
301
|
else
|
|
301
302
|
# send from remote shared folder
|
|
302
|
-
if (m =
|
|
303
|
+
if (m = Parser.percent_selector(shared_folder))
|
|
303
304
|
shared_folder = @api_v5.lookup_entity_by_field(
|
|
304
305
|
entity: 'shared_folders',
|
|
305
306
|
field: m[:field],
|
|
@@ -320,8 +321,9 @@ module Aspera
|
|
|
320
321
|
|
|
321
322
|
# Browse a folder
|
|
322
323
|
# @param browse_endpoint [String] the endpoint to browse
|
|
323
|
-
|
|
324
|
-
|
|
324
|
+
# @param folder_path [String] starting path (default: '/')
|
|
325
|
+
def browse_folder(browse_endpoint, base_query = {}, folder_path: '/')
|
|
326
|
+
folders_to_process = [folder_path]
|
|
325
327
|
query = base_query.merge(query_read_delete(default: {}))
|
|
326
328
|
filters = query.delete('filters'){{}}
|
|
327
329
|
Aspera.assert_type(filters, Hash)
|
|
@@ -374,374 +376,600 @@ module Aspera
|
|
|
374
376
|
return Result::ObjectList.new(all_items, total: total_count)
|
|
375
377
|
end
|
|
376
378
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
379
|
+
# Per-resource configuration for admin CRUD sub-trees.
|
|
380
|
+
# Keys mirror entity_execute kwargs; extra_commands lists additional leaf commands.
|
|
381
|
+
# @return [Hash{Symbol => Hash}]
|
|
382
|
+
RESOURCE_CONFIG = {
|
|
383
|
+
accounts: {
|
|
384
|
+
display_fields: ->{Formatter.all_but('user_profile_data_attributes')},
|
|
385
|
+
extra_commands: [:reset_password],
|
|
386
|
+
instance_arg_commands: {reset_password: {arguments: [{name: :contact_id, type: :identifier, lookup: :lookup_accounts_id}]}},
|
|
387
|
+
query_component: Schema::Registry::FASPEX
|
|
388
|
+
},
|
|
389
|
+
alternate_addresses: {entity: 'configuration/alternate_addresses', query_component: Schema::Registry::FASPEX},
|
|
390
|
+
contacts: {query_component: Schema::Registry::FASPEX},
|
|
391
|
+
distribution_lists: {entity: 'account/distribution_lists', delete_style: 'ids', query_component: Schema::Registry::FASPEX},
|
|
392
|
+
email_notifications: {id_as_arg: 'type', query_component: Schema::Registry::FASPEX},
|
|
393
|
+
file_processing: {
|
|
394
|
+
commands: %i[next modify],
|
|
395
|
+
schema: ->{Schema::Registry.req_body(Schema::Registry::FASPEX, 'file_processing.put')},
|
|
396
|
+
is_singleton: true
|
|
397
|
+
},
|
|
398
|
+
jobs: {display_fields: %w[id job_name job_type status], query_component: Schema::Registry::FASPEX},
|
|
399
|
+
metadata_profiles: {entity: 'configuration/metadata_profiles', items_key: 'profiles', query_component: Schema::Registry::FASPEX},
|
|
400
|
+
nodes: {
|
|
401
|
+
extra_commands: %i[browse],
|
|
402
|
+
instance_arg_commands: {
|
|
403
|
+
browse: {
|
|
404
|
+
arguments: [{name: :node_id, type: :identifier, lookup: :lookup_node_id},
|
|
405
|
+
{name: :folder_path, type: String, mandatory: false, default: '/'}]
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
query_component: Schema::Registry::FASPEX
|
|
409
|
+
},
|
|
410
|
+
oauth_clients: {
|
|
411
|
+
display_fields: ->{Formatter.all_but('public_key')},
|
|
412
|
+
api: ->{Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.kwargs_from_options(options))},
|
|
413
|
+
list_query: {'expand': true, 'no_api_path': true, 'client_types[]': 'public'},
|
|
414
|
+
query_component: Schema::Registry::FASPEX
|
|
415
|
+
},
|
|
416
|
+
registrations: {query_component: Schema::Registry::FASPEX},
|
|
417
|
+
saml_configs: {query_component: Schema::Registry::FASPEX},
|
|
418
|
+
shared_inboxes: {res_id_query: {'all': true}, query_component: Schema::Registry::FASPEX},
|
|
419
|
+
webhooks: {query_component: Schema::Registry::FASPEX},
|
|
420
|
+
workgroups: {res_id_query: {'all': true}, query_component: Schema::Registry::FASPEX}
|
|
421
|
+
}.freeze
|
|
422
|
+
private_constant :RESOURCE_CONFIG
|
|
423
|
+
|
|
424
|
+
# Resolve a RESOURCE_CONFIG value that may be a Proc (evaluated in instance context).
|
|
425
|
+
def resource_config_value(cfg, key)
|
|
426
|
+
v = cfg[key]
|
|
427
|
+
v.is_a?(Proc) ? instance_exec(&v) : v
|
|
423
428
|
end
|
|
424
429
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
430
|
+
# Build exec_args hash for entity_execute from RESOURCE_CONFIG for the given resource.
|
|
431
|
+
def res_exec_args(res_sym)
|
|
432
|
+
cfg = RESOURCE_CONFIG.fetch(res_sym, {})
|
|
433
|
+
{
|
|
434
|
+
api: resource_config_value(cfg, :api) || @api_v5,
|
|
435
|
+
entity: resource_config_value(cfg, :entity) || res_sym.to_s,
|
|
436
|
+
items_key: resource_config_value(cfg, :items_key),
|
|
437
|
+
delete_style: resource_config_value(cfg, :delete_style),
|
|
438
|
+
id_as_arg: resource_config_value(cfg, :id_as_arg) || false,
|
|
439
|
+
display_fields: resource_config_value(cfg, :display_fields),
|
|
440
|
+
list_query: resource_config_value(cfg, :list_query),
|
|
441
|
+
is_singleton: resource_config_value(cfg, :is_singleton) || false,
|
|
442
|
+
schema: resource_config_value(cfg, :schema),
|
|
443
|
+
query_component: resource_config_value(cfg, :query_component)
|
|
444
|
+
}.compact
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Lookup methods for arguments: + lookup: on specific nodes
|
|
448
|
+
|
|
449
|
+
# admin > nodes — lookup node id by field/value
|
|
450
|
+
def lookup_node_id(field, value, **)
|
|
451
|
+
@api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# admin > shared_inboxes — lookup id by field/value
|
|
455
|
+
def lookup_shared_inboxes_id(field, value, **)
|
|
456
|
+
@api_v5.lookup_entity_by_field(entity: 'shared_inboxes', field: field, value: value, query: {'all': true})['id']
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# admin > workgroups — lookup id by field/value
|
|
460
|
+
def lookup_workgroups_id(field, value, **)
|
|
461
|
+
@api_v5.lookup_entity_by_field(entity: 'workgroups', field: field, value: value, query: {'all': true})['id']
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# admin > accounts — lookup id by field/value (used for reset_password arguments:)
|
|
465
|
+
def lookup_accounts_id(field, value, **)
|
|
466
|
+
res_lookup_id(:accounts, field, value)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Lookup id for a RESOURCE_CONFIG resource by field/value.
|
|
470
|
+
def res_lookup_id(res_sym, field, value)
|
|
471
|
+
cfg = RESOURCE_CONFIG.fetch(res_sym, {})
|
|
472
|
+
entity = resource_config_value(cfg, :entity) || res_sym.to_s
|
|
473
|
+
items_key = resource_config_value(cfg, :items_key)
|
|
474
|
+
res_id_query = resource_config_value(cfg, :res_id_query) || :default
|
|
475
|
+
@api_v5.lookup_entity_by_field(entity: entity, value: value, field: field, items_key: items_key, query: res_id_query)['id']
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# --- DSL ---
|
|
479
|
+
|
|
480
|
+
# Commands that need @api_v5 carry setup: :setup_api_v5.
|
|
481
|
+
# :health and :postprocessing work without authentication, so they have no setup.
|
|
482
|
+
command :health, description: 'Check Faspex 5 health'
|
|
483
|
+
command :version, description: 'Show Faspex 5 version', setup: :setup_api_v5, action: ->{Result::SingleObject.new(@api_v5.read('version'))}
|
|
484
|
+
command :bearer_token, description: 'Show OAuth bearer token', setup: :setup_api_v5, action: ->{Result::Text.new(@api_v5.oauth.authorization)}
|
|
485
|
+
command :packages, description: 'Manage packages', setup: :setup_api_v5
|
|
486
|
+
commands_under(:packages) do
|
|
487
|
+
command :list, description: 'List packages'
|
|
488
|
+
command :send, description: 'Send a package', transfer_paths: :send,
|
|
489
|
+
arguments: [{name: :data, type: Hash, schema: Schema::Registry.req_body(Schema::Registry::FASPEX, 'packages.post')}],
|
|
490
|
+
action: ->(data:, **){package_send(data)}
|
|
491
|
+
command :show, description: 'Show a package', setup: :setup_package_id,
|
|
492
|
+
action: ->(package_id:, **){Result::SingleObject.new(@api_v5.read("packages/#{package_id}"))}
|
|
493
|
+
command :browse, description: 'Browse package files', setup: :setup_package_id,
|
|
494
|
+
arguments: [{name: :folder_path, type: String, mandatory: false, default: '/'}],
|
|
495
|
+
action: ->(folder_path:, package_id:, **){browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}", recipient_query(package_id), folder_path: folder_path)}
|
|
496
|
+
command(
|
|
497
|
+
:status, description: 'Wait for package status', setup: :setup_package_id,
|
|
498
|
+
arguments: [{name: :status_list, type: Array, mandatory: false, default: nil}],
|
|
499
|
+
action: ->(status_list:, package_id:, **){Result::SingleObject.new(wait_package_status(package_id, status_list: status_list))}
|
|
500
|
+
)
|
|
501
|
+
command :delete, description: 'Delete package(s)', setup: :setup_package_id
|
|
502
|
+
command :receive, description: 'Receive a package', setup: :setup_package_id, transfer_paths: :receive,
|
|
503
|
+
action: ->(package_id:, **){package_receive(package_id)}
|
|
504
|
+
command(:file_processing, description: 'Show file processing status', setup: :setup_package_id, action: lambda do |package_id:, **|
|
|
505
|
+
result, count = @api_v5.list_entities_limit_offset_total_count(entity: "packages/#{package_id}/file_statuses", items_key: 'files')
|
|
506
|
+
Result::ObjectList.new(result, total: count)
|
|
507
|
+
end)
|
|
508
|
+
end
|
|
509
|
+
command :admin, description: 'Administer Faspex 5', setup: :setup_api_v5
|
|
510
|
+
command :user, description: 'Manage current user', setup: :setup_api_v5
|
|
511
|
+
command :shared_folders, description: 'Browse shared folders', setup: :setup_api_v5
|
|
512
|
+
command :gateway, description: 'Start Faspex 4 gateway emulation', setup: :setup_api_v5,
|
|
513
|
+
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
514
|
+
command :postprocessing, description: 'Start Faspex 4 post-processing server',
|
|
515
|
+
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
516
|
+
command :invitations, description: 'Manage invitations', setup: :setup_api_v5
|
|
517
|
+
|
|
518
|
+
commands_under(:invitations) do
|
|
519
|
+
command :create, description: 'Create an invitation',
|
|
520
|
+
arguments: [{name: :input_data, type: Hash, bulk: true}]
|
|
521
|
+
command :resend, description: 'Resend an invitation',
|
|
522
|
+
arguments: [{name: :invitation_id, type: :identifier}]
|
|
523
|
+
Operations::ALL.reject{ |op| op == :create}.each do |op|
|
|
524
|
+
command(op, description: "#{op.capitalize} invitation(s)")
|
|
464
525
|
end
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
@api_v5.
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
commands_under(:user) do
|
|
529
|
+
command :account, description: 'Show account information', action: ->{Result::SingleObject.new(@api_v5.read('account', query_read_delete))}
|
|
530
|
+
command :profile, description: 'Manage user profile'
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
commands_under(%i[user profile]) do
|
|
534
|
+
command :show, description: 'Show user profile', action: ->{Result::SingleObject.new(@api_v5.read('account/preferences'))}
|
|
535
|
+
command(
|
|
536
|
+
:modify, description: 'Modify user profile',
|
|
537
|
+
arguments: [{name: :properties, type: Hash}],
|
|
538
|
+
action: lambda do |properties:, **|
|
|
539
|
+
@api_v5.update('account/preferences', properties)
|
|
540
|
+
Result::Status.new('modified')
|
|
479
541
|
end
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
case sh_command
|
|
483
|
-
when *Operations::ALL
|
|
484
|
-
return entity_execute(
|
|
485
|
-
api: @api_v5,
|
|
486
|
-
entity: shared_folder_entity,
|
|
487
|
-
command: sh_command
|
|
488
|
-
) do |field, value|
|
|
489
|
-
@api_v5.lookup_entity_by_field(entity: shared_folder_entity, field: field, value: value)['id']
|
|
490
|
-
end
|
|
491
|
-
when :user
|
|
492
|
-
sh_id = options.instance_identifier do |field, value|
|
|
493
|
-
@api_v5.lookup_entity_by_field(entity: shared_folder_entity, field: field, value: value)['id']
|
|
494
|
-
end
|
|
495
|
-
user_path = "#{shared_folder_entity}/#{sh_id}/custom_access_users"
|
|
496
|
-
return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
|
|
497
|
-
@api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
|
|
498
|
-
end
|
|
542
|
+
)
|
|
543
|
+
end
|
|
499
544
|
|
|
545
|
+
commands_under(:shared_folders) do
|
|
546
|
+
command :list, description: 'List shared folders', action: ->{Result::ObjectList.new(@api_v5.read('shared_folders')['shared_folders'])}
|
|
547
|
+
command :browse, description: 'Browse a shared folder',
|
|
548
|
+
arguments: [{name: :shared_folder_id, type: :identifier, lookup: :lookup_shared_folder_id},
|
|
549
|
+
{name: :folder_path, type: String, mandatory: false, default: '/'}]
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# admin sub-tree: fixed commands + all ADMIN_RESOURCES with their sub-commands
|
|
553
|
+
commands_under(:admin) do
|
|
554
|
+
command :configuration, description: 'Manage Faspex 5 configuration'
|
|
555
|
+
command :smtp, description: 'Manage SMTP configuration'
|
|
556
|
+
command :events, description: 'List events'
|
|
557
|
+
command :clean_deleted, description: 'Clean deleted packages',
|
|
558
|
+
arguments: [{name: :input_data, type: Hash, mandatory: false, default: {}}]
|
|
559
|
+
Api::Faspex::ADMIN_RESOURCES.each do |res|
|
|
560
|
+
cfg = RESOURCE_CONFIG.fetch(res, {})
|
|
561
|
+
extra = cfg[:extra_commands] || []
|
|
562
|
+
cmds = cfg[:commands] || (Operations::ALL + extra)
|
|
563
|
+
ia_cmds = cfg[:instance_arg_commands] || {}
|
|
564
|
+
is_singleton = cfg[:is_singleton] || false
|
|
565
|
+
schema_val = cfg[:schema]
|
|
566
|
+
command(res, description: "Manage #{res.to_s.tr('_', ' ')}")
|
|
567
|
+
commands_under([:admin, res]) do
|
|
568
|
+
cmds.each do |c|
|
|
569
|
+
ia = ia_cmds[c] || {}
|
|
570
|
+
extra_args =
|
|
571
|
+
if !is_singleton && c.eql?(:create)
|
|
572
|
+
[{name: :input_data, type: Hash, bulk: true, schema: schema_val}]
|
|
573
|
+
elsif c.eql?(:modify)
|
|
574
|
+
[{name: :input_data, type: Hash, schema: schema_val}]
|
|
575
|
+
else
|
|
576
|
+
[]
|
|
577
|
+
end
|
|
578
|
+
# Merge arguments: from ia (e.g. identifier spec) with extra_args for this operation
|
|
579
|
+
merged_args = Array(ia[:arguments]) + extra_args
|
|
580
|
+
spec_kwargs = merged_args.empty? ? ia.except(:arguments) : ia.except(:arguments).merge(arguments: merged_args)
|
|
581
|
+
# Attach query_schema (full path) to :list/:delete CommandSpec so --help shows the tip.
|
|
582
|
+
# cfg[:query_component] is always a String constant so direct read is safe here (no Proc).
|
|
583
|
+
if QUERY_SCHEMA_COMMANDS.include?(c) && cfg[:query_component]
|
|
584
|
+
entity_path = cfg[:entity] || res.to_s
|
|
585
|
+
spec_kwargs = spec_kwargs.merge(
|
|
586
|
+
query_schema: Schema::Registry.query_params(cfg[:query_component], entity_path)
|
|
587
|
+
)
|
|
588
|
+
end
|
|
589
|
+
command(c, description: c.to_s.tr('_', ' ').capitalize, **spec_kwargs)
|
|
590
|
+
end
|
|
500
591
|
end
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
commands_under(%i[admin nodes]) do
|
|
596
|
+
command :shared_folders, description: 'Manage shared folders',
|
|
597
|
+
arguments: [{name: :node_id, type: :identifier, lookup: :lookup_node_id}],
|
|
598
|
+
setup: :setup_admin_nodes_shared_folders
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# admin > nodes > shared_folders sub-tree
|
|
602
|
+
commands_under(%i[admin nodes shared_folders]) do
|
|
603
|
+
Operations::ALL.each{ |c| command(c, description: c.to_s.tr('_', ' ').capitalize)}
|
|
604
|
+
command :user, description: 'Custom access users',
|
|
605
|
+
arguments: [{name: :sf_id, type: :identifier, lookup: :lookup_sf_id}],
|
|
606
|
+
setup: :setup_admin_nodes_shared_folders_user
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# admin > nodes > shared_folders > user sub-tree (custom access users)
|
|
610
|
+
commands_under(%i[admin nodes shared_folders user]) do
|
|
611
|
+
Operations::ALL.each{ |c| command(c, description: c.to_s.tr('_', ' ').capitalize)}
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
MEMBER_SUBS = %i[members saml_groups].freeze
|
|
615
|
+
CRUD_NO_SHOW = %i[create list modify delete].freeze
|
|
616
|
+
CRUD_NO_LIST = %i[create modify delete show].freeze
|
|
617
|
+
|
|
618
|
+
# admin > shared_inboxes|workgroups > members|saml_groups|invite_external_collaborator:
|
|
619
|
+
# res_id consumed via arguments:(:identifier) + lookup:, builds res_instance_path for all children
|
|
620
|
+
%i[shared_inboxes workgroups].each do |res|
|
|
621
|
+
MEMBER_SUBS.each do |sub|
|
|
622
|
+
commands_under([:admin, res]) do
|
|
623
|
+
command sub, description: sub.to_s.tr('_', ' ').capitalize,
|
|
624
|
+
arguments: [{name: :res_id, type: :identifier, lookup: :"lookup_#{res}_id"}],
|
|
625
|
+
setup: :"setup_admin_#{res}_instance"
|
|
505
626
|
end
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
creation_payload = value_create_modify(command: res_command)
|
|
511
|
-
result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
|
|
512
|
-
formatter.display_status(result['message'])
|
|
513
|
-
result = @api_v5.lookup_entity_by_field(
|
|
514
|
-
entity: "#{res_sym}/#{shared_inbox_id}/members",
|
|
515
|
-
items_key: 'members',
|
|
516
|
-
value: creation_payload['email_address'],
|
|
517
|
-
query: {}
|
|
518
|
-
)
|
|
519
|
-
return Result::SingleObject.new(result)
|
|
520
|
-
when :members, :saml_groups
|
|
521
|
-
# res_command := :shared_inboxes, :workgroups
|
|
522
|
-
res_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
|
|
523
|
-
res_path = "#{res_sym}/#{res_id}/#{res_command}"
|
|
524
|
-
list_key = res_command.to_s
|
|
525
|
-
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
526
|
-
sub_command = options.get_next_command(%i[create list modify delete])
|
|
527
|
-
if sub_command.eql?(:create) && res_command.eql?(:members)
|
|
528
|
-
# first arg is one user name or list of users
|
|
529
|
-
users = options.get_next_argument('user id, %name:, or Array')
|
|
530
|
-
users = [users] unless users.is_a?(Array)
|
|
531
|
-
users = users.map do |user|
|
|
532
|
-
if (m = Manager.percent_selector(user))
|
|
533
|
-
@api_v5.lookup_entity_by_field(
|
|
534
|
-
entity: 'accounts',
|
|
535
|
-
field: m[:field],
|
|
536
|
-
value: m[:value],
|
|
537
|
-
query: Rest.php_style({type: ACCOUNT_TYPES})
|
|
538
|
-
)['id']
|
|
627
|
+
commands_under([:admin, res, sub]) do
|
|
628
|
+
CRUD_NO_SHOW.each do |c|
|
|
629
|
+
args = if c.eql?(:create) && sub.eql?(:members)
|
|
630
|
+
{arguments: [{name: :users, bulk: true}, {name: :access, mandatory: false, default: :standard}]}
|
|
539
631
|
else
|
|
540
|
-
|
|
541
|
-
user
|
|
632
|
+
{}
|
|
542
633
|
end
|
|
634
|
+
command(c, description: c.to_s.capitalize, **args)
|
|
543
635
|
end
|
|
544
|
-
access = options.get_next_argument('level', mandatory: false, accept_list: SHARED_INBOX_MEMBER_LEVELS, default: :standard)
|
|
545
|
-
options.unshift_next_argument({user: users.map{ |u| {id: u, access: access}}})
|
|
546
636
|
end
|
|
547
|
-
# TODO: test SAML group
|
|
548
|
-
return entity_execute(
|
|
549
|
-
api: @api_v5,
|
|
550
|
-
entity: res_path,
|
|
551
|
-
command: sub_command,
|
|
552
|
-
items_key: list_key
|
|
553
|
-
) do |field, value|
|
|
554
|
-
@api_v5.lookup_entity_by_field(
|
|
555
|
-
entity: res_path,
|
|
556
|
-
field: field,
|
|
557
|
-
value: value,
|
|
558
|
-
query: Rest.php_style({type: %w[user]})
|
|
559
|
-
)['user_id']
|
|
560
|
-
end
|
|
561
|
-
when :reset_password
|
|
562
|
-
# :accounts
|
|
563
|
-
contact_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
|
|
564
|
-
@api_v5.create("accounts/#{contact_id}/reset_password", {})
|
|
565
|
-
return Result::Status.new('password reset, user shall check email')
|
|
566
|
-
when :next
|
|
567
|
-
result, count = @api_v5.list_entities_limit_offset_total_count(
|
|
568
|
-
entity: exec_args[:entity],
|
|
569
|
-
operation: 'POST',
|
|
570
|
-
items_key: 'files'
|
|
571
|
-
)
|
|
572
|
-
return Result::ObjectList.new(result, total: count)
|
|
573
637
|
end
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
638
|
+
commands_under([:admin, res]) do
|
|
639
|
+
command :invite_external_collaborator, description: 'Invite external collaborator',
|
|
640
|
+
arguments: [{name: :res_id, type: :identifier, lookup: :"lookup_#{res}_id"},
|
|
641
|
+
{name: :input_data, type: Hash}]
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
commands_under(%i[admin configuration]) do
|
|
646
|
+
command :show, description: 'Show configuration', action: ->{Result::SingleObject.new(@api_v5.read('configuration'))}
|
|
647
|
+
command(
|
|
648
|
+
:modify, description: 'Modify configuration',
|
|
649
|
+
arguments: [{name: :input_data, type: Hash}],
|
|
650
|
+
action: ->(input_data:, **){Result::SingleObject.new(@api_v5.update('configuration', input_data))}
|
|
651
|
+
)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
commands_under(%i[admin smtp]) do
|
|
655
|
+
command :show, description: 'Show SMTP configuration', action: ->{Result::SingleObject.new(@api_v5.read('configuration/smtp'))}
|
|
656
|
+
command(
|
|
657
|
+
:create, description: 'Create SMTP configuration',
|
|
658
|
+
arguments: [{name: :input_data, type: Hash}],
|
|
659
|
+
action: ->(input_data:, **){Result::SingleObject.new(@api_v5.create('configuration/smtp', input_data))}
|
|
660
|
+
)
|
|
661
|
+
command(
|
|
662
|
+
:modify, description: 'Modify SMTP configuration',
|
|
663
|
+
arguments: [{name: :input_data, type: Hash}],
|
|
664
|
+
action: ->(input_data:, **){Result::SingleObject.new(@api_v5.update('configuration/smtp', input_data))}
|
|
665
|
+
)
|
|
666
|
+
command(:delete, description: 'Delete SMTP configuration', action: lambda do
|
|
667
|
+
@api_v5.delete('configuration/smtp')
|
|
668
|
+
Result::Status.new('SMTP configuration deleted')
|
|
669
|
+
end)
|
|
670
|
+
command :test, description: 'Test SMTP configuration',
|
|
671
|
+
arguments: [{name: :test_data, type: nil}]
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
commands_under(%i[admin events]) do
|
|
675
|
+
command(:application, description: 'List application events', action: lambda do
|
|
676
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete)
|
|
677
|
+
Result::ObjectList.new(list, total: total, fields: %w[event_type created_at application user.name])
|
|
678
|
+
end)
|
|
679
|
+
command(:webhook, description: 'List webhook events', action: lambda do
|
|
680
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'all_webhooks_events', query: query_read_delete, items_key: 'events')
|
|
681
|
+
Result::ObjectList.new(list, total: total)
|
|
682
|
+
end)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# admin > clean_deleted handler (leaf, no sub-commands)
|
|
686
|
+
define_action_method(%i[admin clean_deleted]) do |input_data: {}, **|
|
|
687
|
+
input_data = @api_v5.read('configuration').slice('days_before_deleting_package_records') if input_data.empty?
|
|
688
|
+
Result::SingleObject.new(@api_v5.create('internal/packages/clean_deleted', input_data))
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# admin > <resource> > list
|
|
692
|
+
Api::Faspex::ADMIN_RESOURCES.each do |res|
|
|
693
|
+
define_action_method([:admin, res, :list]) do
|
|
694
|
+
args = res_exec_args(res)
|
|
695
|
+
# Special case: email_notifications list returns a fixed value list
|
|
696
|
+
next Result::ValueList.new(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res.eql?(:email_notifications)
|
|
697
|
+
data, total = args[:api].list_entities_limit_offset_total_count(
|
|
698
|
+
entity: args[:entity], items_key: args[:items_key],
|
|
699
|
+
query: query_read_delete(
|
|
700
|
+
default: args[:list_query],
|
|
701
|
+
schema: args[:query_component] ? Schema::Registry.query_params(args[:query_component], args[:entity]) : nil
|
|
598
702
|
)
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
smtp_cmd = options.get_next_command(%i[show create modify delete test])
|
|
614
|
-
case smtp_cmd
|
|
615
|
-
when :show
|
|
616
|
-
return Result::SingleObject.new(@api_v5.read(smtp_path))
|
|
617
|
-
when :create
|
|
618
|
-
return Result::SingleObject.new(@api_v5.create(smtp_path, value_create_modify(command: smtp_cmd)))
|
|
619
|
-
when :modify
|
|
620
|
-
return Result::SingleObject.new(@api_v5.update(smtp_path, value_create_modify(command: smtp_cmd)))
|
|
621
|
-
when :delete
|
|
622
|
-
@api_v5.delete(smtp_path)
|
|
623
|
-
return Result::Status.new('SMTP configuration deleted')
|
|
624
|
-
when :test
|
|
625
|
-
test_data = options.get_next_argument('Email or test data, see API')
|
|
626
|
-
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
|
627
|
-
creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)
|
|
628
|
-
result = wait_for_job(creation['job_id'])
|
|
629
|
-
result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
|
|
630
|
-
return Result::SingleObject.new(result)
|
|
703
|
+
)
|
|
704
|
+
Result::ObjectList.new(data, total: total, fields: args[:display_fields])
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# admin > <resource> > create / modify / delete / show
|
|
709
|
+
Api::Faspex::ADMIN_RESOURCES.each do |res|
|
|
710
|
+
CRUD_NO_LIST.each do |op|
|
|
711
|
+
define_action_method([:admin, res, op]) do |input_data: nil, res_id: nil, **|
|
|
712
|
+
args = res_exec_args(res)
|
|
713
|
+
is_bulk = options.get_option(:bulk)
|
|
714
|
+
items = input_data
|
|
715
|
+
items = [items] if items && !items.is_a?(Array)
|
|
716
|
+
entity_execute(command: op, input_data: items, res_id: res_id, is_bulk: is_bulk, bfail: options.get_option(:bfail), **args){ |f, v| res_lookup_id(res, f, v)}
|
|
631
717
|
end
|
|
632
718
|
end
|
|
633
719
|
end
|
|
634
720
|
|
|
635
|
-
|
|
721
|
+
def action_admin_smtp_test(test_data:, **)
|
|
722
|
+
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
|
723
|
+
creation = @api_v5.create('configuration/smtp/test', test_data)
|
|
724
|
+
result = wait_for_job(creation['job_id'])
|
|
725
|
+
begin
|
|
726
|
+
result['serialized_args'] = JSON.parse(result['serialized_args'])
|
|
727
|
+
rescue JSON::ParserError
|
|
728
|
+
# keep as string if not valid JSON
|
|
729
|
+
end
|
|
730
|
+
Result::SingleObject.new(result)
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# admin > accounts > reset_password
|
|
734
|
+
def action_admin_accounts_reset_password(contact_id:, **)
|
|
735
|
+
@api_v5.create("accounts/#{contact_id}/reset_password", {})
|
|
736
|
+
Result::Status.new('password reset, user shall check email')
|
|
737
|
+
end
|
|
636
738
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
739
|
+
# admin > file_processing > next
|
|
740
|
+
def action_admin_file_processing_next
|
|
741
|
+
args = res_exec_args(:file_processing)
|
|
742
|
+
result, count = @api_v5.list_entities_limit_offset_total_count(entity: args[:entity], operation: 'POST', items_key: 'files')
|
|
743
|
+
Result::ObjectList.new(result, total: count)
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# admin > nodes > browse
|
|
747
|
+
def action_admin_nodes_browse(folder_path:, node_id:, **)
|
|
748
|
+
browse_folder("nodes/#{node_id}/browse", {}, folder_path: folder_path)
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# admin > nodes > shared_folders — node_id: already in ctx via arguments: on the :shared_folders command
|
|
752
|
+
def setup_admin_nodes_shared_folders(node_id:, **)
|
|
753
|
+
{sf_entity: "nodes/#{node_id}/shared_folders"}
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Lookup shared folder id by field/value within a node's shared_folders entity.
|
|
757
|
+
# Used as lookup: on the :user command under admin > nodes > shared_folders.
|
|
758
|
+
# sf_entity is available in ctx because setup_admin_nodes_shared_folders ran first (Phase A of parent).
|
|
759
|
+
def lookup_sf_id(field, value, sf_entity:, **)
|
|
760
|
+
@api_v5.lookup_entity_by_field(entity: sf_entity, items_key: 'shared_folders', field: field, value: value)['id']
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# admin > nodes > shared_folders > create/modify/delete/show/list
|
|
764
|
+
Operations::ALL.each do |op|
|
|
765
|
+
define_action_method([:admin, :nodes, :shared_folders, op]) do |sf_entity:, **|
|
|
766
|
+
entity_execute(api: @api_v5, entity: sf_entity, items_key: 'shared_folders', command: op){ |f, v| @api_v5.lookup_entity_by_field(entity: sf_entity, items_key: 'shared_folders', field: f, value: v)['id']}
|
|
644
767
|
end
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# admin > nodes > shared_folders > user — sf_id: already in ctx via arguments: on the :user command
|
|
771
|
+
def setup_admin_nodes_shared_folders_user(sf_entity:, sf_id:, **)
|
|
772
|
+
{user_path: "#{sf_entity}/#{sf_id}/custom_access_users"}
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
# admin > nodes > shared_folders > user > create/modify/delete/show/list (custom access users)
|
|
776
|
+
Operations::ALL.each do |op|
|
|
777
|
+
define_action_method([:admin, :nodes, :shared_folders, :user, op]) do |user_path:, **|
|
|
778
|
+
entity_execute(api: @api_v5, entity: user_path, items_key: 'users', command: op){ |f, v| @api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: f, value: v)['id']}
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# admin > shared_inboxes|workgroups — res_id: already in ctx via arguments: on the :members|:saml_groups command
|
|
783
|
+
%i[shared_inboxes workgroups].each do |res|
|
|
784
|
+
define_method(:"setup_admin_#{res}_instance") do |res_id:, **|
|
|
785
|
+
{res_instance_path: "#{res}/#{res_id}"}
|
|
786
|
+
end
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
# Resolve user ids for shared_inbox/workgroup members/create, handling percent selectors.
|
|
790
|
+
# @param users [Array] raw user ids or percent-selector strings
|
|
791
|
+
# @return [Array<String>] resolved user ids
|
|
792
|
+
def resolve_member_user_ids(users)
|
|
793
|
+
users = [users] unless users.is_a?(Array)
|
|
794
|
+
users.map do |user|
|
|
795
|
+
if (m = Parser.percent_selector(user))
|
|
796
|
+
@api_v5.lookup_entity_by_field(entity: 'accounts', field: m[:field], value: m[:value], query: Rest.php_style({type: ACCOUNT_TYPES}))['id']
|
|
797
|
+
else
|
|
798
|
+
user
|
|
659
799
|
end
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# admin > shared_inboxes|workgroups > members|saml_groups > create/list/modify/delete
|
|
804
|
+
%i[shared_inboxes workgroups].each do |res|
|
|
805
|
+
MEMBER_SUBS.each do |sub|
|
|
806
|
+
CRUD_NO_SHOW.each do |op|
|
|
807
|
+
if op.eql?(:create) && sub.eql?(:members)
|
|
808
|
+
define_action_method([:admin, res, sub, op]) do |users:, access:, res_instance_path:, **|
|
|
809
|
+
res_path = "#{res_instance_path}/#{sub}"
|
|
810
|
+
list_key = sub.eql?(:saml_groups) ? 'groups' : sub.to_s
|
|
811
|
+
resolved = resolve_member_user_ids(users)
|
|
812
|
+
input_data = [{user: resolved.map{ |u| {id: u, access: access}}}]
|
|
813
|
+
entity_execute(api: @api_v5, entity: res_path, command: op, input_data: input_data, items_key: list_key) do |f, v|
|
|
814
|
+
@api_v5.lookup_entity_by_field(entity: res_path, field: f, value: v, query: Rest.php_style({type: %w[user]}))['user_id']
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
else
|
|
818
|
+
define_action_method([:admin, res, sub, op]) do |res_instance_path:, **|
|
|
819
|
+
res_path = "#{res_instance_path}/#{sub}"
|
|
820
|
+
list_key = sub.eql?(:saml_groups) ? 'groups' : sub.to_s
|
|
821
|
+
entity_execute(api: @api_v5, entity: res_path, command: op, items_key: list_key) do |f, v|
|
|
822
|
+
@api_v5.lookup_entity_by_field(entity: res_path, field: f, value: v, query: Rest.php_style({type: %w[user]}))['user_id']
|
|
823
|
+
end
|
|
824
|
+
end
|
|
672
825
|
end
|
|
673
826
|
end
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# admin > shared_inboxes|workgroups > invite_external_collaborator
|
|
831
|
+
%i[shared_inboxes workgroups].each do |res|
|
|
832
|
+
define_action_method([:admin, res, :invite_external_collaborator]) do |input_data:, res_id:, **|
|
|
833
|
+
res_instance_path = "#{res}/#{res_id}"
|
|
834
|
+
result = @api_v5.create("#{res_instance_path}/external_collaborator", input_data)
|
|
835
|
+
formatter.display_status(result['message'])
|
|
836
|
+
Result::SingleObject.new(@api_v5.lookup_entity_by_field(entity: "#{res_instance_path}/members", items_key: 'members', value: input_data['email_address'], query: {}))
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# --- root setup ---
|
|
841
|
+
|
|
842
|
+
# Build @api_v5 for all commands that need it (all except :health and :postprocessing).
|
|
843
|
+
# @return [Hash] empty ctx (state stored in @api_v5)
|
|
844
|
+
def setup_api_v5(**)
|
|
845
|
+
return {} if @api_v5
|
|
846
|
+
@api_v5 = Api::Faspex.new(**Oauth.kwargs_from_options(options))
|
|
847
|
+
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
848
|
+
transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
|
|
849
|
+
{}
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
# Setup for package sub-commands that need an id: resolves package_id from pub_link or argument.
|
|
853
|
+
# @return [Hash] ctx key: package_id
|
|
854
|
+
def setup_package_id(**)
|
|
855
|
+
package_id = @api_v5.pub_link_context&.key?('package_id') ? @api_v5.pub_link_context['package_id'] : options.instance_identifier
|
|
856
|
+
{package_id: package_id}
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def action_packages_list
|
|
860
|
+
list, total = list_packages_with_filter
|
|
861
|
+
fields = %w[id title status sender.name recipients.0.name release_date total_bytes total_files]
|
|
862
|
+
fields.delete('recipients.0.name') if %w[inbox inbox_history].include?(options.get_option(:box))
|
|
863
|
+
fields.delete('sender.name') if %w[outbox outbox_history].include?(options.get_option(:box))
|
|
864
|
+
Result::ObjectList.new(list, total: total, fields: fields)
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def action_packages_delete(package_id:, **)
|
|
868
|
+
ids = package_id.is_a?(Array) ? package_id : [package_id]
|
|
869
|
+
Aspera.assert_array_all(ids, String){'Package id(s)'}
|
|
870
|
+
# API returns 204, empty on success
|
|
871
|
+
@api_v5.call(
|
|
872
|
+
operation: 'DELETE',
|
|
873
|
+
subpath: 'packages',
|
|
874
|
+
content_type: Mime::JSON,
|
|
875
|
+
body: {ids: ids},
|
|
876
|
+
headers: {'Accept' => Mime::JSON}
|
|
877
|
+
)
|
|
878
|
+
Result::Status.new('Package(s) deleted')
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# --- handlers ---
|
|
882
|
+
|
|
883
|
+
def action_health
|
|
884
|
+
nagios = Nagios.new
|
|
885
|
+
begin
|
|
886
|
+
data, http = Rest.new(base_url: options.get_option(:url, mandatory: true))
|
|
887
|
+
.read('health', ret: :both)
|
|
888
|
+
data.each do |k, v|
|
|
889
|
+
nagios.add_ok(k, v.to_s)
|
|
693
890
|
end
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
891
|
+
nagios.add_ok('version', http['X-IBM-Aspera']) if http['X-IBM-Aspera']
|
|
892
|
+
rescue StandardError => e
|
|
893
|
+
nagios.add_critical('core', e.to_s)
|
|
894
|
+
end
|
|
895
|
+
Result::ObjectList.new(nagios.status_list)
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Lookup a shared folder id by field/value.
|
|
899
|
+
# Called via lookup: :lookup_shared_folder_id on the shared_folders > browse command.
|
|
900
|
+
def lookup_shared_folder_id(field, value, **)
|
|
901
|
+
all = @api_v5.read('shared_folders')['shared_folders']
|
|
902
|
+
matches = all.select{ |i| i[field].eql?(value)}
|
|
903
|
+
Aspera.assert(!matches.empty?){"no match for #{field} = #{value}"}
|
|
904
|
+
Aspera.assert(matches.length == 1){"multiple matches for #{field} = #{value}"}
|
|
905
|
+
matches.first['id']
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
def action_shared_folders_browse(folder_path:, shared_folder_id:, **)
|
|
909
|
+
all_shared_folders = @api_v5.read('shared_folders')['shared_folders']
|
|
910
|
+
node = all_shared_folders.find{ |i| i['id'].eql?(shared_folder_id)}
|
|
911
|
+
Aspera.assert(!node.nil?){"No such shared folder id #{shared_folder_id}"}
|
|
912
|
+
browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse", {}, folder_path: folder_path)
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# invitations sub-handlers
|
|
916
|
+
|
|
917
|
+
def action_invitations_resend(invitation_id:, **)
|
|
918
|
+
@api_v5.create("invitations/#{invitation_id}/resend", nil)
|
|
919
|
+
Result::Status.new('Invitation resent')
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
def action_invitations_create(input_data:, **)
|
|
923
|
+
is_bulk = options.get_option(:bulk)
|
|
924
|
+
Result.bulk(input_data, is_bulk: is_bulk, command: :create, bfail: options.get_option(:bfail)) do |params|
|
|
925
|
+
endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
|
|
926
|
+
@api_v5.create(endpoint, params)
|
|
927
|
+
end
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
# CRUD handlers for invitations (list, show, modify, delete)
|
|
931
|
+
Operations::ALL.reject{ |op| op == :create}.each do |op|
|
|
932
|
+
define_action_method([:invitations, op]) do
|
|
933
|
+
entity_execute(
|
|
934
|
+
api: @api_v5,
|
|
935
|
+
entity: 'invitations',
|
|
936
|
+
command: op,
|
|
937
|
+
items_key: 'invitations',
|
|
938
|
+
display_fields: %w[id public recipient_type recipient_name email_address]
|
|
939
|
+
) do |field, value|
|
|
940
|
+
@api_v5.lookup_entity_by_field(entity: 'invitations', field: field, value: value, query: {})['id']
|
|
718
941
|
end
|
|
719
|
-
when :gateway
|
|
720
|
-
require 'aspera/faspex_gw'
|
|
721
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
722
|
-
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
723
|
-
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
724
|
-
Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?){"unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}"}
|
|
725
|
-
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
|
726
|
-
server.start
|
|
727
|
-
return Result::Status.new('Gateway terminated')
|
|
728
|
-
when :postprocessing
|
|
729
|
-
require 'aspera/faspex_postproc' # cspell:disable-line
|
|
730
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
731
|
-
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
732
|
-
parameters[:root] = uri.path
|
|
733
|
-
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
734
|
-
server.mount(uri.path, Faspex4PostProcServlet, parameters.except(*WebServerSimple::PARAMS))
|
|
735
|
-
server.start
|
|
736
|
-
return Result::Status.new('Gateway terminated')
|
|
737
942
|
end
|
|
738
943
|
end
|
|
944
|
+
|
|
945
|
+
def action_gateway(parameters: {}, **)
|
|
946
|
+
require 'aspera/faspex_gw'
|
|
947
|
+
parameters = parameters.symbolize_keys
|
|
948
|
+
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
949
|
+
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
950
|
+
Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?){"unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}"}
|
|
951
|
+
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
|
952
|
+
server.start
|
|
953
|
+
Result::Status.new('Gateway terminated')
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
def action_postprocessing(parameters: {}, **)
|
|
957
|
+
require 'aspera/faspex_postproc' # cspell:disable-line
|
|
958
|
+
parameters = parameters.symbolize_keys
|
|
959
|
+
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
960
|
+
parameters[:root] = uri.path
|
|
961
|
+
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
962
|
+
server.mount(uri.path, Faspex4PostProcServlet, parameters.except(*WebServerSimple::PARAMS))
|
|
963
|
+
server.start
|
|
964
|
+
Result::Status.new('Gateway terminated')
|
|
965
|
+
end
|
|
739
966
|
SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
|
|
740
967
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
741
968
|
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
742
969
|
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
743
970
|
PACKAGE_RECIPIENT_TYPES = %i{recipients private_recipients notified_on_upload notified_on_download notified_on_receipt}
|
|
744
|
-
private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES, :PACKAGE_RECIPIENT_TYPES
|
|
971
|
+
private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES, :PACKAGE_RECIPIENT_TYPES,
|
|
972
|
+
:MEMBER_SUBS, :CRUD_NO_SHOW, :CRUD_NO_LIST
|
|
745
973
|
end
|
|
746
974
|
end
|
|
747
975
|
end
|