aspera-cli 4.27.2 → 4.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +133 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +3 -1
- data/docs/README.md +1006 -766
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +6 -8
- data/lib/aspera/agent/desktop.rb +2 -6
- data/lib/aspera/agent/direct.rb +52 -22
- data/lib/aspera/agent/node.rb +9 -8
- data/lib/aspera/agent/transferd.rb +2 -2
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +11 -7
- data/lib/aspera/api/httpgw.rb +38 -35
- data/lib/aspera/api/node.rb +39 -34
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +63 -28
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +12 -11
- data/lib/aspera/cli/bootstrapper.rb +14 -16
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +215 -37
- data/lib/aspera/cli/command_spec.rb +104 -15
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +6 -3
- data/lib/aspera/cli/formatter.rb +94 -80
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +22 -34
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +105 -0
- data/lib/aspera/cli/option_value.rb +278 -0
- data/lib/aspera/cli/options.schema.yaml +124 -15
- data/lib/aspera/cli/parser.rb +333 -862
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +545 -518
- data/lib/aspera/cli/plugins/ats.rb +59 -80
- data/lib/aspera/cli/plugins/base.rb +221 -265
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +263 -184
- data/lib/aspera/cli/plugins/console.rb +103 -39
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +204 -182
- data/lib/aspera/cli/plugins/faspio.rb +6 -11
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +300 -327
- data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
- data/lib/aspera/cli/plugins/preview.rb +96 -105
- data/lib/aspera/cli/plugins/server.rb +78 -53
- data/lib/aspera/cli/plugins/shares.rb +80 -131
- data/lib/aspera/cli/preset_actions.rb +44 -27
- data/lib/aspera/cli/preset_manager.rb +44 -19
- data/lib/aspera/cli/prompt.rb +36 -0
- data/lib/aspera/cli/result.rb +42 -36
- data/lib/aspera/cli/runner.rb +32 -59
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +51 -46
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +14 -9
- data/lib/aspera/cli/transfer_agent.rb +34 -38
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -3
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/environment.rb +35 -5
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +47 -27
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +37 -26
- data/lib/aspera/oauth/factory.rb +7 -3
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +4 -5
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +11 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +72 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +523 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +149 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +23 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +43 -9
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/node_simulator.rb +0 -344
- data/lib/aspera/preview/options.rb +0 -45
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
require 'aspera/schema/registry'
|
|
6
6
|
require 'aspera/cli/plugins/oauth'
|
|
7
7
|
require 'aspera/cli/extended_value'
|
|
8
|
+
require 'aspera/cli/formatter'
|
|
8
9
|
require 'aspera/transfer/result'
|
|
9
10
|
require 'aspera/cli/special_values'
|
|
10
11
|
require 'aspera/cli/wizard'
|
|
@@ -15,6 +16,10 @@ require 'aspera/nagios'
|
|
|
15
16
|
require 'aspera/environment'
|
|
16
17
|
require 'aspera/assert'
|
|
17
18
|
require 'securerandom'
|
|
19
|
+
require 'digest'
|
|
20
|
+
require 'json'
|
|
21
|
+
require 'aspera/rainbow'
|
|
22
|
+
using Rainbow
|
|
18
23
|
|
|
19
24
|
module Aspera
|
|
20
25
|
module Cli
|
|
@@ -35,7 +40,7 @@ module Aspera
|
|
|
35
40
|
urls.each do |base_url|
|
|
36
41
|
# Faspex is always HTTPS
|
|
37
42
|
next unless base_url.start_with?('https://')
|
|
38
|
-
api = Rest.new(base_url: base_url, redirect_max: 1)
|
|
43
|
+
api = Rest::Client.new(base_url: base_url, redirect_max: 1)
|
|
39
44
|
response = api.read(Api::Faspex::PATH_API_DETECT, ret: :resp)
|
|
40
45
|
next unless response.code.start_with?('2') && response.body.strip.empty?
|
|
41
46
|
# end is at -1, and subtract 1 for "/"
|
|
@@ -96,13 +101,6 @@ module Aspera
|
|
|
96
101
|
option :shared_folder, description: 'Send package with files from shared folder'
|
|
97
102
|
option :group_type, description: 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes
|
|
98
103
|
|
|
99
|
-
def initialize(**_)
|
|
100
|
-
super
|
|
101
|
-
options.parse_options!
|
|
102
|
-
# [Aspera::Api::Faspex]
|
|
103
|
-
@api_v5 = nil
|
|
104
|
-
end
|
|
105
|
-
|
|
106
104
|
# if recipient is just an email, then convert to expected API hash : name and type
|
|
107
105
|
def normalize_recipients(parameters, type)
|
|
108
106
|
type = type.to_s
|
|
@@ -117,7 +115,7 @@ module Aspera
|
|
|
117
115
|
parameters[type].map! do |recipient_data|
|
|
118
116
|
# If just a string, make a general lookup and build expected name/type hash
|
|
119
117
|
if recipient_data.is_a?(String)
|
|
120
|
-
matched =
|
|
118
|
+
matched = api_v5.lookup_with_q('contacts', value: recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
|
|
121
119
|
recipient_data = {
|
|
122
120
|
name: matched['name'],
|
|
123
121
|
recipient_type: matched['type']
|
|
@@ -132,7 +130,7 @@ module Aspera
|
|
|
132
130
|
def wait_package_status(id, status_list: Api::Faspex::PACKAGE_TERMINATED)
|
|
133
131
|
total_sent = false
|
|
134
132
|
loop do
|
|
135
|
-
status =
|
|
133
|
+
status = api_v5.read("packages/#{id}/upload_details")
|
|
136
134
|
status['id'] = id
|
|
137
135
|
# user asked to not follow
|
|
138
136
|
return status if status_list.nil?
|
|
@@ -147,7 +145,7 @@ module Aspera
|
|
|
147
145
|
end
|
|
148
146
|
if status_list.include?(status['upload_status'])
|
|
149
147
|
progress_bar&.event(:session_end, session_id: id)
|
|
150
|
-
progress_bar&.event(:end)
|
|
148
|
+
progress_bar&.event(:end, info: !status['upload_status'].eql?('failed'))
|
|
151
149
|
return status
|
|
152
150
|
end
|
|
153
151
|
sleep(1.0)
|
|
@@ -159,12 +157,12 @@ module Aspera
|
|
|
159
157
|
def wait_for_job(job_id)
|
|
160
158
|
result = nil
|
|
161
159
|
loop do
|
|
162
|
-
result =
|
|
160
|
+
result = api_v5.read("jobs/#{job_id}", {type: :formatted})
|
|
163
161
|
break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
|
|
164
|
-
|
|
162
|
+
Rest::Parameters.instance.spinner_cb.call(result['status'])
|
|
165
163
|
sleep(0.5)
|
|
166
164
|
end
|
|
167
|
-
|
|
165
|
+
Rest::Parameters.instance.spinner_cb.call(action: :success)
|
|
168
166
|
return result
|
|
169
167
|
end
|
|
170
168
|
|
|
@@ -185,27 +183,39 @@ module Aspera
|
|
|
185
183
|
when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
|
|
186
184
|
else
|
|
187
185
|
group_type = options.get_option(:group_type)
|
|
188
|
-
"#{group_type}/#{
|
|
186
|
+
"#{group_type}/#{api_v5.lookup_entity_by_field(entity: group_type, value: box)['id']}/packages"
|
|
189
187
|
end
|
|
190
188
|
# Merge default query with user-provided query: user values take precedence, but defaults are preserved
|
|
191
189
|
user_query = query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::FASPEX, 'packages'))
|
|
192
190
|
merged_query = user_query.nil? ? query.dup : query.merge(user_query)
|
|
193
191
|
# Extract `max` before the API call so callers can apply it after post-API filtering
|
|
194
|
-
max_items = merged_query.delete(
|
|
195
|
-
list, total =
|
|
192
|
+
max_items = merged_query.delete(Rest::List::MAX_ITEMS)&.to_i
|
|
193
|
+
list, total = api_v5.list_entities_limit_offset_total_count(entity: entity, query: merged_query)
|
|
196
194
|
return list.select(&filter), max_items, total
|
|
197
195
|
end
|
|
198
196
|
|
|
199
197
|
# Build query to get package recipients based on package info in case of shared inbox or workgroup recipient
|
|
200
198
|
# @param package_id [String] the package id to get info from
|
|
201
199
|
def recipient_query(package_id)
|
|
202
|
-
|
|
200
|
+
# With a public link, the recipient context is already encoded in the auth token: no extra query params needed
|
|
201
|
+
return {} if api_v5.pub_link_context
|
|
202
|
+
package_info = api_v5.read("packages/#{package_id}")
|
|
203
203
|
base_query = {}
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
first_recipient = package_info['recipients']&.first
|
|
205
|
+
if first_recipient
|
|
206
|
+
base_query['recipient_workgroup_id'] = first_recipient['id'] if WORKGROUP_TYPES.include?(first_recipient['recipient_type'])
|
|
207
|
+
base_query['recipient_user_id'] = first_recipient['id'] if first_recipient['recipient_type'].eql?('user')
|
|
208
|
+
end
|
|
206
209
|
base_query
|
|
207
210
|
end
|
|
208
211
|
|
|
212
|
+
# @return [String] user part of the persistency id of received packages:
|
|
213
|
+
# the username, or for a public link (no username), a digest of the link context (unique per link)
|
|
214
|
+
def persistency_user_id
|
|
215
|
+
return options.get_option(:username, mandatory: true) if api_v5.pub_link_context.nil?
|
|
216
|
+
Digest::SHA256.hexdigest(JSON.generate(api_v5.pub_link_context))[0, 16]
|
|
217
|
+
end
|
|
218
|
+
|
|
209
219
|
def package_receive(package_ids)
|
|
210
220
|
# prepare persistency if needed
|
|
211
221
|
skip_ids_persistency = nil
|
|
@@ -217,7 +227,7 @@ module Aspera
|
|
|
217
227
|
id: IdGenerator.from_list(
|
|
218
228
|
'faspex_recv',
|
|
219
229
|
options.get_option(:url, mandatory: true),
|
|
220
|
-
|
|
230
|
+
persistency_user_id,
|
|
221
231
|
options.get_option(:box, mandatory: true)
|
|
222
232
|
)
|
|
223
233
|
)
|
|
@@ -233,7 +243,7 @@ module Aspera
|
|
|
233
243
|
# TODO: if packages have same name, they will overwrite ?
|
|
234
244
|
packages, max_items = list_packages_with_filter(query: {'status' => 'completed'}) # no filter: all completed packages
|
|
235
245
|
Log.dump(:package_ids, level: :trace1) { packages.map { |p| p['id'] } }
|
|
236
|
-
Log.dump(:skip_ids, skip_ids_persistency
|
|
246
|
+
Log.dump(:skip_ids, skip_ids_persistency&.data, level: :trace1)
|
|
237
247
|
packages.reject! { |p| skip_ids_persistency.data.include?(p['id']) } if skip_ids_persistency
|
|
238
248
|
# Apply `max` after once_only filtering so we get the N first not-yet-downloaded packages
|
|
239
249
|
packages = packages[0, max_items] if max_items
|
|
@@ -242,13 +252,15 @@ module Aspera
|
|
|
242
252
|
# a single id was provided, or a list of ids
|
|
243
253
|
package_ids = [package_ids] unless package_ids.is_a?(Array)
|
|
244
254
|
Aspera.assert_array_all(package_ids, String) { 'Package id(s)' }
|
|
245
|
-
# packages = package_ids.map{|pkg_id
|
|
255
|
+
# packages = package_ids.map{|pkg_id|api_v5.read("packages/#{pkg_id}")}
|
|
246
256
|
packages = package_ids.map { |pkg_id| {'id'=>pkg_id} }
|
|
257
|
+
# The package of a public link is received only once, like with `ALL`
|
|
258
|
+
packages.reject! { |p| skip_ids_persistency.data.include?(p['id']) } if skip_ids_persistency && api_v5.pub_link_context
|
|
247
259
|
end
|
|
248
260
|
result_transfer = []
|
|
249
261
|
param_file_list = {}
|
|
250
262
|
begin
|
|
251
|
-
param_file_list['paths'] = transfer.ts_source_paths
|
|
263
|
+
param_file_list['paths'] = transfer.ts_source_paths.map { |p| {'path' => p['source']} }
|
|
252
264
|
rescue Cli::MissingArgument
|
|
253
265
|
# paths is optional
|
|
254
266
|
end
|
|
@@ -257,12 +269,12 @@ module Aspera
|
|
|
257
269
|
type: Api::Faspex.box_type(box),
|
|
258
270
|
transfer_type: Api::Faspex::TRANSFER_CONNECT
|
|
259
271
|
}
|
|
260
|
-
# download_params[:recipient_workgroup_id] =
|
|
272
|
+
# download_params[:recipient_workgroup_id] = api_v5.lookup_entity_by_field(entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
|
|
261
273
|
packages.each do |package|
|
|
262
274
|
pkg_id = package['id']
|
|
263
275
|
formatter.display_status("Receiving package #{pkg_id}")
|
|
264
276
|
# TODO: allow from sent as well ?
|
|
265
|
-
transfer_spec =
|
|
277
|
+
transfer_spec = api_v5.call(
|
|
266
278
|
operation: 'POST',
|
|
267
279
|
subpath: "packages/#{pkg_id}/transfer_spec/download",
|
|
268
280
|
query: download_params.merge(recipient_query(pkg_id)),
|
|
@@ -285,10 +297,10 @@ module Aspera
|
|
|
285
297
|
|
|
286
298
|
def package_send(parameters)
|
|
287
299
|
# autofill recipient for public url
|
|
288
|
-
if
|
|
300
|
+
if api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
|
|
289
301
|
parameters['recipients'] = [{
|
|
290
|
-
name:
|
|
291
|
-
recipient_type:
|
|
302
|
+
name: api_v5.pub_link_context['name'],
|
|
303
|
+
recipient_type: api_v5.pub_link_context['recipient_type']
|
|
292
304
|
}]
|
|
293
305
|
end
|
|
294
306
|
PACKAGE_RECIPIENT_TYPES.each { |type| normalize_recipients(parameters, type) }
|
|
@@ -298,22 +310,23 @@ module Aspera
|
|
|
298
310
|
transfer.user_transfer_spec.delete('content_protection')
|
|
299
311
|
parameters['ear_enabled'] = true
|
|
300
312
|
end
|
|
301
|
-
package =
|
|
313
|
+
package = api_v5.create('packages', parameters)
|
|
302
314
|
shared_folder = options.get_option(:shared_folder)
|
|
303
315
|
if shared_folder.nil?
|
|
304
316
|
# send from local files
|
|
305
|
-
transfer_spec =
|
|
317
|
+
transfer_spec = api_v5.create(
|
|
306
318
|
"packages/#{package['id']}/transfer_spec/upload",
|
|
307
319
|
{paths: transfer.source_list},
|
|
308
320
|
query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
|
|
309
321
|
)
|
|
310
322
|
# well, we asked a TS for connect, but we actually want a generic one
|
|
311
323
|
transfer_spec.delete('authentication')
|
|
312
|
-
|
|
324
|
+
Runner.result_transfer(transfer.start(transfer_spec))
|
|
325
|
+
return Result::SingleObject.new(package)
|
|
313
326
|
else
|
|
314
327
|
# send from remote shared folder
|
|
315
328
|
if (m = Parser.percent_selector(shared_folder))
|
|
316
|
-
shared_folder =
|
|
329
|
+
shared_folder = api_v5.lookup_entity_by_field(
|
|
317
330
|
entity: 'shared_folders',
|
|
318
331
|
field: m[:field],
|
|
319
332
|
value: m[:value]
|
|
@@ -321,7 +334,7 @@ module Aspera
|
|
|
321
334
|
end
|
|
322
335
|
transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
|
|
323
336
|
# start remote transfer and get first status
|
|
324
|
-
result =
|
|
337
|
+
result = api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
|
|
325
338
|
result['id'] = package['id']
|
|
326
339
|
unless result['status'].eql?('completed')
|
|
327
340
|
formatter.display_status("Package #{package['id']}")
|
|
@@ -341,7 +354,7 @@ module Aspera
|
|
|
341
354
|
Aspera.assert_type(filters, Hash)
|
|
342
355
|
filters['basenames'] ||= []
|
|
343
356
|
Aspera.assert_type(filters, Hash) { 'filters' }
|
|
344
|
-
max_items = query.delete(
|
|
357
|
+
max_items = query.delete(Rest::List::MAX_ITEMS)
|
|
345
358
|
recursive = query.delete('recursive')
|
|
346
359
|
use_paging = query.delete('paging') { true }
|
|
347
360
|
if use_paging
|
|
@@ -356,7 +369,7 @@ module Aspera
|
|
|
356
369
|
until folders_to_process.empty?
|
|
357
370
|
path = folders_to_process.shift
|
|
358
371
|
loop do
|
|
359
|
-
data, http =
|
|
372
|
+
data, http = api_v5.call(
|
|
360
373
|
operation: 'POST',
|
|
361
374
|
subpath: browse_endpoint,
|
|
362
375
|
query: query,
|
|
@@ -380,49 +393,49 @@ module Aspera
|
|
|
380
393
|
break if data['item_count'].eql?(0)
|
|
381
394
|
query['offset'] += data['item_count']
|
|
382
395
|
end
|
|
383
|
-
|
|
396
|
+
Rest::Parameters.instance.spinner_cb.call(all_items.count)
|
|
384
397
|
end
|
|
385
398
|
query.delete('iteration_token')
|
|
386
399
|
end
|
|
387
|
-
|
|
400
|
+
Rest::Parameters.instance.spinner_cb.call(action: :success)
|
|
388
401
|
return Result::ObjectList.new(all_items, total: total_count)
|
|
389
402
|
end
|
|
390
403
|
|
|
391
404
|
# Per-resource configuration for admin CRUD sub-trees.
|
|
392
|
-
# Keys mirror entity_list/show/create/modify/delete kwargs; extra_commands
|
|
405
|
+
# Keys mirror entity_list/show/create/modify/delete kwargs; extra_commands declares additional leaf commands: {name => command attributes}.
|
|
393
406
|
# @return [Hash{Symbol => Hash}]
|
|
394
407
|
RESOURCE_CONFIG = {
|
|
395
408
|
accounts: {
|
|
396
|
-
display_fields:
|
|
397
|
-
extra_commands:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
body_component: Schema::Registry::FASPEX
|
|
409
|
+
display_fields: Formatter.all_but('user_profile_data_attributes'),
|
|
410
|
+
extra_commands: {reset_password: {description: 'Send password reset email to account', arguments: [{name: :account_id, type: :identifier, lookup: ->(field, value, **) { res_lookup_id(:accounts, field, value) }}]}},
|
|
411
|
+
query_component: Schema::Registry::FASPEX,
|
|
412
|
+
body_component: Schema::Registry::FASPEX
|
|
401
413
|
},
|
|
402
414
|
alternate_addresses: {entity: 'configuration/alternate_addresses', query_component: Schema::Registry::FASPEX, body_component: Schema::Registry::FASPEX},
|
|
403
415
|
contacts: {query_component: Schema::Registry::FASPEX},
|
|
404
416
|
distribution_lists: {entity: 'account/distribution_lists', delete_style: 'ids', query_component: Schema::Registry::FASPEX, body_component: Schema::Registry::FASPEX},
|
|
405
417
|
email_notifications: {id_as_arg: 'type', query_component: Schema::Registry::FASPEX},
|
|
406
418
|
file_processing: {
|
|
407
|
-
commands: %i[
|
|
419
|
+
commands: %i[modify],
|
|
420
|
+
extra_commands: {next: {description: 'List next files to process'}},
|
|
408
421
|
body_component: Schema::Registry::FASPEX,
|
|
409
422
|
is_singleton: true
|
|
410
423
|
},
|
|
411
424
|
jobs: {display_fields: %w[id job_name job_type status], query_component: Schema::Registry::FASPEX},
|
|
412
425
|
metadata_profiles: {entity: 'configuration/metadata_profiles', items_key: 'profiles', query_component: Schema::Registry::FASPEX, body_component: Schema::Registry::FASPEX},
|
|
413
426
|
nodes: {
|
|
414
|
-
extra_commands:
|
|
415
|
-
instance_arg_commands: {
|
|
427
|
+
extra_commands: {
|
|
416
428
|
browse: {
|
|
417
|
-
|
|
418
|
-
|
|
429
|
+
description: 'Browse files of node',
|
|
430
|
+
arguments: [{name: :node_id, type: :identifier, lookup: :lookup_node_id},
|
|
431
|
+
{name: :folder_path, mandatory: false, default: '/'}]
|
|
419
432
|
}
|
|
420
433
|
},
|
|
421
|
-
query_component:
|
|
422
|
-
body_component:
|
|
434
|
+
query_component: Schema::Registry::FASPEX,
|
|
435
|
+
body_component: Schema::Registry::FASPEX
|
|
423
436
|
},
|
|
424
437
|
oauth_clients: {
|
|
425
|
-
display_fields:
|
|
438
|
+
display_fields: Formatter.all_but('public_key'),
|
|
426
439
|
api: -> { Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.kwargs_from_options(options)) },
|
|
427
440
|
list_query: {'expand': true, 'no_api_path': true, 'client_types[]': 'public'},
|
|
428
441
|
query_component: Schema::Registry::FASPEX,
|
|
@@ -446,7 +459,7 @@ module Aspera
|
|
|
446
459
|
def res_exec_args(res_sym)
|
|
447
460
|
cfg = RESOURCE_CONFIG.fetch(res_sym, {})
|
|
448
461
|
{
|
|
449
|
-
api: resource_config_value(cfg, :api) ||
|
|
462
|
+
api: resource_config_value(cfg, :api) || api_v5,
|
|
450
463
|
entity: resource_config_value(cfg, :entity) || res_sym.to_s,
|
|
451
464
|
items_key: resource_config_value(cfg, :items_key),
|
|
452
465
|
delete_style: resource_config_value(cfg, :delete_style),
|
|
@@ -461,7 +474,7 @@ module Aspera
|
|
|
461
474
|
|
|
462
475
|
# admin > nodes — lookup node id by field/value
|
|
463
476
|
def lookup_node_id(field, value, **)
|
|
464
|
-
|
|
477
|
+
api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
|
|
465
478
|
end
|
|
466
479
|
|
|
467
480
|
# Lookup id for a RESOURCE_CONFIG resource by field/value.
|
|
@@ -470,7 +483,7 @@ module Aspera
|
|
|
470
483
|
entity = resource_config_value(cfg, :entity) || res_sym.to_s
|
|
471
484
|
items_key = resource_config_value(cfg, :items_key)
|
|
472
485
|
res_id_query = resource_config_value(cfg, :res_id_query) || :default
|
|
473
|
-
|
|
486
|
+
api_v5.lookup_entity_by_field(entity: entity, value: value, field: field, items_key: items_key, query: res_id_query)['id']
|
|
474
487
|
end
|
|
475
488
|
|
|
476
489
|
# Dynamically define lookup methods for admin resources (used by crud_commands)
|
|
@@ -482,78 +495,86 @@ module Aspera
|
|
|
482
495
|
|
|
483
496
|
# --- DSL ---
|
|
484
497
|
|
|
485
|
-
#
|
|
486
|
-
# :health and :postprocessing work without authentication, so they have no setup.
|
|
498
|
+
# :health and :postprocessing work without authentication: they do not use api_v5.
|
|
487
499
|
command :health, description: 'Check Faspex 5 health'
|
|
488
|
-
command :version, description: 'Show Faspex 5 version',
|
|
489
|
-
command :bearer_token, description: 'Show OAuth bearer token',
|
|
490
|
-
|
|
500
|
+
command :version, description: 'Show Faspex 5 version', action: ->(**) { Result::SingleObject.new(api_v5.read('version')) }
|
|
501
|
+
command :bearer_token, description: 'Show OAuth bearer token', action: ->(**) { Result::Text.new(api_v5.oauth.authorization) }
|
|
502
|
+
# Package id argument; with a public link to a package, setup_package_id provides it instead
|
|
503
|
+
PACKAGE_ID_ARG = [{name: :package_id, type: :identifier}].freeze
|
|
504
|
+
private_constant :PACKAGE_ID_ARG
|
|
505
|
+
command :packages, description: 'Manage packages'
|
|
491
506
|
commands_under :packages do
|
|
492
507
|
command :list, description: 'List packages',
|
|
493
508
|
arguments: [{name: :filter, mandatory: false, default: nil, type: Proc}]
|
|
494
509
|
command :send, description: 'Send a package', transfer_paths: :send,
|
|
495
|
-
arguments: [{name: :
|
|
496
|
-
action: ->(
|
|
497
|
-
command :show, description: 'Show a package', setup: :setup_package_id,
|
|
498
|
-
action: ->(package_id:, **) { Result::SingleObject.new(
|
|
510
|
+
arguments: [{name: :package, type: Hash, schema: Schema::Registry.req_body(Schema::Registry::FASPEX, 'packages.post')}],
|
|
511
|
+
action: ->(package:, **) { package_send(package) }
|
|
512
|
+
command :show, description: 'Show a package', setup: :setup_package_id, arguments: PACKAGE_ID_ARG,
|
|
513
|
+
action: ->(package_id:, **) { Result::SingleObject.new(api_v5.read("packages/#{package_id}", query_read_delete)) }
|
|
499
514
|
command :browse, description: 'Browse package files', setup: :setup_package_id,
|
|
500
|
-
arguments: [{name: :folder_path,
|
|
515
|
+
arguments: PACKAGE_ID_ARG + [{name: :folder_path, mandatory: false, default: '/'}],
|
|
501
516
|
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) }
|
|
502
517
|
command :status, description: 'Wait for package status', setup: :setup_package_id,
|
|
503
|
-
arguments: [{name: :status_list, type: Array, mandatory: false, default: nil}],
|
|
518
|
+
arguments: PACKAGE_ID_ARG + [{name: :status_list, type: Array, mandatory: false, default: nil}],
|
|
504
519
|
action: ->(status_list:, package_id:, **) { Result::SingleObject.new(wait_package_status(package_id, status_list: status_list)) }
|
|
505
|
-
command :delete, description: 'Delete packages', setup: :setup_package_id
|
|
506
|
-
command :receive, description: 'Receive a package', setup: :setup_package_id, transfer_paths: :receive,
|
|
520
|
+
command :delete, description: 'Delete packages', setup: :setup_package_id, arguments: PACKAGE_ID_ARG
|
|
521
|
+
command :receive, description: 'Receive a package', setup: :setup_package_id, transfer_paths: :receive, arguments: PACKAGE_ID_ARG,
|
|
507
522
|
action: ->(package_id:, **) { package_receive(package_id) }
|
|
508
|
-
command(:file_processing, description: 'Show file processing status', setup: :setup_package_id, action: lambda do |package_id:, **|
|
|
509
|
-
result, count =
|
|
523
|
+
command(:file_processing, description: 'Show file processing status', setup: :setup_package_id, arguments: PACKAGE_ID_ARG, action: lambda do |package_id:, **|
|
|
524
|
+
result, count = api_v5.list_entities_limit_offset_total_count(entity: "packages/#{package_id}/file_statuses", items_key: 'files')
|
|
510
525
|
Result::ObjectList.new(result, total: count)
|
|
511
526
|
end)
|
|
512
527
|
end
|
|
513
|
-
command :admin, description: 'Administer Faspex 5'
|
|
514
|
-
command :user, description: 'Manage current user'
|
|
515
|
-
command :shared_folders, description: 'Browse shared folders'
|
|
516
|
-
command :gateway, description: 'Start Faspex 4 gateway emulation',
|
|
528
|
+
command :admin, description: 'Administer Faspex 5'
|
|
529
|
+
command :user, description: 'Manage current user'
|
|
530
|
+
command :shared_folders, description: 'Browse shared folders'
|
|
531
|
+
command :gateway, description: 'Start Faspex 4 gateway emulation',
|
|
517
532
|
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
518
533
|
command :postprocessing, description: 'Start Faspex 4 post-processing server',
|
|
519
534
|
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
520
|
-
command :invitations, description: 'Manage invitations'
|
|
535
|
+
command :invitations, description: 'Manage invitations'
|
|
521
536
|
|
|
522
537
|
commands_under :invitations do
|
|
523
538
|
command :create, description: 'Create an invitation',
|
|
524
|
-
arguments: [{name: :
|
|
525
|
-
command
|
|
526
|
-
|
|
539
|
+
arguments: [{name: :invitation, type: Hash, bulk: true}]
|
|
540
|
+
command(
|
|
541
|
+
:resend, description: 'Resend an invitation',
|
|
542
|
+
arguments: [{name: :invitation_id, type: :identifier}],
|
|
543
|
+
action: lambda do |invitation_id:, **|
|
|
544
|
+
api_v5.create("invitations/#{invitation_id}/resend", nil)
|
|
545
|
+
Result::Status.new('Invitation resent')
|
|
546
|
+
end
|
|
547
|
+
)
|
|
527
548
|
crud_commands entity: 'invitations',
|
|
528
|
-
api:
|
|
549
|
+
api: :api_v5,
|
|
529
550
|
operations: Operations::ALL - %i[create],
|
|
530
551
|
items_key: 'invitations',
|
|
531
552
|
display_fields: %w[id public recipient_type recipient_name email_address]
|
|
532
553
|
end
|
|
533
554
|
|
|
534
555
|
commands_under :user do
|
|
535
|
-
command :account, description: 'Show account information', action: -> { Result::SingleObject.new(
|
|
556
|
+
command :account, description: 'Show account information', action: ->(**) { Result::SingleObject.new(api_v5.read('account', query_read_delete)) }
|
|
536
557
|
command :profile, description: 'Manage user profile'
|
|
537
558
|
end
|
|
538
559
|
|
|
539
560
|
commands_under %i[user profile] do
|
|
540
|
-
command :show, description: 'Show user profile', action: -> { Result::SingleObject.new(
|
|
561
|
+
command :show, description: 'Show user profile', action: ->(**) { Result::SingleObject.new(api_v5.read('account/preferences')) }
|
|
541
562
|
command(
|
|
542
563
|
:modify,
|
|
543
564
|
description: 'Modify user profile',
|
|
544
|
-
arguments: [{name: :
|
|
545
|
-
action: lambda do |
|
|
546
|
-
|
|
565
|
+
arguments: [{name: :profile, type: Hash}],
|
|
566
|
+
action: lambda do |profile:, **|
|
|
567
|
+
api_v5.update('account/preferences', profile)
|
|
547
568
|
Result::Status.new('modified')
|
|
548
569
|
end
|
|
549
570
|
)
|
|
550
571
|
end
|
|
551
572
|
|
|
552
573
|
commands_under :shared_folders do
|
|
553
|
-
command :list, description: 'List shared folders', action: -> { Result::ObjectList.new(
|
|
574
|
+
command :list, description: 'List shared folders', action: ->(**) { Result::ObjectList.new(api_v5.read('shared_folders')['shared_folders']) }
|
|
554
575
|
command :browse, description: 'Browse a shared folder',
|
|
555
576
|
arguments: [{name: :shared_folder_id, type: :identifier, lookup: :lookup_shared_folder_id},
|
|
556
|
-
{name: :folder_path,
|
|
577
|
+
{name: :folder_path, mandatory: false, default: '/'}]
|
|
557
578
|
end
|
|
558
579
|
|
|
559
580
|
# admin sub-tree: fixed commands + all ADMIN_RESOURCES with their sub-commands
|
|
@@ -561,21 +582,26 @@ module Aspera
|
|
|
561
582
|
command :configuration, description: 'Manage Faspex 5 configuration'
|
|
562
583
|
command :smtp, description: 'Manage SMTP configuration'
|
|
563
584
|
command :events, description: 'List events'
|
|
564
|
-
command
|
|
565
|
-
|
|
585
|
+
command(
|
|
586
|
+
:clean_deleted, description: 'Clean deleted packages',
|
|
587
|
+
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}],
|
|
588
|
+
action: lambda do |parameters: {}, **|
|
|
589
|
+
parameters = api_v5.read('configuration').slice('days_before_deleting_package_records') if parameters.empty?
|
|
590
|
+
Result::SingleObject.new(api_v5.create('internal/packages/clean_deleted', parameters))
|
|
591
|
+
end
|
|
592
|
+
)
|
|
566
593
|
Api::Faspex::ADMIN_RESOURCES.each do |res|
|
|
567
594
|
cfg = RESOURCE_CONFIG.fetch(res, {})
|
|
568
|
-
extra = cfg[:extra_commands] ||
|
|
569
|
-
ia_cmds = cfg[:instance_arg_commands] || {}
|
|
595
|
+
extra = cfg[:extra_commands] || {}
|
|
570
596
|
is_singleton = cfg[:is_singleton] || false
|
|
571
597
|
entity_path = cfg[:entity] || res.to_s
|
|
572
598
|
crud_ops = ((cfg[:commands] || Operations::ALL) - %i[list]) & Operations::ALL
|
|
573
599
|
|
|
574
|
-
command res, description: "Manage #{res
|
|
575
|
-
commands_under
|
|
600
|
+
command res, description: "Manage #{entity_noun(res, singular: false)}"
|
|
601
|
+
commands_under res do
|
|
576
602
|
# List is handled with Faspex 5 pagination (item_list_with_total)
|
|
577
603
|
unless is_singleton
|
|
578
|
-
list_kwargs = {description:
|
|
604
|
+
list_kwargs = {description: operation_description(:list, entity_noun(entity_path))}
|
|
579
605
|
list_kwargs[:query_schema] = Schema::Registry.query_params(cfg[:query_component], entity_path) if cfg[:query_component]
|
|
580
606
|
command :list, **list_kwargs
|
|
581
607
|
end
|
|
@@ -594,9 +620,8 @@ module Aspera
|
|
|
594
620
|
)
|
|
595
621
|
|
|
596
622
|
# Extra commands (e.g. browse, reset_password, next)
|
|
597
|
-
extra.each do |
|
|
598
|
-
|
|
599
|
-
command c, description: c.to_s.tr('_', ' ').capitalize, **ia
|
|
623
|
+
extra.each do |name, attrs|
|
|
624
|
+
command name, **attrs
|
|
600
625
|
end
|
|
601
626
|
end
|
|
602
627
|
end
|
|
@@ -612,19 +637,19 @@ module Aspera
|
|
|
612
637
|
# entity: :sf_entity is resolved at runtime from ctx (injected by setup_admin_nodes_shared_folders).
|
|
613
638
|
commands_under %i[admin nodes shared_folders] do
|
|
614
639
|
crud_commands entity: :sf_entity,
|
|
615
|
-
api:
|
|
640
|
+
api: :api_v5,
|
|
616
641
|
name: 'shared folder',
|
|
617
642
|
lookup: :lookup_sf_id,
|
|
618
643
|
items_key: 'shared_folders'
|
|
619
|
-
command :user, description: '
|
|
620
|
-
arguments: [{name: :
|
|
644
|
+
command :user, description: 'Manage custom access users',
|
|
645
|
+
arguments: [{name: :shared_folder_id, type: :identifier, lookup: :lookup_sf_id}],
|
|
621
646
|
setup: :setup_admin_nodes_shared_folders_user
|
|
622
647
|
end
|
|
623
648
|
|
|
624
649
|
# admin > nodes > shared_folders > user sub-tree (custom access users)
|
|
625
650
|
# entity: :user_path is resolved at runtime from ctx (injected by setup_admin_nodes_shared_folders_user).
|
|
626
651
|
commands_under %i[admin nodes shared_folders user] do
|
|
627
|
-
crud_commands entity: :user_path, api:
|
|
652
|
+
crud_commands entity: :user_path, api: :api_v5, name: 'custom access user', lookup: :lookup_sf_user_id, items_key: 'users'
|
|
628
653
|
end
|
|
629
654
|
|
|
630
655
|
# `members` and `saml_groups`
|
|
@@ -632,41 +657,48 @@ module Aspera
|
|
|
632
657
|
CRUD_NO_LIST = %i[create modify delete show].freeze
|
|
633
658
|
|
|
634
659
|
RES_SINGULAR = {shared_inboxes: :shared_inbox, workgroups: :workgroup}.freeze
|
|
635
|
-
|
|
660
|
+
# Access levels of members, per resource
|
|
661
|
+
MEMBER_ACCESS = {
|
|
662
|
+
shared_inboxes: %i[submit_only standard shared_inbox_admin].freeze,
|
|
663
|
+
workgroups: %i[standard workgroup_admin].freeze
|
|
664
|
+
}.freeze
|
|
665
|
+
private_constant :RES_SINGULAR, :MEMBER_ACCESS
|
|
636
666
|
|
|
637
667
|
# admin > shared_inboxes|workgroups > members|saml_groups|invite_external_collaborator:
|
|
638
668
|
# res_id consumed via arguments:(:identifier) + lookup:, builds res_instance_path for all children
|
|
639
669
|
%i[shared_inboxes workgroups].each do |res|
|
|
640
|
-
lookup_res_id = ->(field, value, **) {
|
|
670
|
+
lookup_res_id = ->(field, value, **) { api_v5.lookup_entity_by_field(entity: res.to_s, field: field, value: value, query: {'all': true})['id'] }
|
|
641
671
|
commands_under [:admin, res] do
|
|
642
|
-
command :members, description: '
|
|
672
|
+
command :members, description: 'Manage members',
|
|
643
673
|
arguments: [{name: :"#{RES_SINGULAR[res]}_id", type: :identifier, lookup: lookup_res_id}],
|
|
644
674
|
setup: :"setup_admin_#{res}_instance"
|
|
645
|
-
command :saml_groups, description: '
|
|
675
|
+
command :saml_groups, description: 'Manage SAML groups',
|
|
646
676
|
arguments: [{name: :"#{RES_SINGULAR[res]}_id", type: :identifier, lookup: lookup_res_id}],
|
|
647
677
|
setup: :"setup_admin_#{res}_instance"
|
|
648
678
|
command :invite_external_collaborator, description: 'Invite external collaborator',
|
|
649
679
|
arguments: [{name: :"#{RES_SINGULAR[res]}_id", type: :identifier, lookup: lookup_res_id},
|
|
650
|
-
{name: :
|
|
680
|
+
{name: :collaborator, type: Hash}]
|
|
651
681
|
end
|
|
652
682
|
|
|
653
683
|
commands_under [:admin, res, :members] do
|
|
654
684
|
CRUD_NO_SHOW.each do |c|
|
|
655
685
|
args =
|
|
656
686
|
if c.eql?(:create)
|
|
657
|
-
{arguments: [{name: :users, bulk: true}, {name: :access, mandatory: false, default: :standard}]}
|
|
687
|
+
{arguments: [{name: :users, bulk: true}, {name: :access, allowed: MEMBER_ACCESS[res], mandatory: false, default: :standard}]}
|
|
688
|
+
elsif c.eql?(:modify)
|
|
689
|
+
{arguments: [{name: :member_id, type: :identifier, lookup: :"lookup_#{res}_members_id"}, {name: :member, type: Hash}]}
|
|
658
690
|
elsif Operations::INSTANCE.include?(c)
|
|
659
691
|
{arguments: [{name: :member_id, type: :identifier, lookup: :"lookup_#{res}_members_id"}]}
|
|
660
692
|
else
|
|
661
693
|
{}
|
|
662
694
|
end
|
|
663
|
-
command c, description: c
|
|
695
|
+
command c, description: operation_description(c, 'member'), **args
|
|
664
696
|
end
|
|
665
697
|
end
|
|
666
698
|
|
|
667
699
|
commands_under [:admin, res, :saml_groups] do
|
|
668
700
|
crud_commands entity: :saml_groups_path,
|
|
669
|
-
api:
|
|
701
|
+
api: :api_v5,
|
|
670
702
|
name: 'SAML group',
|
|
671
703
|
operations: CRUD_NO_SHOW,
|
|
672
704
|
lookup: :"lookup_#{res}_saml_groups_id",
|
|
@@ -675,57 +707,51 @@ module Aspera
|
|
|
675
707
|
end
|
|
676
708
|
|
|
677
709
|
commands_under %i[admin configuration] do
|
|
678
|
-
command :show, description: 'Show configuration', action: -> { Result::SingleObject.new(
|
|
710
|
+
command :show, description: 'Show configuration', action: ->(**) { Result::SingleObject.new(api_v5.read('configuration')) }
|
|
679
711
|
command :modify, description: 'Modify configuration',
|
|
680
|
-
arguments: [{name: :
|
|
681
|
-
action: ->(
|
|
712
|
+
arguments: [{name: :configuration, type: Hash}],
|
|
713
|
+
action: ->(configuration:, **) { Result::SingleObject.new(api_v5.update('configuration', configuration)) }
|
|
682
714
|
end
|
|
683
715
|
|
|
684
716
|
commands_under %i[admin smtp] do
|
|
685
|
-
command :show, description: 'Show SMTP configuration', action: -> { Result::SingleObject.new(
|
|
717
|
+
command :show, description: 'Show SMTP configuration', action: ->(**) { Result::SingleObject.new(api_v5.read('configuration/smtp')) }
|
|
686
718
|
command :create, description: 'Create SMTP configuration',
|
|
687
|
-
arguments: [{name: :
|
|
688
|
-
action: ->(
|
|
719
|
+
arguments: [{name: :smtp, type: Hash}],
|
|
720
|
+
action: ->(smtp:, **) { Result::SingleObject.new(api_v5.create('configuration/smtp', smtp)) }
|
|
689
721
|
command :modify, description: 'Modify SMTP configuration',
|
|
690
|
-
arguments: [{name: :
|
|
691
|
-
action: ->(
|
|
722
|
+
arguments: [{name: :smtp, type: Hash}],
|
|
723
|
+
action: ->(smtp:, **) { Result::SingleObject.new(api_v5.update('configuration/smtp', smtp)) }
|
|
692
724
|
command(
|
|
693
725
|
:delete, description: 'Delete SMTP configuration',
|
|
694
|
-
action: lambda do
|
|
695
|
-
|
|
726
|
+
action: lambda do |**|
|
|
727
|
+
api_v5.delete('configuration/smtp')
|
|
696
728
|
Result::Status.new('SMTP configuration deleted')
|
|
697
729
|
end
|
|
698
730
|
)
|
|
699
731
|
command :test, description: 'Test SMTP configuration',
|
|
700
|
-
arguments: [{name: :
|
|
732
|
+
arguments: [{name: :recipient}]
|
|
701
733
|
end
|
|
702
734
|
|
|
703
735
|
commands_under %i[admin events] do
|
|
704
736
|
command(
|
|
705
737
|
:application, description: 'List application events',
|
|
706
|
-
action: lambda do
|
|
707
|
-
list, total =
|
|
738
|
+
action: lambda do |**|
|
|
739
|
+
list, total = api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::FASPEX, 'application_events')))
|
|
708
740
|
Result::ObjectList.new(list, total: total, fields: %w[event_type created_at application user.name])
|
|
709
741
|
end
|
|
710
742
|
)
|
|
711
743
|
command(
|
|
712
744
|
:webhook, description: 'List webhook events',
|
|
713
|
-
action: lambda do
|
|
714
|
-
list, total =
|
|
745
|
+
action: lambda do |**|
|
|
746
|
+
list, total = api_v5.list_entities_limit_offset_total_count(entity: 'all_webhooks_events', query: query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::FASPEX, 'all_webhooks_events')), items_key: 'events')
|
|
715
747
|
Result::ObjectList.new(list, total: total)
|
|
716
748
|
end
|
|
717
749
|
)
|
|
718
750
|
end
|
|
719
751
|
|
|
720
|
-
# admin > clean_deleted handler (leaf, no sub-commands)
|
|
721
|
-
define_action_method(%i[admin clean_deleted]) do |input_data: {}, **|
|
|
722
|
-
input_data = @api_v5.read('configuration').slice('days_before_deleting_package_records') if input_data.empty?
|
|
723
|
-
Result::SingleObject.new(@api_v5.create('internal/packages/clean_deleted', input_data))
|
|
724
|
-
end
|
|
725
|
-
|
|
726
752
|
# admin > <resource> > list
|
|
727
753
|
Api::Faspex::ADMIN_RESOURCES.each do |res|
|
|
728
|
-
define_action_method([:admin, res, :list]) do
|
|
754
|
+
define_action_method([:admin, res, :list]) do |**|
|
|
729
755
|
args = res_exec_args(res)
|
|
730
756
|
# Special case: email_notifications list returns a fixed value list
|
|
731
757
|
next Result::ValueList.new(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res.eql?(:email_notifications)
|
|
@@ -740,9 +766,9 @@ module Aspera
|
|
|
740
766
|
end
|
|
741
767
|
end
|
|
742
768
|
|
|
743
|
-
def action_admin_smtp_test(
|
|
744
|
-
|
|
745
|
-
creation =
|
|
769
|
+
def action_admin_smtp_test(recipient:, **)
|
|
770
|
+
recipient = {test_email_recipient: recipient} if recipient.is_a?(String)
|
|
771
|
+
creation = api_v5.create('configuration/smtp/test', recipient)
|
|
746
772
|
result = wait_for_job(creation['job_id'])
|
|
747
773
|
begin
|
|
748
774
|
result['serialized_args'] = JSON.parse(result['serialized_args'])
|
|
@@ -753,15 +779,15 @@ module Aspera
|
|
|
753
779
|
end
|
|
754
780
|
|
|
755
781
|
# admin > accounts > reset_password
|
|
756
|
-
def action_admin_accounts_reset_password(
|
|
757
|
-
|
|
782
|
+
def action_admin_accounts_reset_password(account_id:, **)
|
|
783
|
+
api_v5.create("accounts/#{account_id}/reset_password", {})
|
|
758
784
|
Result::Status.new('password reset, user shall check email')
|
|
759
785
|
end
|
|
760
786
|
|
|
761
787
|
# admin > file_processing > next
|
|
762
|
-
def action_admin_file_processing_next
|
|
788
|
+
def action_admin_file_processing_next(**)
|
|
763
789
|
args = res_exec_args(:file_processing)
|
|
764
|
-
result, count =
|
|
790
|
+
result, count = api_v5.list_entities_limit_offset_total_count(entity: args[:entity], operation: 'POST', items_key: 'files')
|
|
765
791
|
Result::ObjectList.new(result, total: count)
|
|
766
792
|
end
|
|
767
793
|
|
|
@@ -778,18 +804,18 @@ module Aspera
|
|
|
778
804
|
# Lookup shared folder id by field/value within a node's shared_folders entity.
|
|
779
805
|
# sf_entity is in ctx from setup_admin_nodes_shared_folders (Phase A of parent).
|
|
780
806
|
def lookup_sf_id(field, value, sf_entity:, **)
|
|
781
|
-
|
|
807
|
+
api_v5.lookup_entity_by_field(entity: sf_entity, items_key: 'shared_folders', field: field, value: value)['id']
|
|
782
808
|
end
|
|
783
809
|
|
|
784
810
|
# Lookup custom access user id by field/value within a shared folder's users entity.
|
|
785
811
|
# user_path is in ctx from setup_admin_nodes_shared_folders_user (Phase A of parent).
|
|
786
812
|
def lookup_sf_user_id(field, value, user_path:, **)
|
|
787
|
-
|
|
813
|
+
api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
|
|
788
814
|
end
|
|
789
815
|
|
|
790
|
-
# admin > nodes > shared_folders > user —
|
|
791
|
-
def setup_admin_nodes_shared_folders_user(sf_entity:,
|
|
792
|
-
{user_path: "#{sf_entity}/#{
|
|
816
|
+
# admin > nodes > shared_folders > user — shared_folder_id: already in ctx via arguments: on the :user command
|
|
817
|
+
def setup_admin_nodes_shared_folders_user(sf_entity:, shared_folder_id:, **)
|
|
818
|
+
{user_path: "#{sf_entity}/#{shared_folder_id}/custom_access_users"}
|
|
793
819
|
end
|
|
794
820
|
|
|
795
821
|
# admin > shared_inboxes|workgroups — res_id: already in ctx via arguments: on the :members|:saml_groups command
|
|
@@ -810,7 +836,7 @@ module Aspera
|
|
|
810
836
|
users = [users] unless users.is_a?(Array)
|
|
811
837
|
users.map do |user|
|
|
812
838
|
if (m = Parser.percent_selector(user))
|
|
813
|
-
|
|
839
|
+
api_v5.lookup_entity_by_field(entity: 'accounts', field: m[:field], value: m[:value], query: Rest.php_style({type: ACCOUNT_TYPES}))['id']
|
|
814
840
|
else
|
|
815
841
|
user
|
|
816
842
|
end
|
|
@@ -822,11 +848,13 @@ module Aspera
|
|
|
822
848
|
%i[shared_inboxes workgroups].each do |res|
|
|
823
849
|
define_method(:"lookup_#{res}_members_id") do |field, value, res_instance_path:, **|
|
|
824
850
|
res_path = "#{res_instance_path}/members"
|
|
825
|
-
|
|
851
|
+
api_v5.lookup_entity_by_field(entity: res_path, field: field, value: value, query: Rest.php_style({type: %w[user]}))['user_id']
|
|
826
852
|
end
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
853
|
+
# Like members (`user_id`), a SAML group of the resource is identified by `group_id`; its name is `group_name`
|
|
854
|
+
define_method(:"lookup_#{res}_saml_groups_id") do |field, value, saml_groups_path:, **|
|
|
855
|
+
field = 'group_name' if field.eql?('name')
|
|
856
|
+
query = field.eql?('group_name') ? {'q' => value} : {}
|
|
857
|
+
api_v5.lookup_entity_by_field(entity: saml_groups_path, field: field, value: value, items_key: 'groups', query: query)['group_id']
|
|
830
858
|
end
|
|
831
859
|
end
|
|
832
860
|
|
|
@@ -834,52 +862,52 @@ module Aspera
|
|
|
834
862
|
# (saml_groups CRUD is handled by crud_commands)
|
|
835
863
|
%i[shared_inboxes workgroups].each do |res|
|
|
836
864
|
define_action_method([:admin, res, :members, :list]) do |res_instance_path:, **|
|
|
837
|
-
entity_list(api:
|
|
865
|
+
entity_list(api: api_v5, entity: "#{res_instance_path}/members", items_key: 'members')
|
|
838
866
|
end
|
|
839
867
|
|
|
840
|
-
define_action_method([:admin, res, :members, :modify]) do |res_instance_path:,
|
|
841
|
-
entity_modify(api:
|
|
868
|
+
define_action_method([:admin, res, :members, :modify]) do |res_instance_path:, member_id:, member:, **|
|
|
869
|
+
entity_modify(api: api_v5, entity: "#{res_instance_path}/members", id: member_id, data: member)
|
|
842
870
|
end
|
|
843
871
|
|
|
844
872
|
define_action_method([:admin, res, :members, :delete]) do |res_instance_path:, **kwargs|
|
|
845
|
-
entity_delete(api:
|
|
873
|
+
entity_delete(api: api_v5, entity: "#{res_instance_path}/members", id: kwargs[:member_id])
|
|
846
874
|
end
|
|
847
875
|
|
|
848
876
|
define_action_method([:admin, res, :members, :create]) do |users:, access:, res_instance_path:, **|
|
|
849
877
|
res_path = "#{res_instance_path}/members"
|
|
850
878
|
resolved = resolve_member_user_ids(users)
|
|
851
|
-
|
|
852
|
-
entity_create(api: @api_v5, entity: res_path, input_data: input_data)
|
|
879
|
+
entity_create(api: api_v5, entity: res_path, data: {user: resolved.map { |u| {id: u, access: access} }})
|
|
853
880
|
end
|
|
854
881
|
end
|
|
855
882
|
|
|
856
883
|
# admin > shared_inboxes|workgroups > invite_external_collaborator
|
|
857
884
|
%i[shared_inboxes workgroups].each do |res|
|
|
858
|
-
define_action_method([:admin, res, :invite_external_collaborator]) do |
|
|
885
|
+
define_action_method([:admin, res, :invite_external_collaborator]) do |collaborator:, **kwargs|
|
|
859
886
|
res_instance_path = "#{res}/#{kwargs[:"#{RES_SINGULAR[res]}_id"]}"
|
|
860
|
-
result =
|
|
887
|
+
result = api_v5.create("#{res_instance_path}/external_collaborator", collaborator)
|
|
861
888
|
formatter.display_status(result['message'])
|
|
862
|
-
Result::SingleObject.new(
|
|
889
|
+
Result::SingleObject.new(api_v5.lookup_entity_by_field(entity: "#{res_instance_path}/members", items_key: 'members', value: collaborator['email_address'], query: {}))
|
|
863
890
|
end
|
|
864
891
|
end
|
|
865
892
|
|
|
866
|
-
# ---
|
|
893
|
+
# --- API ---
|
|
867
894
|
|
|
868
|
-
#
|
|
869
|
-
# @return [
|
|
870
|
-
def
|
|
871
|
-
return
|
|
895
|
+
# Faspex 5 API, built from CLI options on first use.
|
|
896
|
+
# @return [Api::Faspex]
|
|
897
|
+
def api_v5
|
|
898
|
+
return @api_v5 if @api_v5
|
|
872
899
|
@api_v5 = Api::Faspex.new(**Oauth.kwargs_from_options(options))
|
|
873
900
|
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
874
901
|
transfer.httpgw_url_cb = lambda { @api_v5.read('account')['gateway_url'] }
|
|
875
|
-
|
|
902
|
+
@api_v5
|
|
876
903
|
end
|
|
877
904
|
|
|
878
|
-
# Setup for package sub-commands that need an id:
|
|
879
|
-
#
|
|
905
|
+
# Setup for package sub-commands that need an id: with a public link to a package, the id comes from the link.
|
|
906
|
+
# Leaf setup runs before argument resolution: the declared package_id argument is then not read.
|
|
907
|
+
# @return [Hash] ctx key: package_id, or empty (package_id is read from the command line)
|
|
880
908
|
def setup_package_id(**)
|
|
881
|
-
|
|
882
|
-
{package_id: package_id}
|
|
909
|
+
return {} unless api_v5.pub_link_context&.key?('package_id')
|
|
910
|
+
{package_id: api_v5.pub_link_context['package_id']}
|
|
883
911
|
end
|
|
884
912
|
|
|
885
913
|
def action_packages_list(filter: nil, **)
|
|
@@ -895,7 +923,7 @@ module Aspera
|
|
|
895
923
|
ids = package_id.is_a?(Array) ? package_id : [package_id]
|
|
896
924
|
Aspera.assert_array_all(ids, String) { 'Package id(s)' }
|
|
897
925
|
# API returns 204, empty on success
|
|
898
|
-
|
|
926
|
+
api_v5.call(
|
|
899
927
|
operation: 'DELETE',
|
|
900
928
|
subpath: 'packages',
|
|
901
929
|
content_type: Mime::JSON,
|
|
@@ -907,10 +935,10 @@ module Aspera
|
|
|
907
935
|
|
|
908
936
|
# --- handlers ---
|
|
909
937
|
|
|
910
|
-
def action_health
|
|
938
|
+
def action_health(**)
|
|
911
939
|
nagios = Nagios.new
|
|
912
940
|
begin
|
|
913
|
-
data, http = Rest.new(base_url: options.get_option(:url, mandatory: true))
|
|
941
|
+
data, http = Rest::Client.new(base_url: options.get_option(:url, mandatory: true))
|
|
914
942
|
.read('health', ret: :both)
|
|
915
943
|
data.each do |k, v|
|
|
916
944
|
nagios.add_ok(k, v.to_s)
|
|
@@ -925,7 +953,7 @@ module Aspera
|
|
|
925
953
|
# Lookup a shared folder id by field/value.
|
|
926
954
|
# Called via lookup: :lookup_shared_folder_id on the shared_folders > browse command.
|
|
927
955
|
def lookup_shared_folder_id(field, value, **)
|
|
928
|
-
all =
|
|
956
|
+
all = api_v5.read('shared_folders')['shared_folders']
|
|
929
957
|
matches = all.select { |i| i[field].eql?(value) }
|
|
930
958
|
Aspera.assert(!matches.empty?) { "no match for #{field} = #{value}" }
|
|
931
959
|
Aspera.assert(matches.length == 1) { "multiple matches for #{field} = #{value}" }
|
|
@@ -933,7 +961,7 @@ module Aspera
|
|
|
933
961
|
end
|
|
934
962
|
|
|
935
963
|
def action_shared_folders_browse(folder_path:, shared_folder_id:, **)
|
|
936
|
-
all_shared_folders =
|
|
964
|
+
all_shared_folders = api_v5.read('shared_folders')['shared_folders']
|
|
937
965
|
node = all_shared_folders.find { |i| i['id'].eql?(shared_folder_id) }
|
|
938
966
|
Aspera.assert(!node.nil?) { "No such shared folder id #{shared_folder_id}" }
|
|
939
967
|
browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse", {}, folder_path: folder_path)
|
|
@@ -941,15 +969,10 @@ module Aspera
|
|
|
941
969
|
|
|
942
970
|
# invitations sub-handlers
|
|
943
971
|
|
|
944
|
-
def
|
|
945
|
-
|
|
946
|
-
Result::Status.new('Invitation resent')
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
def action_invitations_create(input_data:, **)
|
|
950
|
-
bulk_result(input_data, command: :create) do |params|
|
|
972
|
+
def action_invitations_create(invitation:, **)
|
|
973
|
+
bulk_result(invitation, command: :create) do |params|
|
|
951
974
|
endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
|
|
952
|
-
|
|
975
|
+
api_v5.create(endpoint, params)
|
|
953
976
|
end
|
|
954
977
|
end
|
|
955
978
|
|
|
@@ -959,7 +982,7 @@ module Aspera
|
|
|
959
982
|
uri = URI.parse(parameters.delete(:url) { WebServerSimple::DEFAULT_URL })
|
|
960
983
|
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
961
984
|
Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?) { "unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}" }
|
|
962
|
-
server.mount(uri.path, Faspex4GWServlet,
|
|
985
|
+
server.mount(uri.path, Faspex4GWServlet, api_v5, nil)
|
|
963
986
|
server.start
|
|
964
987
|
Result::Status.new('Gateway terminated')
|
|
965
988
|
end
|
|
@@ -974,12 +997,11 @@ module Aspera
|
|
|
974
997
|
server.start
|
|
975
998
|
Result::Status.new('Gateway terminated')
|
|
976
999
|
end
|
|
977
|
-
SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
|
|
978
1000
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
979
1001
|
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
980
1002
|
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
981
1003
|
PACKAGE_RECIPIENT_TYPES = %i{recipients private_recipients notified_on_upload notified_on_download notified_on_receipt}
|
|
982
|
-
private_constant :
|
|
1004
|
+
private_constant :ACCOUNT_TYPES, :CONTACT_TYPES, :PACKAGE_RECIPIENT_TYPES,
|
|
983
1005
|
:CRUD_NO_SHOW, :CRUD_NO_LIST
|
|
984
1006
|
end
|
|
985
1007
|
end
|