aspera-cli 4.25.4 → 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 +22 -6
- data/CONTRIBUTING.md +31 -22
- 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 +10 -6
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -0
- data/lib/aspera/api/faspex.rb +17 -19
- data/lib/aspera/api/httpgw.rb +19 -3
- data/lib/aspera/api/node.rb +43 -2
- data/lib/aspera/ascp/installation.rb +9 -10
- data/lib/aspera/cli/error.rb +8 -0
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/main.rb +30 -12
- data/lib/aspera/cli/manager.rb +31 -28
- data/lib/aspera/cli/plugins/aoc.rb +2 -2
- data/lib/aspera/cli/plugins/base.rb +1 -88
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +41 -53
- data/lib/aspera/cli/plugins/node.rb +26 -68
- data/lib/aspera/cli/plugins/shares.rb +4 -2
- data/lib/aspera/cli/transfer_agent.rb +3 -0
- data/lib/aspera/cli/version.rb +1 -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 +8 -36
- 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/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,7 +151,7 @@ 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
|
|
@@ -182,13 +176,9 @@ 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
|
|
|
@@ -251,7 +241,7 @@ module Aspera
|
|
|
251
241
|
type: Api::Faspex.box_type(box),
|
|
252
242
|
transfer_type: Api::Faspex::TRANSFER_CONNECT
|
|
253
243
|
}
|
|
254
|
-
# 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
|
|
255
245
|
packages.each do |package|
|
|
256
246
|
pkg_id = package['id']
|
|
257
247
|
formatter.display_status("Receiving package #{pkg_id}")
|
|
@@ -286,7 +276,7 @@ module Aspera
|
|
|
286
276
|
recipient_type: @api_v5.pub_link_context['recipient_type']
|
|
287
277
|
}]
|
|
288
278
|
end
|
|
289
|
-
normalize_recipients(parameters)
|
|
279
|
+
PACKAGE_RECIPIENT_TYPES.each{ |type| normalize_recipients(parameters, type)}
|
|
290
280
|
# User specified content prot in tspec, but faspex requires in package creation
|
|
291
281
|
# `transfer_spec/upload` will set `content_protection`
|
|
292
282
|
if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
|
|
@@ -308,8 +298,7 @@ module Aspera
|
|
|
308
298
|
else
|
|
309
299
|
# send from remote shared folder
|
|
310
300
|
if (m = Base.percent_selector(shared_folder))
|
|
311
|
-
shared_folder = lookup_entity_by_field(
|
|
312
|
-
api: @api_v5,
|
|
301
|
+
shared_folder = @api_v5.lookup_entity_by_field(
|
|
313
302
|
entity: 'shared_folders',
|
|
314
303
|
field: m[:field],
|
|
315
304
|
value: m[:value]
|
|
@@ -336,7 +325,7 @@ module Aspera
|
|
|
336
325
|
Aspera.assert_type(filters, Hash)
|
|
337
326
|
filters['basenames'] ||= []
|
|
338
327
|
Aspera.assert_type(filters, Hash){'filters'}
|
|
339
|
-
max_items = query.delete(MAX_ITEMS)
|
|
328
|
+
max_items = query.delete(RestList::MAX_ITEMS)
|
|
340
329
|
recursive = query.delete('recursive')
|
|
341
330
|
use_paging = query.delete('paging'){true}
|
|
342
331
|
if use_paging
|
|
@@ -427,8 +416,7 @@ module Aspera
|
|
|
427
416
|
def execute_resource(res_sym)
|
|
428
417
|
exec_args = {
|
|
429
418
|
api: @api_v5,
|
|
430
|
-
entity: res_sym.to_s
|
|
431
|
-
tclo: true
|
|
419
|
+
entity: res_sym.to_s
|
|
432
420
|
}
|
|
433
421
|
res_id_query = :default
|
|
434
422
|
available_commands = ALL_OPS
|
|
@@ -449,7 +437,7 @@ module Aspera
|
|
|
449
437
|
available_commands += [:reset_password]
|
|
450
438
|
when :oauth_clients
|
|
451
439
|
exec_args[:display_fields] = Formatter.all_but('public_key')
|
|
452
|
-
exec_args[:api] =
|
|
440
|
+
exec_args[:api] = Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.args_from_options(options))
|
|
453
441
|
exec_args[:list_query] = {'expand': true, 'no_api_path': true, 'client_types[]': 'public'}
|
|
454
442
|
when :shared_inboxes, :workgroups
|
|
455
443
|
available_commands += %i[members saml_groups invite_external_collaborator]
|
|
@@ -462,14 +450,17 @@ module Aspera
|
|
|
462
450
|
res_command = options.get_next_command(available_commands)
|
|
463
451
|
return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
|
|
464
452
|
case res_command
|
|
465
|
-
when
|
|
453
|
+
when :create, :modify, :delete, :show
|
|
466
454
|
return entity_execute(command: res_command, **exec_args) do |field, value|
|
|
467
|
-
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']
|
|
468
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])
|
|
469
460
|
when :shared_folders
|
|
470
461
|
# nodes
|
|
471
462
|
node_id = instance_identifier do |field, value|
|
|
472
|
-
lookup_entity_by_field(
|
|
463
|
+
@api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
|
|
473
464
|
end
|
|
474
465
|
shfld_entity = "nodes/#{node_id}/shared_folders"
|
|
475
466
|
sh_command = options.get_next_command(ALL_OPS + [:user])
|
|
@@ -480,33 +471,32 @@ module Aspera
|
|
|
480
471
|
entity: shfld_entity,
|
|
481
472
|
command: sh_command
|
|
482
473
|
) do |field, value|
|
|
483
|
-
lookup_entity_by_field(
|
|
474
|
+
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
484
475
|
end
|
|
485
476
|
when :user
|
|
486
477
|
sh_id = instance_identifier do |field, value|
|
|
487
|
-
lookup_entity_by_field(
|
|
478
|
+
@api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
|
|
488
479
|
end
|
|
489
480
|
user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
|
|
490
481
|
return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
|
|
491
|
-
lookup_entity_by_field(
|
|
482
|
+
@api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
|
|
492
483
|
end
|
|
493
484
|
|
|
494
485
|
end
|
|
495
486
|
when :browse
|
|
496
487
|
# nodes
|
|
497
488
|
node_id = instance_identifier do |field, value|
|
|
498
|
-
lookup_entity_by_field(
|
|
489
|
+
@api_v5.lookup_entity_by_field(entity: 'nodes', value: value, field: field)['id']
|
|
499
490
|
end
|
|
500
491
|
return browse_folder("nodes/#{node_id}/browse")
|
|
501
492
|
when :invite_external_collaborator
|
|
502
493
|
# :shared_inboxes, :workgroups
|
|
503
|
-
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']}
|
|
504
495
|
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
|
505
496
|
creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
|
|
506
497
|
result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
|
|
507
498
|
formatter.display_status(result['message'])
|
|
508
|
-
result = lookup_entity_by_field(
|
|
509
|
-
api: @api_v5,
|
|
499
|
+
result = @api_v5.lookup_entity_by_field(
|
|
510
500
|
entity: "#{res_sym}/#{shared_inbox_id}/members",
|
|
511
501
|
items_key: 'members',
|
|
512
502
|
value: creation_payload['email_address'],
|
|
@@ -515,7 +505,7 @@ module Aspera
|
|
|
515
505
|
return Main.result_single_object(result)
|
|
516
506
|
when :members, :saml_groups
|
|
517
507
|
# res_command := :shared_inboxes, :workgroups
|
|
518
|
-
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']}
|
|
519
509
|
res_path = "#{res_sym}/#{res_id}/#{res_command}"
|
|
520
510
|
list_key = res_command.to_s
|
|
521
511
|
list_key = 'groups' if res_command.eql?(:saml_groups)
|
|
@@ -526,8 +516,7 @@ module Aspera
|
|
|
526
516
|
users = [users] unless users.is_a?(Array)
|
|
527
517
|
users = users.map do |user|
|
|
528
518
|
if (m = Base.percent_selector(user))
|
|
529
|
-
lookup_entity_by_field(
|
|
530
|
-
api: @api_v5,
|
|
519
|
+
@api_v5.lookup_entity_by_field(
|
|
531
520
|
entity: 'accounts',
|
|
532
521
|
field: m[:field],
|
|
533
522
|
value: m[:value],
|
|
@@ -548,8 +537,7 @@ module Aspera
|
|
|
548
537
|
command: sub_command,
|
|
549
538
|
items_key: list_key
|
|
550
539
|
) do |field, value|
|
|
551
|
-
lookup_entity_by_field(
|
|
552
|
-
api: @api_v5,
|
|
540
|
+
@api_v5.lookup_entity_by_field(
|
|
553
541
|
entity: res_path,
|
|
554
542
|
field: field,
|
|
555
543
|
value: value,
|
|
@@ -558,7 +546,7 @@ module Aspera
|
|
|
558
546
|
end
|
|
559
547
|
when :reset_password
|
|
560
548
|
# :accounts
|
|
561
|
-
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']}
|
|
562
550
|
@api_v5.create("accounts/#{contact_id}/reset_password", {})
|
|
563
551
|
return Main.result_status('password reset, user shall check email')
|
|
564
552
|
end
|
|
@@ -579,16 +567,10 @@ module Aspera
|
|
|
579
567
|
event_type = options.get_next_command(%i[application webhook])
|
|
580
568
|
case event_type
|
|
581
569
|
when :application
|
|
582
|
-
list, total = list_entities_limit_offset_total_count(
|
|
583
|
-
api: @api_v5,
|
|
584
|
-
entity: 'application_events',
|
|
585
|
-
query: query_read_delete
|
|
586
|
-
)
|
|
587
|
-
|
|
570
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete)
|
|
588
571
|
return Main.result_object_list(list, total: total, fields: %w[event_type created_at application user.name])
|
|
589
572
|
when :webhook
|
|
590
|
-
list, total = list_entities_limit_offset_total_count(
|
|
591
|
-
api: @api_v5,
|
|
573
|
+
list, total = @api_v5.list_entities_limit_offset_total_count(
|
|
592
574
|
entity: 'all_webhooks_events',
|
|
593
575
|
query: query_read_delete,
|
|
594
576
|
items_key: 'events'
|
|
@@ -633,7 +615,12 @@ module Aspera
|
|
|
633
615
|
|
|
634
616
|
def execute_action
|
|
635
617
|
command = options.get_next_command(ACTIONS)
|
|
636
|
-
|
|
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
|
|
637
624
|
case command
|
|
638
625
|
when :version
|
|
639
626
|
return Main.result_single_object(@api_v5.read('version'))
|
|
@@ -705,7 +692,7 @@ module Aspera
|
|
|
705
692
|
items_key: invitation_endpoint,
|
|
706
693
|
display_fields: %w[id public recipient_type recipient_name email_address]
|
|
707
694
|
) do |field, value|
|
|
708
|
-
lookup_entity_by_field(
|
|
695
|
+
@api_v5.lookup_entity_by_field(entity: invitation_endpoint, field: field, value: value, query: {})['id']
|
|
709
696
|
end
|
|
710
697
|
end
|
|
711
698
|
when :gateway
|
|
@@ -732,7 +719,8 @@ module Aspera
|
|
|
732
719
|
ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
|
|
733
720
|
WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
|
|
734
721
|
CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
|
|
735
|
-
|
|
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
|
|
736
724
|
end
|
|
737
725
|
end
|
|
738
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'
|
|
@@ -166,7 +167,7 @@ module Aspera
|
|
|
166
167
|
# @param api [Rest] an existing API object for the Node API
|
|
167
168
|
# @param prefix_path [String,nil] for Faspex 4, allows browsing a package without full path in node (removes storage prefix)
|
|
168
169
|
def initialize(context:, api: nil, prefix_path: nil)
|
|
169
|
-
@
|
|
170
|
+
@node_path_prefix = prefix_path ? NodePathPrefix.new(prefix_path) : nil
|
|
170
171
|
super(context: context, basic_options: api.nil?)
|
|
171
172
|
Node.declare_options(options)
|
|
172
173
|
return if context.only_manual?
|
|
@@ -196,11 +197,11 @@ module Aspera
|
|
|
196
197
|
# Gen3 API
|
|
197
198
|
def browse_gen3
|
|
198
199
|
folders_to_process = options.get_next_argument('path', validation: String)
|
|
199
|
-
folders_to_process = @
|
|
200
|
+
folders_to_process = @node_path_prefix.add_to_path(folders_to_process) unless @node_path_prefix.nil?
|
|
200
201
|
folders_to_process = [folders_to_process]
|
|
201
202
|
query = options.get_option(:query) || {}
|
|
202
203
|
# special parameter: max number of entries in result
|
|
203
|
-
max_items = query.delete(MAX_ITEMS)
|
|
204
|
+
max_items = query.delete(RestList::MAX_ITEMS)
|
|
204
205
|
# special parameter: recursive browsing
|
|
205
206
|
recursive = query.delete('recursive')
|
|
206
207
|
# special parameter: only return one entry for the path, even if folder
|
|
@@ -221,7 +222,7 @@ module Aspera
|
|
|
221
222
|
response = @api_node.create('files/browse', query)
|
|
222
223
|
# 'file','symbolic_link'
|
|
223
224
|
if !Node.gen3_entry_folder?(response['self']) || only_path
|
|
224
|
-
@
|
|
225
|
+
@node_path_prefix&.remove_in_object_list!([response['self']])
|
|
225
226
|
return Main.result_single_object(response['self'])
|
|
226
227
|
end
|
|
227
228
|
items = response['items']
|
|
@@ -243,7 +244,7 @@ module Aspera
|
|
|
243
244
|
end
|
|
244
245
|
query.delete('skip')
|
|
245
246
|
end
|
|
246
|
-
@
|
|
247
|
+
@node_path_prefix&.remove_in_object_list!(all_items)
|
|
247
248
|
return Main.result_object_list(all_items)
|
|
248
249
|
ensure
|
|
249
250
|
RestParameters.instance.spinner_cb.call(action: :success)
|
|
@@ -286,12 +287,12 @@ module Aspera
|
|
|
286
287
|
when :delete
|
|
287
288
|
# TODO: add query for recursive
|
|
288
289
|
paths_to_delete = options.get_next_argument('file list', multiple: true)
|
|
289
|
-
@
|
|
290
|
+
@node_path_prefix&.add_to_paths!(paths_to_delete)
|
|
290
291
|
resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
|
|
291
292
|
return cli_result_from_paths_response(resp, 'file deleted')
|
|
292
293
|
when :search
|
|
293
294
|
search_root = options.get_next_argument('search root', validation: String)
|
|
294
|
-
search_root = @
|
|
295
|
+
search_root = @node_path_prefix.add_to_path(search_root) unless @node_path_prefix.nil?
|
|
295
296
|
parameters = {'path' => search_root}
|
|
296
297
|
other_options = options.get_option(:query)
|
|
297
298
|
parameters.merge!(other_options) unless other_options.nil?
|
|
@@ -300,40 +301,40 @@ module Aspera
|
|
|
300
301
|
fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
|
|
301
302
|
formatter.display_item_count(resp['item_count'], resp['total_count'])
|
|
302
303
|
formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
|
303
|
-
@
|
|
304
|
+
@node_path_prefix&.remove_in_object_list!(resp['items'])
|
|
304
305
|
return Main.result_object_list(resp['items'], fields: fields)
|
|
305
306
|
when :space
|
|
306
307
|
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
307
|
-
@
|
|
308
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
308
309
|
resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
|
|
309
|
-
@
|
|
310
|
+
@node_path_prefix&.remove_in_object_list!(resp['paths'])
|
|
310
311
|
return Main.result_object_list(resp['paths'])
|
|
311
312
|
when :mkdir
|
|
312
313
|
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
313
|
-
@
|
|
314
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
314
315
|
resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
|
|
315
316
|
return cli_result_from_paths_response(resp, 'folder created')
|
|
316
317
|
when :mklink
|
|
317
318
|
target = options.get_next_argument('target', validation: String)
|
|
318
|
-
target = @
|
|
319
|
+
target = @node_path_prefix.add_to_path(target) unless @node_path_prefix.nil?
|
|
319
320
|
one_path = options.get_next_argument('link path', validation: String)
|
|
320
|
-
one_path = @
|
|
321
|
+
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
321
322
|
resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
|
|
322
323
|
return cli_result_from_paths_response(resp, 'link created')
|
|
323
324
|
when :mkfile
|
|
324
325
|
one_path = options.get_next_argument('file path', validation: String)
|
|
325
|
-
one_path = @
|
|
326
|
+
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
326
327
|
contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
|
|
327
328
|
resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
|
|
328
329
|
return cli_result_from_paths_response(resp, 'file created')
|
|
329
330
|
when :rename
|
|
330
331
|
# TODO: multiple ?
|
|
331
332
|
path_base = options.get_next_argument('path_base', validation: String)
|
|
332
|
-
path_base = @
|
|
333
|
+
path_base = @node_path_prefix.add_to_path(path_base) unless @node_path_prefix.nil?
|
|
333
334
|
path_src = options.get_next_argument('path_src', validation: String)
|
|
334
|
-
path_src = @
|
|
335
|
+
path_src = @node_path_prefix.add_to_path(path_src) unless @node_path_prefix.nil?
|
|
335
336
|
path_dst = options.get_next_argument('path_dst', validation: String)
|
|
336
|
-
path_dst = @
|
|
337
|
+
path_dst = @node_path_prefix.add_to_path(path_dst) unless @node_path_prefix.nil?
|
|
337
338
|
resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
|
|
338
339
|
return cli_result_from_paths_response(resp, 'entry moved')
|
|
339
340
|
when :browse
|
|
@@ -378,7 +379,7 @@ module Aspera
|
|
|
378
379
|
return Main.result_transfer(transfer.start(transfer_spec))
|
|
379
380
|
when :cat
|
|
380
381
|
remote_path = options.get_next_argument('remote path', validation: String)
|
|
381
|
-
remote_path = @
|
|
382
|
+
remote_path = @node_path_prefix.add_to_path(remote_path) unless @node_path_prefix.nil?
|
|
382
383
|
File.basename(remote_path)
|
|
383
384
|
http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
|
|
384
385
|
return Main.result_text(http.body)
|
|
@@ -882,10 +883,10 @@ module Aspera
|
|
|
882
883
|
iteration_persistency.save
|
|
883
884
|
return Main.result_status('Persistency reset')
|
|
884
885
|
end
|
|
886
|
+
else
|
|
887
|
+
Aspera.assert(!transfer_filter.key?('reset'), type: Cli::BadArgument){'reset only with once_only'}
|
|
885
888
|
end
|
|
886
|
-
|
|
887
|
-
max_items = transfer_filter.delete(MAX_ITEMS)
|
|
888
|
-
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)
|
|
889
890
|
iteration_persistency&.save
|
|
890
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])
|
|
891
892
|
when :sessions
|
|
@@ -928,10 +929,8 @@ module Aspera
|
|
|
928
929
|
# do not process last one
|
|
929
930
|
break if end_date.nil?
|
|
930
931
|
# init data for this period
|
|
931
|
-
period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).
|
|
932
|
-
|
|
933
|
-
h2[k2] = 0
|
|
934
|
-
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]}]
|
|
935
934
|
end
|
|
936
935
|
# find all transfers that were active at this time
|
|
937
936
|
transfers_data.each do |transfer|
|
|
@@ -1094,7 +1093,7 @@ module Aspera
|
|
|
1094
1093
|
}
|
|
1095
1094
|
loop do
|
|
1096
1095
|
timestamp = Time.now
|
|
1097
|
-
transfers_data =
|
|
1096
|
+
transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
|
|
1098
1097
|
datapoint[:asInt] = transfers_data.length
|
|
1099
1098
|
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
|
1100
1099
|
Log.log.info("#{datapoint[:asInt]} active transfers")
|
|
@@ -1132,50 +1131,9 @@ module Aspera
|
|
|
1132
1131
|
# Translates paths results into CLI result, and removes prefix
|
|
1133
1132
|
def cli_result_from_paths_response(response, success_msg)
|
|
1134
1133
|
obj_list = response_to_result(response, success_msg)
|
|
1135
|
-
@
|
|
1134
|
+
@node_path_prefix&.remove_in_object_list!(obj_list)
|
|
1136
1135
|
return Main.result_object_list(obj_list, fields: %w[path result])
|
|
1137
1136
|
end
|
|
1138
|
-
|
|
1139
|
-
# Executes the provided API call in loop
|
|
1140
|
-
# @param api [Rest] the API to call
|
|
1141
|
-
# @param iteration [Array] a single element array with the iteration token or nil
|
|
1142
|
-
# @param max [Integer] maximum number of items to return, or nil for no limit
|
|
1143
|
-
# @param query [Hash] query parameters to use for the API call
|
|
1144
|
-
# @param call_args [Hash] additional arguments to pass to the API call
|
|
1145
|
-
# @return [Array] list of items returned by the API call
|
|
1146
|
-
def call_with_iteration(api:, iteration: nil, max: nil, query: nil, **call_args)
|
|
1147
|
-
Aspera.assert_type(iteration, Array, NilClass){'iteration'}
|
|
1148
|
-
Aspera.assert_type(query, Hash, NilClass){'query'}
|
|
1149
|
-
query_token = query&.dup || {}
|
|
1150
|
-
item_list = []
|
|
1151
|
-
query_token[:iteration_token] = iteration[0] unless iteration.nil?
|
|
1152
|
-
loop do
|
|
1153
|
-
data, http = api.call(**call_args, query: query_token, ret: :both)
|
|
1154
|
-
Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
|
|
1155
|
-
# no data
|
|
1156
|
-
break if data.empty?
|
|
1157
|
-
# get next iteration token from link
|
|
1158
|
-
next_iteration_token = nil
|
|
1159
|
-
link_info = http['Link']
|
|
1160
|
-
unless link_info.nil?
|
|
1161
|
-
m = link_info.match(/<([^>]+)>/)
|
|
1162
|
-
Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
|
|
1163
|
-
next_iteration_token = Rest.query_to_h(URI.parse(m[1]).query)['iteration_token']
|
|
1164
|
-
end
|
|
1165
|
-
# same as last iteration: stop
|
|
1166
|
-
break if next_iteration_token&.eql?(query_token[:iteration_token])
|
|
1167
|
-
query_token[:iteration_token] = next_iteration_token
|
|
1168
|
-
item_list.concat(data)
|
|
1169
|
-
if max&.<=(item_list.length)
|
|
1170
|
-
item_list = item_list.slice(0, max)
|
|
1171
|
-
break
|
|
1172
|
-
end
|
|
1173
|
-
break if next_iteration_token.nil?
|
|
1174
|
-
end
|
|
1175
|
-
# save iteration token if needed
|
|
1176
|
-
iteration[0] = query_token[:iteration_token] unless iteration.nil?
|
|
1177
|
-
item_list
|
|
1178
|
-
end
|
|
1179
1137
|
end
|
|
1180
1138
|
end
|
|
1181
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)
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/dot_container.rb
CHANGED
|
@@ -7,13 +7,14 @@ module Aspera
|
|
|
7
7
|
class DotContainer
|
|
8
8
|
class << self
|
|
9
9
|
# Insert extended value `value` into struct `result` at `path`
|
|
10
|
-
# @param path [String]
|
|
11
|
-
# @param value [
|
|
12
|
-
# @param result [
|
|
13
|
-
# @return [Hash, Array]
|
|
10
|
+
# @param path [Array<String>] Path in container
|
|
11
|
+
# @param value [Object] Value to insert in deep container
|
|
12
|
+
# @param result [nil, Hash, Array] Current container to use (or nil to create a new one)
|
|
13
|
+
# @return [Hash, Array] Container
|
|
14
14
|
def dotted_to_container(path, value, result = nil)
|
|
15
|
+
Aspera.assert_array_all(path, String)
|
|
15
16
|
# Typed keys
|
|
16
|
-
keys = path.
|
|
17
|
+
keys = path.map{ |k| int_or_string(k)}
|
|
17
18
|
# Create, or re-use first level container
|
|
18
19
|
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
19
20
|
# walk the path, and create sub-containers if necessary
|
|
@@ -78,7 +79,7 @@ module Aspera
|
|
|
78
79
|
to_insert = current.map{ |i| i['name']}
|
|
79
80
|
# Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
|
|
80
81
|
elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
|
|
81
|
-
add_elements(path, current.
|
|
82
|
+
add_elements(path, current.to_h{ |i| [i['name'], i['value']]})
|
|
82
83
|
else
|
|
83
84
|
add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
|
|
84
85
|
end
|
|
@@ -86,7 +87,7 @@ module Aspera
|
|
|
86
87
|
to_insert = current
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
|
-
result[path.
|
|
90
|
+
result[path.join(SEPARATOR)] = to_insert unless to_insert.nil?
|
|
90
91
|
end
|
|
91
92
|
result
|
|
92
93
|
end
|
|
@@ -101,8 +102,7 @@ module Aspera
|
|
|
101
102
|
nil
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
private_constant :OPTION_DOTTED_SEPARATOR
|
|
105
|
+
# Dot-path separator: `.`
|
|
106
|
+
SEPARATOR = '.'
|
|
107
107
|
end
|
|
108
108
|
end
|
data/lib/aspera/log.rb
CHANGED
|
@@ -26,7 +26,7 @@ class Logger
|
|
|
26
26
|
# Hash
|
|
27
27
|
# key [Integer] Log level (e.g. 0 for DEBUG)
|
|
28
28
|
# value [Symbol] Uppercase log level label (e.g. :DEBUG)
|
|
29
|
-
SEVERITY_LABEL = Severity.constants.
|
|
29
|
+
SEVERITY_LABEL = Severity.constants.to_h{ |name| [Severity.const_get(name), name]}
|
|
30
30
|
|
|
31
31
|
# Override
|
|
32
32
|
# @param severity [Integer] Log severity as int
|
data/lib/aspera/markdown.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Aspera
|
|
|
33
33
|
|
|
34
34
|
# type: NOTE CAUTION WARNING IMPORTANT TIP INFO
|
|
35
35
|
def admonition(lines, type: 'INFO')
|
|
36
|
-
"> [
|
|
36
|
+
"> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def code(lines, type: 'shell')
|
|
@@ -79,7 +79,7 @@ module Aspera
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def current_items(persist_category)
|
|
82
|
-
current_files(persist_category).
|
|
82
|
+
current_files(persist_category).to_h{ |i| [File.basename(i, FILE_SUFFIX), File.read(i)]}
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
private
|