aspera-cli 4.22.0 → 4.24.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 +405 -364
- data/CONTRIBUTING.md +86 -29
- data/README.md +1856 -961
- data/bin/ascli +2 -1
- data/bin/asession +4 -4
- data/lib/aspera/agent/base.rb +4 -0
- data/lib/aspera/agent/connect.rb +20 -18
- data/lib/aspera/agent/desktop.rb +14 -11
- data/lib/aspera/agent/direct.rb +39 -31
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +9 -11
- data/lib/aspera/agent/transferd.rb +18 -11
- data/lib/aspera/api/aoc.rb +53 -43
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +23 -22
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +35 -21
- data/lib/aspera/ascp/installation.rb +43 -43
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +55 -24
- data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
- data/lib/aspera/cli/error.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +28 -29
- data/lib/aspera/cli/formatter.rb +191 -168
- data/lib/aspera/cli/hints.rb +38 -4
- data/lib/aspera/cli/main.rb +139 -108
- data/lib/aspera/cli/manager.rb +51 -31
- data/lib/aspera/cli/plugin.rb +149 -78
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +217 -88
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +105 -227
- data/lib/aspera/cli/plugins/console.rb +49 -18
- data/lib/aspera/cli/plugins/cos.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +45 -51
- data/lib/aspera/cli/plugins/faspex5.rb +162 -163
- data/lib/aspera/cli/plugins/faspio.rb +6 -5
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +233 -247
- data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
- data/lib/aspera/cli/plugins/preview.rb +26 -29
- data/lib/aspera/cli/plugins/server.rb +29 -28
- data/lib/aspera/cli/plugins/shares.rb +40 -28
- data/lib/aspera/cli/sync_actions.rb +101 -80
- data/lib/aspera/cli/transfer_agent.rb +55 -58
- data/lib/aspera/cli/transfer_progress.rb +29 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +160 -0
- data/lib/aspera/colors.rb +13 -8
- data/lib/aspera/command_line_builder.rb +28 -22
- data/lib/aspera/command_line_converter.rb +31 -0
- data/lib/aspera/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +144 -100
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +3 -2
- data/lib/aspera/hash_ext.rb +1 -1
- data/lib/aspera/id_generator.rb +10 -10
- data/lib/aspera/keychain/base.rb +18 -0
- data/lib/aspera/keychain/encrypted_hash.rb +6 -12
- data/lib/aspera/keychain/factory.rb +9 -3
- data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +70 -20
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +6 -2
- data/lib/aspera/oauth/factory.rb +25 -18
- data/lib/aspera/oauth/jwt.rb +13 -1
- data/lib/aspera/oauth/url_json.rb +3 -3
- data/lib/aspera/oauth/web.rb +5 -3
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +43 -35
- data/lib/aspera/preview/generator.rb +26 -13
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +2 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +8 -6
- data/lib/aspera/proxy_auto_config.rb +1 -1
- data/lib/aspera/rest.rb +46 -28
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +14 -4
- data/lib/aspera/sync/args.schema.yaml +102 -0
- data/lib/aspera/sync/conf.schema.yaml +701 -0
- data/lib/aspera/sync/database.rb +83 -0
- data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
- data/lib/aspera/temp_file_manager.rb +4 -2
- data/lib/aspera/timer_limiter.rb +7 -5
- data/lib/aspera/transfer/error.rb +1 -1
- data/lib/aspera/transfer/error_info.rb +1 -2
- data/lib/aspera/transfer/faux_file.rb +11 -10
- data/lib/aspera/transfer/parameters.rb +6 -5
- data/lib/aspera/transfer/spec.rb +15 -1
- data/lib/aspera/transfer/spec.schema.yaml +316 -293
- data/lib/aspera/transfer/spec_doc.rb +34 -16
- data/lib/aspera/transfer/uri.rb +5 -5
- data/lib/aspera/uri_reader.rb +14 -10
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +15 -15
- metadata.gz.sig +0 -0
- data/examples/dascli +0 -30
- data/examples/get_proto_file.rb +0 -8
- data/examples/proxy.pac +0 -60
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
- data/lib/aspera/transfer/sync_session.schema.yaml +0 -79
@@ -21,6 +21,7 @@ module Aspera
|
|
21
21
|
module Plugins
|
22
22
|
class Node < Cli::BasicAuthPlugin
|
23
23
|
include SyncActions
|
24
|
+
|
24
25
|
class << self
|
25
26
|
# directory: node, container: shares
|
26
27
|
FOLDER_TYPES = %w[directory container].freeze
|
@@ -58,10 +59,10 @@ module Aspera
|
|
58
59
|
Log.log.debug{"detect error: #{e}"}
|
59
60
|
end
|
60
61
|
raise error if error
|
61
|
-
return
|
62
|
+
return
|
62
63
|
end
|
63
64
|
|
64
|
-
def wizard(object:,
|
65
|
+
def wizard(object:, _private_key_path: nil, _pub_key_pem: nil)
|
65
66
|
options = object.options
|
66
67
|
return {
|
67
68
|
preset_value: {
|
@@ -76,20 +77,24 @@ module Aspera
|
|
76
77
|
def declare_options(options)
|
77
78
|
return if @options_declared
|
78
79
|
@options_declared = true
|
80
|
+
@dynamic_key = nil
|
79
81
|
options.declare(:validator, 'Identifier of validator (optional for central)')
|
80
82
|
options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
|
81
|
-
options.declare(:sync_name, 'Sync name')
|
82
83
|
options.declare(
|
83
|
-
:default_ports, 'Use standard FASP ports or get from node
|
84
|
-
handler: {o: Api::Node, m: :use_standard_ports}
|
84
|
+
:default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', values: :bool, default: :yes,
|
85
|
+
handler: {o: Api::Node, m: :use_standard_ports}
|
86
|
+
)
|
85
87
|
options.declare(
|
86
|
-
:node_cache, 'Set to no to force actual file system read
|
87
|
-
handler: {o: Api::Node, m: :use_node_cache}
|
88
|
-
|
88
|
+
:node_cache, 'Gen4: Set to no to force actual file system read', values: :bool,
|
89
|
+
handler: {o: Api::Node, m: :use_node_cache}
|
90
|
+
)
|
91
|
+
options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
|
92
|
+
options.declare(:dynamic_key, 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key})
|
89
93
|
SyncActions.declare_options(options)
|
90
94
|
options.parse_options!
|
91
95
|
end
|
92
96
|
|
97
|
+
# Using /files/browse: is it a folder (node and shares)
|
93
98
|
def gen3_entry_folder?(entry)
|
94
99
|
FOLDER_TYPES.include?(entry['type'])
|
95
100
|
end
|
@@ -104,10 +109,10 @@ module Aspera
|
|
104
109
|
'</soapenv:Envelope>'
|
105
110
|
# spellchecker: enable
|
106
111
|
|
107
|
-
#
|
112
|
+
# Fields removed in result of search
|
108
113
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
109
114
|
|
110
|
-
#
|
115
|
+
# Actions in execute_command_gen3
|
111
116
|
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport]
|
112
117
|
|
113
118
|
BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
|
@@ -120,7 +125,7 @@ module Aspera
|
|
120
125
|
# actions used commonly when a node is involved
|
121
126
|
COMMON_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
122
127
|
|
123
|
-
private_constant
|
128
|
+
private_constant :CENTRAL_SOAP_API_TEST, :SEARCH_REMOVE_FIELDS, :BASE_ACTIONS, :SPECIAL_ACTIONS, :V3_IN_V4_ACTIONS, :COMMON_ACTIONS
|
124
129
|
|
125
130
|
# used in aoc
|
126
131
|
NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
|
@@ -137,16 +142,16 @@ module Aspera
|
|
137
142
|
|
138
143
|
# @param api [Rest] an existing API object for the Node API
|
139
144
|
# @param prefix_path [String,nil] for Faspex 4, allows browsing a package
|
140
|
-
def initialize(api: nil, prefix_path: nil
|
145
|
+
def initialize(context:, api: nil, prefix_path: nil)
|
141
146
|
@prefix_path = prefix_path
|
142
|
-
super(
|
147
|
+
super(context: context, basic_options: api.nil?)
|
143
148
|
Node.declare_options(options)
|
144
|
-
return if
|
149
|
+
return if context.only_manual?
|
145
150
|
@api_node =
|
146
151
|
if !api.nil?
|
147
152
|
# this can be Api::Node or Rest (Shares)
|
148
153
|
api
|
149
|
-
elsif OAuth::Factory.
|
154
|
+
elsif OAuth::Factory.bearer_auth?(options.get_option(:password, mandatory: true))
|
150
155
|
# info is provided like node_info of aoc
|
151
156
|
Api::Node.new(
|
152
157
|
base_url: options.get_option(:url, mandatory: true),
|
@@ -160,7 +165,8 @@ module Aspera
|
|
160
165
|
type: :basic,
|
161
166
|
username: options.get_option(:username, mandatory: true),
|
162
167
|
password: options.get_option(:password, mandatory: true)
|
163
|
-
}
|
168
|
+
}
|
169
|
+
)
|
164
170
|
end
|
165
171
|
end
|
166
172
|
|
@@ -187,18 +193,17 @@ module Aspera
|
|
187
193
|
result = success_msg
|
188
194
|
if p.key?('error')
|
189
195
|
Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
|
190
|
-
result =
|
196
|
+
result = p['error']['user_message']
|
191
197
|
errors.push([p['path'], p['error']['user_message']])
|
192
198
|
end
|
193
199
|
final_result[:data].push({type => p['path'], 'result' => result})
|
194
200
|
end
|
195
201
|
# one error make all fail
|
196
|
-
unless errors.empty?
|
197
|
-
raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ')
|
198
|
-
end
|
202
|
+
raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ') unless errors.empty?
|
199
203
|
return c_result_remove_prefix_path(final_result, type)
|
200
204
|
end
|
201
205
|
|
206
|
+
# Gen3 API
|
202
207
|
def browse_gen3
|
203
208
|
folders_to_process = [get_one_argument_with_prefix('path')]
|
204
209
|
query = options.get_option(:query, default: {})
|
@@ -235,9 +240,7 @@ module Aspera
|
|
235
240
|
formatter.display_item_count(response['item_count'], total_count)
|
236
241
|
break
|
237
242
|
end
|
238
|
-
if recursive
|
239
|
-
folders_to_process.concat(items.select{ |i| Node.gen3_entry_folder?(i)}.map{ |i| i['path']})
|
240
|
-
end
|
243
|
+
folders_to_process.concat(items.select{ |i| Node.gen3_entry_folder?(i)}.map{ |i| i['path']}) if recursive
|
241
244
|
if !max_items.nil? && (all_items.count >= max_items)
|
242
245
|
all_items = all_items.slice(0, max_items) if all_items.count > max_items
|
243
246
|
break
|
@@ -349,7 +352,7 @@ module Aspera
|
|
349
352
|
transfer_spec.delete_if{ |_k, v| v.nil?}
|
350
353
|
# delete this part, as the returned value contains only destination, and not sources
|
351
354
|
# transfer_spec.delete('paths') if command.eql?(:upload)
|
352
|
-
Log.
|
355
|
+
Log.dump(:ts, transfer_spec)
|
353
356
|
transfer_spec
|
354
357
|
end
|
355
358
|
when :upload, :download
|
@@ -363,10 +366,12 @@ module Aspera
|
|
363
366
|
end
|
364
367
|
# add fixed parameters if any (for COS)
|
365
368
|
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
369
|
+
Api::Node.add_public_key(request_transfer_spec)
|
366
370
|
# prepare payload for single request
|
367
371
|
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
368
372
|
# only one request, so only one answer
|
369
373
|
transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)['transfer_specs'].first['transfer_spec']
|
374
|
+
Api::Node.add_private_key(transfer_spec)
|
370
375
|
# delete this part, as the returned value contains only destination, and not sources
|
371
376
|
transfer_spec.delete('paths') if command.eql?(:upload)
|
372
377
|
return Main.result_transfer(transfer.start(transfer_spec))
|
@@ -375,7 +380,8 @@ module Aspera
|
|
375
380
|
File.basename(remote_path)
|
376
381
|
result = @api_node.call(
|
377
382
|
operation: 'GET',
|
378
|
-
subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents"
|
383
|
+
subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents"
|
384
|
+
)
|
379
385
|
return Main.result_text(result[:http].body)
|
380
386
|
when :transport
|
381
387
|
return Main.result_single_object(@api_node.transport_params)
|
@@ -392,8 +398,12 @@ module Aspera
|
|
392
398
|
ak_command = options.get_next_command(%i[do set_bearer_key].concat(Plugin::ALL_OPS))
|
393
399
|
case ak_command
|
394
400
|
when *Plugin::ALL_OPS
|
395
|
-
return
|
396
|
-
|
401
|
+
return entity_execute(
|
402
|
+
api: @api_node,
|
403
|
+
entity: 'access_keys',
|
404
|
+
command: ak_command
|
405
|
+
) do |field, value|
|
406
|
+
raise BadArgument, 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
|
397
407
|
@api_node.read('access_keys/self')['id']
|
398
408
|
end
|
399
409
|
when :do
|
@@ -455,9 +465,7 @@ module Aspera
|
|
455
465
|
when :license
|
456
466
|
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
457
467
|
node_license = @api_node.read('license')
|
458
|
-
if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
|
459
|
-
Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal')
|
460
|
-
end
|
468
|
+
Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal') if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
|
461
469
|
return Main.result_single_object(node_license)
|
462
470
|
when :api_details
|
463
471
|
return Main.result_single_object({base_url: @api_node.base_url}.merge(@api_node.params))
|
@@ -468,7 +476,7 @@ module Aspera
|
|
468
476
|
# @return [Hash] api and main file id for given path or id in next argument
|
469
477
|
def apifid_from_next_arg(top_file_id)
|
470
478
|
file_path = instance_identifier(description: 'path or %id:<id>') do |attribute, value|
|
471
|
-
raise
|
479
|
+
raise BadArgument, 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
472
480
|
# directly return result for method
|
473
481
|
return {api: @api_node, file_id: value}
|
474
482
|
end
|
@@ -486,7 +494,7 @@ module Aspera
|
|
486
494
|
command_legacy = options.get_next_command(V3_IN_V4_ACTIONS)
|
487
495
|
# TODO: shall we support all methods here ? what if there is a link ?
|
488
496
|
apifid = @api_node.resolve_api_fid(top_file_id, '')
|
489
|
-
return Node.new(
|
497
|
+
return Node.new(context: context, api: apifid[:api]).execute_action(command_legacy)
|
490
498
|
when :node_info, :bearer_token_node
|
491
499
|
apifid = apifid_from_next_arg(top_file_id)
|
492
500
|
result = {
|
@@ -505,8 +513,8 @@ module Aspera
|
|
505
513
|
end
|
506
514
|
return Main.result_single_object(result) if command_repo.eql?(:node_info)
|
507
515
|
# check format of bearer token
|
508
|
-
OAuth::Factory.
|
509
|
-
return Main.
|
516
|
+
OAuth::Factory.bearer_token(result[:password])
|
517
|
+
return Main.result_text(result[:password])
|
510
518
|
when :browse
|
511
519
|
apifid = apifid_from_next_arg(top_file_id)
|
512
520
|
file_info = apifid[:api].read_with_cache("files/#{apifid[:file_id]}")
|
@@ -520,9 +528,8 @@ module Aspera
|
|
520
528
|
find_lambda = Api::Node.file_matcher_from_argument(options)
|
521
529
|
return Main.result_object_list(@api_node.find_files(apifid[:file_id], find_lambda), fields: ['path'])
|
522
530
|
when :mkdir, :mklink, :mkfile
|
523
|
-
containing_folder_path = options.get_next_argument('path')
|
524
|
-
|
525
|
-
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Api::Node::PATH_SEPARATOR), true)
|
531
|
+
containing_folder_path, new_item = Api::Node.split_folder(options.get_next_argument('path'))
|
532
|
+
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
|
526
533
|
query = options.get_option(:query, mandatory: false)
|
527
534
|
check_exists = true
|
528
535
|
payload = {name: new_item}
|
@@ -576,61 +583,28 @@ module Aspera
|
|
576
583
|
when :sync
|
577
584
|
return execute_sync_action do |sync_direction, _local_path, remote_path|
|
578
585
|
# Gen4 API
|
579
|
-
# direction is push pull, bidi
|
580
586
|
Aspera.assert_values(sync_direction, %i[push pull bidi])
|
581
587
|
ts_direction = case sync_direction
|
582
588
|
when :push, :bidi then Transfer::Spec::DIRECTION_SEND
|
583
589
|
when :pull then Transfer::Spec::DIRECTION_RECEIVE
|
584
590
|
else Aspera.error_unreachable_line
|
585
591
|
end
|
586
|
-
# remote is specified by option to_folder
|
592
|
+
# remote is specified by option: `to_folder`
|
587
593
|
apifid = @api_node.resolve_api_fid(top_file_id, remote_path)
|
588
|
-
|
589
|
-
Log.log.debug{Log.dump(:ts, transfer_spec)}
|
590
|
-
transfer_spec
|
594
|
+
apifid[:api].transfer_spec_gen4(apifid[:file_id], ts_direction)
|
591
595
|
end
|
592
596
|
when :upload
|
593
597
|
apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
|
594
598
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_SEND)))
|
595
599
|
when :download
|
596
|
-
source_paths = transfer.ts_source_paths
|
597
|
-
# special case for AoC : all files must be in same folder
|
598
|
-
source_folder = source_paths.shift['source']
|
599
|
-
# if a single file: split into folder and path
|
600
|
-
apifid = @api_node.resolve_api_fid(top_file_id, source_folder, true)
|
601
|
-
if source_paths.empty?
|
602
|
-
# get precise info in this element
|
603
|
-
file_info = apifid[:api].read("files/#{apifid[:file_id]}")
|
604
|
-
case file_info['type']
|
605
|
-
when 'file'
|
606
|
-
# if the single source is a file, we need to split into folder path and filename
|
607
|
-
src_dir_elements = source_folder.split(Api::Node::PATH_SEPARATOR)
|
608
|
-
# filename is the last one, source folder is what remains
|
609
|
-
source_paths = [{'source' => src_dir_elements.pop}]
|
610
|
-
apifid = @api_node.resolve_api_fid(top_file_id, src_dir_elements.join(Api::Node::PATH_SEPARATOR), true)
|
611
|
-
when 'link', 'folder'
|
612
|
-
# single source is 'folder' or 'link'
|
613
|
-
# TODO: add this ? , 'destination'=>file_info['name']
|
614
|
-
source_paths = [{'source' => '.'}]
|
615
|
-
else
|
616
|
-
raise BadArgument, "Unknown source type: #{file_info['type']}"
|
617
|
-
end
|
618
|
-
end
|
600
|
+
apifid, source_paths = @api_node.resolve_api_fid_paths(top_file_id, transfer.ts_source_paths)
|
619
601
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
|
620
602
|
when :cat
|
621
|
-
|
622
|
-
source_folder = source_paths.shift['source']
|
623
|
-
if source_paths.empty?
|
624
|
-
source_folder = source_folder.split(Api::Node::PATH_SEPARATOR)
|
625
|
-
source_paths = [{'source' => source_folder.pop}]
|
626
|
-
source_folder = source_folder.join(Api::Node::PATH_SEPARATOR)
|
627
|
-
end
|
628
|
-
raise Cli::BadArgument, 'one file at a time only in HTTP mode' if source_paths.length > 1
|
629
|
-
file_name = source_paths.first['source']
|
630
|
-
apifid = @api_node.resolve_api_fid(top_file_id, File.join(source_folder, file_name))
|
603
|
+
apifid = apifid_from_next_arg(top_file_id)
|
631
604
|
result = apifid[:api].call(
|
632
605
|
operation: 'GET',
|
633
|
-
subpath: "files/#{apifid[:file_id]}/content"
|
606
|
+
subpath: "files/#{apifid[:file_id]}/content"
|
607
|
+
)
|
634
608
|
return Main.result_text(result[:http].body)
|
635
609
|
when :show
|
636
610
|
apifid = apifid_from_next_arg(top_file_id)
|
@@ -648,7 +622,7 @@ module Aspera
|
|
648
622
|
subpath: "files/#{apifid[:file_id]}/preview",
|
649
623
|
headers: {'Accept' => 'image/png'}
|
650
624
|
)
|
651
|
-
return Main.result_image(result[:http].body
|
625
|
+
return Main.result_image(result[:http].body)
|
652
626
|
when :permission
|
653
627
|
apifid = apifid_from_next_arg(top_file_id)
|
654
628
|
command_perm = options.get_next_command(%i[list show create delete])
|
@@ -665,7 +639,7 @@ module Aspera
|
|
665
639
|
perm_id = instance_identifier
|
666
640
|
return Main.result_single_object(apifid[:api].read("permissions/#{perm_id}"))
|
667
641
|
when :delete
|
668
|
-
return do_bulk_operation(command: command_perm,
|
642
|
+
return do_bulk_operation(command: command_perm, values: :identifier) do |one_id|
|
669
643
|
apifid[:api].delete("permissions/#{one_id}")
|
670
644
|
# notify application of deletion
|
671
645
|
the_app = apifid[:api].app_info
|
@@ -692,25 +666,30 @@ module Aspera
|
|
692
666
|
Aspera.error_unreachable_line
|
693
667
|
end
|
694
668
|
|
695
|
-
#
|
669
|
+
# Search /async by name
|
670
|
+
# @param field [String] name of the field to search
|
671
|
+
# @param value [String] value of the field to search
|
672
|
+
# @return [Integer] id of the sync
|
673
|
+
# @raise [Cli::BadArgument] if no such sync, or not by name
|
674
|
+
def async_lookup(field, value)
|
675
|
+
raise Cli::BadArgument, "Only search by name is supported (#{field})" unless field.eql?('name')
|
676
|
+
async_ids = @api_node.read('async/list')['sync_ids']
|
677
|
+
summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
|
678
|
+
selected = summaries.find{ |s| s['name'].eql?(value)}
|
679
|
+
raise Cli::BadIdentifier.new('sync', "#{field}=#{value}") if selected.nil?
|
680
|
+
return selected['snid']
|
681
|
+
end
|
682
|
+
|
683
|
+
# Node API: /async (stats only)
|
696
684
|
def execute_async
|
697
685
|
command = options.get_next_command(%i[list delete files show counters bandwidth])
|
698
686
|
unless command.eql?(:list)
|
699
|
-
|
700
|
-
if
|
701
|
-
|
702
|
-
if async_id.eql?(SpecialValues::ALL) && %i[show delete].include?(command)
|
703
|
-
async_ids = @api_node.read('async/list')['sync_ids']
|
704
|
-
else
|
705
|
-
Integer(async_id) # must be integer
|
706
|
-
async_ids = [async_id]
|
707
|
-
end
|
708
|
-
else
|
687
|
+
async_id = instance_identifier{ |field, value| async_lookup(field, value)}
|
688
|
+
if async_id.eql?(SpecialValues::ALL)
|
689
|
+
raise Cli::BadArgument, 'ALL only for show and delete' unless %i[show delete].include?(command)
|
709
690
|
async_ids = @api_node.read('async/list')['sync_ids']
|
710
|
-
|
711
|
-
|
712
|
-
raise Cli::BadIdentifier, "no such sync: #{async_name}" if selected.nil?
|
713
|
-
async_id = selected['snid']
|
691
|
+
else
|
692
|
+
Integer(async_id) # must be integer
|
714
693
|
async_ids = [async_id]
|
715
694
|
end
|
716
695
|
post_data = {'syncs' => async_ids}
|
@@ -718,7 +697,7 @@ module Aspera
|
|
718
697
|
case command
|
719
698
|
when :list
|
720
699
|
resp = @api_node.read('async/list')['sync_ids']
|
721
|
-
return Main.result_value_list(resp
|
700
|
+
return Main.result_value_list(resp)
|
722
701
|
when :show
|
723
702
|
resp = @api_node.create('async/summary', post_data)['sync_summaries']
|
724
703
|
return Main.result_empty if resp.empty?
|
@@ -728,7 +707,7 @@ module Aspera
|
|
728
707
|
resp = @api_node.create('async/delete', post_data)
|
729
708
|
return Main.result_single_object(resp)
|
730
709
|
when :bandwidth
|
731
|
-
post_data['seconds'] = 100 # TODO: as parameter with --
|
710
|
+
post_data['seconds'] = 100 # TODO: as parameter with --query
|
732
711
|
resp = @api_node.create('async/bandwidth', post_data)
|
733
712
|
data = resp['bandwidth_data']
|
734
713
|
return Main.result_empty if data.empty?
|
@@ -754,10 +733,10 @@ module Aspera
|
|
754
733
|
'sync_files',
|
755
734
|
options.get_option(:url, mandatory: true),
|
756
735
|
options.get_option(:username, mandatory: true),
|
757
|
-
async_id
|
758
|
-
|
759
|
-
|
760
|
-
|
736
|
+
async_id
|
737
|
+
])
|
738
|
+
)
|
739
|
+
data.select!{ |l| l['fnid'].to_i > iteration_data.first} unless iteration_data.first.nil?
|
761
740
|
iteration_data[0] = data.last['fnid'].to_i unless data.empty?
|
762
741
|
end
|
763
742
|
return Main.result_empty if data.empty?
|
@@ -770,10 +749,11 @@ module Aspera
|
|
770
749
|
end
|
771
750
|
end
|
772
751
|
|
752
|
+
# Search /asyncs by name
|
753
|
+
# @param field [String] name of the field to search
|
754
|
+
# @param value [String] value of the field to search
|
773
755
|
# @return [Integer] id of the sync
|
774
756
|
# @raise [Cli::BadArgument] if no such sync, or not by name
|
775
|
-
# @param [String] field name of the field to search
|
776
|
-
# @param [String] value value of the field to search
|
777
757
|
def ssync_lookup(field, value)
|
778
758
|
raise Cli::BadArgument, "Only search by name is supported (#{field})" unless field.eql?('name')
|
779
759
|
@api_node.read('asyncs')['ids'].each do |id|
|
@@ -781,7 +761,41 @@ module Aspera
|
|
781
761
|
# name is unique, so we can return
|
782
762
|
return id if sync_info[field].eql?(value)
|
783
763
|
end
|
784
|
-
raise Cli::BadIdentifier, "
|
764
|
+
raise Cli::BadIdentifier.new('ssync', "#{field}=#{value}")
|
765
|
+
end
|
766
|
+
|
767
|
+
WATCH_FOLDER_MUL = %i[create list].freeze
|
768
|
+
WATCH_FOLDER_SING = %i[show modify delete state].freeze
|
769
|
+
private_constant :WATCH_FOLDER_MUL, :WATCH_FOLDER_SING
|
770
|
+
|
771
|
+
def watch_folder_action
|
772
|
+
res_class_path = 'v3/watchfolders'
|
773
|
+
command = options.get_next_command(WATCH_FOLDER_MUL + WATCH_FOLDER_SING)
|
774
|
+
if WATCH_FOLDER_SING.include?(command)
|
775
|
+
one_res_id = instance_identifier
|
776
|
+
one_res_path = "#{res_class_path}/#{one_res_id}"
|
777
|
+
end
|
778
|
+
# hum, to avoid: Unable to convert 2016_09_14 configuration
|
779
|
+
@api_node.params[:headers] ||= {}
|
780
|
+
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
781
|
+
case command
|
782
|
+
when :create
|
783
|
+
resp = @api_node.create(res_class_path, value_create_modify(command: command))
|
784
|
+
return Main.result_status("#{resp['id']} created")
|
785
|
+
when :list
|
786
|
+
resp = @api_node.read(res_class_path, query_read_delete)
|
787
|
+
return Main.result_value_list(resp['ids'])
|
788
|
+
when :show
|
789
|
+
return Main.result_single_object(@api_node.read(one_res_path))
|
790
|
+
when :modify
|
791
|
+
@api_node.update(one_res_path, options.get_option(:query, mandatory: true))
|
792
|
+
return Main.result_status("#{one_res_id} updated")
|
793
|
+
when :delete
|
794
|
+
@api_node.delete(one_res_path)
|
795
|
+
return Main.result_status("#{one_res_id} deleted")
|
796
|
+
when :state
|
797
|
+
return Main.result_single_object(@api_node.read("#{one_res_path}/state"))
|
798
|
+
end
|
785
799
|
end
|
786
800
|
|
787
801
|
ACTIONS = %i[
|
@@ -799,16 +813,22 @@ module Aspera
|
|
799
813
|
telemetry
|
800
814
|
].concat(COMMON_ACTIONS).freeze
|
801
815
|
|
802
|
-
def execute_action(command=nil)
|
816
|
+
def execute_action(command = nil)
|
803
817
|
command ||= options.get_next_command(ACTIONS)
|
804
818
|
case command
|
805
819
|
when *COMMON_ACTIONS then return execute_simple_common(command)
|
806
820
|
when :async then return execute_async # former API
|
807
821
|
when :ssync
|
808
|
-
#
|
809
|
-
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary]
|
822
|
+
# Node API: /asyncs (newer)
|
823
|
+
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary] + Plugin::ALL_OPS - %i[modify])
|
810
824
|
case sync_command
|
811
|
-
when *Plugin::ALL_OPS
|
825
|
+
when *Plugin::ALL_OPS
|
826
|
+
return entity_execute(
|
827
|
+
api: @api_node,
|
828
|
+
entity: :asyncs,
|
829
|
+
command: sync_command,
|
830
|
+
items_key: 'ids'
|
831
|
+
){ |field, value| ssync_lookup(field, value)}
|
812
832
|
else
|
813
833
|
asyncs_id = instance_identifier{ |field, value| ssync_lookup(field, value)}
|
814
834
|
if %i[start stop].include?(sync_command)
|
@@ -849,7 +869,6 @@ module Aspera
|
|
849
869
|
case command
|
850
870
|
when :list
|
851
871
|
transfer_filter = query_read_delete(default: {})
|
852
|
-
last_iteration_token = nil
|
853
872
|
iteration_persistency = nil
|
854
873
|
if options.get_option(:once_only, mandatory: true)
|
855
874
|
iteration_persistency = PersistencyActionOnce.new(
|
@@ -859,47 +878,19 @@ module Aspera
|
|
859
878
|
'node_transfers',
|
860
879
|
options.get_option(:url, mandatory: true),
|
861
880
|
options.get_option(:username, mandatory: true)
|
862
|
-
])
|
881
|
+
])
|
882
|
+
)
|
863
883
|
if transfer_filter.delete('reset')
|
864
884
|
iteration_persistency.data.clear
|
865
885
|
iteration_persistency.save
|
866
886
|
return Main.result_status('Persistency reset')
|
867
887
|
end
|
868
|
-
last_iteration_token = iteration_persistency.data.first
|
869
888
|
end
|
870
889
|
raise Cli::BadArgument, 'reset only with once_only' if transfer_filter.key?('reset') && iteration_persistency.nil?
|
871
890
|
max_items = transfer_filter.delete(MAX_ITEMS)
|
872
|
-
transfers_data =
|
873
|
-
loop do
|
874
|
-
transfer_filter['iteration_token'] = last_iteration_token unless last_iteration_token.nil?
|
875
|
-
result = @api_node.call(operation: 'GET', subpath: 'ops/transfers', query: transfer_filter)
|
876
|
-
# no data
|
877
|
-
break if result[:data].empty?
|
878
|
-
# get next iteration token from link
|
879
|
-
next_iteration_token = nil
|
880
|
-
link_info = result[:http]['Link']
|
881
|
-
unless link_info.nil?
|
882
|
-
m = link_info.match(/<([^>]+)>/)
|
883
|
-
raise "Cannot parse iteration in Link: #{link_info}" if m.nil?
|
884
|
-
next_iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
|
885
|
-
end
|
886
|
-
# same as last iteration: stop
|
887
|
-
break if next_iteration_token&.eql?(last_iteration_token)
|
888
|
-
last_iteration_token = next_iteration_token
|
889
|
-
transfers_data.concat(result[:data])
|
890
|
-
if max_items&.<=(transfers_data.length)
|
891
|
-
transfers_data = transfers_data.slice(0, max_items)
|
892
|
-
break
|
893
|
-
end
|
894
|
-
break if last_iteration_token.nil?
|
895
|
-
end
|
896
|
-
iteration_persistency&.data&.[]=(0, last_iteration_token)
|
891
|
+
transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', max: max_items, query: transfer_filter, iteration: iteration_persistency&.data)
|
897
892
|
iteration_persistency&.save
|
898
|
-
return
|
899
|
-
type: :object_list,
|
900
|
-
data: transfers_data,
|
901
|
-
fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
|
902
|
-
}
|
893
|
+
return Main.result_object_list(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
|
903
894
|
when :sessions
|
904
895
|
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
905
896
|
sessions = transfers_data.map{ |t| t['sessions']}.flatten
|
@@ -907,11 +898,7 @@ module Aspera
|
|
907
898
|
session['start_time'] = Time.at(session['start_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
908
899
|
session['end_time'] = Time.at(session['end_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
909
900
|
end
|
910
|
-
return
|
911
|
-
type: :object_list,
|
912
|
-
data: sessions,
|
913
|
-
fields: %w[id status start_time end_time target_rate_kbps]
|
914
|
-
}
|
901
|
+
return Main.result_object_list(sessions, fields: %w[id status start_time end_time target_rate_kbps])
|
915
902
|
when :cancel
|
916
903
|
resp = @api_node.cancel("ops/transfers/#{instance_identifier}")
|
917
904
|
return Main.result_single_object(resp)
|
@@ -970,9 +957,7 @@ module Aspera
|
|
970
957
|
end
|
971
958
|
when :service
|
972
959
|
command = options.get_next_command(%i[list create delete])
|
973
|
-
if [:delete].include?(command)
|
974
|
-
service_id = instance_identifier
|
975
|
-
end
|
960
|
+
service_id = instance_identifier if [:delete].include?(command)
|
976
961
|
case command
|
977
962
|
when :list
|
978
963
|
resp = @api_node.read('rund/services')
|
@@ -987,33 +972,7 @@ module Aspera
|
|
987
972
|
return Main.result_status("#{service_id} deleted")
|
988
973
|
end
|
989
974
|
when :watch_folder
|
990
|
-
|
991
|
-
command = options.get_next_command(%i[create list show modify delete state])
|
992
|
-
if %i[show modify delete state].include?(command)
|
993
|
-
one_res_id = instance_identifier
|
994
|
-
one_res_path = "#{res_class_path}/#{one_res_id}"
|
995
|
-
end
|
996
|
-
# hum, to avoid: Unable to convert 2016_09_14 configuration
|
997
|
-
@api_node.params[:headers] ||= {}
|
998
|
-
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
999
|
-
case command
|
1000
|
-
when :create
|
1001
|
-
resp = @api_node.create(res_class_path, value_create_modify(command: command))
|
1002
|
-
return Main.result_status("#{resp['id']} created")
|
1003
|
-
when :list
|
1004
|
-
resp = @api_node.read(res_class_path, query_read_delete)
|
1005
|
-
return Main.result_value_list(resp['ids'], name: 'id')
|
1006
|
-
when :show
|
1007
|
-
return Main.result_single_object(@api_node.read(one_res_path))
|
1008
|
-
when :modify
|
1009
|
-
@api_node.update(one_res_path, options.get_option(:query, mandatory: true))
|
1010
|
-
return Main.result_status("#{one_res_id} updated")
|
1011
|
-
when :delete
|
1012
|
-
@api_node.delete(one_res_path)
|
1013
|
-
return Main.result_status("#{one_res_id} deleted")
|
1014
|
-
when :state
|
1015
|
-
return Main.result_single_object(@api_node.read("#{one_res_path}/state"))
|
1016
|
-
end
|
975
|
+
return watch_folder_action
|
1017
976
|
when :central
|
1018
977
|
command = options.get_next_command(%i[session file])
|
1019
978
|
validator_id = options.get_option(:validator)
|
@@ -1027,11 +986,7 @@ module Aspera
|
|
1027
986
|
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
1028
987
|
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
1029
988
|
resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
|
1030
|
-
return
|
1031
|
-
type: :object_list,
|
1032
|
-
data: resp['session_info_result']['session_info'],
|
1033
|
-
fields: %w[session_uuid status transport direction bytes_transferred]
|
1034
|
-
}
|
989
|
+
return Main.result_object_list(resp['session_info_result']['session_info'], fields: %w[session_uuid status transport direction bytes_transferred])
|
1035
990
|
end
|
1036
991
|
when :file
|
1037
992
|
command = options.get_next_command(%i[list modify])
|
@@ -1041,7 +996,7 @@ module Aspera
|
|
1041
996
|
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
1042
997
|
resp = @api_node.create('services/rest/transfers/v1/files', request_data)
|
1043
998
|
resp = JSON.parse(resp) if resp.is_a?(String)
|
1044
|
-
Log.
|
999
|
+
Log.dump(:resp, resp)
|
1045
1000
|
return Main.result_object_list(resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path])
|
1046
1001
|
when :modify
|
1047
1002
|
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
@@ -1061,12 +1016,12 @@ module Aspera
|
|
1061
1016
|
Environment.instance.open_uri("#{options.get_option(:asperabrowserurl)}?goto=#{encoded_params}")
|
1062
1017
|
return Main.result_status('done')
|
1063
1018
|
when :basic_token
|
1064
|
-
return Main.
|
1019
|
+
return Main.result_text(Rest.basic_authorization(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true)))
|
1065
1020
|
when :bearer_token
|
1066
1021
|
private_key = OpenSSL::PKey::RSA.new(options.get_next_argument('private RSA key PEM value', validation: String))
|
1067
1022
|
token_info = options.get_next_argument('user and group identification', validation: Hash)
|
1068
1023
|
access_key = options.get_option(:username, mandatory: true)
|
1069
|
-
return Main.
|
1024
|
+
return Main.result_text(Api::Node.bearer_token(payload: token_info, access_key: access_key, private_key: private_key))
|
1070
1025
|
when :simulator
|
1071
1026
|
require 'aspera/node_simulator'
|
1072
1027
|
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
@@ -1077,7 +1032,7 @@ module Aspera
|
|
1077
1032
|
return Main.result_status('Simulator terminated')
|
1078
1033
|
when :telemetry
|
1079
1034
|
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
1080
|
-
%i[url
|
1035
|
+
%i[url key].each do |psym|
|
1081
1036
|
raise Cli::BadArgument, "Missing parameter: #{psym}" unless parameters.key?(psym)
|
1082
1037
|
end
|
1083
1038
|
require 'socket'
|
@@ -1085,79 +1040,69 @@ module Aspera
|
|
1085
1040
|
parameters[:hostname] = Socket.gethostname unless parameters.key?(:hostname)
|
1086
1041
|
interval = parameters[:interval].to_f
|
1087
1042
|
raise Cli::BadArgument, 'Interval must be a positive number in seconds' if interval <= 0
|
1088
|
-
|
1043
|
+
otel_api = Rest.new(
|
1089
1044
|
base_url: "#{parameters[:url]}/v1",
|
1090
1045
|
headers: {
|
1091
|
-
# 'Authorization' => "apiToken #{parameters[:
|
1092
|
-
'x-instana-key' => parameters[:
|
1046
|
+
# 'Authorization' => "apiToken #{parameters[:key]}",
|
1047
|
+
'x-instana-key' => parameters[:key],
|
1093
1048
|
'x-instana-host' => parameters[:hostname]
|
1094
1049
|
}
|
1095
1050
|
)
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
resource: {
|
1126
|
-
attributes: [
|
1051
|
+
datapoint = {
|
1052
|
+
attributes: [
|
1053
|
+
{
|
1054
|
+
key: 'server.name',
|
1055
|
+
value: {
|
1056
|
+
stringValue: 'HSTS1'
|
1057
|
+
}
|
1058
|
+
}
|
1059
|
+
],
|
1060
|
+
asInt: nil,
|
1061
|
+
timeUnixNano: nil
|
1062
|
+
}
|
1063
|
+
# https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge
|
1064
|
+
metrics = {
|
1065
|
+
resourceMetrics: [
|
1066
|
+
{
|
1067
|
+
resource: {
|
1068
|
+
attributes: [
|
1069
|
+
{
|
1070
|
+
key: 'service.name',
|
1071
|
+
value: {
|
1072
|
+
stringValue: 'IBMAspera'
|
1073
|
+
}
|
1074
|
+
}
|
1075
|
+
]
|
1076
|
+
},
|
1077
|
+
scopeMetrics: [
|
1078
|
+
{
|
1079
|
+
metrics: [
|
1127
1080
|
{
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1081
|
+
name: 'active.transfers',
|
1082
|
+
description: 'Number of active transfers',
|
1083
|
+
unit: '1',
|
1084
|
+
gauge: {
|
1085
|
+
dataPoints: [
|
1086
|
+
datapoint
|
1087
|
+
]
|
1131
1088
|
}
|
1132
1089
|
}
|
1133
1090
|
]
|
1134
|
-
}
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
timeUnixNano: epoch_nsec
|
1150
|
-
}
|
1151
|
-
]
|
1152
|
-
}
|
1153
|
-
}
|
1154
|
-
]
|
1155
|
-
}
|
1156
|
-
]
|
1157
|
-
}
|
1158
|
-
]
|
1159
|
-
})
|
1160
|
-
sleep([0, interval - (Time.now - start_time)].max)
|
1091
|
+
}
|
1092
|
+
]
|
1093
|
+
}
|
1094
|
+
]
|
1095
|
+
}
|
1096
|
+
loop do
|
1097
|
+
timestamp = Time.now
|
1098
|
+
transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', query: {active_only: true})
|
1099
|
+
datapoint[:asInt] = transfers_data.length
|
1100
|
+
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
1101
|
+
Log.log.info("#{datapoint[:asInt]} active transfers")
|
1102
|
+
# https://www.ibm.com/docs/en/instana-observability/current?topic=instana-backend
|
1103
|
+
otel_api.create('metrics', metrics)
|
1104
|
+
break if interval.eql?(0.0)
|
1105
|
+
sleep([0.0, interval - (Time.now - timestamp)].max)
|
1161
1106
|
end
|
1162
1107
|
end
|
1163
1108
|
Aspera.error_unreachable_line
|
@@ -1178,6 +1123,47 @@ module Aspera
|
|
1178
1123
|
return path_arg if @prefix_path.nil?
|
1179
1124
|
return File.join(@prefix_path, path_arg)
|
1180
1125
|
end
|
1126
|
+
|
1127
|
+
# Executes the provided API call in loop
|
1128
|
+
# @param api [Rest] the API to call
|
1129
|
+
# @param iteration [Array] a single element array with the iteration token or nil
|
1130
|
+
# @param max [Integer] maximum number of items to return, or nil for no limit
|
1131
|
+
# @param query [Hash] query parameters to use for the API call
|
1132
|
+
# @param call_args [Hash] additional arguments to pass to the API call
|
1133
|
+
# @return [Array] list of items returned by the API call
|
1134
|
+
def call_with_iteration(api:, iteration: nil, max: nil, query: nil, **call_args)
|
1135
|
+
Aspera.assert_type(iteration, Array, NilClass){'iteration'}
|
1136
|
+
Aspera.assert_type(query, Hash, NilClass){'query'}
|
1137
|
+
query_token = query&.dup || {}
|
1138
|
+
item_list = []
|
1139
|
+
query_token[:iteration_token] = iteration[0] unless iteration.nil?
|
1140
|
+
loop do
|
1141
|
+
result = api.call(**call_args, query: query_token)
|
1142
|
+
Aspera.assert_type(result[:data], Array){"Expected data to be an Array, got: #{result[:data].class}"}
|
1143
|
+
# no data
|
1144
|
+
break if result[:data].empty?
|
1145
|
+
# get next iteration token from link
|
1146
|
+
next_iteration_token = nil
|
1147
|
+
link_info = result[:http]['Link']
|
1148
|
+
unless link_info.nil?
|
1149
|
+
m = link_info.match(/<([^>]+)>/)
|
1150
|
+
Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
|
1151
|
+
next_iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
|
1152
|
+
end
|
1153
|
+
# same as last iteration: stop
|
1154
|
+
break if next_iteration_token&.eql?(query_token[:iteration_token])
|
1155
|
+
query_token[:iteration_token] = next_iteration_token
|
1156
|
+
item_list.concat(result[:data])
|
1157
|
+
if max&.<=(item_list.length)
|
1158
|
+
item_list = item_list.slice(0, max)
|
1159
|
+
break
|
1160
|
+
end
|
1161
|
+
break if next_iteration_token.nil?
|
1162
|
+
end
|
1163
|
+
# save iteration token if needed
|
1164
|
+
iteration[0] = query_token[:iteration_token] unless iteration.nil?
|
1165
|
+
item_list
|
1166
|
+
end
|
1181
1167
|
end
|
1182
1168
|
end
|
1183
1169
|
end
|