aspera-cli 4.25.3 → 4.25.5
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 +40 -6
- data/CONTRIBUTING.md +122 -111
- data/README.md +9 -7
- data/lib/aspera/agent/direct.rb +10 -8
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/node.rb +1 -1
- data/lib/aspera/api/alee.rb +1 -0
- data/lib/aspera/api/aoc.rb +15 -14
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +5 -0
- data/lib/aspera/api/faspex.rb +27 -20
- data/lib/aspera/api/httpgw.rb +19 -3
- data/lib/aspera/api/node.rb +122 -29
- data/lib/aspera/ascp/installation.rb +9 -10
- data/lib/aspera/cli/error.rb +8 -0
- data/lib/aspera/cli/formatter.rb +27 -11
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/main.rb +30 -12
- data/lib/aspera/cli/manager.rb +43 -31
- data/lib/aspera/cli/plugins/aoc.rb +7 -5
- data/lib/aspera/cli/plugins/base.rb +1 -88
- data/lib/aspera/cli/plugins/config.rb +2 -1
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +64 -64
- data/lib/aspera/cli/plugins/node.rb +33 -78
- data/lib/aspera/cli/plugins/shares.rb +4 -2
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/transfer_agent.rb +3 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +2 -1
- data/lib/aspera/dot_container.rb +10 -10
- data/lib/aspera/log.rb +1 -1
- data/lib/aspera/markdown.rb +1 -1
- data/lib/aspera/persistency_folder.rb +1 -1
- data/lib/aspera/rest.rb +39 -54
- data/lib/aspera/rest_list.rb +121 -0
- data/lib/aspera/sync/operations.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +8 -8
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/yaml.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
|
@@ -100,24 +100,18 @@ module Aspera
|
|
|
100
100
|
options.parse_options!
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def set_api
|
|
104
|
-
# create an API object with the same options, but with a different subpath
|
|
105
|
-
@api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
|
|
106
|
-
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
107
|
-
transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
|
|
108
|
-
end
|
|
109
|
-
|
|
110
103
|
# if recipient is just an email, then convert to expected API hash : name and type
|
|
111
|
-
def normalize_recipients(parameters)
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
def normalize_recipients(parameters, type)
|
|
105
|
+
type = type.to_s
|
|
106
|
+
return unless parameters.key?(type)
|
|
107
|
+
Aspera.assert_type(parameters[type], Array){type}
|
|
114
108
|
recipient_types = Api::Faspex::RECIPIENT_TYPES
|
|
115
109
|
if parameters.key?('recipient_types')
|
|
116
110
|
recipient_types = parameters['recipient_types']
|
|
117
111
|
parameters.delete('recipient_types')
|
|
118
112
|
recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
|
|
119
113
|
end
|
|
120
|
-
parameters[
|
|
114
|
+
parameters[type].map! do |recipient_data|
|
|
121
115
|
# If just a string, make a general lookup and build expected name/type hash
|
|
122
116
|
if recipient_data.is_a?(String)
|
|
123
117
|
matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
|
|
@@ -157,17 +151,17 @@ module Aspera
|
|
|
157
151
|
end
|
|
158
152
|
end
|
|
159
153
|
|
|
160
|
-
# @param [
|
|
154
|
+
# @param [String] job identifier
|
|
161
155
|
# @return [Hash] result of API call for job status
|
|
162
156
|
def wait_for_job(job_id)
|
|
163
157
|
result = nil
|
|
164
158
|
loop do
|
|
165
159
|
result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
|
|
166
160
|
break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
|
|
167
|
-
|
|
161
|
+
RestParameters.instance.spinner_cb.call(result['status'])
|
|
168
162
|
sleep(0.5)
|
|
169
163
|
end
|
|
170
|
-
|
|
164
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
171
165
|
return result
|
|
172
166
|
end
|
|
173
167
|
|
|
@@ -182,16 +176,22 @@ module Aspera
|
|
|
182
176
|
when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
|
|
183
177
|
else
|
|
184
178
|
group_type = options.get_option(:group_type)
|
|
185
|
-
"#{group_type}/#{lookup_entity_by_field(
|
|
179
|
+
"#{group_type}/#{@api_v5.lookup_entity_by_field(entity: group_type, value: box)['id']}/packages"
|
|
186
180
|
end
|
|
187
|
-
list, total = list_entities_limit_offset_total_count(
|
|
188
|
-
api: @api_v5,
|
|
189
|
-
entity: entity,
|
|
190
|
-
query: query_read_delete(default: query)
|
|
191
|
-
)
|
|
181
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(entity: entity, query: query_read_delete(default: query))
|
|
192
182
|
return list.select(&filter), total
|
|
193
183
|
end
|
|
194
184
|
|
|
185
|
+
# Build query to get package recipients based on package info in case of shared inbox or workgroup recipient
|
|
186
|
+
# @param package_id [String] the package id to get info from
|
|
187
|
+
def recipient_query(package_id)
|
|
188
|
+
package_info = @api_v5.read("packages/#{package_id}")
|
|
189
|
+
base_query = {}
|
|
190
|
+
base_query['recipient_workgroup_id'] = package_info['recipients'].first['id'] if WORKGROUP_TYPES.include?(package_info['recipients'].first['recipient_type'])
|
|
191
|
+
base_query['recipient_user_id'] = package_info['recipients'].first['id'] if package_info['recipients'].first['recipient_type'].eql?('user')
|
|
192
|
+
base_query
|
|
193
|
+
end
|
|
194
|
+
|
|
195
195
|
def package_receive(package_ids)
|
|
196
196
|
# prepare persistency if needed
|
|
197
197
|
skip_ids_persistency = nil
|
|
@@ -241,7 +241,7 @@ module Aspera
|
|
|
241
241
|
type: Api::Faspex.box_type(box),
|
|
242
242
|
transfer_type: Api::Faspex::TRANSFER_CONNECT
|
|
243
243
|
}
|
|
244
|
-
download_params[:recipient_workgroup_id] = lookup_entity_by_field(
|
|
244
|
+
# 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
|
|
245
245
|
packages.each do |package|
|
|
246
246
|
pkg_id = package['id']
|
|
247
247
|
formatter.display_status("Receiving package #{pkg_id}")
|
|
@@ -249,7 +249,7 @@ module Aspera
|
|
|
249
249
|
transfer_spec = @api_v5.call(
|
|
250
250
|
operation: 'POST',
|
|
251
251
|
subpath: "packages/#{pkg_id}/transfer_spec/download",
|
|
252
|
-
query: download_params,
|
|
252
|
+
query: download_params.merge(recipient_query(pkg_id)),
|
|
253
253
|
content_type: Mime::JSON,
|
|
254
254
|
body: param_file_list,
|
|
255
255
|
headers: {'Accept' => Mime::JSON}
|
|
@@ -276,7 +276,7 @@ module Aspera
|
|
|
276
276
|
recipient_type: @api_v5.pub_link_context['recipient_type']
|
|
277
277
|
}]
|
|
278
278
|
end
|
|
279
|
-
normalize_recipients(parameters)
|
|
279
|
+
PACKAGE_RECIPIENT_TYPES.each{ |type| normalize_recipients(parameters, type)}
|
|
280
280
|
# User specified content prot in tspec, but faspex requires in package creation
|
|
281
281
|
# `transfer_spec/upload` will set `content_protection`
|
|
282
282
|
if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
|
|
@@ -298,8 +298,7 @@ module Aspera
|
|
|
298
298
|
else
|
|
299
299
|
# send from remote shared folder
|
|
300
300
|
if (m = Base.percent_selector(shared_folder))
|
|
301
|
-
shared_folder = lookup_entity_by_field(
|
|
302
|
-
api: @api_v5,
|
|
301
|
+
shared_folder = @api_v5.lookup_entity_by_field(
|
|
303
302
|
entity: 'shared_folders',
|
|
304
303
|
field: m[:field],
|
|
305
304
|
value: m[:value]
|
|
@@ -319,14 +318,14 @@ module Aspera
|
|
|
319
318
|
|
|
320
319
|
# Browse a folder
|
|
321
320
|
# @param browse_endpoint [String] the endpoint to browse
|
|
322
|
-
def browse_folder(browse_endpoint)
|
|
321
|
+
def browse_folder(browse_endpoint, base_query = {})
|
|
323
322
|
folders_to_process = [options.get_next_argument('folder path', default: '/')]
|
|
324
|
-
query = query_read_delete(default: {})
|
|
323
|
+
query = base_query.merge(query_read_delete(default: {}))
|
|
325
324
|
filters = query.delete('filters'){{}}
|
|
326
325
|
Aspera.assert_type(filters, Hash)
|
|
327
326
|
filters['basenames'] ||= []
|
|
328
327
|
Aspera.assert_type(filters, Hash){'filters'}
|
|
329
|
-
max_items = query.delete(MAX_ITEMS)
|
|
328
|
+
max_items = query.delete(RestList::MAX_ITEMS)
|
|
330
329
|
recursive = query.delete('recursive')
|
|
331
330
|
use_paging = query.delete('paging'){true}
|
|
332
331
|
if use_paging
|
|
@@ -357,7 +356,7 @@ module Aspera
|
|
|
357
356
|
end
|
|
358
357
|
folders_to_process.concat(data['items'].select{ |i| i['type'].eql?('directory')}.map{ |i| i['path']}) if recursive
|
|
359
358
|
if use_paging
|
|
360
|
-
iteration_token = http[Api::Faspex::
|
|
359
|
+
iteration_token = http[Api::Faspex::HEADER_X_NEXT_ITER_TOKEN]
|
|
361
360
|
break if iteration_token.nil? || iteration_token.empty?
|
|
362
361
|
query['iteration_token'] = iteration_token
|
|
363
362
|
else
|
|
@@ -365,12 +364,11 @@ module Aspera
|
|
|
365
364
|
break if data['item_count'].eql?(0)
|
|
366
365
|
query['offset'] += data['item_count']
|
|
367
366
|
end
|
|
368
|
-
|
|
367
|
+
RestParameters.instance.spinner_cb.call(all_items.count)
|
|
369
368
|
end
|
|
370
369
|
query.delete('iteration_token')
|
|
371
370
|
end
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
374
372
|
return Main.result_object_list(all_items, total: total_count)
|
|
375
373
|
end
|
|
376
374
|
|
|
@@ -384,7 +382,7 @@ module Aspera
|
|
|
384
382
|
when :show
|
|
385
383
|
return Main.result_single_object(@api_v5.read("packages/#{package_id}"))
|
|
386
384
|
when :browse
|
|
387
|
-
return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}")
|
|
385
|
+
return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}", recipient_query(package_id))
|
|
388
386
|
when :status
|
|
389
387
|
status_list = options.get_next_argument('list of states, or nothing', mandatory: false, validation: Array)
|
|
390
388
|
status = wait_package_status(package_id, status_list: status_list)
|
|
@@ -418,8 +416,7 @@ module Aspera
|
|
|
418
416
|
def execute_resource(res_sym)
|
|
419
417
|
exec_args = {
|
|
420
418
|
api: @api_v5,
|
|
421
|
-
entity: res_sym.to_s
|
|
422
|
-
tclo: true
|
|
419
|
+
entity: res_sym.to_s
|
|
423
420
|
}
|
|
424
421
|
res_id_query = :default
|
|
425
422
|
available_commands = ALL_OPS
|
|
@@ -440,25 +437,30 @@ module Aspera
|
|
|
440
437
|
available_commands += [:reset_password]
|
|
441
438
|
when :oauth_clients
|
|
442
439
|
exec_args[:display_fields] = Formatter.all_but('public_key')
|
|
443
|
-
exec_args[:api] =
|
|
440
|
+
exec_args[:api] = Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.args_from_options(options))
|
|
444
441
|
exec_args[:list_query] = {'expand': true, 'no_api_path': true, 'client_types[]': 'public'}
|
|
445
442
|
when :shared_inboxes, :workgroups
|
|
446
443
|
available_commands += %i[members saml_groups invite_external_collaborator]
|
|
447
444
|
res_id_query = {'all': true}
|
|
448
445
|
when :nodes
|
|
449
446
|
available_commands += %i[shared_folders browse]
|
|
447
|
+
when :jobs
|
|
448
|
+
exec_args[:display_fields] = %w[id job_name job_type status]
|
|
450
449
|
end
|
|
451
450
|
res_command = options.get_next_command(available_commands)
|
|
452
451
|
return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
|
|
453
452
|
case res_command
|
|
454
|
-
when
|
|
453
|
+
when :create, :modify, :delete, :show
|
|
455
454
|
return entity_execute(command: res_command, **exec_args) do |field, value|
|
|
456
|
-
lookup_entity_by_field(
|
|
455
|
+
@api_v5.lookup_entity_by_field(entity: exec_args[:entity], value: value, field: field, items_key: exec_args[:items_key], query: res_id_query)['id']
|
|
457
456
|
end
|
|
457
|
+
when :list
|
|
458
|
+
data, total = exec_args[:api].list_entities_limit_offset_total_count(entity: exec_args[:entity], items_key: exec_args[:items_key], query: query_read_delete(default: exec_args[:list_query]))
|
|
459
|
+
return Main.result_object_list(data, total: total, fields: exec_args[:display_fields])
|
|
458
460
|
when :shared_folders
|
|
459
461
|
# nodes
|
|
460
462
|
node_id = instance_identifier do |field, value|
|
|
461
|
-
lookup_entity_by_field(
|
|
463
|
+
@api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
|
|
462
464
|
end
|
|
463
465
|
shfld_entity = "nodes/#{node_id}/shared_folders"
|
|
464
466
|
sh_command = options.get_next_command(ALL_OPS + [:user])
|
|
@@ -469,33 +471,32 @@ module Aspera
|
|
|
469
471
|
entity: shfld_entity,
|
|
470
472
|
command: sh_command
|
|
471
473
|
) do |field, value|
|
|
472
|
-
lookup_entity_by_field(
|
|
474
|
+
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
473
475
|
end
|
|
474
476
|
when :user
|
|
475
477
|
sh_id = instance_identifier do |field, value|
|
|
476
|
-
lookup_entity_by_field(
|
|
478
|
+
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
477
479
|
end
|
|
478
480
|
user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
|
|
479
481
|
return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
|
|
480
|
-
lookup_entity_by_field(
|
|
482
|
+
@api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
|
|
481
483
|
end
|
|
482
484
|
|
|
483
485
|
end
|
|
484
486
|
when :browse
|
|
485
487
|
# nodes
|
|
486
488
|
node_id = instance_identifier do |field, value|
|
|
487
|
-
lookup_entity_by_field(
|
|
489
|
+
@api_v5.lookup_entity_by_field(entity: 'nodes', value: value, field: field)['id']
|
|
488
490
|
end
|
|
489
491
|
return browse_folder("nodes/#{node_id}/browse")
|
|
490
492
|
when :invite_external_collaborator
|
|
491
493
|
# :shared_inboxes, :workgroups
|
|
492
|
-
shared_inbox_id = instance_identifier{ |field, value| lookup_entity_by_field(
|
|
494
|
+
shared_inbox_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
|
|
493
495
|
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
|
494
496
|
creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
|
|
495
497
|
result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
|
|
496
498
|
formatter.display_status(result['message'])
|
|
497
|
-
result = lookup_entity_by_field(
|
|
498
|
-
api: @api_v5,
|
|
499
|
+
result = @api_v5.lookup_entity_by_field(
|
|
499
500
|
entity: "#{res_sym}/#{shared_inbox_id}/members",
|
|
500
501
|
items_key: 'members',
|
|
501
502
|
value: creation_payload['email_address'],
|
|
@@ -504,7 +505,7 @@ module Aspera
|
|
|
504
505
|
return Main.result_single_object(result)
|
|
505
506
|
when :members, :saml_groups
|
|
506
507
|
# res_command := :shared_inboxes, :workgroups
|
|
507
|
-
res_id = instance_identifier{ |field, value| lookup_entity_by_field(
|
|
508
|
+
res_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
|
|
508
509
|
res_path = "#{res_sym}/#{res_id}/#{res_command}"
|
|
509
510
|
list_key = res_command.to_s
|
|
510
511
|
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
@@ -515,8 +516,7 @@ module Aspera
|
|
|
515
516
|
users = [users] unless users.is_a?(Array)
|
|
516
517
|
users = users.map do |user|
|
|
517
518
|
if (m = Base.percent_selector(user))
|
|
518
|
-
lookup_entity_by_field(
|
|
519
|
-
api: @api_v5,
|
|
519
|
+
@api_v5.lookup_entity_by_field(
|
|
520
520
|
entity: 'accounts',
|
|
521
521
|
field: m[:field],
|
|
522
522
|
value: m[:value],
|
|
@@ -537,8 +537,7 @@ module Aspera
|
|
|
537
537
|
command: sub_command,
|
|
538
538
|
items_key: list_key
|
|
539
539
|
) do |field, value|
|
|
540
|
-
lookup_entity_by_field(
|
|
541
|
-
api: @api_v5,
|
|
540
|
+
@api_v5.lookup_entity_by_field(
|
|
542
541
|
entity: res_path,
|
|
543
542
|
field: field,
|
|
544
543
|
value: value,
|
|
@@ -547,7 +546,7 @@ module Aspera
|
|
|
547
546
|
end
|
|
548
547
|
when :reset_password
|
|
549
548
|
# :accounts
|
|
550
|
-
contact_id = instance_identifier{ |field, value| lookup_entity_by_field(
|
|
549
|
+
contact_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
|
|
551
550
|
@api_v5.create("accounts/#{contact_id}/reset_password", {})
|
|
552
551
|
return Main.result_status('password reset, user shall check email')
|
|
553
552
|
end
|
|
@@ -568,16 +567,10 @@ module Aspera
|
|
|
568
567
|
event_type = options.get_next_command(%i[application webhook])
|
|
569
568
|
case event_type
|
|
570
569
|
when :application
|
|
571
|
-
list, total = list_entities_limit_offset_total_count(
|
|
572
|
-
api: @api_v5,
|
|
573
|
-
entity: 'application_events',
|
|
574
|
-
query: query_read_delete
|
|
575
|
-
)
|
|
576
|
-
|
|
570
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete)
|
|
577
571
|
return Main.result_object_list(list, total: total, fields: %w[event_type created_at application user.name])
|
|
578
572
|
when :webhook
|
|
579
|
-
list, total = list_entities_limit_offset_total_count(
|
|
580
|
-
api: @api_v5,
|
|
573
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(
|
|
581
574
|
entity: 'all_webhooks_events',
|
|
582
575
|
query: query_read_delete,
|
|
583
576
|
items_key: 'events'
|
|
@@ -622,7 +615,12 @@ module Aspera
|
|
|
622
615
|
|
|
623
616
|
def execute_action
|
|
624
617
|
command = options.get_next_command(ACTIONS)
|
|
625
|
-
|
|
618
|
+
unless %i{postprocessing health}.include?(command)
|
|
619
|
+
# create an API object with the same options, but with a different subpath
|
|
620
|
+
@api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
|
|
621
|
+
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
622
|
+
transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
|
|
623
|
+
end
|
|
626
624
|
case command
|
|
627
625
|
when :version
|
|
628
626
|
return Main.result_single_object(@api_v5.read('version'))
|
|
@@ -694,7 +692,7 @@ module Aspera
|
|
|
694
692
|
items_key: invitation_endpoint,
|
|
695
693
|
display_fields: %w[id public recipient_type recipient_name email_address]
|
|
696
694
|
) do |field, value|
|
|
697
|
-
lookup_entity_by_field(
|
|
695
|
+
@api_v5.lookup_entity_by_field(entity: invitation_endpoint, field: field, value: value, query: {})['id']
|
|
698
696
|
end
|
|
699
697
|
end
|
|
700
698
|
when :gateway
|
|
@@ -719,8 +717,10 @@ module Aspera
|
|
|
719
717
|
end
|
|
720
718
|
SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
|
|
721
719
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
722
|
-
|
|
723
|
-
|
|
720
|
+
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
721
|
+
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
722
|
+
PACKAGE_RECIPIENT_TYPES = %i{recipients private_recipients notified_on_upload notified_on_download notified_on_receipt}
|
|
723
|
+
private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES, :PACKAGE_RECIPIENT_TYPES
|
|
724
724
|
end
|
|
725
725
|
end
|
|
726
726
|
end
|
|
@@ -11,6 +11,7 @@ require 'aspera/id_generator'
|
|
|
11
11
|
require 'aspera/api/node'
|
|
12
12
|
require 'aspera/oauth'
|
|
13
13
|
require 'aspera/node_simulator'
|
|
14
|
+
require 'aspera/rest_list'
|
|
14
15
|
require 'aspera/assert'
|
|
15
16
|
require 'base64'
|
|
16
17
|
require 'zlib'
|
|
@@ -93,12 +94,9 @@ module Aspera
|
|
|
93
94
|
options.declare(:validator, 'Identifier of validator (optional for central)')
|
|
94
95
|
options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
|
|
95
96
|
options.declare(
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
options.declare(
|
|
100
|
-
:node_cache, 'Gen4: Set to no to force actual file system read', allowed: Allowed::TYPES_BOOLEAN,
|
|
101
|
-
handler: {o: Api::Node, m: :use_node_cache}
|
|
97
|
+
:node_api, 'Gen4: standard_ports: Use standard FASP ports (true) or get from node API (false). cache: Set to false to force actual file system read',
|
|
98
|
+
allowed: Hash,
|
|
99
|
+
handler: {o: Api::Node, m: :api_options}
|
|
102
100
|
)
|
|
103
101
|
options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
|
|
104
102
|
options.declare(:dynamic_key, 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key})
|
|
@@ -169,7 +167,7 @@ module Aspera
|
|
|
169
167
|
# @param api [Rest] an existing API object for the Node API
|
|
170
168
|
# @param prefix_path [String,nil] for Faspex 4, allows browsing a package without full path in node (removes storage prefix)
|
|
171
169
|
def initialize(context:, api: nil, prefix_path: nil)
|
|
172
|
-
@
|
|
170
|
+
@node_path_prefix = prefix_path ? NodePathPrefix.new(prefix_path) : nil
|
|
173
171
|
super(context: context, basic_options: api.nil?)
|
|
174
172
|
Node.declare_options(options)
|
|
175
173
|
return if context.only_manual?
|
|
@@ -199,11 +197,11 @@ module Aspera
|
|
|
199
197
|
# Gen3 API
|
|
200
198
|
def browse_gen3
|
|
201
199
|
folders_to_process = options.get_next_argument('path', validation: String)
|
|
202
|
-
folders_to_process = @
|
|
200
|
+
folders_to_process = @node_path_prefix.add_to_path(folders_to_process) unless @node_path_prefix.nil?
|
|
203
201
|
folders_to_process = [folders_to_process]
|
|
204
202
|
query = options.get_option(:query) || {}
|
|
205
203
|
# special parameter: max number of entries in result
|
|
206
|
-
max_items = query.delete(MAX_ITEMS)
|
|
204
|
+
max_items = query.delete(RestList::MAX_ITEMS)
|
|
207
205
|
# special parameter: recursive browsing
|
|
208
206
|
recursive = query.delete('recursive')
|
|
209
207
|
# special parameter: only return one entry for the path, even if folder
|
|
@@ -224,7 +222,7 @@ module Aspera
|
|
|
224
222
|
response = @api_node.create('files/browse', query)
|
|
225
223
|
# 'file','symbolic_link'
|
|
226
224
|
if !Node.gen3_entry_folder?(response['self']) || only_path
|
|
227
|
-
@
|
|
225
|
+
@node_path_prefix&.remove_in_object_list!([response['self']])
|
|
228
226
|
return Main.result_single_object(response['self'])
|
|
229
227
|
end
|
|
230
228
|
items = response['items']
|
|
@@ -242,14 +240,14 @@ module Aspera
|
|
|
242
240
|
break if all_items.count >= total_count
|
|
243
241
|
offset += items.count
|
|
244
242
|
query['skip'] = offset
|
|
245
|
-
|
|
243
|
+
RestParameters.instance.spinner_cb.call(all_items.count)
|
|
246
244
|
end
|
|
247
245
|
query.delete('skip')
|
|
248
246
|
end
|
|
249
|
-
@
|
|
247
|
+
@node_path_prefix&.remove_in_object_list!(all_items)
|
|
250
248
|
return Main.result_object_list(all_items)
|
|
251
249
|
ensure
|
|
252
|
-
|
|
250
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
253
251
|
end
|
|
254
252
|
|
|
255
253
|
# Create async transfer spec request from direction and folders
|
|
@@ -289,12 +287,12 @@ module Aspera
|
|
|
289
287
|
when :delete
|
|
290
288
|
# TODO: add query for recursive
|
|
291
289
|
paths_to_delete = options.get_next_argument('file list', multiple: true)
|
|
292
|
-
@
|
|
290
|
+
@node_path_prefix&.add_to_paths!(paths_to_delete)
|
|
293
291
|
resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
|
|
294
292
|
return cli_result_from_paths_response(resp, 'file deleted')
|
|
295
293
|
when :search
|
|
296
294
|
search_root = options.get_next_argument('search root', validation: String)
|
|
297
|
-
search_root = @
|
|
295
|
+
search_root = @node_path_prefix.add_to_path(search_root) unless @node_path_prefix.nil?
|
|
298
296
|
parameters = {'path' => search_root}
|
|
299
297
|
other_options = options.get_option(:query)
|
|
300
298
|
parameters.merge!(other_options) unless other_options.nil?
|
|
@@ -303,40 +301,40 @@ module Aspera
|
|
|
303
301
|
fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
|
|
304
302
|
formatter.display_item_count(resp['item_count'], resp['total_count'])
|
|
305
303
|
formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
|
306
|
-
@
|
|
304
|
+
@node_path_prefix&.remove_in_object_list!(resp['items'])
|
|
307
305
|
return Main.result_object_list(resp['items'], fields: fields)
|
|
308
306
|
when :space
|
|
309
307
|
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
310
|
-
@
|
|
308
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
311
309
|
resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
|
|
312
|
-
@
|
|
310
|
+
@node_path_prefix&.remove_in_object_list!(resp['paths'])
|
|
313
311
|
return Main.result_object_list(resp['paths'])
|
|
314
312
|
when :mkdir
|
|
315
313
|
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
316
|
-
@
|
|
314
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
317
315
|
resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
|
|
318
316
|
return cli_result_from_paths_response(resp, 'folder created')
|
|
319
317
|
when :mklink
|
|
320
318
|
target = options.get_next_argument('target', validation: String)
|
|
321
|
-
target = @
|
|
319
|
+
target = @node_path_prefix.add_to_path(target) unless @node_path_prefix.nil?
|
|
322
320
|
one_path = options.get_next_argument('link path', validation: String)
|
|
323
|
-
one_path = @
|
|
321
|
+
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
324
322
|
resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
|
|
325
323
|
return cli_result_from_paths_response(resp, 'link created')
|
|
326
324
|
when :mkfile
|
|
327
325
|
one_path = options.get_next_argument('file path', validation: String)
|
|
328
|
-
one_path = @
|
|
326
|
+
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
329
327
|
contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
|
|
330
328
|
resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
|
|
331
329
|
return cli_result_from_paths_response(resp, 'file created')
|
|
332
330
|
when :rename
|
|
333
331
|
# TODO: multiple ?
|
|
334
332
|
path_base = options.get_next_argument('path_base', validation: String)
|
|
335
|
-
path_base = @
|
|
333
|
+
path_base = @node_path_prefix.add_to_path(path_base) unless @node_path_prefix.nil?
|
|
336
334
|
path_src = options.get_next_argument('path_src', validation: String)
|
|
337
|
-
path_src = @
|
|
335
|
+
path_src = @node_path_prefix.add_to_path(path_src) unless @node_path_prefix.nil?
|
|
338
336
|
path_dst = options.get_next_argument('path_dst', validation: String)
|
|
339
|
-
path_dst = @
|
|
337
|
+
path_dst = @node_path_prefix.add_to_path(path_dst) unless @node_path_prefix.nil?
|
|
340
338
|
resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
|
|
341
339
|
return cli_result_from_paths_response(resp, 'entry moved')
|
|
342
340
|
when :browse
|
|
@@ -381,14 +379,14 @@ module Aspera
|
|
|
381
379
|
return Main.result_transfer(transfer.start(transfer_spec))
|
|
382
380
|
when :cat
|
|
383
381
|
remote_path = options.get_next_argument('remote path', validation: String)
|
|
384
|
-
remote_path = @
|
|
382
|
+
remote_path = @node_path_prefix.add_to_path(remote_path) unless @node_path_prefix.nil?
|
|
385
383
|
File.basename(remote_path)
|
|
386
384
|
http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
|
|
387
385
|
return Main.result_text(http.body)
|
|
388
386
|
when :transport
|
|
389
387
|
return Main.result_single_object(@api_node.transport_params)
|
|
390
388
|
when :spec
|
|
391
|
-
return Main.result_single_object(@api_node.base_spec)
|
|
389
|
+
return Main.result_single_object(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC))
|
|
392
390
|
end
|
|
393
391
|
Aspera.error_unreachable_line
|
|
394
392
|
end
|
|
@@ -523,7 +521,7 @@ module Aspera
|
|
|
523
521
|
return Main.result_text(result[:password])
|
|
524
522
|
when :browse
|
|
525
523
|
apifid = apifid_from_next_arg(top_file_id)
|
|
526
|
-
file_info = apifid[:api].read("files/#{apifid[:file_id]}",
|
|
524
|
+
file_info = apifid[:api].read("files/#{apifid[:file_id]}", headers: Api::Node.add_cache_control)
|
|
527
525
|
unless file_info['type'].eql?('folder')
|
|
528
526
|
# a single file
|
|
529
527
|
return Main.result_object_list([file_info], fields: GEN4_LS_FIELDS)
|
|
@@ -885,10 +883,10 @@ module Aspera
|
|
|
885
883
|
iteration_persistency.save
|
|
886
884
|
return Main.result_status('Persistency reset')
|
|
887
885
|
end
|
|
886
|
+
else
|
|
887
|
+
Aspera.assert(!transfer_filter.key?('reset'), type: Cli::BadArgument){'reset only with once_only'}
|
|
888
888
|
end
|
|
889
|
-
|
|
890
|
-
max_items = transfer_filter.delete(MAX_ITEMS)
|
|
891
|
-
transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', max: max_items, query: transfer_filter, iteration: iteration_persistency&.data)
|
|
889
|
+
transfers_data = @api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
|
|
892
890
|
iteration_persistency&.save
|
|
893
891
|
return Main.result_object_list(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
|
|
894
892
|
when :sessions
|
|
@@ -931,10 +929,8 @@ module Aspera
|
|
|
931
929
|
# do not process last one
|
|
932
930
|
break if end_date.nil?
|
|
933
931
|
# init data for this period
|
|
934
|
-
period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).
|
|
935
|
-
|
|
936
|
-
h2[k2] = 0
|
|
937
|
-
end
|
|
932
|
+
period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).to_h do |direction|
|
|
933
|
+
[direction, dir_info.to_h{ |k2| [k2, 0]}]
|
|
938
934
|
end
|
|
939
935
|
# find all transfers that were active at this time
|
|
940
936
|
transfers_data.each do |transfer|
|
|
@@ -1097,7 +1093,7 @@ module Aspera
|
|
|
1097
1093
|
}
|
|
1098
1094
|
loop do
|
|
1099
1095
|
timestamp = Time.now
|
|
1100
|
-
transfers_data =
|
|
1096
|
+
transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
|
|
1101
1097
|
datapoint[:asInt] = transfers_data.length
|
|
1102
1098
|
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
|
1103
1099
|
Log.log.info("#{datapoint[:asInt]} active transfers")
|
|
@@ -1135,50 +1131,9 @@ module Aspera
|
|
|
1135
1131
|
# Translates paths results into CLI result, and removes prefix
|
|
1136
1132
|
def cli_result_from_paths_response(response, success_msg)
|
|
1137
1133
|
obj_list = response_to_result(response, success_msg)
|
|
1138
|
-
@
|
|
1134
|
+
@node_path_prefix&.remove_in_object_list!(obj_list)
|
|
1139
1135
|
return Main.result_object_list(obj_list, fields: %w[path result])
|
|
1140
1136
|
end
|
|
1141
|
-
|
|
1142
|
-
# Executes the provided API call in loop
|
|
1143
|
-
# @param api [Rest] the API to call
|
|
1144
|
-
# @param iteration [Array] a single element array with the iteration token or nil
|
|
1145
|
-
# @param max [Integer] maximum number of items to return, or nil for no limit
|
|
1146
|
-
# @param query [Hash] query parameters to use for the API call
|
|
1147
|
-
# @param call_args [Hash] additional arguments to pass to the API call
|
|
1148
|
-
# @return [Array] list of items returned by the API call
|
|
1149
|
-
def call_with_iteration(api:, iteration: nil, max: nil, query: nil, **call_args)
|
|
1150
|
-
Aspera.assert_type(iteration, Array, NilClass){'iteration'}
|
|
1151
|
-
Aspera.assert_type(query, Hash, NilClass){'query'}
|
|
1152
|
-
query_token = query&.dup || {}
|
|
1153
|
-
item_list = []
|
|
1154
|
-
query_token[:iteration_token] = iteration[0] unless iteration.nil?
|
|
1155
|
-
loop do
|
|
1156
|
-
data, http = api.call(**call_args, query: query_token, ret: :both)
|
|
1157
|
-
Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
|
|
1158
|
-
# no data
|
|
1159
|
-
break if data.empty?
|
|
1160
|
-
# get next iteration token from link
|
|
1161
|
-
next_iteration_token = nil
|
|
1162
|
-
link_info = http['Link']
|
|
1163
|
-
unless link_info.nil?
|
|
1164
|
-
m = link_info.match(/<([^>]+)>/)
|
|
1165
|
-
Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
|
|
1166
|
-
next_iteration_token = Rest.query_to_h(URI.parse(m[1]).query)['iteration_token']
|
|
1167
|
-
end
|
|
1168
|
-
# same as last iteration: stop
|
|
1169
|
-
break if next_iteration_token&.eql?(query_token[:iteration_token])
|
|
1170
|
-
query_token[:iteration_token] = next_iteration_token
|
|
1171
|
-
item_list.concat(data)
|
|
1172
|
-
if max&.<=(item_list.length)
|
|
1173
|
-
item_list = item_list.slice(0, max)
|
|
1174
|
-
break
|
|
1175
|
-
end
|
|
1176
|
-
break if next_iteration_token.nil?
|
|
1177
|
-
end
|
|
1178
|
-
# save iteration token if needed
|
|
1179
|
-
iteration[0] = query_token[:iteration_token] unless iteration.nil?
|
|
1180
|
-
item_list
|
|
1181
|
-
end
|
|
1182
1137
|
end
|
|
1183
1138
|
end
|
|
1184
1139
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'aspera/cli/plugins/basic_auth'
|
|
4
4
|
require 'aspera/cli/plugins/node'
|
|
5
5
|
require 'aspera/assert'
|
|
6
|
+
require 'aspera/rest_list'
|
|
7
|
+
|
|
6
8
|
module Aspera
|
|
7
9
|
module Cli
|
|
8
10
|
module Plugins
|
|
@@ -128,7 +130,7 @@ module Aspera
|
|
|
128
130
|
when :admin
|
|
129
131
|
api_shares_admin = basic_auth_api(ADMIN_API_PATH)
|
|
130
132
|
admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
|
|
131
|
-
lookup_share = ->(field, value){lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
|
|
133
|
+
lookup_share = ->(field, value){RestList.lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
|
|
132
134
|
case admin_command
|
|
133
135
|
when :node
|
|
134
136
|
return entity_execute(api: api_shares_admin, entity: 'data/nodes')
|
|
@@ -177,7 +179,7 @@ module Aspera
|
|
|
177
179
|
entity_commands = %i[import].freeze
|
|
178
180
|
end
|
|
179
181
|
entity_verb = options.get_next_command(entity_commands)
|
|
180
|
-
lookup_block = ->(field, value){lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
|
|
182
|
+
lookup_block = ->(field, value){RestList.lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
|
|
181
183
|
case entity_verb
|
|
182
184
|
when *ALL_OPS # list, show, delete, create, modify
|
|
183
185
|
display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
|
|
@@ -33,6 +33,9 @@ module Aspera
|
|
|
33
33
|
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
|
34
34
|
|
|
35
35
|
class << self
|
|
36
|
+
# Analyze transfer session statuses and return a global status
|
|
37
|
+
#
|
|
38
|
+
# @param statuses [Array] list of session status, each status is :success or an error message string
|
|
36
39
|
# @return [:success] if all sessions statuses returned by "start" are success
|
|
37
40
|
# @return [Exception] if one sessions statuses returned by "start" is failed
|
|
38
41
|
def session_status(statuses)
|