aspera-cli 4.24.2 → 4.25.0.pre2
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 +1067 -758
- data/CONTRIBUTING.md +93 -120
- data/README.md +817 -510
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/transferd.rb +4 -4
- data/lib/aspera/api/aoc.rb +71 -43
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +6 -5
- data/lib/aspera/api/node.rb +10 -12
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +55 -41
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +28 -6
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +94 -62
- data/lib/aspera/cli/formatter.rb +55 -22
- data/lib/aspera/cli/main.rb +21 -14
- data/lib/aspera/cli/manager.rb +349 -248
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +94 -51
- data/lib/aspera/cli/plugins/base.rb +62 -49
- data/lib/aspera/cli/plugins/config.rb +85 -96
- data/lib/aspera/cli/plugins/console.rb +15 -9
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +34 -27
- data/lib/aspera/cli/plugins/faspex5.rb +47 -44
- data/lib/aspera/cli/plugins/faspio.rb +7 -6
- data/lib/aspera/cli/plugins/httpgw.rb +3 -2
- data/lib/aspera/cli/plugins/node.rb +132 -120
- data/lib/aspera/cli/plugins/oauth.rb +1 -1
- data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
- data/lib/aspera/cli/plugins/preview.rb +26 -46
- data/lib/aspera/cli/plugins/server.rb +9 -10
- data/lib/aspera/cli/plugins/shares.rb +77 -43
- data/lib/aspera/cli/sync_actions.rb +49 -38
- data/lib/aspera/cli/transfer_agent.rb +16 -34
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +8 -5
- data/lib/aspera/command_line_builder.rb +20 -17
- data/lib/aspera/coverage.rb +6 -2
- data/lib/aspera/environment.rb +71 -84
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +6 -5
- data/lib/aspera/oauth/base.rb +17 -27
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/products/connect.rb +3 -3
- data/lib/aspera/rest.rb +51 -39
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/conf.schema.yaml +182 -34
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +128 -72
- data/lib/aspera/transfer/parameters.rb +3 -4
- data/lib/aspera/transfer/spec.rb +2 -3
- data/lib/aspera/transfer/spec.schema.yaml +49 -19
- data/lib/aspera/transfer/spec_doc.rb +14 -14
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +33 -6
- metadata.gz.sig +0 -0
|
@@ -17,13 +17,13 @@ module Aspera
|
|
|
17
17
|
nagios = Nagios.new
|
|
18
18
|
begin
|
|
19
19
|
api = Api::Alee.new(nil, nil, version: 'ping')
|
|
20
|
-
|
|
21
|
-
raise "unexpected response: #{
|
|
20
|
+
http = api.read(nil, ret: :resp)
|
|
21
|
+
raise "unexpected response: #{http.body}" unless http.body.eql?('pong')
|
|
22
22
|
nagios.add_ok('api', 'answered ok')
|
|
23
23
|
rescue StandardError => e
|
|
24
24
|
nagios.add_critical('api', e.to_s)
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
Main.result_object_list(nagios.status_list)
|
|
27
27
|
when :entitlement
|
|
28
28
|
entitlement_id = options.get_option(:username, mandatory: true)
|
|
29
29
|
customer_id = options.get_option(:password, mandatory: true)
|
|
@@ -62,6 +62,7 @@ module Aspera
|
|
|
62
62
|
'Aspera on Cloud'
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# @return [Hash,NilClass]
|
|
65
66
|
def detect(base_url)
|
|
66
67
|
# no protocol ?
|
|
67
68
|
base_url = "https://#{base_url}" unless base_url.match?(%r{^[a-z]{1,6}://})
|
|
@@ -69,9 +70,9 @@ module Aspera
|
|
|
69
70
|
base_url = "#{base_url}.#{Api::AoC::SAAS_DOMAIN_PROD}" unless base_url.include?('.')
|
|
70
71
|
# AoC is only https
|
|
71
72
|
return unless base_url.start_with?('https://')
|
|
72
|
-
|
|
73
|
-
return if
|
|
74
|
-
redirect_uri = URI.parse(
|
|
73
|
+
location = Rest.new(base_url: base_url, redirect_max: 0).call(operation: 'GET', subpath: 'auth/ping', exception: false, ret: :resp)['Location']
|
|
74
|
+
return if location.nil?
|
|
75
|
+
redirect_uri = URI.parse(location)
|
|
75
76
|
od = Api::AoC.split_org_domain(URI.parse(base_url))
|
|
76
77
|
return unless redirect_uri.path.end_with?("oauth2/#{od[:organization]}/login")
|
|
77
78
|
# either in standard domain, or product name in page
|
|
@@ -81,7 +82,8 @@ module Aspera
|
|
|
81
82
|
}
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
# @param base
|
|
85
|
+
# @param base [String] Base folder path
|
|
86
|
+
# @param always [Bool] `true` always add number, `false` only if base folder already exists
|
|
85
87
|
# @return [String] Folder path that does jot exist, with possible .<number> extension
|
|
86
88
|
def next_available_folder(base, always: false)
|
|
87
89
|
counter = always ? 1 : 0
|
|
@@ -95,27 +97,26 @@ module Aspera
|
|
|
95
97
|
# Get folder path that does not exist
|
|
96
98
|
# If it exists, an extension is added
|
|
97
99
|
# or a sequential number if extension == :seq
|
|
98
|
-
# @param
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
else
|
|
111
|
-
folder
|
|
112
|
-
end
|
|
100
|
+
# @param package_info [Hash] Package information
|
|
101
|
+
# @param destination_folder [String] Base folder
|
|
102
|
+
# @param fld. [Array] List of fields of package
|
|
103
|
+
def unique_folder(package_info, destination_folder, fld: nil, seq: false, opt: false)
|
|
104
|
+
Aspera.assert_array_all(fld, String, type: Cli::BadArgument){'fld'}
|
|
105
|
+
Aspera.assert([1, 2].include?(fld.length)){'fld must have 1 or 2 elements'}
|
|
106
|
+
folder = Environment.instance.sanitized_filename(package_info[fld[0]])
|
|
107
|
+
if seq
|
|
108
|
+
folder = next_available_folder(folder, always: !opt)
|
|
109
|
+
elsif fld[1] && (Dir.exist?(folder) || !opt)
|
|
110
|
+
# NOTE: it might already exist
|
|
111
|
+
folder = "#{folder}.#{Environment.instance.sanitized_filename(fld[1])}"
|
|
113
112
|
end
|
|
113
|
+
puts("sub= #{folder}")
|
|
114
|
+
File.join(destination_folder, folder)
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# @param wizard [Wizard] The wizard object
|
|
118
|
-
# @param app_url [
|
|
119
|
+
# @param app_url [String] Tested URL
|
|
119
120
|
# @return [Hash] :preset_value, :test_args
|
|
120
121
|
def wizard(wizard, app_url)
|
|
121
122
|
pub_link_info = Api::AoC.link_info(app_url)
|
|
@@ -132,7 +133,7 @@ module Aspera
|
|
|
132
133
|
test_args: 'organization'
|
|
133
134
|
}
|
|
134
135
|
end
|
|
135
|
-
options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id',
|
|
136
|
+
options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', allowed: Allowed::TYPES_BOOLEAN, default: Api::AoC.saas_url?(app_url))
|
|
136
137
|
options.parse_options!
|
|
137
138
|
# make username mandatory for jwt, this triggers interactive input
|
|
138
139
|
wiz_username = options.get_option(:username, mandatory: true)
|
|
@@ -205,10 +206,10 @@ module Aspera
|
|
|
205
206
|
@cache_workspace_info = nil
|
|
206
207
|
@cache_home_node_file = nil
|
|
207
208
|
@cache_api_aoc = nil
|
|
208
|
-
options.declare(:workspace, 'Name of workspace',
|
|
209
|
-
options.declare(:new_user_option, 'New user creation option for unknown package recipients',
|
|
210
|
-
options.declare(:validate_metadata, 'Validate shared inbox metadata',
|
|
211
|
-
options.declare(:package_folder, '
|
|
209
|
+
options.declare(:workspace, 'Name of workspace', allowed: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
|
|
210
|
+
options.declare(:new_user_option, 'New user creation option for unknown package recipients', allowed: Hash)
|
|
211
|
+
options.declare(:validate_metadata, 'Validate shared inbox metadata', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
212
|
+
options.declare(:package_folder, 'Handling of reception of packages in folders', allowed: Hash, default: {})
|
|
212
213
|
options.parse_options!
|
|
213
214
|
# add node plugin options (for manual)
|
|
214
215
|
Node.declare_options(options)
|
|
@@ -245,7 +246,7 @@ module Aspera
|
|
|
245
246
|
|
|
246
247
|
# Generate or update Hash with workspace id and name (option), if not already set
|
|
247
248
|
# @param hash [Hash, Nil] set in provided hash
|
|
248
|
-
# @param string [
|
|
249
|
+
# @param string [TrueClass,FalseClass] true to set key as string, else as symbol
|
|
249
250
|
# @param name [Bool] include name
|
|
250
251
|
# @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
|
|
251
252
|
def workspace_id_hash(hash: nil, string: false, name: false)
|
|
@@ -288,7 +289,7 @@ module Aspera
|
|
|
288
289
|
query = query_read_delete(default: default_query)
|
|
289
290
|
# caller may add specific modifications or checks to query
|
|
290
291
|
yield(query) if block_given?
|
|
291
|
-
result = aoc_api.read_with_paging(resource_class_path,
|
|
292
|
+
result = aoc_api.read_with_paging(resource_class_path, base_query.merge(query).compact, formatter: formatter)
|
|
292
293
|
return Main.result_object_list(result[:items], fields: fields, total: result[:total])
|
|
293
294
|
end
|
|
294
295
|
|
|
@@ -313,7 +314,7 @@ module Aspera
|
|
|
313
314
|
Aspera.assert_type(query, Hash){'query'}
|
|
314
315
|
PACKAGE_RECEIVED_BASE_QUERY.each{ |k, v| query[k] = v unless query.key?(k)}
|
|
315
316
|
resolve_dropbox_name_default_ws_id(query)
|
|
316
|
-
return aoc_api.read_with_paging('packages', query
|
|
317
|
+
return aoc_api.read_with_paging('packages', query.compact, formatter: formatter)
|
|
317
318
|
end
|
|
318
319
|
|
|
319
320
|
NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
|
|
@@ -374,6 +375,7 @@ module Aspera
|
|
|
374
375
|
Aspera.error_unreachable_line
|
|
375
376
|
end
|
|
376
377
|
|
|
378
|
+
# @param resource_type [Symbol] One of ADMIN_OBJECTS
|
|
377
379
|
def execute_resource_action(resource_type)
|
|
378
380
|
# get path on API, resource type is singular, but api is plural
|
|
379
381
|
resource_class_path =
|
|
@@ -391,8 +393,9 @@ module Aspera
|
|
|
391
393
|
global_operations = %i[create list]
|
|
392
394
|
supported_operations = %i[show modify]
|
|
393
395
|
supported_operations.push(:delete, *global_operations) unless singleton_object
|
|
394
|
-
supported_operations.push(:do) if resource_type.eql?(:node)
|
|
396
|
+
supported_operations.push(:do, :bearer_token) if resource_type.eql?(:node)
|
|
395
397
|
supported_operations.push(:set_pub_key) if resource_type.eql?(:client)
|
|
398
|
+
supported_operations.push(:shared_folder, :dropbox) if resource_type.eql?(:workspace)
|
|
396
399
|
command = options.get_next_command(supported_operations)
|
|
397
400
|
# require identifier for non global commands
|
|
398
401
|
if !singleton_object && !global_operations.include?(command)
|
|
@@ -454,6 +457,60 @@ module Aspera
|
|
|
454
457
|
when :do
|
|
455
458
|
command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
|
|
456
459
|
return execute_nodegen4_command(command_repo, res_id)
|
|
460
|
+
when :bearer_token
|
|
461
|
+
node_api = aoc_api.node_api_from(
|
|
462
|
+
node_id: res_id,
|
|
463
|
+
scope: options.get_next_argument('scope')
|
|
464
|
+
)
|
|
465
|
+
return Main.result_text(node_api.oauth.authorization)
|
|
466
|
+
when :dropbox
|
|
467
|
+
command_shared = options.get_next_command(%i[list])
|
|
468
|
+
case command_shared
|
|
469
|
+
when :list
|
|
470
|
+
query = options.get_option(:query) || {}
|
|
471
|
+
res_data = aoc_api.read('dropboxes', query.merge({'workspace_id'=>res_id}))
|
|
472
|
+
return Main.result_object_list(res_data, fields: %w[id name description])
|
|
473
|
+
end
|
|
474
|
+
when :shared_folder
|
|
475
|
+
query = options.get_option(:query) || Api::AoC.workspace_access(res_id).merge({'admin' => true})
|
|
476
|
+
shared_folders = aoc_api.read_with_paging("#{resource_instance_path}/permissions", query)[:items]
|
|
477
|
+
# inside a workspace
|
|
478
|
+
command_shared = options.get_next_command(%i[list member])
|
|
479
|
+
case command_shared
|
|
480
|
+
when :list
|
|
481
|
+
return Main.result_object_list(shared_folders, fields: %w[id node_name node_id file_id file.path tags.aspera.files.workspace.share_as])
|
|
482
|
+
when :member
|
|
483
|
+
shared_folder_id = instance_identifier
|
|
484
|
+
shared_folder = shared_folders.find{ |i| i['id'].eql?(shared_folder_id)}
|
|
485
|
+
Aspera.assert(shared_folder)
|
|
486
|
+
command_shared_member = options.get_next_command(%i[list])
|
|
487
|
+
case command_shared_member
|
|
488
|
+
when :list
|
|
489
|
+
node_api = aoc_api.node_api_from(
|
|
490
|
+
node_id: shared_folder['node_id'],
|
|
491
|
+
workspace_id: res_id,
|
|
492
|
+
workspace_name: nil,
|
|
493
|
+
scope: Api::Node::SCOPE_USER
|
|
494
|
+
)
|
|
495
|
+
result = node_api.read(
|
|
496
|
+
'permissions',
|
|
497
|
+
{'file_id' => shared_folder['file_id'], 'tag' => "aspera.files.workspace.id=#{res_id}"}
|
|
498
|
+
)
|
|
499
|
+
result.each do |item|
|
|
500
|
+
item['member'] = begin
|
|
501
|
+
if Api::AoC.workspace_access?(item)
|
|
502
|
+
{'name'=>'[Internal permission]'}
|
|
503
|
+
else
|
|
504
|
+
aoc_api.read("admin/#{item['access_type']}s/#{item['access_id']}") rescue {'name': 'not found'}
|
|
505
|
+
end
|
|
506
|
+
rescue => e
|
|
507
|
+
{'name'=>e.to_s}
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
# TODO : read users and group name and add, if query "include_members"
|
|
511
|
+
return Main.result_object_list(result, fields: %w[access_type access_id access_level last_updated_at member.name member.email member.system_group_type member.system_group])
|
|
512
|
+
end
|
|
513
|
+
end
|
|
457
514
|
else Aspera.error_unexpected_value(command)
|
|
458
515
|
end
|
|
459
516
|
end
|
|
@@ -736,7 +793,7 @@ module Aspera
|
|
|
736
793
|
}
|
|
737
794
|
return result_list('short_links', fields: Formatter.all_but('data'), base_query: list_params) if command.eql?(:list)
|
|
738
795
|
one_id = instance_identifier
|
|
739
|
-
found = aoc_api.read_with_paging('short_links',
|
|
796
|
+
found = aoc_api.read_with_paging('short_links', list_params, formatter: formatter)[:items].find{ |item| item['id'].eql?(one_id)}
|
|
740
797
|
raise Cli::BadIdentifier.new('Short link', one_id) if found.nil?
|
|
741
798
|
return Main.result_single_object(found, fields: Formatter.all_but('data'))
|
|
742
799
|
when :modify
|
|
@@ -886,7 +943,7 @@ module Aspera
|
|
|
886
943
|
# enforce workspace id from link (should be already ok, but in case user wanted to override)
|
|
887
944
|
package_data['workspace_id'] = aoc_api.public_link['data']['workspace_id']
|
|
888
945
|
end
|
|
889
|
-
package_data['encryption_at_rest'] = true if transfer.
|
|
946
|
+
package_data['encryption_at_rest'] = true if transfer.user_transfer_spec['content_protection'].eql?('encrypt')
|
|
890
947
|
# transfer may raise an error
|
|
891
948
|
created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
|
|
892
949
|
Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
|
|
@@ -921,13 +978,8 @@ module Aspera
|
|
|
921
978
|
end
|
|
922
979
|
# download all files, or specified list only
|
|
923
980
|
ts_paths = transfer.ts_source_paths(default: ['.'])
|
|
924
|
-
per_package_def = options.get_option(:package_folder)
|
|
925
|
-
|
|
926
|
-
raise Cli::BadArgument, "Invalid package folder option : #{per_package_def}" unless per_package_def =~ /\A([^+]+)(?:\+([^?]+)(\?)?)?\z/
|
|
927
|
-
per_package_field1 = Regexp.last_match(1)
|
|
928
|
-
per_package_field2 = Regexp.last_match(2)
|
|
929
|
-
per_package_sub_always = Regexp.last_match(3).nil?
|
|
930
|
-
end
|
|
981
|
+
per_package_def = options.get_option(:package_folder).symbolize_keys
|
|
982
|
+
save_metadata = per_package_def.delete(:inf)
|
|
931
983
|
# get value outside of loop
|
|
932
984
|
destination_folder = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
|
|
933
985
|
result_transfer = []
|
|
@@ -943,23 +995,14 @@ module Aspera
|
|
|
943
995
|
Transfer::Spec::DIRECTION_RECEIVE,
|
|
944
996
|
{'paths'=> ts_paths}
|
|
945
997
|
)
|
|
946
|
-
unless per_package_def.
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
destination_folder,
|
|
950
|
-
Environment.instance.sanitized_filename(package_info[per_package_field1])
|
|
951
|
-
)
|
|
952
|
-
transfer.option_transfer_spec['destination_root'] = self.class.unique_folder(
|
|
953
|
-
folder,
|
|
954
|
-
extension: per_package_field2.eql?('seq') ? :seq : package_info[per_package_field2],
|
|
955
|
-
always: per_package_sub_always
|
|
956
|
-
)
|
|
957
|
-
end
|
|
958
|
-
formatter.display_status(%Q{Downloading package: [#{package_info['id']}] "#{package_info['name']}" to [#{destination_folder}]})
|
|
998
|
+
transfer.user_transfer_spec['destination_root'] = self.class.unique_folder(package_info, destination_folder, **per_package_def) unless per_package_def.empty?
|
|
999
|
+
dest_folder = transfer.user_transfer_spec['destination_root'] || destination_folder
|
|
1000
|
+
formatter.display_status(%Q{Downloading package: [#{package_info['id']}] "#{package_info['name']}" to [#{dest_folder}]})
|
|
959
1001
|
statuses = transfer.start(
|
|
960
1002
|
transfer_spec,
|
|
961
1003
|
rest_token: package_node_api
|
|
962
1004
|
)
|
|
1005
|
+
File.write(File.join(dest_folder, "#{package_id}.info.json"), package_info.to_json) if save_metadata
|
|
963
1006
|
result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
|
|
964
1007
|
# update skip list only if all transfer sessions completed
|
|
965
1008
|
if skip_ids_persistency && TransferAgent.session_status(statuses).eql?(:success)
|
|
@@ -18,15 +18,22 @@ module Aspera
|
|
|
18
18
|
MAX_ITEMS = 'max'
|
|
19
19
|
# Special query parameter: `pmax`: max number of pages for list command
|
|
20
20
|
MAX_PAGES = 'pmax'
|
|
21
|
-
# Special identifier format: look for this name to find where supported
|
|
22
|
-
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
23
21
|
|
|
24
22
|
class << self
|
|
25
23
|
def declare_options(options)
|
|
26
|
-
options.declare(:query, 'Additional filter for for some commands (list/delete)',
|
|
24
|
+
options.declare(:query, 'Additional filter for for some commands (list/delete)', allowed: [Hash, Array, NilClass])
|
|
27
25
|
options.declare(:property, 'Name of property to set (modify operation)')
|
|
28
|
-
options.declare(:bulk, 'Bulk operation (only some)',
|
|
29
|
-
options.declare(:bfail, 'Bulk operation error handling',
|
|
26
|
+
options.declare(:bulk, 'Bulk operation (only some)', allowed: Allowed::TYPES_BOOLEAN, default: false)
|
|
27
|
+
options.declare(:bfail, 'Bulk operation error handling', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Hash,NilClass] `{field:,value:}` if identifier is a percent selector, else `nil`
|
|
31
|
+
def percent_selector(identifier)
|
|
32
|
+
Aspera.assert_type(identifier, String)
|
|
33
|
+
if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
34
|
+
return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")}
|
|
35
|
+
end
|
|
36
|
+
return
|
|
30
37
|
end
|
|
31
38
|
end
|
|
32
39
|
|
|
@@ -41,10 +48,15 @@ module Aspera
|
|
|
41
48
|
# Global objects
|
|
42
49
|
attr_reader :context
|
|
43
50
|
|
|
51
|
+
# @return [Manager]
|
|
44
52
|
def options; @context.options; end
|
|
53
|
+
# @return [TransferAgent]
|
|
45
54
|
def transfer; @context.transfer; end
|
|
55
|
+
# @return [Config]
|
|
46
56
|
def config; @context.config; end
|
|
57
|
+
# @return [Formatter]
|
|
47
58
|
def formatter; @context.formatter; end
|
|
59
|
+
# @return [PersistencyFolder]
|
|
48
60
|
def persistency; @context.persistency; end
|
|
49
61
|
|
|
50
62
|
def add_manual_header(has_options = true)
|
|
@@ -55,26 +67,17 @@ module Aspera
|
|
|
55
67
|
options.parser.separator('OPTIONS:') if has_options
|
|
56
68
|
end
|
|
57
69
|
|
|
58
|
-
#
|
|
59
|
-
# ... folder browse _call_instance_identifier
|
|
70
|
+
# Resource identifier as positional parameter
|
|
60
71
|
#
|
|
61
72
|
# @param description [String] description of the identifier
|
|
62
|
-
# @param as_option [Symbol] option name to use if identifier is an option
|
|
63
73
|
# @param block [Proc] block to search for identifier based on attribute value
|
|
64
74
|
# @return [String, Array] identifier or list of ids
|
|
65
|
-
def instance_identifier(description: 'identifier',
|
|
66
|
-
if
|
|
67
|
-
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
68
|
-
else
|
|
69
|
-
res_id = options.get_option(as_option)
|
|
70
|
-
end
|
|
75
|
+
def instance_identifier(description: 'identifier', &block)
|
|
76
|
+
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
71
77
|
# Can be an Array
|
|
72
|
-
if res_id.is_a?(String) && (m =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
raise Cli::BadArgument, "Percent syntax for #{description} not supported in this context"
|
|
77
|
-
end
|
|
78
|
+
if res_id.is_a?(String) && (m = Base.percent_selector(res_id))
|
|
79
|
+
Aspera.assert(block, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
|
|
80
|
+
res_id = yield(m[:field], m[:value])
|
|
78
81
|
end
|
|
79
82
|
return res_id
|
|
80
83
|
end
|
|
@@ -172,12 +175,11 @@ module Aspera
|
|
|
172
175
|
if !delete_style.nil?
|
|
173
176
|
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
|
174
177
|
Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
|
|
175
|
-
api.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
api.delete(
|
|
179
|
+
entity,
|
|
180
|
+
nil,
|
|
178
181
|
content_type: Rest::MIME_JSON,
|
|
179
|
-
body: {delete_style => one_res_id}
|
|
180
|
-
headers: {'Accept' => Rest::MIME_JSON}
|
|
182
|
+
body: {delete_style => one_res_id}
|
|
181
183
|
)
|
|
182
184
|
return Main.result_status('deleted')
|
|
183
185
|
end
|
|
@@ -192,11 +194,10 @@ module Aspera
|
|
|
192
194
|
data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
|
|
193
195
|
return Main.result_object_list(data, total: total, fields: display_fields)
|
|
194
196
|
end
|
|
195
|
-
|
|
196
|
-
return Main.result_empty if
|
|
197
|
-
data = resp[:data]
|
|
197
|
+
data, http = api.read(entity, query_read_delete, ret: :both)
|
|
198
|
+
return Main.result_empty if http.code == '204'
|
|
198
199
|
# TODO: not generic : which application is this for ?
|
|
199
|
-
if
|
|
200
|
+
if http['Content-Type'].start_with?('application/vnd.api+json')
|
|
200
201
|
Log.log.debug('is vnd.api')
|
|
201
202
|
data = data[entity]
|
|
202
203
|
end
|
|
@@ -223,9 +224,8 @@ module Aspera
|
|
|
223
224
|
|
|
224
225
|
# Query parameters in URL suitable for REST: list/GET and delete/DELETE
|
|
225
226
|
def query_read_delete(default: nil)
|
|
226
|
-
query = options.get_option(:query)
|
|
227
227
|
# Dup default, as it could be frozen
|
|
228
|
-
query =
|
|
228
|
+
query = options.get_option(:query) || default.dup
|
|
229
229
|
Log.log.debug{"query_read_delete=#{query}".bg_red}
|
|
230
230
|
begin
|
|
231
231
|
# Check it is suitable
|
|
@@ -249,7 +249,7 @@ module Aspera
|
|
|
249
249
|
value = default if value.nil?
|
|
250
250
|
unless type.nil?
|
|
251
251
|
type = [type] unless type.is_a?(Array)
|
|
252
|
-
Aspera.
|
|
252
|
+
Aspera.assert_array_all(type, Class){'check types'}
|
|
253
253
|
if bulk
|
|
254
254
|
Aspera.assert_type(value, Array, type: Cli::BadArgument)
|
|
255
255
|
value.each do |v|
|
|
@@ -263,10 +263,10 @@ module Aspera
|
|
|
263
263
|
end
|
|
264
264
|
|
|
265
265
|
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
266
|
-
# @param
|
|
267
|
-
# @param
|
|
268
|
-
# @param
|
|
269
|
-
# @param
|
|
266
|
+
# @param api [Rest] API object
|
|
267
|
+
# @param entity [String,Symbol] API endpoint of entity to list
|
|
268
|
+
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
269
|
+
# @param query [Hash,nil] Additional query parameters
|
|
270
270
|
# @return [Array] items, total_count
|
|
271
271
|
def list_entities_limit_offset_total_count(
|
|
272
272
|
api:,
|
|
@@ -309,26 +309,39 @@ module Aspera
|
|
|
309
309
|
return result, total_count
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
# Lookup an entity id from its name
|
|
313
|
-
#
|
|
314
|
-
# @param
|
|
315
|
-
# @param
|
|
316
|
-
# @param
|
|
317
|
-
# @param
|
|
312
|
+
# Lookup an entity id from its name.
|
|
313
|
+
# Uses query `q` if `query` is `:default` and `field` is `name`.
|
|
314
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
315
|
+
# @param value [String] Value to lookup
|
|
316
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
317
|
+
# @param items_key [String] Key in the result to get the list of items (override entity)
|
|
318
|
+
# @param query [Hash] Additional query parameters (Default: `:default`)
|
|
318
319
|
def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
319
320
|
if query.eql?(:default)
|
|
320
321
|
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
321
322
|
query = {'q'=> value}
|
|
322
323
|
end
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
324
|
+
lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first}
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Lookup entity by field and value. Extract single result from list of result returned by block.
|
|
328
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
329
|
+
# @param value [String] Value to lookup
|
|
330
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
331
|
+
# @param block [Proc] Get list of entity matching query.
|
|
332
|
+
def lookup_entity_generic(entity:, value:, field: 'name', &block)
|
|
333
|
+
Aspera.assert(block_given?)
|
|
334
|
+
found = yield
|
|
335
|
+
Aspera.assert_array_all(found, Hash)
|
|
336
|
+
found = found.select{ |i| i[field].eql?(value)}
|
|
337
|
+
return found.first if found.length.eql?(1)
|
|
338
|
+
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
329
339
|
end
|
|
340
|
+
|
|
330
341
|
PER_PAGE_DEFAULT = 1000
|
|
331
|
-
|
|
342
|
+
# Percent selector: select by this field for this value
|
|
343
|
+
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
344
|
+
private_constant :PER_PAGE_DEFAULT, :REGEX_LOOKUP_ID_BY_FIELD
|
|
332
345
|
end
|
|
333
346
|
end
|
|
334
347
|
end
|