aspera-cli 4.26.2 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +281 -21
- data/CONTRIBUTING.md +98 -7
- data/bin/ascli +3 -3
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +26 -24
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/agent/node.rb +32 -0
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -16
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +15 -14
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +26 -23
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +21 -19
- data/lib/aspera/ascp/installation.rb +37 -38
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +16 -3
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +2 -1
- data/lib/aspera/cli/formatter.rb +41 -27
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +70 -19
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1166 -853
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +457 -100
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +428 -754
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +294 -257
- data/lib/aspera/cli/plugins/faspex5.rb +585 -357
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +998 -794
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
- data/lib/aspera/cli/plugins/preview.rb +180 -139
- data/lib/aspera/cli/plugins/server.rb +124 -79
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +13 -12
- data/lib/aspera/cli/result.rb +61 -11
- data/lib/aspera/cli/runner.rb +275 -141
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +83 -59
- data/lib/aspera/cli/terminal_formatter.rb +1 -1
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +3 -3
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +21 -20
- data/lib/aspera/command_line_builder.rb +22 -22
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +4 -4
- data/lib/aspera/environment.rb +18 -11
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +3 -3
- data/lib/aspera/keychain/macos_security.rb +3 -3
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +17 -3
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +5 -5
- data/lib/aspera/oauth/boot.rb +2 -2
- data/lib/aspera/oauth/factory.rb +7 -5
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_action_once.rb +8 -8
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +3 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +7 -6
- data/lib/aspera/rest.rb +10 -36
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +3 -3
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +59 -10
- data/lib/aspera/schema/registry.rb +39 -6
- data/lib/aspera/secret_hider.rb +7 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/operations.rb +10 -13
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +23 -21
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +6 -6
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +25 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/json_rpc.rb +0 -52
|
@@ -46,15 +46,16 @@ module Aspera
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
application_name 'HSTS Node API'
|
|
50
|
+
|
|
51
|
+
SESSION_TIME_FIELDS = %i[start end].freeze
|
|
52
|
+
private_constant :SESSION_TIME_FIELDS
|
|
53
|
+
|
|
49
54
|
class << self
|
|
50
55
|
# directory: node, container: shares
|
|
51
56
|
FOLDER_TYPES = %w[directory container].freeze
|
|
52
57
|
private_constant :FOLDER_TYPES
|
|
53
58
|
|
|
54
|
-
def application_name
|
|
55
|
-
'HSTS Node API'
|
|
56
|
-
end
|
|
57
|
-
|
|
58
59
|
# @return [Hash,NilClass]
|
|
59
60
|
def detect(address_or_url)
|
|
60
61
|
urls = if address_or_url.match?(%r{^[a-z]{1,6}://})
|
|
@@ -68,7 +69,7 @@ module Aspera
|
|
|
68
69
|
end
|
|
69
70
|
error = nil
|
|
70
71
|
urls.each do |base_url|
|
|
71
|
-
next unless base_url.match?(
|
|
72
|
+
next unless base_url.match?(%r{^https?://})
|
|
72
73
|
api = Rest.new(base_url: base_url)
|
|
73
74
|
test_endpoint = 'ping'
|
|
74
75
|
http = api.read(test_endpoint, ret: :resp)
|
|
@@ -87,20 +88,22 @@ module Aspera
|
|
|
87
88
|
return
|
|
88
89
|
end
|
|
89
90
|
|
|
91
|
+
# Called by non-Node plugins (Ats, Cos, Aoc) to declare Node options on their
|
|
92
|
+
# own options object. The DSL registry is walked so descriptions stay in one place.
|
|
90
93
|
def declare_options(options)
|
|
91
|
-
return if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
return if options.option_declared?(:root_id)
|
|
95
|
+
command_registry.option_specs.each_value do |spec|
|
|
96
|
+
next if options.option_declared?(spec.name)
|
|
97
|
+
options.declare(
|
|
98
|
+
spec.name,
|
|
99
|
+
description: spec.description,
|
|
100
|
+
short: spec.short,
|
|
101
|
+
allowed: spec.allowed,
|
|
102
|
+
default: spec.default,
|
|
103
|
+
handler: spec.handler,
|
|
104
|
+
schema: spec.schema
|
|
105
|
+
)
|
|
106
|
+
end
|
|
104
107
|
options.parse_options!
|
|
105
108
|
end
|
|
106
109
|
|
|
@@ -110,8 +113,17 @@ module Aspera
|
|
|
110
113
|
end
|
|
111
114
|
end
|
|
112
115
|
|
|
116
|
+
# DSL option declarations - at class level, picked up by Base#initialize via ancestor chain.
|
|
117
|
+
# Also exposed via Node.declare_options for non-Node plugins (Ats, Cos, Aoc).
|
|
118
|
+
option :validator, description: 'Identifier of validator (optional for central)'
|
|
119
|
+
option :asperabrowserurl, description: 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net'
|
|
120
|
+
option :node_api, description: 'Gen4: standard_ports: Use standard FASP ports (true) or get from node API (false). cache: Set to false to force actual file system read',
|
|
121
|
+
allowed: Hash, handler: {o: Api::Node, m: :api_options}
|
|
122
|
+
option :root_id, description: 'Gen4: File id of top folder when using access key (override AK root id)'
|
|
123
|
+
option :dynamic_key, description: 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key}
|
|
124
|
+
|
|
113
125
|
# @param wizard [Wizard] The wizard object
|
|
114
|
-
# @param app_url [
|
|
126
|
+
# @param app_url [String] Tested URL
|
|
115
127
|
# @return [Hash] :preset_value, :test_args
|
|
116
128
|
def wizard(wizard, app_url)
|
|
117
129
|
return {
|
|
@@ -146,10 +158,7 @@ module Aspera
|
|
|
146
158
|
# commands for : `execute_simple_common`: actions used commonly when a node is involved
|
|
147
159
|
COMMON_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
|
148
160
|
|
|
149
|
-
|
|
150
|
-
V3_IN_V4_ACTIONS = %i[transfer].concat(COMMON_ACTIONS).freeze
|
|
151
|
-
|
|
152
|
-
private_constant :CENTRAL_SOAP_API_TEST, :SEARCH_REMOVE_FIELDS, :BASE_ACTIONS, :SPECIAL_ACTIONS, :V3_IN_V4_ACTIONS, :COMMON_ACTIONS
|
|
161
|
+
private_constant :CENTRAL_SOAP_API_TEST, :SEARCH_REMOVE_FIELDS, :BASE_ACTIONS, :SPECIAL_ACTIONS, :COMMON_ACTIONS
|
|
153
162
|
|
|
154
163
|
# used in aoc
|
|
155
164
|
NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
|
|
@@ -196,9 +205,9 @@ module Aspera
|
|
|
196
205
|
end
|
|
197
206
|
|
|
198
207
|
# Gen3 API
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
folders_to_process = @node_path_prefix.
|
|
208
|
+
# @param path [String] starting path
|
|
209
|
+
def browse_gen3(path)
|
|
210
|
+
folders_to_process = @node_path_prefix.nil? ? path : @node_path_prefix.add_to_path(path)
|
|
202
211
|
folders_to_process = [folders_to_process]
|
|
203
212
|
query = options.get_option(:query) || {}
|
|
204
213
|
# special parameter: max number of entries in result
|
|
@@ -211,7 +220,7 @@ module Aspera
|
|
|
211
220
|
single_call = query.key?('skip')
|
|
212
221
|
# API default is 100, so use 1000 for default
|
|
213
222
|
query['count'] ||= 1000
|
|
214
|
-
|
|
223
|
+
Aspera.assert(!(recursive && single_call), type: Cli::BadArgument){'options `recursive` and `skip` cannot be used together'}
|
|
215
224
|
all_items = []
|
|
216
225
|
until folders_to_process.empty?
|
|
217
226
|
path = folders_to_process.shift
|
|
@@ -252,9 +261,9 @@ module Aspera
|
|
|
252
261
|
end
|
|
253
262
|
|
|
254
263
|
# Create async transfer spec request from direction and folders
|
|
255
|
-
# @param sync_direction one of push pull bidi
|
|
256
|
-
# @param local_path local folder to sync
|
|
257
|
-
# @param remote_path remote folder to sync
|
|
264
|
+
# @param sync_direction [Symbol] one of push pull bidi
|
|
265
|
+
# @param local_path [String] local folder to sync
|
|
266
|
+
# @param remote_path [String] remote folder to sync
|
|
258
267
|
def sync_spec_request(sync_direction, local_path, remote_path)
|
|
259
268
|
case sync_direction
|
|
260
269
|
when :push then {
|
|
@@ -282,382 +291,28 @@ module Aspera
|
|
|
282
291
|
end
|
|
283
292
|
end
|
|
284
293
|
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return
|
|
294
|
-
when :search
|
|
295
|
-
search_root = options.get_next_argument('search root', validation: String)
|
|
296
|
-
search_root = @node_path_prefix.add_to_path(search_root) unless @node_path_prefix.nil?
|
|
297
|
-
parameters = {'path' => search_root}
|
|
298
|
-
other_options = options.get_option(:query)
|
|
299
|
-
parameters.merge!(other_options) unless other_options.nil?
|
|
300
|
-
resp = @api_node.create('files/search', parameters)
|
|
301
|
-
return Result::Empty.new if resp['items'].empty?
|
|
302
|
-
fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
|
|
303
|
-
formatter.display_item_count(resp['item_count'], resp['total_count'])
|
|
304
|
-
formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
|
305
|
-
@node_path_prefix&.remove_in_object_list!(resp['items'])
|
|
306
|
-
return Result::ObjectList.new(resp['items'], fields: fields)
|
|
307
|
-
when :space
|
|
308
|
-
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
309
|
-
@node_path_prefix&.add_to_paths!(path_list)
|
|
310
|
-
resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
|
|
311
|
-
@node_path_prefix&.remove_in_object_list!(resp['paths'])
|
|
312
|
-
return Result::ObjectList.new(resp['paths'])
|
|
313
|
-
when :mkdir
|
|
314
|
-
path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
|
|
315
|
-
@node_path_prefix&.add_to_paths!(path_list)
|
|
316
|
-
resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
|
|
317
|
-
return cli_result_from_paths_response(resp, 'folder created')
|
|
318
|
-
when :mklink
|
|
319
|
-
target = options.get_next_argument('target', validation: String)
|
|
320
|
-
target = @node_path_prefix.add_to_path(target) unless @node_path_prefix.nil?
|
|
321
|
-
one_path = options.get_next_argument('link path', validation: String)
|
|
322
|
-
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
323
|
-
resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
|
|
324
|
-
return cli_result_from_paths_response(resp, 'link created')
|
|
325
|
-
when :mkfile
|
|
326
|
-
one_path = options.get_next_argument('file path', validation: String)
|
|
327
|
-
one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
|
|
328
|
-
contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
|
|
329
|
-
resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
|
|
330
|
-
return cli_result_from_paths_response(resp, 'file created')
|
|
331
|
-
when :rename
|
|
332
|
-
# TODO: multiple ?
|
|
333
|
-
path_base = options.get_next_argument('path_base', validation: String)
|
|
334
|
-
path_base = @node_path_prefix.add_to_path(path_base) unless @node_path_prefix.nil?
|
|
335
|
-
path_src = options.get_next_argument('path_src', validation: String)
|
|
336
|
-
path_src = @node_path_prefix.add_to_path(path_src) unless @node_path_prefix.nil?
|
|
337
|
-
path_dst = options.get_next_argument('path_dst', validation: String)
|
|
338
|
-
path_dst = @node_path_prefix.add_to_path(path_dst) unless @node_path_prefix.nil?
|
|
339
|
-
resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
|
|
340
|
-
return cli_result_from_paths_response(resp, 'entry moved')
|
|
341
|
-
when :browse
|
|
342
|
-
return browse_gen3
|
|
343
|
-
when :sync
|
|
344
|
-
return execute_sync_action do |sync_direction, local_path, remote_path|
|
|
345
|
-
# Gen3 API
|
|
346
|
-
# empty transfer spec for authorization request
|
|
347
|
-
request_transfer_spec = sync_spec_request(sync_direction, local_path, remote_path)
|
|
348
|
-
# add fixed parameters if any (for COS)
|
|
349
|
-
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
|
350
|
-
# prepare payload for single request
|
|
351
|
-
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
|
352
|
-
# only one request, so only one answer
|
|
353
|
-
transfer_spec = @api_node.create('files/sync_setup', setup_payload)['transfer_specs'].first['transfer_spec']
|
|
354
|
-
# API returns null tag... but async does not like it
|
|
355
|
-
transfer_spec.delete_if{ |_k, v| v.nil?}
|
|
356
|
-
# delete this part, as the returned value contains only destination, and not sources
|
|
357
|
-
# transfer_spec.delete('paths') if command.eql?(:upload)
|
|
358
|
-
Log.dump(:ts, transfer_spec)
|
|
359
|
-
transfer_spec
|
|
360
|
-
end
|
|
361
|
-
when :upload, :download
|
|
362
|
-
# empty transfer spec for authorization request
|
|
363
|
-
request_transfer_spec = {}
|
|
364
|
-
# set requested paths depending on direction
|
|
365
|
-
request_transfer_spec[:paths] = if command.eql?(:download)
|
|
366
|
-
transfer.ts_source_paths
|
|
367
|
-
else
|
|
368
|
-
[{destination: transfer.destination_folder(Transfer::Spec::DIRECTION_SEND)}]
|
|
369
|
-
end
|
|
370
|
-
# add fixed parameters if any (for COS)
|
|
371
|
-
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
|
372
|
-
Api::Node.add_public_key(request_transfer_spec)
|
|
373
|
-
# prepare payload for single request
|
|
374
|
-
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
|
375
|
-
# only one request, so only one answer
|
|
376
|
-
transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)['transfer_specs'].first['transfer_spec']
|
|
377
|
-
Api::Node.add_private_key(transfer_spec)
|
|
378
|
-
# delete this part, as the returned value contains only destination, and not sources
|
|
379
|
-
transfer_spec.delete('paths') if command.eql?(:upload)
|
|
380
|
-
return Runner.result_transfer(transfer.start(transfer_spec))
|
|
381
|
-
when :cat
|
|
382
|
-
remote_path = options.get_next_argument('remote path', validation: String)
|
|
383
|
-
remote_path = @node_path_prefix.add_to_path(remote_path) unless @node_path_prefix.nil?
|
|
384
|
-
File.basename(remote_path)
|
|
385
|
-
http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
|
|
386
|
-
return Result::Text.new(http.body)
|
|
387
|
-
when :transport
|
|
388
|
-
return Result::SingleObject.new(@api_node.transport_params)
|
|
389
|
-
when :spec
|
|
390
|
-
return Result::SingleObject.new(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC))
|
|
391
|
-
end
|
|
392
|
-
Aspera.error_unreachable_line
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# common API to node and Shares
|
|
396
|
-
def execute_simple_common(command)
|
|
397
|
-
case command
|
|
398
|
-
when *COMMANDS_GEN3
|
|
399
|
-
execute_command_gen3(command)
|
|
400
|
-
when :access_keys
|
|
401
|
-
ak_command = options.get_next_command(%i[do set_bearer_key].concat(Operations::ALL))
|
|
402
|
-
case ak_command
|
|
403
|
-
when *Operations::ALL
|
|
404
|
-
return entity_execute(
|
|
405
|
-
api: @api_node,
|
|
406
|
-
entity: 'access_keys',
|
|
407
|
-
command: ak_command
|
|
408
|
-
) do |field, value|
|
|
409
|
-
raise BadArgument, 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
|
|
410
|
-
@api_node.read('access_keys/self')['id']
|
|
411
|
-
end
|
|
412
|
-
when :do
|
|
413
|
-
access_key_id = options.get_next_argument('access key id')
|
|
414
|
-
root_file_id = options.get_option(:root_id)
|
|
415
|
-
if root_file_id.nil?
|
|
416
|
-
ak_info = @api_node.read("access_keys/#{access_key_id}")
|
|
417
|
-
ak_secret = config.lookup_secret(url: @api_node.base_url, username: ak_info['id'])
|
|
418
|
-
# change API credentials if different access key
|
|
419
|
-
if !access_key_id.eql?('self')
|
|
420
|
-
Aspera.assert(ak_secret, type: Cli::MissingArgument){"Please provide secret for #{ak_info['id']} using option: secret or by setting a preset for #{ak_info['id']}@#{@api_node.base_url}."}
|
|
421
|
-
@api_node.auth_params[:username] = ak_info['id']
|
|
422
|
-
@api_node.auth_params[:password] = ak_secret
|
|
423
|
-
end
|
|
424
|
-
root_file_id = ak_info['root_file_id']
|
|
425
|
-
end
|
|
426
|
-
command_repo = options.get_next_command(COMMANDS_GEN4)
|
|
427
|
-
return execute_command_gen4(command_repo, root_file_id)
|
|
428
|
-
when :set_bearer_key
|
|
429
|
-
access_key_id = options.get_next_argument('access key id')
|
|
430
|
-
access_key_id = @api_node.read('access_keys/self')['id'] if access_key_id.eql?('self')
|
|
431
|
-
bearer_key_pem = options.get_next_argument('public or private RSA key PEM value', validation: String)
|
|
432
|
-
key = OpenSSL::PKey.read(bearer_key_pem)
|
|
433
|
-
key = key.public_key if key.private?
|
|
434
|
-
bearer_key_pem = key.to_pem
|
|
435
|
-
@api_node.update("access_keys/#{access_key_id}", {token_verification_key: bearer_key_pem})
|
|
436
|
-
return Result::Status.new('public key updated')
|
|
437
|
-
end
|
|
438
|
-
when :health
|
|
439
|
-
nagios = Nagios.new
|
|
440
|
-
begin
|
|
441
|
-
info = @api_node.read('info')
|
|
442
|
-
nagios.add_ok('node api', 'accessible')
|
|
443
|
-
nagios.check_time_offset(info['current_time'], 'node api')
|
|
444
|
-
nagios.check_product_version('node api', 'entsrv', info['version'])
|
|
445
|
-
rescue StandardError => e
|
|
446
|
-
nagios.add_critical('node api', e.to_s)
|
|
447
|
-
end
|
|
448
|
-
begin
|
|
449
|
-
@api_node.call(
|
|
450
|
-
operation: 'POST',
|
|
451
|
-
subpath: 'services/soap/Transfer-201210',
|
|
452
|
-
content_type: Mime::TEXT,
|
|
453
|
-
body: CENTRAL_SOAP_API_TEST,
|
|
454
|
-
headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
|
|
455
|
-
ret: :resp
|
|
456
|
-
).body
|
|
457
|
-
nagios.add_ok('central', 'accessible by node')
|
|
458
|
-
rescue StandardError => e
|
|
459
|
-
nagios.add_critical('central', e.to_s)
|
|
460
|
-
end
|
|
461
|
-
Result::ObjectList.new(nagios.status_list)
|
|
462
|
-
when :events
|
|
463
|
-
events = @api_node.read('events', query_read_delete)
|
|
464
|
-
return Result::ObjectList.new(events, fields: ->(f){!f.start_with?('data')})
|
|
465
|
-
when :info
|
|
466
|
-
nd_info = @api_node.read('info')
|
|
467
|
-
return Result::SingleObject.new(nd_info)
|
|
468
|
-
when :slash
|
|
469
|
-
nd_info = @api_node.read('')
|
|
470
|
-
return Result::SingleObject.new(nd_info)
|
|
471
|
-
when :license
|
|
472
|
-
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
|
473
|
-
return Result::SingleObject.new(@api_node.read('license'))
|
|
474
|
-
when :api_details
|
|
475
|
-
return Result::SingleObject.new({base_url: @api_node.base_url}.merge(@api_node.params))
|
|
294
|
+
# Resolve a NodeFileId from a path argument.
|
|
295
|
+
# Supports %id:<file_id> syntax (returns NodeFileId for that id) or a plain path.
|
|
296
|
+
# @param top_file_id [String] root file id for path resolution
|
|
297
|
+
# @param path [String] plain path or %id:<file_id> selector
|
|
298
|
+
def apifid_from_path(top_file_id, path)
|
|
299
|
+
if (m = Parser.percent_selector(path))
|
|
300
|
+
Aspera.assert_values(m[:field], ['id'], type: BadArgument){'file id'}
|
|
301
|
+
val = m[:value]
|
|
302
|
+
return Api::NodeFileId.new(@api_node, val.nil? || val.empty? ? top_file_id : val)
|
|
476
303
|
end
|
|
304
|
+
@api_node.resolve_api_fid(top_file_id, path)
|
|
477
305
|
end
|
|
478
306
|
|
|
479
|
-
#
|
|
480
|
-
#
|
|
307
|
+
# Legacy helper: resolve a NodeFileId by reading path from the CLI argument stream.
|
|
308
|
+
# Used only by setup_access_key_do_permission and action_access_keys_do_permission
|
|
309
|
+
# (intermediate nodes whose path cannot yet be injected via execute_leaf).
|
|
481
310
|
def apifid_from_next_arg(top_file_id)
|
|
482
311
|
file_path = options.instance_identifier(description: 'path or %id:<id> or %id:') do |attribute, value|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return Api::NodeFileId.new(@api_node, value)
|
|
486
|
-
end
|
|
487
|
-
# There was no selector, so it is a path
|
|
488
|
-
return @api_node.resolve_api_fid(top_file_id, file_path)
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
def execute_command_gen4(command_repo, top_file_id)
|
|
492
|
-
override_file_id = options.get_option(:root_id)
|
|
493
|
-
top_file_id = override_file_id unless override_file_id.nil?
|
|
494
|
-
raise Cli::Error, 'Specify root file id with option root_id' if top_file_id.nil?
|
|
495
|
-
case command_repo
|
|
496
|
-
when :v3
|
|
497
|
-
# NOTE: other common actions are unauthorized with user scope
|
|
498
|
-
command_legacy = options.get_next_command(V3_IN_V4_ACTIONS)
|
|
499
|
-
# TODO: shall we support all methods here ? what if there is a link ?
|
|
500
|
-
apifid = @api_node.resolve_api_fid(top_file_id, '')
|
|
501
|
-
return Node.new(context: context, api: apifid.node_api).execute_action(command_legacy)
|
|
502
|
-
when :node_info, :bearer_token_node
|
|
503
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
504
|
-
result = {
|
|
505
|
-
url: apifid.node_api.base_url,
|
|
506
|
-
root_id: apifid.file_id
|
|
507
|
-
}
|
|
508
|
-
case apifid.node_api.auth_params[:type]
|
|
509
|
-
when :basic
|
|
510
|
-
result[:username] = apifid.node_api.auth_params[:username]
|
|
511
|
-
result[:password] = apifid.node_api.auth_params[:password]
|
|
512
|
-
when :oauth2
|
|
513
|
-
result[:username] = apifid.node_api.params[:headers][Api::Node::HEADER_X_ASPERA_ACCESS_KEY]
|
|
514
|
-
result[:password] = apifid.node_api.oauth.authorization
|
|
515
|
-
else Aspera.error_unexpected_value(apifid.node_api.auth_params[:type]){'Node API Auth type'}
|
|
516
|
-
end
|
|
517
|
-
return Result::SingleObject.new(result) if command_repo.eql?(:node_info)
|
|
518
|
-
Log.dump(:result, result)
|
|
519
|
-
raise BadArgument, "Cannot get bearer token if authenticating with secret (#{apifid.node_api.auth_params[:type]})" unless apifid.node_api.auth_params[:type].eql?(:oauth2)
|
|
520
|
-
Aspera.assert(OAuth::Factory.bearer_auth?(result[:password]), 'Not using bearer token auth')
|
|
521
|
-
return Result::Text.new(result[:password])
|
|
522
|
-
when :browse
|
|
523
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
524
|
-
file_info = apifid.node_api.read("files/#{apifid.file_id}", headers: Api::Node.add_cache_control)
|
|
525
|
-
# a single file
|
|
526
|
-
return Result::ObjectList.new([file_info], fields: GEN4_LS_FIELDS) unless file_info['type'].eql?('folder')
|
|
527
|
-
return Result::ObjectList.new(apifid.node_api.list_files(apifid.file_id, query: query_read_delete), fields: GEN4_LS_FIELDS)
|
|
528
|
-
when :find
|
|
529
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
530
|
-
find_lambda = Api::Node.file_matcher_from_argument(options)
|
|
531
|
-
return Result::ObjectList.new(@api_node.find_files(apifid.file_id, find_lambda), fields: ['path'])
|
|
532
|
-
when :mkdir, :mklink, :mkfile
|
|
533
|
-
containing_folder_path, new_item = Api::Node.split_folder(options.get_next_argument('path'))
|
|
534
|
-
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
|
|
535
|
-
query = options.get_option(:query)
|
|
536
|
-
check_exists = true
|
|
537
|
-
payload = {name: new_item}
|
|
538
|
-
if query
|
|
539
|
-
check_exists = !query.delete('check').eql?(false)
|
|
540
|
-
target = query.delete('target')
|
|
541
|
-
if target
|
|
542
|
-
target_apifid = @api_node.resolve_api_fid(top_file_id, target, true)
|
|
543
|
-
payload[:target_id] = target_apifid.file_id
|
|
544
|
-
end
|
|
545
|
-
payload.merge!(query.symbolize_keys)
|
|
546
|
-
end
|
|
547
|
-
if check_exists
|
|
548
|
-
folder_content = apifid.node_api.read("files/#{apifid.file_id}/files")
|
|
549
|
-
link_name = ".#{new_item}.asp-lnk"
|
|
550
|
-
found = folder_content.find{ |i| i['name'].eql?(new_item) || i['name'].eql?(link_name)}
|
|
551
|
-
raise "A #{found['type']} already exists with name #{new_item}" if found
|
|
552
|
-
end
|
|
553
|
-
case command_repo
|
|
554
|
-
when :mkdir
|
|
555
|
-
payload[:type] = :folder
|
|
556
|
-
when :mklink
|
|
557
|
-
payload[:type] = :link
|
|
558
|
-
Aspera.assert(payload[:target_id], 'Missing target_id')
|
|
559
|
-
Aspera.assert(payload[:target_node_id], 'Missing target_node_id')
|
|
560
|
-
when :mkfile
|
|
561
|
-
payload[:type] = :file
|
|
562
|
-
payload[:contents] = Base64.strict_encode64(options.get_next_argument('contents'))
|
|
563
|
-
end
|
|
564
|
-
result = apifid.node_api.create("files/#{apifid.file_id}/files", payload)
|
|
565
|
-
return Result::SingleObject.new(result)
|
|
566
|
-
when :rename
|
|
567
|
-
file_path = options.get_next_argument('source path')
|
|
568
|
-
apifid = @api_node.resolve_api_fid(top_file_id, file_path)
|
|
569
|
-
newname = options.get_next_argument('new name')
|
|
570
|
-
result = apifid.node_api.update("files/#{apifid.file_id}", {name: newname})
|
|
571
|
-
return Result::Status.new("renamed to #{newname}")
|
|
572
|
-
when :delete
|
|
573
|
-
return do_bulk_operation(command: command_repo, descr: 'path', values: String, id_result: 'path') do |l_path|
|
|
574
|
-
apifid = if (m = Manager.percent_selector(l_path))
|
|
575
|
-
Aspera.assert_values(m[:field], ['id'], type: BadIdentifier)
|
|
576
|
-
Api::NodeFileId.new(@api_node, m[:value])
|
|
577
|
-
else
|
|
578
|
-
@api_node.resolve_api_fid(top_file_id, l_path)
|
|
579
|
-
end
|
|
580
|
-
result = apifid.node_api.delete("files/#{apifid.file_id}")
|
|
581
|
-
{'path' => l_path}
|
|
582
|
-
end
|
|
583
|
-
when :sync
|
|
584
|
-
return execute_sync_action do |sync_direction, _local_path, remote_path|
|
|
585
|
-
# Gen4 API
|
|
586
|
-
Aspera.assert_values(sync_direction, %i[push pull bidi])
|
|
587
|
-
ts_direction = case sync_direction
|
|
588
|
-
when :push, :bidi then Transfer::Spec::DIRECTION_SEND
|
|
589
|
-
when :pull then Transfer::Spec::DIRECTION_RECEIVE
|
|
590
|
-
else Aspera.error_unreachable_line
|
|
591
|
-
end
|
|
592
|
-
# remote is specified by option: `to_folder`
|
|
593
|
-
apifid = @api_node.resolve_api_fid(top_file_id, remote_path)
|
|
594
|
-
apifid.node_api.transfer_spec_gen4(apifid.file_id, ts_direction)
|
|
595
|
-
end
|
|
596
|
-
when :upload
|
|
597
|
-
apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
|
|
598
|
-
return Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_SEND)))
|
|
599
|
-
when :download
|
|
600
|
-
apifid, source_paths = @api_node.resolve_api_fid_paths(top_file_id, transfer.ts_source_paths)
|
|
601
|
-
return Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
|
|
602
|
-
when :cat
|
|
603
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
604
|
-
http = apifid.node_api.read("files/#{apifid.file_id}/content", ret: :resp)
|
|
605
|
-
return Result::Text.new(http.body)
|
|
606
|
-
when :show
|
|
607
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
608
|
-
items = apifid.node_api.read("files/#{apifid.file_id}")
|
|
609
|
-
return Result::SingleObject.new(items)
|
|
610
|
-
when :modify
|
|
611
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
612
|
-
update_param = options.get_next_argument('update data', validation: Hash)
|
|
613
|
-
apifid.node_api.update("files/#{apifid.file_id}", update_param)
|
|
614
|
-
return Result::Status.new('Done')
|
|
615
|
-
when :thumbnail
|
|
616
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
617
|
-
http = apifid.node_api.read("files/#{apifid.file_id}/preview", headers: {'Accept' => 'image/png'}, ret: :resp)
|
|
618
|
-
return Result::Image.new(http.body)
|
|
619
|
-
when :permission
|
|
620
|
-
apifid = apifid_from_next_arg(top_file_id)
|
|
621
|
-
command_perm = options.get_next_command(%i[list show create delete modify])
|
|
622
|
-
case command_perm
|
|
623
|
-
when :modify
|
|
624
|
-
apifid.node_api.update("permissions/#{options.instance_identifier}", value_create_modify(command: 'permission modify'))
|
|
625
|
-
return Result::Status.new('Updated')
|
|
626
|
-
when :list
|
|
627
|
-
list_query = query_read_delete(default: Rest.php_style({'include' => %w[access_level permission_count]}))
|
|
628
|
-
# Specify file to get permissions for unless not specified (then, get all permissions)
|
|
629
|
-
list_query['file_id'] = apifid.file_id unless apifid.file_id.to_s.empty?
|
|
630
|
-
list_query['inherited'] = false if list_query.key?('file_id') && !list_query.key?('inherited')
|
|
631
|
-
items = apifid.node_api.read_with_pages('permissions', list_query)
|
|
632
|
-
return Result::ObjectList.new(items)
|
|
633
|
-
when :show
|
|
634
|
-
return Result::SingleObject.new(apifid.node_api.read("permissions/#{options.instance_identifier}"))
|
|
635
|
-
when :delete
|
|
636
|
-
return do_bulk_operation(command: command_perm, values: :identifier) do |one_id|
|
|
637
|
-
apifid.node_api.delete("permissions/#{one_id}")
|
|
638
|
-
# notify application of deletion
|
|
639
|
-
the_app = apifid.node_api.app_info
|
|
640
|
-
the_app&.api&.permissions_send_event(event_data: {}, app_info: the_app, types: ['permission.deleted'])
|
|
641
|
-
{'id' => one_id}
|
|
642
|
-
end
|
|
643
|
-
when :create
|
|
644
|
-
create_param = options.get_next_argument('creation data', validation: Hash)
|
|
645
|
-
raise Cli::BadArgument, 'no file_id' if create_param.key?('file_id')
|
|
646
|
-
create_param['file_id'] = apifid.file_id
|
|
647
|
-
create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
|
648
|
-
# add application specific tags (AoC)
|
|
649
|
-
the_app = apifid.node_api.app_info
|
|
650
|
-
the_app&.api&.permissions_set_create_params(perm_data: create_param, app_info: the_app)
|
|
651
|
-
# create permission
|
|
652
|
-
created_data = apifid.node_api.create('permissions', create_param)
|
|
653
|
-
# notify application of creation
|
|
654
|
-
the_app&.api&.permissions_send_event(event_data: created_data, app_info: the_app)
|
|
655
|
-
return Result::SingleObject.new(created_data)
|
|
656
|
-
else Aspera.error_unreachable_line
|
|
657
|
-
end
|
|
658
|
-
else Aspera.error_unreachable_line
|
|
312
|
+
Aspera.assert_values(attribute, ['id'], type: BadArgument){'file id'}
|
|
313
|
+
return Api::NodeFileId.new(@api_node, value.nil? || value.empty? ? top_file_id : value)
|
|
659
314
|
end
|
|
660
|
-
|
|
315
|
+
@api_node.resolve_api_fid(top_file_id, file_path)
|
|
661
316
|
end
|
|
662
317
|
|
|
663
318
|
# Search /async by name
|
|
@@ -666,7 +321,7 @@ module Aspera
|
|
|
666
321
|
# @return [Integer] id of the sync
|
|
667
322
|
# @raise [Cli::BadArgument] if no such sync, or not by name
|
|
668
323
|
def async_lookup(field, value)
|
|
669
|
-
|
|
324
|
+
Aspera.assert_values(field, ['name'], type: Cli::BadArgument){'search field'}
|
|
670
325
|
async_ids = @api_node.read('async/list')['sync_ids']
|
|
671
326
|
summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
|
|
672
327
|
selected = summaries.find{ |s| s['name'].eql?(value)}
|
|
@@ -674,82 +329,13 @@ module Aspera
|
|
|
674
329
|
return selected['snid']
|
|
675
330
|
end
|
|
676
331
|
|
|
677
|
-
# Node API: /async (stats only)
|
|
678
|
-
def execute_async
|
|
679
|
-
command = options.get_next_command(%i[list delete files show counters bandwidth])
|
|
680
|
-
unless command.eql?(:list)
|
|
681
|
-
async_id = options.instance_identifier{ |field, value| async_lookup(field, value)}
|
|
682
|
-
if async_id.eql?(SpecialValues::ALL)
|
|
683
|
-
raise Cli::BadArgument, 'ALL only for show and delete' unless %i[show delete].include?(command)
|
|
684
|
-
async_ids = @api_node.read('async/list')['sync_ids']
|
|
685
|
-
else
|
|
686
|
-
Integer(async_id) # must be integer
|
|
687
|
-
async_ids = [async_id]
|
|
688
|
-
end
|
|
689
|
-
post_data = {'syncs' => async_ids}
|
|
690
|
-
end
|
|
691
|
-
case command
|
|
692
|
-
when :list
|
|
693
|
-
resp = @api_node.read('async/list')['sync_ids']
|
|
694
|
-
return Result::ValueList.new(resp)
|
|
695
|
-
when :show
|
|
696
|
-
resp = @api_node.create('async/summary', post_data)['sync_summaries']
|
|
697
|
-
return Result::Empty.new if resp.empty?
|
|
698
|
-
return Result::ObjectList.new(resp, fields: %w[snid name local_dir remote_dir]) if async_id.eql?(SpecialValues::ALL)
|
|
699
|
-
return Result::SingleObject.new(resp.first)
|
|
700
|
-
when :delete
|
|
701
|
-
resp = @api_node.create('async/delete', post_data)
|
|
702
|
-
return Result::SingleObject.new(resp)
|
|
703
|
-
when :bandwidth
|
|
704
|
-
post_data['seconds'] = 100 # TODO: as parameter with --query
|
|
705
|
-
resp = @api_node.create('async/bandwidth', post_data)
|
|
706
|
-
data = resp['bandwidth_data']
|
|
707
|
-
return Result::Empty.new if data.empty?
|
|
708
|
-
data = data.first[async_id]['data']
|
|
709
|
-
return Result::ObjectList.new(data)
|
|
710
|
-
when :files
|
|
711
|
-
# count int
|
|
712
|
-
# filename str
|
|
713
|
-
# skip int
|
|
714
|
-
# status int
|
|
715
|
-
filter = options.get_option(:query)
|
|
716
|
-
post_data.merge!(filter) unless filter.nil?
|
|
717
|
-
resp = @api_node.create('async/files', post_data)
|
|
718
|
-
data = resp['sync_files']
|
|
719
|
-
data = data.first[async_id] unless data.empty?
|
|
720
|
-
iteration_data = []
|
|
721
|
-
skip_ids_persistency = nil
|
|
722
|
-
if options.get_option(:once_only, mandatory: true)
|
|
723
|
-
skip_ids_persistency = PersistencyActionOnce.new(
|
|
724
|
-
manager: persistency,
|
|
725
|
-
data: iteration_data,
|
|
726
|
-
id: IdGenerator.from_list(
|
|
727
|
-
'sync_files',
|
|
728
|
-
options.get_option(:url, mandatory: true),
|
|
729
|
-
options.get_option(:username, mandatory: true),
|
|
730
|
-
async_id
|
|
731
|
-
)
|
|
732
|
-
)
|
|
733
|
-
data.select!{ |l| l['fnid'].to_i > iteration_data.first} unless iteration_data.first.nil?
|
|
734
|
-
iteration_data[0] = data.last['fnid'].to_i unless data.empty?
|
|
735
|
-
end
|
|
736
|
-
return Result::Empty.new if data.empty?
|
|
737
|
-
skip_ids_persistency&.save
|
|
738
|
-
return Result::ObjectList.new(data)
|
|
739
|
-
when :counters
|
|
740
|
-
resp = @api_node.create('async/counters', post_data)['sync_counters'].first[async_id].last
|
|
741
|
-
return Result::Empty.new if resp.nil?
|
|
742
|
-
return Result::SingleObject.new(resp)
|
|
743
|
-
end
|
|
744
|
-
end
|
|
745
|
-
|
|
746
332
|
# Search /asyncs by name
|
|
747
333
|
# @param field [String] name of the field to search
|
|
748
334
|
# @param value [String] value of the field to search
|
|
749
335
|
# @return [Integer] id of the sync
|
|
750
336
|
# @raise [Cli::BadArgument] if no such sync, or not by name
|
|
751
337
|
def ssync_lookup(field, value)
|
|
752
|
-
|
|
338
|
+
Aspera.assert_values(field, ['name'], type: Cli::BadArgument){'search field'}
|
|
753
339
|
@api_node.read('asyncs')['ids'].each do |id|
|
|
754
340
|
sync_info = @api_node.read("asyncs/#{id}")['configuration']
|
|
755
341
|
# name is unique, so we can return
|
|
@@ -758,355 +344,973 @@ module Aspera
|
|
|
758
344
|
raise Cli::BadIdentifier.new('ssync', value, field: field)
|
|
759
345
|
end
|
|
760
346
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
347
|
+
# --- DSL command declarations ---
|
|
348
|
+
|
|
349
|
+
# Gen3 leaf commands
|
|
350
|
+
command :search, description: 'Search for files',
|
|
351
|
+
arguments: [{name: :search_root, type: String}]
|
|
352
|
+
command :space, description: 'Show space information',
|
|
353
|
+
arguments: [{name: :path_list, multiple: true}]
|
|
354
|
+
command :mkdir, description: 'Create a folder (Gen3)',
|
|
355
|
+
arguments: [{name: :path_list, multiple: true}]
|
|
356
|
+
command :mklink, description: 'Create a symbolic link (Gen3)',
|
|
357
|
+
arguments: [{name: :target, type: String}, {name: :link_path, type: String}]
|
|
358
|
+
command :mkfile, description: 'Create a file (Gen3)',
|
|
359
|
+
arguments: [{name: :file_path, type: String}, {name: :contents, mandatory: false, default: nil}]
|
|
360
|
+
command :rename, description: 'Rename a file or folder (Gen3)',
|
|
361
|
+
arguments: [{name: :path_base, type: String}, {name: :path_src, type: String}, {name: :path_dst, type: String}]
|
|
362
|
+
command :delete, description: 'Delete files or folders (Gen3)',
|
|
363
|
+
arguments: [{name: :paths, multiple: true}]
|
|
364
|
+
command :browse, description: 'Browse files (Gen3)',
|
|
365
|
+
arguments: [{name: :path, type: String}],
|
|
366
|
+
action: ->(path:, **){browse_gen3(path)}
|
|
367
|
+
command :upload, description: 'Upload files (Gen3)', transfer_paths: :send
|
|
368
|
+
command :download, description: 'Download files (Gen3)', transfer_paths: :receive
|
|
369
|
+
command :cat, description: 'Show file contents (Gen3)',
|
|
370
|
+
arguments: [{name: :remote_path, type: String}]
|
|
371
|
+
command :sync, description: 'Synchronize folders (Gen3)'
|
|
372
|
+
commands_under(:sync) do
|
|
373
|
+
Sync::Operations::DIRECTIONS.each do |dir|
|
|
374
|
+
command(dir, description: "#{dir.capitalize}-sync (Gen3)", transfer_paths: :send)
|
|
375
|
+
end
|
|
376
|
+
command :admin, description: 'Manage sync database (admin operations)'
|
|
377
|
+
SyncActions.register_sync_admin_commands(self, %i[sync admin])
|
|
378
|
+
end
|
|
379
|
+
command :transport, description: 'Show transport parameters',
|
|
380
|
+
action: ->{Result::SingleObject.new(@api_node.transport_params)}
|
|
381
|
+
command :spec, description: 'Show transfer spec base',
|
|
382
|
+
action: ->{Result::SingleObject.new(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC))}
|
|
383
|
+
# Other common leaf commands
|
|
384
|
+
command :api_details, description: 'Show API details',
|
|
385
|
+
action: ->{Result::SingleObject.new({base_url: @api_node.base_url}.merge(@api_node.params))}
|
|
386
|
+
command :health, description: 'Check node health'
|
|
387
|
+
command :events, description: 'List events',
|
|
388
|
+
action: ->{Result::ObjectList.new(@api_node.read('events', query_read_delete), fields: ->(f){!f.start_with?('data')})}
|
|
389
|
+
command :info, description: 'Show node info',
|
|
390
|
+
action: ->{Result::SingleObject.new(@api_node.read('info'))}
|
|
391
|
+
command :slash, description: 'Show root info',
|
|
392
|
+
action: ->{Result::SingleObject.new(@api_node.read(''))}
|
|
393
|
+
command :license, description: 'Show license',
|
|
394
|
+
action: ->{Result::SingleObject.new(@api_node.read('license'))}
|
|
395
|
+
# access_keys sub-tree
|
|
396
|
+
command :access_keys, description: 'Manage access keys'
|
|
397
|
+
commands_under(:access_keys) do
|
|
398
|
+
command :do, description: 'Execute Gen4 command via access key',
|
|
399
|
+
arguments: [{name: :access_key_id, type: :identifier}],
|
|
400
|
+
setup: :setup_access_key_do
|
|
401
|
+
command :set_bearer_key, description: 'Set bearer key on access key',
|
|
402
|
+
arguments: [{name: :access_key_id}, {name: :bearer_key_pem, type: String}]
|
|
403
|
+
Operations::ALL.each{ |op| command(op, description: "#{op.capitalize} access key(s)")}
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Commands requiring a single path argument (Gen4)
|
|
407
|
+
COMMANDS_GEN4_SINGLE_PATH = %i[mkdir mklink browse cat show find thumbnail bearer_token_node node_info].freeze
|
|
408
|
+
private_constant :COMMANDS_GEN4_SINGLE_PATH
|
|
764
409
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
410
|
+
commands_under(%i[access_keys do]) do
|
|
411
|
+
COMMANDS_GEN4.each do |cmd|
|
|
412
|
+
next if cmd.eql?(:sync) # sync is an intermediate node declared below
|
|
413
|
+
kwargs = {description: "Gen4 #{cmd} command"}
|
|
414
|
+
kwargs[:setup] = :setup_access_key_do_permission if cmd.eql?(:permission)
|
|
415
|
+
kwargs[:arguments] = [{name: :source_path, type: String}, {name: :new_name, type: String}] if cmd.eql?(:rename)
|
|
416
|
+
kwargs[:arguments] = [{name: :paths, type: String, bulk: true}] if cmd.eql?(:delete)
|
|
417
|
+
kwargs[:arguments] = [{name: :path, type: String}, {name: :contents, mandatory: false, default: nil}] if cmd.eql?(:mkfile)
|
|
418
|
+
kwargs[:arguments] = [{name: :path, type: String}, {name: :update_value, type: Hash}] if cmd.eql?(:modify)
|
|
419
|
+
kwargs[:arguments] = [{name: :path, type: String}] if COMMANDS_GEN4_SINGLE_PATH.include?(cmd)
|
|
420
|
+
kwargs[:transfer_paths] = :send if cmd.eql?(:upload)
|
|
421
|
+
kwargs[:transfer_paths] = :receive if cmd.eql?(:download)
|
|
422
|
+
command(cmd, **kwargs)
|
|
423
|
+
end
|
|
424
|
+
command :sync, description: 'Synchronize folders (Gen4)'
|
|
425
|
+
commands_under(%i[access_keys do sync]) do
|
|
426
|
+
Sync::Operations::DIRECTIONS.each do |dir|
|
|
427
|
+
command(dir, description: "#{dir.capitalize}-sync (Gen4)", transfer_paths: :send)
|
|
428
|
+
end
|
|
429
|
+
command :admin, description: 'Manage sync database (admin operations)'
|
|
430
|
+
SyncActions.register_sync_admin_commands(self, %i[access_keys do sync admin])
|
|
771
431
|
end
|
|
772
|
-
|
|
432
|
+
end
|
|
433
|
+
commands_under(%i[access_keys do permission]) do
|
|
434
|
+
command :list, description: 'List permissions on a file'
|
|
435
|
+
command :show, description: 'Show a permission',
|
|
436
|
+
arguments: [{name: :perm_id, type: :identifier}],
|
|
437
|
+
action: ->(apifid:, perm_id:, **){Result::SingleObject.new(apifid.node_api.read("permissions/#{perm_id}"))}
|
|
438
|
+
command :create, description: 'Create a permission',
|
|
439
|
+
arguments: [{name: :data, type: Hash}]
|
|
440
|
+
command :modify, description: 'Modify a permission',
|
|
441
|
+
arguments: [{name: :perm_id, type: :identifier}, {name: :data, type: Hash}]
|
|
442
|
+
command :delete, description: 'Delete permission(s)',
|
|
443
|
+
arguments: [{name: :perm_id, bulk: true}]
|
|
444
|
+
end
|
|
445
|
+
# async (legacy /async)
|
|
446
|
+
command :async, description: 'Manage async operations (legacy /async)'
|
|
447
|
+
commands_under(:async) do
|
|
448
|
+
command :list, description: 'List async sync IDs', action: ->{Result::ValueList.new(@api_node.read('async/list')['sync_ids'])}
|
|
449
|
+
command :show, description: 'Show async summary',
|
|
450
|
+
arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
|
|
451
|
+
command :delete, description: 'Delete async',
|
|
452
|
+
arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
|
|
453
|
+
command :bandwidth, description: 'Show async bandwidth',
|
|
454
|
+
arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
|
|
455
|
+
command :files, description: 'List async files',
|
|
456
|
+
arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
|
|
457
|
+
command :counters, description: 'Show async counters',
|
|
458
|
+
arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
|
|
459
|
+
end
|
|
460
|
+
# ssync (/asyncs)
|
|
461
|
+
command :ssync, description: 'Manage sync operations (/asyncs)'
|
|
462
|
+
commands_under(:ssync) do
|
|
463
|
+
command :create, description: 'Create ssync', action: ->{entity_execute(api: @api_node, entity: :asyncs, command: :create, items_key: 'ids'){ |f, v| ssync_lookup(f, v)}}
|
|
464
|
+
command :list, description: 'List ssync', action: ->{entity_execute(api: @api_node, entity: :asyncs, command: :list, items_key: 'ids'){ |f, v| ssync_lookup(f, v)}}
|
|
465
|
+
command :show, description: 'Show ssync',
|
|
466
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
467
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}"))}
|
|
468
|
+
command :delete, description: 'Delete ssync',
|
|
469
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
470
|
+
action: :action_ssync_delete
|
|
471
|
+
command :start, description: 'Start a sync',
|
|
472
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
473
|
+
action: :action_ssync_start
|
|
474
|
+
command :stop, description: 'Stop a sync',
|
|
475
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
476
|
+
action: :action_ssync_stop
|
|
477
|
+
command :bandwidth, description: 'Show sync bandwidth',
|
|
478
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
479
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/bandwidth", options.get_option(:query) || {}))}
|
|
480
|
+
command :counters, description: 'Show sync counters',
|
|
481
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
482
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/counters", options.get_option(:query) || {}))}
|
|
483
|
+
command :files, description: 'List sync files',
|
|
484
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
485
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/files", options.get_option(:query) || {}))}
|
|
486
|
+
command :state, description: 'Show sync state',
|
|
487
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
488
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/state"))}
|
|
489
|
+
command :summary, description: 'Show sync summary',
|
|
490
|
+
arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
|
|
491
|
+
action: ->(ssync_id:, **){Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/summary"))}
|
|
492
|
+
end
|
|
493
|
+
# stream
|
|
494
|
+
command :stream, description: 'Manage stream operations'
|
|
495
|
+
commands_under(:stream) do
|
|
496
|
+
command :list, description: 'List streams', action: ->{Result::ObjectList.new(@api_node.read('ops/transfers', query_read_delete), fields: %w[id status])}
|
|
497
|
+
command :create, description: 'Create a stream',
|
|
498
|
+
arguments: [{name: :data, type: Hash}],
|
|
499
|
+
action: ->(data:, **){Result::SingleObject.new(@api_node.create('streams', data))}
|
|
500
|
+
command :show, description: 'Show a stream',
|
|
501
|
+
arguments: [{name: :transfer_id, type: :identifier}],
|
|
502
|
+
action: ->(transfer_id:, **){Result::SingleObject.new(@api_node.read("ops/transfers/#{transfer_id}"))}
|
|
503
|
+
command :modify, description: 'Modify a stream',
|
|
504
|
+
arguments: [{name: :transfer_id, type: :identifier}, {name: :data, type: Hash}],
|
|
505
|
+
action: ->(data:, transfer_id:, **){Result::SingleObject.new(@api_node.update("streams/#{transfer_id}", data))}
|
|
506
|
+
command :cancel, description: 'Cancel a stream',
|
|
507
|
+
arguments: [{name: :transfer_id, type: :identifier}],
|
|
508
|
+
action: ->(transfer_id:, **){Result::SingleObject.new(@api_node.cancel("streams/#{transfer_id}"))}
|
|
509
|
+
end
|
|
510
|
+
# transfer
|
|
511
|
+
command :transfer, description: 'Manage transfer operations'
|
|
512
|
+
commands_under(:transfer) do
|
|
513
|
+
command :list, description: 'List transfers'
|
|
514
|
+
command :cancel, description: 'Cancel a transfer',
|
|
515
|
+
arguments: [{name: :transfer_id, type: :identifier}]
|
|
516
|
+
command :show, description: 'Show a transfer',
|
|
517
|
+
arguments: [{name: :transfer_id, type: :identifier}],
|
|
518
|
+
action: ->(transfer_id:, **){Result::SingleObject.new(@api_node.read("ops/transfers/#{transfer_id}"))}
|
|
519
|
+
command :modify, description: 'Modify a transfer',
|
|
520
|
+
arguments: [{name: :transfer_id, type: :identifier}, {name: :update_value, type: Hash}]
|
|
521
|
+
command :bandwidth_average, description: 'Show average bandwidth per period'
|
|
522
|
+
command :sessions, description: 'List transfer sessions'
|
|
523
|
+
end
|
|
524
|
+
# service
|
|
525
|
+
command :service, description: 'Manage services'
|
|
526
|
+
commands_under(:service) do
|
|
527
|
+
command :list, description: 'List services', action: ->{Result::ObjectList.new(@api_node.read('rund/services')['services'])}
|
|
528
|
+
command :create, description: 'Create a service',
|
|
529
|
+
arguments: [{name: :creation_data, type: Hash}]
|
|
530
|
+
command :delete, description: 'Delete a service',
|
|
531
|
+
arguments: [{name: :service_id, type: :identifier}]
|
|
532
|
+
end
|
|
533
|
+
# watch_folder
|
|
534
|
+
command :watch_folder, description: 'Manage watch folders', setup: :setup_watch_folder
|
|
535
|
+
commands_under(:watch_folder) do
|
|
536
|
+
command :create, description: 'Create a watch folder',
|
|
537
|
+
arguments: [{name: :data, type: Hash}],
|
|
538
|
+
action: ->(data:, **){Result::Status.new("#{@api_node.create('v3/watchfolders', data)['id']} created")}
|
|
539
|
+
command :list, description: 'List watch folders',
|
|
540
|
+
action: ->{Result::ValueList.new(@api_node.read('v3/watchfolders', query_read_delete)['ids'])}
|
|
541
|
+
command :show, description: 'Show a watch folder',
|
|
542
|
+
arguments: [{name: :res_id, type: :identifier}],
|
|
543
|
+
action: ->(res_id:, **){Result::SingleObject.new(@api_node.read("v3/watchfolders/#{res_id}"))}
|
|
544
|
+
command :modify, description: 'Modify a watch folder',
|
|
545
|
+
arguments: [{name: :res_id, type: :identifier}, {name: :data, type: Hash}]
|
|
546
|
+
command :delete, description: 'Delete a watch folder',
|
|
547
|
+
arguments: [{name: :res_id, type: :identifier}]
|
|
548
|
+
command :state, description: 'Show watch folder state',
|
|
549
|
+
arguments: [{name: :res_id, type: :identifier}],
|
|
550
|
+
action: ->(res_id:, **){Result::SingleObject.new(@api_node.read("v3/watchfolders/#{res_id}/state"))}
|
|
551
|
+
end
|
|
552
|
+
# central
|
|
553
|
+
command :central, description: 'Query Central service'
|
|
554
|
+
commands_under(:central) do
|
|
555
|
+
command :session, description: 'Query sessions'
|
|
556
|
+
command :file, description: 'Query files'
|
|
557
|
+
end
|
|
558
|
+
commands_under(%i[central session]) do
|
|
559
|
+
command :list, description: 'List sessions',
|
|
560
|
+
arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
|
|
561
|
+
end
|
|
562
|
+
commands_under(%i[central file]) do
|
|
563
|
+
command :list, description: 'List file transfers',
|
|
564
|
+
arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
|
|
565
|
+
command :modify, description: 'Modify file transfer validation',
|
|
566
|
+
arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
|
|
567
|
+
end
|
|
568
|
+
# Standalone leaf commands
|
|
569
|
+
command :asperabrowser, description: 'Open Aspera browser'
|
|
570
|
+
command :basic_token, description: 'Generate basic auth token', action: ->{Result::Text.new(Rest.basic_authorization(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true)))}
|
|
571
|
+
command :bearer_token, description: 'Generate bearer token',
|
|
572
|
+
arguments: [{name: :private_key_pem, type: String}, {name: :user_group_id, type: Hash}]
|
|
573
|
+
command :simulator, description: 'Start node simulator',
|
|
574
|
+
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
575
|
+
command :telemetry, description: 'Report telemetry to external system',
|
|
576
|
+
arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
|
|
577
|
+
|
|
578
|
+
# --- Handler methods (Gen3) ---
|
|
579
|
+
|
|
580
|
+
def action_delete(paths:, **)
|
|
581
|
+
# TODO: add query for recursive
|
|
582
|
+
paths_to_delete = Array(paths)
|
|
583
|
+
@node_path_prefix&.add_to_paths!(paths_to_delete)
|
|
584
|
+
resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
|
|
585
|
+
cli_result_from_paths_response(resp, 'file deleted')
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def action_search(search_root:, **)
|
|
589
|
+
search_root = @node_path_prefix.nil? ? search_root : @node_path_prefix.add_to_path(search_root)
|
|
590
|
+
parameters = {'path' => search_root}
|
|
591
|
+
other_options = options.get_option(:query)
|
|
592
|
+
parameters.merge!(other_options) unless other_options.nil?
|
|
593
|
+
resp = @api_node.create('files/search', parameters)
|
|
594
|
+
return Result::Empty.new if resp['items'].empty?
|
|
595
|
+
fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
|
|
596
|
+
formatter.display_item_count(resp['item_count'], resp['total_count'])
|
|
597
|
+
formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
|
598
|
+
@node_path_prefix&.remove_in_object_list!(resp['items'])
|
|
599
|
+
Result::ObjectList.new(resp['items'], fields: fields)
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def action_space(path_list:, **)
|
|
603
|
+
path_list = Array(path_list)
|
|
604
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
605
|
+
resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
|
|
606
|
+
@node_path_prefix&.remove_in_object_list!(resp['paths'])
|
|
607
|
+
Result::ObjectList.new(resp['paths'])
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def action_mkdir(path_list:, **)
|
|
611
|
+
path_list = Array(path_list)
|
|
612
|
+
@node_path_prefix&.add_to_paths!(path_list)
|
|
613
|
+
resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
|
|
614
|
+
cli_result_from_paths_response(resp, 'folder created')
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def action_mklink(target:, link_path:, **)
|
|
618
|
+
target = @node_path_prefix.nil? ? target : @node_path_prefix.add_to_path(target)
|
|
619
|
+
one_path = @node_path_prefix.nil? ? link_path : @node_path_prefix.add_to_path(link_path)
|
|
620
|
+
resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
|
|
621
|
+
cli_result_from_paths_response(resp, 'link created')
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def action_mkfile(file_path:, contents:, **)
|
|
625
|
+
one_path = @node_path_prefix.nil? ? file_path : @node_path_prefix.add_to_path(file_path)
|
|
626
|
+
contents64 = contents.nil? ? '' : Base64.strict_encode64(contents)
|
|
627
|
+
resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
|
|
628
|
+
cli_result_from_paths_response(resp, 'file created')
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def action_rename(path_base:, path_src:, path_dst:, **)
|
|
632
|
+
# TODO: multiple ?
|
|
633
|
+
path_base = @node_path_prefix.nil? ? path_base : @node_path_prefix.add_to_path(path_base)
|
|
634
|
+
path_src = @node_path_prefix.nil? ? path_src : @node_path_prefix.add_to_path(path_src)
|
|
635
|
+
path_dst = @node_path_prefix.nil? ? path_dst : @node_path_prefix.add_to_path(path_dst)
|
|
636
|
+
resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
|
|
637
|
+
cli_result_from_paths_response(resp, 'entry moved')
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Obtains a transfer spec via the Node API for the given direction/folders.
|
|
641
|
+
def sync_gen3_block
|
|
642
|
+
lambda do |direction, local_path, remote_path|
|
|
643
|
+
request_transfer_spec = sync_spec_request(direction, local_path, remote_path)
|
|
644
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
|
645
|
+
transfer_spec = @api_node.create(
|
|
646
|
+
'files/sync_setup',
|
|
647
|
+
{transfer_requests: [{transfer_request: request_transfer_spec}]}
|
|
648
|
+
)['transfer_specs'].first['transfer_spec']
|
|
649
|
+
transfer_spec.delete_if{ |_k, v| v.nil?}
|
|
650
|
+
Log.dump(:ts, transfer_spec)
|
|
651
|
+
transfer_spec
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
Sync::Operations::DIRECTIONS.each do |dir|
|
|
656
|
+
define_method(:"action_sync_#{dir}"){ |**| run_sync_transfer(dir, &sync_gen3_block)}
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def action_upload
|
|
660
|
+
# empty transfer spec for authorization request
|
|
661
|
+
request_transfer_spec = {}
|
|
662
|
+
request_transfer_spec[:paths] = [{destination: transfer.destination_folder(Transfer::Spec::DIRECTION_SEND)}]
|
|
663
|
+
# add fixed parameters if any (for COS)
|
|
664
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
|
665
|
+
Api::Node.add_public_key(request_transfer_spec)
|
|
666
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
|
667
|
+
transfer_spec = @api_node.create('files/upload_setup', setup_payload)['transfer_specs'].first['transfer_spec']
|
|
668
|
+
Api::Node.add_private_key(transfer_spec)
|
|
669
|
+
transfer_spec.delete('paths')
|
|
670
|
+
Runner.result_transfer(transfer.start(transfer_spec))
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def action_download
|
|
674
|
+
# empty transfer spec for authorization request
|
|
675
|
+
request_transfer_spec = {}
|
|
676
|
+
request_transfer_spec[:paths] = transfer.ts_source_paths
|
|
677
|
+
# add fixed parameters if any (for COS)
|
|
678
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
|
679
|
+
Api::Node.add_public_key(request_transfer_spec)
|
|
680
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
|
681
|
+
transfer_spec = @api_node.create('files/download_setup', setup_payload)['transfer_specs'].first['transfer_spec']
|
|
682
|
+
Api::Node.add_private_key(transfer_spec)
|
|
683
|
+
Runner.result_transfer(transfer.start(transfer_spec))
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def action_cat(remote_path:, **)
|
|
687
|
+
remote_path = @node_path_prefix.nil? ? remote_path : @node_path_prefix.add_to_path(remote_path)
|
|
688
|
+
http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
|
|
689
|
+
Result::Text.new(http.body)
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def action_health
|
|
693
|
+
nagios = Nagios.new
|
|
694
|
+
begin
|
|
695
|
+
info = @api_node.read('info')
|
|
696
|
+
nagios.add_ok('node api', 'accessible')
|
|
697
|
+
nagios.check_time_offset(info['current_time'], 'node api')
|
|
698
|
+
nagios.check_product_version('node api', 'entsrv', info['version'])
|
|
699
|
+
rescue StandardError => e
|
|
700
|
+
nagios.add_critical('node api', e.to_s)
|
|
701
|
+
end
|
|
702
|
+
begin
|
|
703
|
+
@api_node.call(
|
|
704
|
+
operation: 'POST',
|
|
705
|
+
subpath: 'services/soap/Transfer-201210',
|
|
706
|
+
content_type: Mime::TEXT,
|
|
707
|
+
body: CENTRAL_SOAP_API_TEST,
|
|
708
|
+
headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
|
|
709
|
+
ret: :resp
|
|
710
|
+
).body
|
|
711
|
+
nagios.add_ok('central', 'accessible by node')
|
|
712
|
+
rescue StandardError => e
|
|
713
|
+
nagios.add_critical('central', e.to_s)
|
|
714
|
+
end
|
|
715
|
+
Result::ObjectList.new(nagios.status_list)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
# watch_folder setup: inject required API header (avoids "Unable to convert 2016_09_14 configuration")
|
|
719
|
+
def setup_watch_folder(**)
|
|
773
720
|
@api_node.params[:headers] ||= {}
|
|
774
721
|
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
722
|
+
{}
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
def action_access_keys_do_permission_modify(data:, apifid:, perm_id:, **)
|
|
726
|
+
apifid.node_api.update("permissions/#{perm_id}", data)
|
|
727
|
+
Result::Status.new('Updated')
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def action_watch_folder_modify(data:, res_id:, **)
|
|
731
|
+
@api_node.update("v3/watchfolders/#{res_id}", data)
|
|
732
|
+
Result::Status.new("#{res_id} updated")
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def action_watch_folder_delete(res_id:, **)
|
|
736
|
+
@api_node.delete("v3/watchfolders/#{res_id}")
|
|
737
|
+
Result::Status.new("#{res_id} deleted")
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# access_keys > CRUD
|
|
741
|
+
Operations::ALL.each do |op|
|
|
742
|
+
define_action_method([:access_keys, op]) do
|
|
743
|
+
entity_execute(api: @api_node, entity: 'access_keys', command: op) do |field, value|
|
|
744
|
+
Aspera.assert(field.eql?('id') && value.eql?('self'), type: BadArgument){'only selector: %id:self'}
|
|
745
|
+
@api_node.read('access_keys/self')['id']
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# access_keys > do - setup: resolve access key and root file id
|
|
751
|
+
# access_key_id is resolved by execute_leaf (via instance_arg in CommandSpec)
|
|
752
|
+
# @return [Hash] context hash containing :do_root_file_id
|
|
753
|
+
def setup_access_key_do(access_key_id:, **)
|
|
754
|
+
@do_root_file_id = options.get_option(:root_id)
|
|
755
|
+
if @do_root_file_id.nil?
|
|
756
|
+
ak_info = @api_node.read("access_keys/#{access_key_id}")
|
|
757
|
+
ak_secret = context.secret_finder.lookup(url: @api_node.base_url, username: ak_info['id'])
|
|
758
|
+
if !access_key_id.eql?('self')
|
|
759
|
+
Aspera.assert(ak_secret, type: Cli::MissingArgument){"Please provide secret for #{ak_info['id']} using option: secret or by setting a preset for #{ak_info['id']}@#{@api_node.base_url}."}
|
|
760
|
+
@api_node.auth_params[:username] = ak_info['id']
|
|
761
|
+
@api_node.auth_params[:password] = ak_secret
|
|
762
|
+
end
|
|
763
|
+
@do_root_file_id = ak_info['root_file_id']
|
|
792
764
|
end
|
|
765
|
+
{do_root_file_id: @do_root_file_id}
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# access_keys > do > permission - setup: resolve apifid from CLI path argument
|
|
769
|
+
# (path is read from CLI here as 'permission' is an intermediate node)
|
|
770
|
+
# @return [Hash] context hash containing :apifid
|
|
771
|
+
def setup_access_key_do_permission(**)
|
|
772
|
+
{apifid: apifid_from_next_arg(@do_root_file_id)}
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
# access_keys > do > browse
|
|
776
|
+
def action_access_keys_do_browse(path:, do_root_file_id:, **)
|
|
777
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
778
|
+
file_info = apifid.node_api.read("files/#{apifid.file_id}", headers: Api::Node.add_cache_control)
|
|
779
|
+
return Result::ObjectList.new([file_info], fields: GEN4_LS_FIELDS) unless file_info['type'].eql?('folder')
|
|
780
|
+
Result::ObjectList.new(apifid.node_api.list_files(apifid.file_id, query: query_read_delete), fields: GEN4_LS_FIELDS)
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# access_keys > do > find
|
|
784
|
+
def action_access_keys_do_find(path:, do_root_file_id:, **)
|
|
785
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
786
|
+
Result::ObjectList.new(@api_node.find_files(apifid.file_id, Api::Node.file_matcher_from_argument(options)), fields: ['path'])
|
|
793
787
|
end
|
|
794
788
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
789
|
+
# access_keys > do > cat
|
|
790
|
+
def action_access_keys_do_cat(path:, do_root_file_id:, **)
|
|
791
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
792
|
+
Result::Text.new(apifid.node_api.read("files/#{apifid.file_id}/content", ret: :resp).body)
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
# access_keys > do > show
|
|
796
|
+
def action_access_keys_do_show(path:, do_root_file_id:, **)
|
|
797
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
798
|
+
Result::SingleObject.new(apifid.node_api.read("files/#{apifid.file_id}"))
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# access_keys > do > modify
|
|
802
|
+
def action_access_keys_do_modify(path:, update_value:, do_root_file_id:, **)
|
|
803
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
804
|
+
apifid.node_api.update("files/#{apifid.file_id}", update_value)
|
|
805
|
+
Result::Status.new('Done')
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# access_keys > do > thumbnail
|
|
809
|
+
def action_access_keys_do_thumbnail(path:, do_root_file_id:, **)
|
|
810
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
811
|
+
Result::Image.new(apifid.node_api.read("files/#{apifid.file_id}/preview", headers: {'Accept' => 'image/png'}, ret: :resp).body)
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# access_keys > do > rename
|
|
815
|
+
def action_access_keys_do_rename(source_path:, new_name:, do_root_file_id:, **)
|
|
816
|
+
apifid = @api_node.resolve_api_fid(do_root_file_id, source_path)
|
|
817
|
+
apifid.node_api.update("files/#{apifid.file_id}", {name: new_name})
|
|
818
|
+
Result::Status.new("renamed to #{new_name}")
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# access_keys > do > delete
|
|
822
|
+
def action_access_keys_do_delete(paths:, do_root_file_id:, **)
|
|
823
|
+
is_bulk = options.get_option(:bulk)
|
|
824
|
+
Result.bulk(paths, is_bulk: is_bulk, command: :delete, id_result: 'path', bfail: options.get_option(:bfail)) do |l_path|
|
|
825
|
+
apifid = if (m = Parser.percent_selector(l_path))
|
|
826
|
+
Aspera.assert_values(m[:field], ['id'], type: BadIdentifier)
|
|
827
|
+
Api::NodeFileId.new(@api_node, m[:value])
|
|
826
828
|
else
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
829
|
+
@api_node.resolve_api_fid(do_root_file_id, l_path)
|
|
830
|
+
end
|
|
831
|
+
apifid.node_api.delete("files/#{apifid.file_id}")
|
|
832
|
+
{'path' => l_path}
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
# Shared Gen4 sync block: obtains a transfer spec via the Gen4 API for the given direction/remote_path.
|
|
837
|
+
def sync_gen4_block(do_root_file_id)
|
|
838
|
+
lambda do |direction, _local_path, remote_path|
|
|
839
|
+
ts_direction = direction.eql?(:pull) ? Transfer::Spec::DIRECTION_RECEIVE : Transfer::Spec::DIRECTION_SEND
|
|
840
|
+
apifid = @api_node.resolve_api_fid(do_root_file_id, remote_path)
|
|
841
|
+
apifid.node_api.transfer_spec_gen4(apifid.file_id, ts_direction)
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
Sync::Operations::DIRECTIONS.each do |dir|
|
|
846
|
+
define_method(:"action_access_keys_do_sync_#{dir}") do |do_root_file_id:, **|
|
|
847
|
+
run_sync_transfer(dir, &sync_gen4_block(do_root_file_id))
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# access_keys > do > upload
|
|
852
|
+
def action_access_keys_do_upload(do_root_file_id:, **)
|
|
853
|
+
apifid = @api_node.resolve_api_fid(do_root_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
|
|
854
|
+
Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_SEND)))
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
# access_keys > do > download
|
|
858
|
+
def action_access_keys_do_download(do_root_file_id:, **)
|
|
859
|
+
apifid, source_paths = @api_node.resolve_api_fid_paths(do_root_file_id, transfer.ts_source_paths)
|
|
860
|
+
Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
# access_keys > do > permission (intermediate node: re-enter DSL at permission sub-tree)
|
|
864
|
+
def action_access_keys_do_permission(do_root_file_id:, **)
|
|
865
|
+
apifid = apifid_from_next_arg(do_root_file_id)
|
|
866
|
+
dispatch_from_registry(%i[access_keys do permission], {apifid: apifid}, skip_setup: true)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# access_keys > do > v3
|
|
870
|
+
def action_access_keys_do_v3(do_root_file_id:, **)
|
|
871
|
+
# Build a fresh Node for the v3 root, then let the DSL dispatcher consume the next command.
|
|
872
|
+
v3_node = Node.new(context: context, api: @api_node.resolve_api_fid(do_root_file_id, '').node_api)
|
|
873
|
+
v3_node.execute_action
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# access_keys > do > node_info / bearer_token_node — shared helper builds the result hash
|
|
877
|
+
def gen4_apifid_info(do_root_file_id, path)
|
|
878
|
+
apifid = apifid_from_path(do_root_file_id, path)
|
|
879
|
+
result = {url: apifid.node_api.base_url, root_id: apifid.file_id}
|
|
880
|
+
case apifid.node_api.auth_params[:type]
|
|
881
|
+
when :basic
|
|
882
|
+
result[:username] = apifid.node_api.auth_params[:username]
|
|
883
|
+
result[:password] = apifid.node_api.auth_params[:password]
|
|
884
|
+
when :oauth2
|
|
885
|
+
result[:username] = apifid.node_api.params[:headers][Api::Node::HEADER_X_ASPERA_ACCESS_KEY]
|
|
886
|
+
result[:password] = apifid.node_api.oauth.authorization
|
|
887
|
+
else Aspera.error_unexpected_value(apifid.node_api.auth_params[:type]){'Node API Auth type'}
|
|
888
|
+
end
|
|
889
|
+
[apifid, result]
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def action_access_keys_do_node_info(path:, do_root_file_id:, **)
|
|
893
|
+
_apifid, result = gen4_apifid_info(do_root_file_id, path)
|
|
894
|
+
Result::SingleObject.new(result)
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
def action_access_keys_do_bearer_token_node(path:, do_root_file_id:, **)
|
|
898
|
+
apifid, result = gen4_apifid_info(do_root_file_id, path)
|
|
899
|
+
Log.dump(:result, result)
|
|
900
|
+
Aspera.assert(apifid.node_api.auth_params[:type].eql?(:oauth2), type: BadArgument){"Cannot get bearer token if authenticating with secret (#{apifid.node_api.auth_params[:type]})"}
|
|
901
|
+
Aspera.assert(OAuth::Factory.bearer_auth?(result[:password]), 'Not using bearer token auth')
|
|
902
|
+
Result::Text.new(result[:password])
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# access_keys > do > mkdir / mklink / mkfile — shared helper: resolve parent folder,
|
|
906
|
+
# build payload from query option, optionally check for name collision.
|
|
907
|
+
# @return [Array(NodeFileId, Hash)] parent apifid and payload with :name set
|
|
908
|
+
# @param path [String] full path (folder/name)
|
|
909
|
+
def gen4_mk_resolve(top_file_id, path)
|
|
910
|
+
containing_folder_path, new_item = Api::Node.split_folder(path)
|
|
911
|
+
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
|
|
912
|
+
query = options.get_option(:query)
|
|
913
|
+
check_exists = true
|
|
914
|
+
payload = {name: new_item}
|
|
915
|
+
if query
|
|
916
|
+
check_exists = !query.delete('check').eql?(false)
|
|
917
|
+
target = query.delete('target')
|
|
918
|
+
if target
|
|
919
|
+
target_apifid = @api_node.resolve_api_fid(top_file_id, target, true)
|
|
920
|
+
payload[:target_id] = target_apifid.file_id
|
|
840
921
|
end
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
922
|
+
payload.merge!(query.symbolize_keys)
|
|
923
|
+
end
|
|
924
|
+
if check_exists
|
|
925
|
+
folder_content = apifid.node_api.read("files/#{apifid.file_id}/files")
|
|
926
|
+
link_name = ".#{new_item}.asp-lnk"
|
|
927
|
+
found = folder_content.find{ |i| i['name'].eql?(new_item) || i['name'].eql?(link_name)}
|
|
928
|
+
Aspera.assert(!found, type: Cli::Error){"A #{found['type']} already exists with name #{new_item}"}
|
|
929
|
+
end
|
|
930
|
+
[apifid, payload]
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
def action_access_keys_do_mkdir(path:, do_root_file_id:, **)
|
|
934
|
+
apifid, payload = gen4_mk_resolve(do_root_file_id, path)
|
|
935
|
+
payload[:type] = :folder
|
|
936
|
+
Result::SingleObject.new(apifid.node_api.create("files/#{apifid.file_id}/files", payload))
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
def action_access_keys_do_mklink(path:, do_root_file_id:, **)
|
|
940
|
+
apifid, payload = gen4_mk_resolve(do_root_file_id, path)
|
|
941
|
+
payload[:type] = :link
|
|
942
|
+
Aspera.assert(payload[:target_id], 'Missing target_id')
|
|
943
|
+
Aspera.assert(payload[:target_node_id], 'Missing target_node_id')
|
|
944
|
+
Result::SingleObject.new(apifid.node_api.create("files/#{apifid.file_id}/files", payload))
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
def action_access_keys_do_mkfile(path:, contents:, do_root_file_id:, **)
|
|
948
|
+
apifid, payload = gen4_mk_resolve(do_root_file_id, path)
|
|
949
|
+
payload[:type] = :file
|
|
950
|
+
payload[:contents] = contents.nil? ? '' : Base64.strict_encode64(contents)
|
|
951
|
+
Result::SingleObject.new(apifid.node_api.create("files/#{apifid.file_id}/files", payload))
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
# access_keys > do > permission > list/show/create/modify/delete
|
|
955
|
+
def action_access_keys_do_permission_list(apifid:, **)
|
|
956
|
+
list_query = query_read_delete(default: Rest.php_style({'include' => %w[access_level permission_count]}))
|
|
957
|
+
# Specify file to get permissions for unless not specified (then, get all permissions)
|
|
958
|
+
list_query['file_id'] = apifid.file_id unless apifid.file_id.to_s.empty?
|
|
959
|
+
list_query['inherited'] = false if list_query.key?('file_id') && !list_query.key?('inherited')
|
|
960
|
+
Result::ObjectList.new(apifid.node_api.read_with_pages('permissions', list_query))
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def action_access_keys_do_permission_delete(perm_id:, apifid:, **)
|
|
964
|
+
is_bulk = options.get_option(:bulk)
|
|
965
|
+
items = perm_id.is_a?(Array) ? perm_id : [perm_id]
|
|
966
|
+
Result.bulk(items, is_bulk: is_bulk, command: :delete, bfail: options.get_option(:bfail)) do |one_id|
|
|
967
|
+
apifid.node_api.delete("permissions/#{one_id}")
|
|
968
|
+
the_app = apifid.node_api.app_info
|
|
969
|
+
the_app&.api&.permissions_send_event(event_data: {}, app_info: the_app, types: ['permission.deleted'])
|
|
970
|
+
{'id' => one_id}
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
def action_access_keys_do_permission_create(data:, apifid:, **)
|
|
975
|
+
create_param = data
|
|
976
|
+
Aspera.assert(!create_param.key?('file_id'), type: Cli::BadArgument){'no file_id'}
|
|
977
|
+
create_param['file_id'] = apifid.file_id
|
|
978
|
+
create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
|
979
|
+
the_app = apifid.node_api.app_info
|
|
980
|
+
the_app&.api&.permissions_set_create_params(perm_data: create_param, app_info: the_app)
|
|
981
|
+
created_data = apifid.node_api.create('permissions', create_param)
|
|
982
|
+
the_app&.api&.permissions_send_event(event_data: created_data, app_info: the_app)
|
|
983
|
+
Result::SingleObject.new(created_data)
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
# access_keys > set_bearer_key
|
|
987
|
+
def action_access_keys_set_bearer_key(access_key_id:, bearer_key_pem:, **)
|
|
988
|
+
access_key_id = @api_node.read('access_keys/self')['id'] if access_key_id.eql?('self')
|
|
989
|
+
key = OpenSSL::PKey.read(bearer_key_pem)
|
|
990
|
+
key = key.public_key if key.private?
|
|
991
|
+
@api_node.update("access_keys/#{access_key_id}", {token_verification_key: key.to_pem})
|
|
992
|
+
Result::Status.new('public key updated')
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
# async sub-commands: individual handlers
|
|
996
|
+
def action_async_show(async_id:, **)
|
|
997
|
+
async_ids = @api_node.read('async/list')['sync_ids']
|
|
998
|
+
if async_id.eql?(SpecialValues::ALL)
|
|
999
|
+
resp = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
|
|
1000
|
+
return Result::Empty.new if resp.empty?
|
|
1001
|
+
return Result::ObjectList.new(resp, fields: %w[snid name local_dir remote_dir])
|
|
1002
|
+
end
|
|
1003
|
+
Integer(async_id)
|
|
1004
|
+
resp = @api_node.create('async/summary', {'syncs' => [async_id]})['sync_summaries']
|
|
1005
|
+
return Result::Empty.new if resp.empty?
|
|
1006
|
+
Result::SingleObject.new(resp.first)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
def action_async_delete(async_id:, **)
|
|
1010
|
+
async_ids = async_id.eql?(SpecialValues::ALL) ? @api_node.read('async/list')['sync_ids'] : [async_id]
|
|
1011
|
+
Result::SingleObject.new(@api_node.create('async/delete', {'syncs' => async_ids}))
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
def action_async_bandwidth(async_id:, **)
|
|
1015
|
+
Integer(async_id)
|
|
1016
|
+
post_data = {'syncs' => [async_id], 'seconds' => 100}
|
|
1017
|
+
resp = @api_node.create('async/bandwidth', post_data)
|
|
1018
|
+
data = resp['bandwidth_data']
|
|
1019
|
+
return Result::Empty.new if data.empty?
|
|
1020
|
+
Result::ObjectList.new(data.first[async_id]['data'])
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def action_async_files(async_id:, **)
|
|
1024
|
+
Integer(async_id)
|
|
1025
|
+
post_data = {'syncs' => [async_id]}
|
|
1026
|
+
filter = options.get_option(:query)
|
|
1027
|
+
post_data.merge!(filter) unless filter.nil?
|
|
1028
|
+
resp = @api_node.create('async/files', post_data)
|
|
1029
|
+
data = resp['sync_files']
|
|
1030
|
+
data = data.first[async_id] unless data.empty?
|
|
1031
|
+
iteration_data = []
|
|
1032
|
+
skip_ids_persistency = nil
|
|
1033
|
+
if options.get_option(:once_only, mandatory: true)
|
|
1034
|
+
skip_ids_persistency = PersistencyActionOnce.new(
|
|
1035
|
+
manager: persistency,
|
|
1036
|
+
data: iteration_data,
|
|
1037
|
+
id: IdGenerator.from_list('sync_files', options.get_option(:url, mandatory: true), options.get_option(:username, mandatory: true), async_id)
|
|
1038
|
+
)
|
|
1039
|
+
data.select!{ |l| l['fnid'].to_i > iteration_data.first} unless iteration_data.first.nil?
|
|
1040
|
+
iteration_data[0] = data.last['fnid'].to_i unless data.empty?
|
|
1041
|
+
end
|
|
1042
|
+
return Result::Empty.new if data.empty?
|
|
1043
|
+
skip_ids_persistency&.save
|
|
1044
|
+
Result::ObjectList.new(data)
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
def action_async_counters(async_id:, **)
|
|
1048
|
+
Integer(async_id)
|
|
1049
|
+
resp = @api_node.create('async/counters', {'syncs' => [async_id]})['sync_counters'].first[async_id].last
|
|
1050
|
+
return Result::Empty.new if resp.nil?
|
|
1051
|
+
Result::SingleObject.new(resp)
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
def action_ssync_delete(ssync_id:, **)
|
|
1055
|
+
is_bulk = options.get_option(:bulk)
|
|
1056
|
+
items = ssync_id.is_a?(Array) ? ssync_id : [ssync_id]
|
|
1057
|
+
Result.bulk(items, is_bulk: is_bulk, command: :delete, bfail: options.get_option(:bfail)) do |one_id|
|
|
1058
|
+
@api_node.delete("asyncs/#{one_id}", query_read_delete)
|
|
1059
|
+
{'id' => one_id}
|
|
1060
|
+
end
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
def action_ssync_start(ssync_id:, **)
|
|
1064
|
+
@api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/start", content_type: Mime::TEXT, body: '', ret: :resp).body
|
|
1065
|
+
Result::Status.new('Done')
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
def action_ssync_stop(ssync_id:, **)
|
|
1069
|
+
@api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/stop", content_type: Mime::TEXT, body: '', ret: :resp).body
|
|
1070
|
+
Result::Status.new('Done')
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# transfer sub-commands
|
|
1074
|
+
def action_transfer_list
|
|
1075
|
+
transfer_filter = query_read_delete(default: {})
|
|
1076
|
+
iteration_persistency = nil
|
|
1077
|
+
if options.get_option(:once_only, mandatory: true)
|
|
1078
|
+
iteration_persistency = PersistencyActionOnce.new(
|
|
1079
|
+
manager: persistency,
|
|
1080
|
+
data: [],
|
|
1081
|
+
id: IdGenerator.from_list('node_transfers', options.get_option(:url, mandatory: true), options.get_option(:username, mandatory: true))
|
|
1082
|
+
)
|
|
1083
|
+
if transfer_filter.delete('reset')
|
|
1084
|
+
iteration_persistency.data.clear
|
|
1085
|
+
iteration_persistency.save
|
|
1086
|
+
return Result::Status.new('Persistency reset')
|
|
860
1087
|
end
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
)
|
|
876
|
-
)
|
|
877
|
-
if transfer_filter.delete('reset')
|
|
878
|
-
iteration_persistency.data.clear
|
|
879
|
-
iteration_persistency.save
|
|
880
|
-
return Result::Status.new('Persistency reset')
|
|
881
|
-
end
|
|
882
|
-
else
|
|
883
|
-
Aspera.assert(!transfer_filter.key?('reset'), 'reset only with once_only', type: Cli::BadArgument)
|
|
884
|
-
end
|
|
885
|
-
transfers_data = @api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
|
|
886
|
-
iteration_persistency&.save
|
|
887
|
-
return Result::ObjectList.new(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
|
|
888
|
-
when :sessions
|
|
889
|
-
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
|
890
|
-
sessions = transfers_data.flat_map{ |t| t['sessions']}
|
|
891
|
-
start_end = %i[start end].freeze
|
|
892
|
-
sessions.each do |session|
|
|
893
|
-
start_end.each do |what|
|
|
894
|
-
session["#{what}_time"] = session["#{what}_time_usec"] ? Time.at(session["#{what}_time_usec"] / 1_000_000.0).utc.iso8601(0) : nil
|
|
895
|
-
end
|
|
896
|
-
end
|
|
897
|
-
return Result::ObjectList.new(sessions, fields: %w[id status start_time end_time target_rate_kbps])
|
|
898
|
-
when :cancel
|
|
899
|
-
@api_node.cancel("ops/transfers/#{options.instance_identifier}")
|
|
900
|
-
return Result::Status.new('Cancelled')
|
|
901
|
-
when :show
|
|
902
|
-
resp = @api_node.read("ops/transfers/#{options.instance_identifier}")
|
|
903
|
-
return Result::SingleObject.new(resp)
|
|
904
|
-
when :modify
|
|
905
|
-
@api_node.update("ops/transfers/#{options.instance_identifier}", options.get_next_argument('update value', validation: Hash))
|
|
906
|
-
return Result::Status.new('Modified')
|
|
907
|
-
when :bandwidth_average
|
|
908
|
-
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
|
909
|
-
# collect all key dates
|
|
910
|
-
bandwidth_period = {}
|
|
911
|
-
dir_info = %i[avg_kbps sessions].freeze
|
|
912
|
-
transfers_data.each do |transfer|
|
|
913
|
-
session = transfer
|
|
914
|
-
# transfer['sessions'].each do |session|
|
|
915
|
-
next if session['avg_rate_kbps'].zero?
|
|
916
|
-
bandwidth_period[session['start_time_usec']] = 0
|
|
917
|
-
bandwidth_period[session['end_time_usec']] = 0
|
|
918
|
-
# end
|
|
919
|
-
end
|
|
920
|
-
result = []
|
|
921
|
-
# all dates sorted numerically
|
|
922
|
-
all_dates = bandwidth_period.keys.sort
|
|
923
|
-
all_dates.each_with_index do |start_date, index|
|
|
924
|
-
end_date = all_dates[index + 1]
|
|
925
|
-
# do not process last one
|
|
926
|
-
break if end_date.nil?
|
|
927
|
-
# init data for this period
|
|
928
|
-
period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).to_h do |direction|
|
|
929
|
-
[direction, dir_info.to_h{ |k2| [k2, 0]}]
|
|
930
|
-
end
|
|
931
|
-
# find all transfers that were active at this time
|
|
932
|
-
transfers_data.each do |transfer|
|
|
933
|
-
session = transfer
|
|
934
|
-
# transfer['sessions'].each do |session|
|
|
935
|
-
# skip if not information for this period
|
|
936
|
-
next if session['avg_rate_kbps'].zero?
|
|
937
|
-
# skip if not in this period
|
|
938
|
-
next if session['start_time_usec'] >= end_date || session['end_time_usec'] <= start_date
|
|
939
|
-
info = period_bandwidth[transfer['start_spec']['direction'].to_sym]
|
|
940
|
-
info[:avg_kbps] += session['avg_rate_kbps']
|
|
941
|
-
info[:sessions] += 1
|
|
942
|
-
# end
|
|
943
|
-
end
|
|
944
|
-
next if Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).all? do |dir|
|
|
945
|
-
period_bandwidth[dir][:sessions].zero?
|
|
946
|
-
end
|
|
947
|
-
result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
|
|
948
|
-
end
|
|
949
|
-
return Result::ObjectList.new(result)
|
|
950
|
-
else Aspera.error_unexpected_value(command)
|
|
1088
|
+
else
|
|
1089
|
+
Aspera.assert(!transfer_filter.key?('reset'), 'reset only with once_only', type: Cli::BadArgument)
|
|
1090
|
+
end
|
|
1091
|
+
transfers_data = @api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
|
|
1092
|
+
iteration_persistency&.save
|
|
1093
|
+
Result::ObjectList.new(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
def action_transfer_sessions
|
|
1097
|
+
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
|
1098
|
+
sessions = transfers_data.flat_map{ |t| t['sessions']}
|
|
1099
|
+
sessions.each do |session|
|
|
1100
|
+
SESSION_TIME_FIELDS.each do |what|
|
|
1101
|
+
session["#{what}_time"] = session["#{what}_time_usec"] ? Time.at(session["#{what}_time_usec"] / 1_000_000.0).utc.iso8601(0) : nil
|
|
951
1102
|
end
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1103
|
+
end
|
|
1104
|
+
Result::ObjectList.new(sessions, fields: %w[id status start_time end_time target_rate_kbps])
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
def action_transfer_cancel(transfer_id:, **)
|
|
1108
|
+
@api_node.cancel("ops/transfers/#{transfer_id}")
|
|
1109
|
+
Result::Status.new('Cancelled')
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
def action_transfer_modify(update_value:, transfer_id:, **)
|
|
1113
|
+
@api_node.update("ops/transfers/#{transfer_id}", update_value)
|
|
1114
|
+
Result::Status.new('Modified')
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
def action_transfer_bandwidth_average
|
|
1118
|
+
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
|
1119
|
+
bandwidth_period = {}
|
|
1120
|
+
dir_info = %i[avg_kbps sessions].freeze
|
|
1121
|
+
transfers_data.each do |t|
|
|
1122
|
+
next if t['avg_rate_kbps'].zero?
|
|
1123
|
+
bandwidth_period[t['start_time_usec']] = 0
|
|
1124
|
+
bandwidth_period[t['end_time_usec']] = 0
|
|
1125
|
+
end
|
|
1126
|
+
result = []
|
|
1127
|
+
all_dates = bandwidth_period.keys.sort
|
|
1128
|
+
all_dates.each_with_index do |start_date, index|
|
|
1129
|
+
end_date = all_dates[index + 1]
|
|
1130
|
+
break if end_date.nil?
|
|
1131
|
+
period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).to_h do |dir|
|
|
1132
|
+
[dir, dir_info.to_h{ |k2| [k2, 0]}]
|
|
967
1133
|
end
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
request_data = options.get_option(:query) || {}
|
|
975
|
-
case command
|
|
976
|
-
when :session
|
|
977
|
-
command = options.get_next_command([:list])
|
|
978
|
-
case command
|
|
979
|
-
when :list
|
|
980
|
-
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
|
981
|
-
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
|
982
|
-
resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
|
|
983
|
-
return Result::ObjectList.new(resp['session_info_result']['session_info'], fields: %w[session_uuid status transport direction bytes_transferred])
|
|
984
|
-
end
|
|
985
|
-
when :file
|
|
986
|
-
command = options.get_next_command(%i[list modify])
|
|
987
|
-
case command
|
|
988
|
-
when :list
|
|
989
|
-
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
|
990
|
-
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
|
991
|
-
resp = @api_node.create('services/rest/transfers/v1/files', request_data)
|
|
992
|
-
resp = JSON.parse(resp) if resp.is_a?(String)
|
|
993
|
-
Log.dump(:resp, resp)
|
|
994
|
-
return Result::ObjectList.new(resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path])
|
|
995
|
-
when :modify
|
|
996
|
-
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
|
997
|
-
request_data.deep_merge!(validation) unless validation.nil?
|
|
998
|
-
@api_node.update('services/rest/transfers/v1/files', request_data)
|
|
999
|
-
return Result::Status.new('updated')
|
|
1000
|
-
end
|
|
1134
|
+
transfers_data.each do |t|
|
|
1135
|
+
next if t['avg_rate_kbps'].zero?
|
|
1136
|
+
next if t['start_time_usec'] >= end_date || t['end_time_usec'] <= start_date
|
|
1137
|
+
info = period_bandwidth[t['start_spec']['direction'].to_sym]
|
|
1138
|
+
info[:avg_kbps] += t['avg_rate_kbps']
|
|
1139
|
+
info[:sessions] += 1
|
|
1001
1140
|
end
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1141
|
+
next if Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).all?{ |dir| period_bandwidth[dir][:sessions].zero?}
|
|
1142
|
+
result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
|
|
1143
|
+
end
|
|
1144
|
+
Result::ObjectList.new(result)
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
# service sub-commands
|
|
1148
|
+
def action_service_create(creation_data:, **)
|
|
1149
|
+
resp = @api_node.create('rund/services', creation_data)
|
|
1150
|
+
Result::Status.new("#{resp['id']} created")
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
def action_service_delete(service_id:, **)
|
|
1154
|
+
@api_node.delete("rund/services/#{service_id}")
|
|
1155
|
+
Result::Status.new("#{service_id} deleted")
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
# central: shared helper
|
|
1159
|
+
def central_validation
|
|
1160
|
+
validator_id = options.get_option(:validator)
|
|
1161
|
+
validator_id ? {'validator_id' => validator_id} : nil
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
# central > session > list
|
|
1165
|
+
def action_central_session_list(request_data:, **)
|
|
1166
|
+
request_data ||= {}
|
|
1167
|
+
validation = central_validation
|
|
1168
|
+
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
|
1169
|
+
resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
|
|
1170
|
+
Result::ObjectList.new(resp['session_info_result']['session_info'], fields: %w[session_uuid status transport direction bytes_transferred])
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
# central > file > list
|
|
1174
|
+
def action_central_file_list(request_data:, **)
|
|
1175
|
+
request_data ||= {}
|
|
1176
|
+
validation = central_validation
|
|
1177
|
+
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
|
1178
|
+
resp = @api_node.create('services/rest/transfers/v1/files', request_data)
|
|
1179
|
+
resp = JSON.parse(resp) if resp.is_a?(String)
|
|
1180
|
+
Log.dump(:resp, resp)
|
|
1181
|
+
Result::ObjectList.new(resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path])
|
|
1182
|
+
end
|
|
1183
|
+
|
|
1184
|
+
# central > file > modify
|
|
1185
|
+
def action_central_file_modify(request_data:, **)
|
|
1186
|
+
request_data ||= {}
|
|
1187
|
+
validation = central_validation
|
|
1188
|
+
request_data.deep_merge!(validation) unless validation.nil?
|
|
1189
|
+
@api_node.update('services/rest/transfers/v1/files', request_data)
|
|
1190
|
+
Result::Status.new('updated')
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
def action_asperabrowser
|
|
1194
|
+
browse_params = {
|
|
1195
|
+
'nodeUser' => options.get_option(:username, mandatory: true),
|
|
1196
|
+
'nodePW' => options.get_option(:password, mandatory: true),
|
|
1197
|
+
'nodeURL' => options.get_option(:url, mandatory: true)
|
|
1198
|
+
}
|
|
1199
|
+
# encode parameters so that it looks good in url
|
|
1200
|
+
encoded_params = Base64.strict_encode64(Zlib::Deflate.deflate(JSON.generate(browse_params))).gsub(/=+$/, '').tr('+/', '-_').reverse
|
|
1201
|
+
Environment.instance.open_uri("#{options.get_option(:asperabrowserurl)}?goto=#{encoded_params}")
|
|
1202
|
+
return Result::Status.new('done')
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
def action_bearer_token(private_key_pem:, user_group_id:, **)
|
|
1206
|
+
private_key = OpenSSL::PKey::RSA.new(private_key_pem)
|
|
1207
|
+
access_key = options.get_option(:username, mandatory: true)
|
|
1208
|
+
Result::Text.new(Api::Node.bearer_token(payload: user_group_id, access_key: access_key, private_key: private_key))
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
def action_simulator(parameters: {}, **)
|
|
1212
|
+
require 'aspera/node_simulator'
|
|
1213
|
+
parameters = parameters.symbolize_keys
|
|
1214
|
+
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
1215
|
+
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
1216
|
+
server.mount(uri.path, NodeSimulatorServlet, parameters.except(*WebServerSimple::PARAMS), NodeSimulator.new)
|
|
1217
|
+
server.start
|
|
1218
|
+
return Result::Status.new('Simulator terminated')
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
def action_telemetry(parameters: {}, **)
|
|
1222
|
+
parameters = parameters.symbolize_keys
|
|
1223
|
+
%i[url key].each do |psym|
|
|
1224
|
+
Aspera.assert(parameters.key?(psym), type: Cli::BadArgument){"Missing parameter: #{psym}"}
|
|
1225
|
+
end
|
|
1226
|
+
require 'socket'
|
|
1227
|
+
parameters[:interval] = 10 unless parameters.key?(:interval)
|
|
1228
|
+
parameters[:hostname] = Socket.gethostname unless parameters.key?(:hostname)
|
|
1229
|
+
interval = parameters[:interval].to_f
|
|
1230
|
+
Aspera.assert(interval > 0, type: Cli::BadArgument){'Interval must be a positive number in seconds'}
|
|
1231
|
+
otel_api = Rest.new(
|
|
1232
|
+
base_url: "#{parameters[:url]}/v1",
|
|
1233
|
+
headers: {
|
|
1234
|
+
# 'Authorization' => "apiToken #{parameters[:key]}",
|
|
1235
|
+
'x-instana-key' => parameters[:key],
|
|
1236
|
+
'x-instana-host' => parameters[:hostname]
|
|
1007
1237
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
private_key = OpenSSL::PKey::RSA.new(options.get_next_argument('private RSA key PEM value', validation: String))
|
|
1016
|
-
token_info = options.get_next_argument('user and group identification', validation: Hash)
|
|
1017
|
-
access_key = options.get_option(:username, mandatory: true)
|
|
1018
|
-
return Result::Text.new(Api::Node.bearer_token(payload: token_info, access_key: access_key, private_key: private_key))
|
|
1019
|
-
when :simulator
|
|
1020
|
-
require 'aspera/node_simulator'
|
|
1021
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
1022
|
-
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
|
1023
|
-
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
|
1024
|
-
server.mount(uri.path, NodeSimulatorServlet, parameters.except(*WebServerSimple::PARAMS), NodeSimulator.new)
|
|
1025
|
-
server.start
|
|
1026
|
-
return Result::Status.new('Simulator terminated')
|
|
1027
|
-
when :telemetry
|
|
1028
|
-
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
|
1029
|
-
%i[url key].each do |psym|
|
|
1030
|
-
raise Cli::BadArgument, "Missing parameter: #{psym}" unless parameters.key?(psym)
|
|
1031
|
-
end
|
|
1032
|
-
require 'socket'
|
|
1033
|
-
parameters[:interval] = 10 unless parameters.key?(:interval)
|
|
1034
|
-
parameters[:hostname] = Socket.gethostname unless parameters.key?(:hostname)
|
|
1035
|
-
interval = parameters[:interval].to_f
|
|
1036
|
-
raise Cli::BadArgument, 'Interval must be a positive number in seconds' if interval <= 0
|
|
1037
|
-
otel_api = Rest.new(
|
|
1038
|
-
base_url: "#{parameters[:url]}/v1",
|
|
1039
|
-
headers: {
|
|
1040
|
-
# 'Authorization' => "apiToken #{parameters[:key]}",
|
|
1041
|
-
'x-instana-key' => parameters[:key],
|
|
1042
|
-
'x-instana-host' => parameters[:hostname]
|
|
1043
|
-
}
|
|
1044
|
-
)
|
|
1045
|
-
datapoint = {
|
|
1046
|
-
attributes: [
|
|
1047
|
-
{
|
|
1048
|
-
key: 'server.name',
|
|
1049
|
-
value: {
|
|
1050
|
-
stringValue: 'HSTS1'
|
|
1051
|
-
}
|
|
1238
|
+
)
|
|
1239
|
+
datapoint = {
|
|
1240
|
+
attributes: [
|
|
1241
|
+
{
|
|
1242
|
+
key: 'server.name',
|
|
1243
|
+
value: {
|
|
1244
|
+
stringValue: 'HSTS1'
|
|
1052
1245
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1246
|
+
}
|
|
1247
|
+
],
|
|
1248
|
+
asInt: nil,
|
|
1249
|
+
timeUnixNano: nil
|
|
1250
|
+
}
|
|
1251
|
+
# https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge
|
|
1252
|
+
metrics = {
|
|
1253
|
+
resourceMetrics: [
|
|
1254
|
+
{
|
|
1255
|
+
resource: {
|
|
1256
|
+
attributes: [
|
|
1257
|
+
{
|
|
1258
|
+
key: 'service.name',
|
|
1259
|
+
value: {
|
|
1260
|
+
stringValue: 'IBMAspera'
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
]
|
|
1264
|
+
},
|
|
1265
|
+
scopeMetrics: [
|
|
1266
|
+
{
|
|
1267
|
+
metrics: [
|
|
1063
1268
|
{
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1269
|
+
name: 'active.transfers',
|
|
1270
|
+
description: 'Number of active transfers',
|
|
1271
|
+
unit: '1',
|
|
1272
|
+
gauge: {
|
|
1273
|
+
dataPoints: [
|
|
1274
|
+
datapoint
|
|
1275
|
+
]
|
|
1067
1276
|
}
|
|
1068
1277
|
}
|
|
1069
1278
|
]
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
]
|
|
1087
|
-
}
|
|
1088
|
-
]
|
|
1089
|
-
}
|
|
1090
|
-
loop do
|
|
1091
|
-
timestamp = Time.now
|
|
1092
|
-
transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
|
|
1093
|
-
datapoint[:asInt] = transfers_data.length
|
|
1094
|
-
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
|
1095
|
-
Log.log.info("#{datapoint[:asInt]} active transfers")
|
|
1096
|
-
# https://www.ibm.com/docs/en/instana-observability/current?topic=instana-backend
|
|
1097
|
-
otel_api.create('metrics', metrics)
|
|
1098
|
-
break if interval.eql?(0.0)
|
|
1099
|
-
sleep([0.0, interval - (Time.now - timestamp)].max)
|
|
1100
|
-
end
|
|
1279
|
+
}
|
|
1280
|
+
]
|
|
1281
|
+
}
|
|
1282
|
+
]
|
|
1283
|
+
}
|
|
1284
|
+
loop do
|
|
1285
|
+
timestamp = Time.now
|
|
1286
|
+
transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
|
|
1287
|
+
datapoint[:asInt] = transfers_data.length
|
|
1288
|
+
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
|
1289
|
+
Log.log.info("#{datapoint[:asInt]} active transfers")
|
|
1290
|
+
# https://www.ibm.com/docs/en/instana-observability/current?topic=instana-backend
|
|
1291
|
+
otel_api.create('metrics', metrics)
|
|
1292
|
+
break if interval.eql?(0.0)
|
|
1293
|
+
sleep([0.0, interval - (Time.now - timestamp)].max)
|
|
1101
1294
|
end
|
|
1102
|
-
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
# Dispatch a command that has already been read from the argument stream.
|
|
1298
|
+
# Used when a new Node instance is created and the command was already consumed
|
|
1299
|
+
# (e.g., :v3 delegation, shares, cos, faspex).
|
|
1300
|
+
# Re-enters the DSL registry for all commands so Proc handlers and sub-trees
|
|
1301
|
+
# are resolved normally.
|
|
1302
|
+
# @param command [Symbol] command already consumed from the argument stream
|
|
1303
|
+
# @return [Object] CLI result
|
|
1304
|
+
def dispatch_v3_command(command)
|
|
1305
|
+
Aspera.assert_values(command, COMMANDS_GEN3 + %i[health events info slash license api_details access_keys transfer]){'v3 command'}
|
|
1306
|
+
dispatch_from_registry([command], {})
|
|
1103
1307
|
end
|
|
1104
1308
|
|
|
1105
1309
|
private
|
|
1106
1310
|
|
|
1107
1311
|
# Response has key `paths`.
|
|
1108
1312
|
# From those, check if there is an error
|
|
1109
|
-
# @return [Array] of
|
|
1313
|
+
# @return [Array<Hash>] list of hashes with 2 keys: `path` and `result`
|
|
1110
1314
|
def response_to_result(response, success_msg)
|
|
1111
1315
|
errors = []
|
|
1112
1316
|
obj_list = []
|