aspera-cli 4.26.1 → 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 +300 -22
- data/CONTRIBUTING.md +100 -12
- data/bin/ascli +5 -5
- 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 +27 -25
- data/lib/aspera/agent/factory.rb +4 -0
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +56 -51
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +22 -20
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +33 -30
- 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 +25 -23
- data/lib/aspera/ascp/installation.rb +43 -40
- 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 +71 -0
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +5 -3
- data/lib/aspera/cli/formatter.rb +122 -209
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +218 -0
- 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} +337 -144
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1173 -849
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +470 -107
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +434 -1032
- 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 +7 -4
- data/lib/aspera/cli/plugins/faspex.rb +296 -259
- data/lib/aspera/cli/plugins/faspex5.rb +592 -344
- 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 +1001 -797
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
- data/lib/aspera/cli/plugins/preview.rb +183 -142
- data/lib/aspera/cli/plugins/server.rb +125 -80
- 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 +236 -0
- data/lib/aspera/cli/result.rb +360 -0
- data/lib/aspera/cli/runner.rb +473 -0
- 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 +84 -56
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- 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 +9 -9
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +26 -26
- data/lib/aspera/command_line_builder.rb +23 -23
- data/lib/aspera/data_repository.rb +6 -1
- data/lib/aspera/dot_container.rb +5 -5
- data/lib/aspera/environment.rb +39 -13
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +2 -2
- 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/base.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +4 -4
- data/lib/aspera/keychain/macos_security.rb +4 -4
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +23 -5
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/nagios.rb +2 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +10 -10
- data/lib/aspera/oauth/boot.rb +43 -0
- data/lib/aspera/oauth/factory.rb +41 -15
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_action_once.rb +10 -10
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +7 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +4 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -9
- data/lib/aspera/rest.rb +18 -40
- data/lib/aspera/rest_error_analyzer.rb +7 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +13 -6
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +60 -11
- data/lib/aspera/schema/registry.rb +50 -10
- data/lib/aspera/secret_hider.rb +11 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +15 -18
- data/lib/aspera/temp_file_manager.rb +5 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +27 -25
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +7 -7
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +26 -8
- data/lib/aspera/transfer/uri.rb +1 -1
- 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 +45 -16
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/main.rb +0 -480
- data/lib/aspera/json_rpc.rb +0 -51
|
@@ -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
|
|
@@ -213,10 +215,10 @@ module Aspera
|
|
|
213
215
|
packages = []
|
|
214
216
|
case package_ids
|
|
215
217
|
when SpecialValues::INIT
|
|
216
|
-
Aspera.assert(skip_ids_persistency
|
|
218
|
+
Aspera.assert(skip_ids_persistency, 'Only with option once_only')
|
|
217
219
|
skip_ids_persistency.data.clear.concat(list_packages_with_filter.first.map{ |p| p['id']})
|
|
218
220
|
skip_ids_persistency.save
|
|
219
|
-
return
|
|
221
|
+
return Result::Status.new("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
|
220
222
|
when SpecialValues::ALL
|
|
221
223
|
# TODO: if packages have same name, they will overwrite ?
|
|
222
224
|
packages = list_packages_with_filter(query: {'status' => 'completed'}).first
|
|
@@ -259,18 +261,17 @@ module Aspera
|
|
|
259
261
|
# delete flag for Connect Client
|
|
260
262
|
transfer_spec.delete('authentication')
|
|
261
263
|
statuses = transfer.start(transfer_spec)
|
|
262
|
-
result_transfer.push({'package' => pkg_id,
|
|
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
|
|
268
270
|
end
|
|
269
|
-
return
|
|
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'] = [{
|
|
@@ -296,10 +297,10 @@ module Aspera
|
|
|
296
297
|
)
|
|
297
298
|
# well, we asked a TS for connect, but we actually want a generic one
|
|
298
299
|
transfer_spec.delete('authentication')
|
|
299
|
-
return
|
|
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],
|
|
@@ -314,14 +315,15 @@ module Aspera
|
|
|
314
315
|
formatter.display_status("Package #{package['id']}")
|
|
315
316
|
result = wait_package_status(package['id'])
|
|
316
317
|
end
|
|
317
|
-
return
|
|
318
|
+
return Result::SingleObject.new(result)
|
|
318
319
|
end
|
|
319
320
|
end
|
|
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)
|
|
@@ -371,357 +373,603 @@ module Aspera
|
|
|
371
373
|
query.delete('iteration_token')
|
|
372
374
|
end
|
|
373
375
|
RestParameters.instance.spinner_cb.call(action: :success)
|
|
374
|
-
return
|
|
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
|
-
|
|
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
|
|
416
428
|
end
|
|
417
429
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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)")
|
|
451
525
|
end
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
@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')
|
|
466
541
|
end
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
case sh_command
|
|
470
|
-
when *Operations::ALL
|
|
471
|
-
return entity_execute(
|
|
472
|
-
api: @api_v5,
|
|
473
|
-
entity: shfld_entity,
|
|
474
|
-
command: sh_command
|
|
475
|
-
) do |field, value|
|
|
476
|
-
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
477
|
-
end
|
|
478
|
-
when :user
|
|
479
|
-
sh_id = options.instance_identifier do |field, value|
|
|
480
|
-
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
481
|
-
end
|
|
482
|
-
user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
|
|
483
|
-
return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
|
|
484
|
-
@api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
|
|
485
|
-
end
|
|
542
|
+
)
|
|
543
|
+
end
|
|
486
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
|
|
487
591
|
end
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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"
|
|
492
626
|
end
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
creation_payload = value_create_modify(command: res_command)
|
|
498
|
-
result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
|
|
499
|
-
formatter.display_status(result['message'])
|
|
500
|
-
result = @api_v5.lookup_entity_by_field(
|
|
501
|
-
entity: "#{res_sym}/#{shared_inbox_id}/members",
|
|
502
|
-
items_key: 'members',
|
|
503
|
-
value: creation_payload['email_address'],
|
|
504
|
-
query: {}
|
|
505
|
-
)
|
|
506
|
-
return Main.result_single_object(result)
|
|
507
|
-
when :members, :saml_groups
|
|
508
|
-
# res_command := :shared_inboxes, :workgroups
|
|
509
|
-
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']}
|
|
510
|
-
res_path = "#{res_sym}/#{res_id}/#{res_command}"
|
|
511
|
-
list_key = res_command.to_s
|
|
512
|
-
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
513
|
-
sub_command = options.get_next_command(%i[create list modify delete])
|
|
514
|
-
if sub_command.eql?(:create) && res_command.eql?(:members)
|
|
515
|
-
# first arg is one user name or list of users
|
|
516
|
-
users = options.get_next_argument('user id, %name:, or Array')
|
|
517
|
-
users = [users] unless users.is_a?(Array)
|
|
518
|
-
users = users.map do |user|
|
|
519
|
-
if (m = Manager.percent_selector(user))
|
|
520
|
-
@api_v5.lookup_entity_by_field(
|
|
521
|
-
entity: 'accounts',
|
|
522
|
-
field: m[:field],
|
|
523
|
-
value: m[:value],
|
|
524
|
-
query: Rest.php_style({type: ACCOUNT_TYPES})
|
|
525
|
-
)['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}]}
|
|
526
631
|
else
|
|
527
|
-
|
|
528
|
-
user
|
|
632
|
+
{}
|
|
529
633
|
end
|
|
634
|
+
command(c, description: c.to_s.capitalize, **args)
|
|
530
635
|
end
|
|
531
|
-
access = options.get_next_argument('level', mandatory: false, accept_list: SHARED_INBOX_MEMBER_LEVELS, default: :standard)
|
|
532
|
-
options.unshift_next_argument({user: users.map{ |u| {id: u, access: access}}})
|
|
533
636
|
end
|
|
534
|
-
# TODO: test SAML group
|
|
535
|
-
return entity_execute(
|
|
536
|
-
api: @api_v5,
|
|
537
|
-
entity: res_path,
|
|
538
|
-
command: sub_command,
|
|
539
|
-
items_key: list_key
|
|
540
|
-
) do |field, value|
|
|
541
|
-
@api_v5.lookup_entity_by_field(
|
|
542
|
-
entity: res_path,
|
|
543
|
-
field: field,
|
|
544
|
-
value: value,
|
|
545
|
-
query: Rest.php_style({type: %w[user]})
|
|
546
|
-
)['user_id']
|
|
547
|
-
end
|
|
548
|
-
when :reset_password
|
|
549
|
-
# :accounts
|
|
550
|
-
contact_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
|
|
551
|
-
@api_v5.create("accounts/#{contact_id}/reset_password", {})
|
|
552
|
-
return Main.result_status('password reset, user shall check email')
|
|
553
637
|
end
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
|
578
702
|
)
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
smtp_cmd = options.get_next_command(%i[show create modify delete test])
|
|
594
|
-
case smtp_cmd
|
|
595
|
-
when :show
|
|
596
|
-
return Main.result_single_object(@api_v5.read(smtp_path))
|
|
597
|
-
when :create
|
|
598
|
-
return Main.result_single_object(@api_v5.create(smtp_path, value_create_modify(command: smtp_cmd)))
|
|
599
|
-
when :modify
|
|
600
|
-
return Main.result_single_object(@api_v5.update(smtp_path, value_create_modify(command: smtp_cmd)))
|
|
601
|
-
when :delete
|
|
602
|
-
@api_v5.delete(smtp_path)
|
|
603
|
-
return Main.result_status('SMTP configuration deleted')
|
|
604
|
-
when :test
|
|
605
|
-
test_data = options.get_next_argument('Email or test data, see API')
|
|
606
|
-
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
|
607
|
-
creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)
|
|
608
|
-
result = wait_for_job(creation['job_id'])
|
|
609
|
-
result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
|
|
610
|
-
return Main.result_single_object(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)}
|
|
611
717
|
end
|
|
612
718
|
end
|
|
613
719
|
end
|
|
614
720
|
|
|
615
|
-
|
|
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
|
|
616
738
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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']}
|
|
624
767
|
end
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
|
639
799
|
end
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
|
652
825
|
end
|
|
653
826
|
end
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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)
|
|
673
890
|
end
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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']
|
|
698
941
|
end
|
|
699
|
-
when :gateway
|
|
700
|
-
require 'aspera/faspex_gw'
|
|
701
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
702
|
-
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
703
|
-
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
704
|
-
Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?)
|
|
705
|
-
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
|
706
|
-
server.start
|
|
707
|
-
return Main.result_status('Gateway terminated')
|
|
708
|
-
when :postprocessing
|
|
709
|
-
require 'aspera/faspex_postproc' # cspell:disable-line
|
|
710
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
711
|
-
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
712
|
-
parameters[:root] = uri.path
|
|
713
|
-
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
714
|
-
server.mount(uri.path, Faspex4PostProcServlet, parameters.except(*WebServerSimple::PARAMS))
|
|
715
|
-
server.start
|
|
716
|
-
return Main.result_status('Gateway terminated')
|
|
717
942
|
end
|
|
718
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
|
|
719
966
|
SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
|
|
720
967
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
721
968
|
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
722
969
|
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
723
970
|
PACKAGE_RECIPIENT_TYPES = %i{recipients private_recipients notified_on_upload notified_on_download notified_on_receipt}
|
|
724
|
-
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
|
|
725
973
|
end
|
|
726
974
|
end
|
|
727
975
|
end
|