aspera-cli 4.21.2 → 4.23.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/BUGS.md +1 -1
- data/CHANGELOG.md +402 -374
- data/CONTRIBUTING.md +6 -10
- data/README.md +1018 -687
- data/lib/aspera/agent/base.rb +9 -5
- data/lib/aspera/agent/connect.rb +30 -28
- data/lib/aspera/agent/desktop.rb +29 -25
- data/lib/aspera/agent/direct.rb +137 -125
- data/lib/aspera/agent/httpgw.rb +22 -26
- data/lib/aspera/agent/node.rb +14 -11
- data/lib/aspera/agent/transferd.rb +6 -2
- data/lib/aspera/api/aoc.rb +15 -18
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/httpgw.rb +15 -7
- data/lib/aspera/api/node.rb +6 -4
- data/lib/aspera/ascmd.rb +17 -9
- data/lib/aspera/ascp/installation.rb +21 -19
- data/lib/aspera/ascp/management.rb +1 -1
- data/lib/aspera/assert.rb +14 -5
- data/lib/aspera/cli/error.rb +2 -2
- data/lib/aspera/cli/extended_value.rb +38 -19
- data/lib/aspera/cli/formatter.rb +48 -48
- data/lib/aspera/cli/hints.rb +10 -2
- data/lib/aspera/cli/main.rb +190 -168
- data/lib/aspera/cli/manager.rb +16 -16
- data/lib/aspera/cli/plugin.rb +24 -21
- data/lib/aspera/cli/plugin_factory.rb +1 -1
- data/lib/aspera/cli/plugins/alee.rb +1 -1
- data/lib/aspera/cli/plugins/aoc.rb +173 -126
- data/lib/aspera/cli/plugins/ats.rb +19 -17
- data/lib/aspera/cli/plugins/config.rb +87 -98
- data/lib/aspera/cli/plugins/console.rb +5 -3
- data/lib/aspera/cli/plugins/faspex.rb +39 -35
- data/lib/aspera/cli/plugins/faspex5.rb +104 -80
- data/lib/aspera/cli/plugins/faspio.rb +13 -1
- data/lib/aspera/cli/plugins/httpgw.rb +13 -1
- data/lib/aspera/cli/plugins/node.rb +336 -205
- data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
- data/lib/aspera/cli/plugins/preview.rb +3 -3
- data/lib/aspera/cli/plugins/server.rb +7 -6
- data/lib/aspera/cli/plugins/shares.rb +5 -5
- data/lib/aspera/cli/sync_actions.rb +19 -18
- data/lib/aspera/cli/transfer_agent.rb +11 -15
- data/lib/aspera/cli/transfer_progress.rb +2 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +116 -95
- data/lib/aspera/coverage.rb +4 -3
- data/lib/aspera/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +7 -6
- data/lib/aspera/faspex_gw.rb +14 -14
- data/lib/aspera/faspex_postproc.rb +7 -6
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +47 -34
- data/lib/aspera/keychain/factory.rb +41 -0
- data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
- data/lib/aspera/keychain/macos_security.rb +19 -11
- data/lib/aspera/log.rb +29 -34
- data/lib/aspera/nagios.rb +6 -6
- data/lib/aspera/node_simulator.rb +8 -8
- data/lib/aspera/oauth/base.rb +10 -6
- data/lib/aspera/oauth/factory.rb +6 -6
- data/lib/aspera/oauth/url_json.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +6 -4
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +40 -33
- data/lib/aspera/preview/generator.rb +1 -1
- data/lib/aspera/preview/options.rb +16 -16
- data/lib/aspera/preview/terminal.rb +3 -3
- data/lib/aspera/preview/utils.rb +11 -13
- data/lib/aspera/products/connect.rb +2 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +2 -2
- data/lib/aspera/rest.rb +70 -50
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +1 -1
- data/lib/aspera/secret_hider.rb +5 -5
- data/lib/aspera/ssh.rb +5 -5
- data/lib/aspera/temp_file_manager.rb +1 -0
- data/lib/aspera/timer_limiter.rb +7 -5
- data/lib/aspera/transfer/async_conf.schema.yaml +716 -0
- data/lib/aspera/transfer/convert.rb +29 -0
- data/lib/aspera/transfer/error_info.rb +66 -66
- data/lib/aspera/transfer/parameters.rb +13 -68
- data/lib/aspera/transfer/spec.rb +5 -6
- data/lib/aspera/transfer/spec.schema.yaml +753 -0
- data/lib/aspera/transfer/spec_doc.rb +62 -0
- data/lib/aspera/transfer/sync.rb +37 -76
- data/lib/aspera/transfer/sync_instance.schema.yaml +20 -0
- data/lib/aspera/transfer/sync_session.schema.yaml +86 -0
- data/lib/aspera/transfer/uri.rb +6 -6
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +1 -1
- data/lib/aspera/web_server_simple.rb +53 -44
- data.tar.gz.sig +0 -0
- metadata +38 -7
- metadata.gz.sig +0 -0
- data/examples/build_package.sh +0 -28
- data/examples/dascli +0 -30
- data/examples/get_proto_file.rb +0 -8
- data/examples/proxy.pac +0 -60
- data/lib/aspera/transfer/spec.yaml +0 -718
@@ -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
|
@@ -47,6 +48,7 @@ module Aspera
|
|
47
48
|
test_endpoint = 'ping'
|
48
49
|
result = api.call(operation: 'GET', subpath: test_endpoint)
|
49
50
|
next unless result[:http].body.eql?('')
|
51
|
+
# also remove "/"
|
50
52
|
url_end = -2 - test_endpoint.length
|
51
53
|
return {
|
52
54
|
url: result[:http].uri.to_s[0..url_end],
|
@@ -107,7 +109,7 @@ module Aspera
|
|
107
109
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
108
110
|
|
109
111
|
# actions in execute_command_gen3
|
110
|
-
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download
|
112
|
+
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport]
|
111
113
|
|
112
114
|
BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
|
113
115
|
|
@@ -125,7 +127,7 @@ module Aspera
|
|
125
127
|
NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
|
126
128
|
|
127
129
|
# commands for execute_command_gen4
|
128
|
-
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync
|
130
|
+
COMMANDS_GEN4 = %i[mkdir mklink mkfile rename delete upload download sync cat show modify permission thumbnail v3].concat(NODE4_READ_ACTIONS).freeze
|
129
131
|
|
130
132
|
# commands supported in ATS for COS
|
131
133
|
COMMANDS_COS = %i[upload download info access_keys api_details transfer].freeze
|
@@ -134,13 +136,16 @@ module Aspera
|
|
134
136
|
|
135
137
|
GEN4_LS_FIELDS = %w[name type recursive_size size modified_time access_level].freeze
|
136
138
|
|
137
|
-
|
139
|
+
# @param api [Rest] an existing API object for the Node API
|
140
|
+
# @param prefix_path [String,nil] for Faspex 4, allows browsing a package
|
141
|
+
def initialize(api: nil, prefix_path: nil, **env)
|
142
|
+
@prefix_path = prefix_path
|
138
143
|
super(**env, basic_options: api.nil?)
|
139
|
-
Node.declare_options(options)
|
140
|
-
return if only_manual
|
144
|
+
Node.declare_options(options)
|
145
|
+
return if env[:broker].only_manual?
|
141
146
|
@api_node =
|
142
147
|
if !api.nil?
|
143
|
-
# this can be Api::Node or Rest (
|
148
|
+
# this can be Api::Node or Rest (Shares)
|
144
149
|
api
|
145
150
|
elsif OAuth::Factory.bearer?(options.get_option(:password, mandatory: true))
|
146
151
|
# info is provided like node_info of aoc
|
@@ -161,23 +166,22 @@ module Aspera
|
|
161
166
|
end
|
162
167
|
|
163
168
|
# reduce the path from a result on given named column
|
164
|
-
def c_result_remove_prefix_path(result, column
|
165
|
-
if
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
171
|
-
when :single_object
|
172
|
-
item = result[:data]
|
173
|
-
item[column] = item[column][path_prefix.length..-1] if item[column].start_with?(path_prefix)
|
169
|
+
def c_result_remove_prefix_path(result, column)
|
170
|
+
return result if @prefix_path.nil?
|
171
|
+
case result[:type]
|
172
|
+
when :object_list
|
173
|
+
result[:data].each do |item|
|
174
|
+
item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
|
174
175
|
end
|
176
|
+
when :single_object
|
177
|
+
item = result[:data]
|
178
|
+
item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
|
175
179
|
end
|
176
180
|
return result
|
177
181
|
end
|
178
182
|
|
179
183
|
# translates paths results into CLI result, and removes prefix
|
180
|
-
def c_result_translate_rem_prefix(response, type, success_msg
|
184
|
+
def c_result_translate_rem_prefix(response, type, success_msg)
|
181
185
|
errors = []
|
182
186
|
final_result = {type: :object_list, data: [], fields: [type, 'result']}
|
183
187
|
response['paths'].each do |p|
|
@@ -191,16 +195,16 @@ module Aspera
|
|
191
195
|
end
|
192
196
|
# one error make all fail
|
193
197
|
unless errors.empty?
|
194
|
-
raise errors.map{|i|"#{i.first}: #{i.last}"}.join(', ')
|
198
|
+
raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ')
|
195
199
|
end
|
196
|
-
return c_result_remove_prefix_path(final_result, type
|
200
|
+
return c_result_remove_prefix_path(final_result, type)
|
197
201
|
end
|
198
202
|
|
199
|
-
def browse_gen3
|
200
|
-
folders_to_process = [get_one_argument_with_prefix(
|
203
|
+
def browse_gen3
|
204
|
+
folders_to_process = [get_one_argument_with_prefix('path')]
|
201
205
|
query = options.get_option(:query, default: {})
|
202
206
|
# special parameter: max number of entries in result
|
203
|
-
max_items = query.delete(
|
207
|
+
max_items = query.delete(MAX_ITEMS)
|
204
208
|
# special parameter: recursive browsing
|
205
209
|
recursive = query.delete('recursive')
|
206
210
|
# special parameter: only return one entry for the path, even if folder
|
@@ -209,7 +213,7 @@ module Aspera
|
|
209
213
|
single_call = query.key?('skip')
|
210
214
|
# API default is 100, so use 1000 for default
|
211
215
|
query['count'] ||= 1000
|
212
|
-
raise Cli::BadArgument, 'options recursive and skip cannot be used together' if recursive && single_call
|
216
|
+
raise Cli::BadArgument, 'options `recursive` and `skip` cannot be used together' if recursive && single_call
|
213
217
|
all_items = []
|
214
218
|
until folders_to_process.empty?
|
215
219
|
path = folders_to_process.shift
|
@@ -222,7 +226,7 @@ module Aspera
|
|
222
226
|
response = @api_node.create('files/browse', query)
|
223
227
|
# 'file','symbolic_link'
|
224
228
|
if !Node.gen3_entry_folder?(response['self']) || only_path
|
225
|
-
result = {
|
229
|
+
result = {type: :single_object, data: response['self']}
|
226
230
|
break
|
227
231
|
end
|
228
232
|
items = response['items']
|
@@ -233,7 +237,7 @@ module Aspera
|
|
233
237
|
break
|
234
238
|
end
|
235
239
|
if recursive
|
236
|
-
folders_to_process.concat(items.select{|i|Node.gen3_entry_folder?(i)}.map{|i|i['path']})
|
240
|
+
folders_to_process.concat(items.select{ |i| Node.gen3_entry_folder?(i)}.map{ |i| i['path']})
|
237
241
|
end
|
238
242
|
if !max_items.nil? && (all_items.count >= max_items)
|
239
243
|
all_items = all_items.slice(0, max_items) if all_items.count > max_items
|
@@ -248,73 +252,94 @@ module Aspera
|
|
248
252
|
end
|
249
253
|
result ||= {type: :object_list, data: all_items}
|
250
254
|
formatter.long_operation_terminated
|
251
|
-
return c_result_remove_prefix_path(result, 'path'
|
255
|
+
return c_result_remove_prefix_path(result, 'path')
|
256
|
+
end
|
257
|
+
|
258
|
+
# Create async transfer spec request from direction and folders
|
259
|
+
# @param sync_direction one of push pull bidi
|
260
|
+
# @param local_path local folder to sync
|
261
|
+
# @param remote_path remote folder to sync
|
262
|
+
def sync_spec_request(sync_direction, local_path, remote_path)
|
263
|
+
case sync_direction
|
264
|
+
when :push then {
|
265
|
+
type: :sync_upload,
|
266
|
+
paths: [{
|
267
|
+
source: local_path,
|
268
|
+
destination: remote_path
|
269
|
+
}]
|
270
|
+
}
|
271
|
+
when :pull then {
|
272
|
+
type: :sync_download,
|
273
|
+
paths: [{
|
274
|
+
source: remote_path,
|
275
|
+
destination: local_path
|
276
|
+
}]
|
277
|
+
}
|
278
|
+
when :bidi then {
|
279
|
+
type: :sync,
|
280
|
+
paths: [{
|
281
|
+
source: local_path,
|
282
|
+
destination: remote_path
|
283
|
+
}]
|
284
|
+
}
|
285
|
+
else Aspera.error_unexpected_value(sync_direction)
|
286
|
+
end
|
252
287
|
end
|
253
288
|
|
254
|
-
# file and folder
|
255
|
-
def execute_command_gen3(command
|
289
|
+
# Commands based on Gen3 API for file and folder
|
290
|
+
def execute_command_gen3(command)
|
256
291
|
case command
|
257
292
|
when :delete
|
258
293
|
# TODO: add query for recursive
|
259
|
-
paths_to_delete = get_all_arguments_with_prefix(
|
260
|
-
resp = @api_node.create('files/delete', {
|
261
|
-
return c_result_translate_rem_prefix(resp, 'file', 'deleted'
|
294
|
+
paths_to_delete = get_all_arguments_with_prefix('file list')
|
295
|
+
resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
|
296
|
+
return c_result_translate_rem_prefix(resp, 'file', 'deleted')
|
262
297
|
when :search
|
263
|
-
search_root = get_one_argument_with_prefix(
|
298
|
+
search_root = get_one_argument_with_prefix('search root')
|
264
299
|
parameters = {'path' => search_root}
|
265
300
|
other_options = options.get_option(:query)
|
266
301
|
parameters.merge!(other_options) unless other_options.nil?
|
267
302
|
resp = @api_node.create('files/search', parameters)
|
268
|
-
result = {
|
303
|
+
result = {type: :object_list, data: resp['items']}
|
269
304
|
return Main.result_empty if result[:data].empty?
|
270
|
-
result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
|
305
|
+
result[:fields] = result[:data].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
|
271
306
|
formatter.display_item_count(resp['item_count'], resp['total_count'])
|
272
|
-
formatter.display_status("params: #{resp['parameters'].keys.map{|k|"#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
273
|
-
return c_result_remove_prefix_path(result, 'path'
|
307
|
+
formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
|
308
|
+
return c_result_remove_prefix_path(result, 'path')
|
274
309
|
when :space
|
275
|
-
path_list = get_all_arguments_with_prefix(
|
276
|
-
resp = @api_node.create('space', {
|
277
|
-
result = {
|
278
|
-
# return c_result_translate_rem_prefix(resp,'folder','created'
|
279
|
-
return c_result_remove_prefix_path(result, 'path'
|
310
|
+
path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
|
311
|
+
resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
|
312
|
+
result = {type: :object_list, data: resp['paths']}
|
313
|
+
# return c_result_translate_rem_prefix(resp,'folder','created',@prefix_path)
|
314
|
+
return c_result_remove_prefix_path(result, 'path')
|
280
315
|
when :mkdir
|
281
|
-
path_list = get_all_arguments_with_prefix(
|
282
|
-
resp = @api_node.create('files/create', {
|
283
|
-
return c_result_translate_rem_prefix(resp, 'folder', 'created'
|
316
|
+
path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
|
317
|
+
resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
|
318
|
+
return c_result_translate_rem_prefix(resp, 'folder', 'created')
|
284
319
|
when :mklink
|
285
|
-
target = get_one_argument_with_prefix(
|
286
|
-
one_path = get_one_argument_with_prefix(
|
287
|
-
resp = @api_node.create('files/create', {
|
288
|
-
return c_result_translate_rem_prefix(resp, 'folder', 'created'
|
320
|
+
target = get_one_argument_with_prefix('target')
|
321
|
+
one_path = get_one_argument_with_prefix('link path')
|
322
|
+
resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
|
323
|
+
return c_result_translate_rem_prefix(resp, 'folder', 'created')
|
289
324
|
when :mkfile
|
290
|
-
one_path = get_one_argument_with_prefix(
|
325
|
+
one_path = get_one_argument_with_prefix('file path')
|
291
326
|
contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
|
292
|
-
resp = @api_node.create('files/create', {
|
293
|
-
return c_result_translate_rem_prefix(resp, 'folder', 'created'
|
327
|
+
resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
|
328
|
+
return c_result_translate_rem_prefix(resp, 'folder', 'created')
|
294
329
|
when :rename
|
295
330
|
# TODO: multiple ?
|
296
|
-
path_base = get_one_argument_with_prefix(
|
297
|
-
path_src = get_one_argument_with_prefix(
|
298
|
-
path_dst = get_one_argument_with_prefix(
|
299
|
-
resp = @api_node.create('files/rename', {
|
300
|
-
return c_result_translate_rem_prefix(resp, 'entry', 'moved'
|
331
|
+
path_base = get_one_argument_with_prefix('path_base')
|
332
|
+
path_src = get_one_argument_with_prefix('path_src')
|
333
|
+
path_dst = get_one_argument_with_prefix('path_dst')
|
334
|
+
resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
|
335
|
+
return c_result_translate_rem_prefix(resp, 'entry', 'moved')
|
301
336
|
when :browse
|
302
|
-
return browse_gen3
|
337
|
+
return browse_gen3
|
303
338
|
when :sync
|
304
339
|
return execute_sync_action do |sync_direction, local_path, remote_path|
|
305
340
|
# Gen3 API
|
306
341
|
# empty transfer spec for authorization request
|
307
|
-
request_transfer_spec =
|
308
|
-
type: case sync_direction
|
309
|
-
when :push then :sync_upload
|
310
|
-
when :pull then :sync_download
|
311
|
-
when :bidi then :sync
|
312
|
-
end,
|
313
|
-
paths: [{
|
314
|
-
source: remote_path,
|
315
|
-
destination: local_path
|
316
|
-
}]
|
317
|
-
}
|
342
|
+
request_transfer_spec = sync_spec_request(sync_direction, local_path, remote_path)
|
318
343
|
# add fixed parameters if any (for COS)
|
319
344
|
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
320
345
|
# prepare payload for single request
|
@@ -322,7 +347,7 @@ module Aspera
|
|
322
347
|
# only one request, so only one answer
|
323
348
|
transfer_spec = @api_node.create('files/sync_setup', setup_payload)['transfer_specs'].first['transfer_spec']
|
324
349
|
# API returns null tag... but async does not like it
|
325
|
-
transfer_spec.delete_if{ |_k, v| v.nil?
|
350
|
+
transfer_spec.delete_if{ |_k, v| v.nil?}
|
326
351
|
# delete this part, as the returned value contains only destination, and not sources
|
327
352
|
# transfer_spec.delete('paths') if command.eql?(:upload)
|
328
353
|
Log.log.debug{Log.dump(:ts, transfer_spec)}
|
@@ -335,7 +360,7 @@ module Aspera
|
|
335
360
|
request_transfer_spec[:paths] = if command.eql?(:download)
|
336
361
|
transfer.ts_source_paths
|
337
362
|
else
|
338
|
-
[{
|
363
|
+
[{destination: transfer.destination_folder(Transfer::Spec::DIRECTION_SEND)}]
|
339
364
|
end
|
340
365
|
# add fixed parameters if any (for COS)
|
341
366
|
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
@@ -346,32 +371,30 @@ module Aspera
|
|
346
371
|
# delete this part, as the returned value contains only destination, and not sources
|
347
372
|
transfer_spec.delete('paths') if command.eql?(:upload)
|
348
373
|
return Main.result_transfer(transfer.start(transfer_spec))
|
349
|
-
when :
|
350
|
-
remote_path = get_one_argument_with_prefix(
|
351
|
-
|
352
|
-
@api_node.call(
|
374
|
+
when :cat
|
375
|
+
remote_path = get_one_argument_with_prefix('remote path')
|
376
|
+
File.basename(remote_path)
|
377
|
+
result = @api_node.call(
|
353
378
|
operation: 'GET',
|
354
|
-
subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents"
|
355
|
-
|
356
|
-
return Main.result_status("downloaded: #{file_name}")
|
379
|
+
subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents")
|
380
|
+
return Main.result_text(result[:http].body)
|
357
381
|
when :transport
|
358
|
-
return
|
382
|
+
return Main.result_single_object(@api_node.transport_params)
|
359
383
|
end
|
360
384
|
Aspera.error_unreachable_line
|
361
385
|
end
|
362
386
|
|
363
387
|
# common API to node and Shares
|
364
|
-
|
365
|
-
def execute_simple_common(command, prefix_path)
|
388
|
+
def execute_simple_common(command)
|
366
389
|
case command
|
367
390
|
when *COMMANDS_GEN3
|
368
|
-
execute_command_gen3(command
|
391
|
+
execute_command_gen3(command)
|
369
392
|
when :access_keys
|
370
393
|
ak_command = options.get_next_command(%i[do set_bearer_key].concat(Plugin::ALL_OPS))
|
371
394
|
case ak_command
|
372
395
|
when *Plugin::ALL_OPS
|
373
396
|
return entity_command(ak_command, @api_node, 'access_keys') do |field, value|
|
374
|
-
raise 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
|
397
|
+
raise Cli::BadIdentifier, 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
|
375
398
|
@api_node.read('access_keys/self')['id']
|
376
399
|
end
|
377
400
|
when :do
|
@@ -410,11 +433,12 @@ module Aspera
|
|
410
433
|
end
|
411
434
|
begin
|
412
435
|
@api_node.call(
|
413
|
-
operation:
|
414
|
-
subpath:
|
415
|
-
|
416
|
-
body:
|
417
|
-
|
436
|
+
operation: 'POST',
|
437
|
+
subpath: 'services/soap/Transfer-201210',
|
438
|
+
content_type: Rest::MIME_TEXT,
|
439
|
+
body: CENTRAL_SOAP_API_TEST,
|
440
|
+
headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'}
|
441
|
+
)[:http].body
|
418
442
|
nagios.add_ok('central', 'accessible by node')
|
419
443
|
rescue StandardError => e
|
420
444
|
nagios.add_critical('central', e.to_s)
|
@@ -422,22 +446,22 @@ module Aspera
|
|
422
446
|
return nagios.result
|
423
447
|
when :events
|
424
448
|
events = @api_node.read('events', query_read_delete)
|
425
|
-
return
|
449
|
+
return Main.result_object_list(events, fields: ->(f){!f.start_with?('data')})
|
426
450
|
when :info
|
427
451
|
nd_info = @api_node.read('info')
|
428
|
-
return
|
452
|
+
return Main.result_single_object(nd_info)
|
429
453
|
when :slash
|
430
454
|
nd_info = @api_node.read('')
|
431
|
-
return
|
455
|
+
return Main.result_single_object(nd_info)
|
432
456
|
when :license
|
433
457
|
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
434
458
|
node_license = @api_node.read('license')
|
435
459
|
if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
|
436
460
|
Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal')
|
437
461
|
end
|
438
|
-
return
|
462
|
+
return Main.result_single_object(node_license)
|
439
463
|
when :api_details
|
440
|
-
return {
|
464
|
+
return Main.result_single_object({base_url: @api_node.base_url}.merge(@api_node.params))
|
441
465
|
end
|
442
466
|
end
|
443
467
|
|
@@ -445,7 +469,7 @@ module Aspera
|
|
445
469
|
# @return [Hash] api and main file id for given path or id in next argument
|
446
470
|
def apifid_from_next_arg(top_file_id)
|
447
471
|
file_path = instance_identifier(description: 'path or %id:<id>') do |attribute, value|
|
448
|
-
raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
472
|
+
raise Cli::BadIdentifier, 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
449
473
|
# directly return result for method
|
450
474
|
return {api: @api_node, file_id: value}
|
451
475
|
end
|
@@ -456,7 +480,7 @@ module Aspera
|
|
456
480
|
def execute_command_gen4(command_repo, top_file_id)
|
457
481
|
override_file_id = options.get_option(:root_id)
|
458
482
|
top_file_id = override_file_id unless override_file_id.nil?
|
459
|
-
raise 'Specify root file id with option root_id' if top_file_id.nil?
|
483
|
+
raise Cli::Error, 'Specify root file id with option root_id' if top_file_id.nil?
|
460
484
|
case command_repo
|
461
485
|
when :v3
|
462
486
|
# NOTE: other common actions are unauthorized with user scope
|
@@ -480,7 +504,7 @@ module Aspera
|
|
480
504
|
result[:password] = apifid[:api].oauth.authorization
|
481
505
|
else Aspera.error_unreachable_line
|
482
506
|
end
|
483
|
-
return
|
507
|
+
return Main.result_single_object(result) if command_repo.eql?(:node_info)
|
484
508
|
# check format of bearer token
|
485
509
|
OAuth::Factory.bearer_extract(result[:password])
|
486
510
|
return Main.result_status(result[:password])
|
@@ -489,20 +513,48 @@ module Aspera
|
|
489
513
|
file_info = apifid[:api].read_with_cache("files/#{apifid[:file_id]}")
|
490
514
|
unless file_info['type'].eql?('folder')
|
491
515
|
# a single file
|
492
|
-
return
|
516
|
+
return Main.result_object_list([file_info], fields: GEN4_LS_FIELDS)
|
493
517
|
end
|
494
|
-
return
|
518
|
+
return Main.result_object_list(apifid[:api].list_files(apifid[:file_id]), fields: GEN4_LS_FIELDS)
|
495
519
|
when :find
|
496
520
|
apifid = apifid_from_next_arg(top_file_id)
|
497
521
|
find_lambda = Api::Node.file_matcher_from_argument(options)
|
498
|
-
return
|
499
|
-
when :mkdir
|
522
|
+
return Main.result_object_list(@api_node.find_files(apifid[:file_id], find_lambda), fields: ['path'])
|
523
|
+
when :mkdir, :mklink, :mkfile
|
500
524
|
containing_folder_path = options.get_next_argument('path').split(Api::Node::PATH_SEPARATOR)
|
501
|
-
|
502
|
-
# add trailing slash to force last link to be resolved
|
525
|
+
new_item = containing_folder_path.pop
|
503
526
|
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Api::Node::PATH_SEPARATOR), true)
|
504
|
-
|
505
|
-
|
527
|
+
query = options.get_option(:query, mandatory: false)
|
528
|
+
check_exists = true
|
529
|
+
payload = {name: new_item}
|
530
|
+
if query
|
531
|
+
check_exists = !query.delete('check').eql?(false)
|
532
|
+
target = query.delete('target')
|
533
|
+
if target
|
534
|
+
target_apifid = @api_node.resolve_api_fid(top_file_id, target, true)
|
535
|
+
payload[:target_id] = target_apifid[:file_id]
|
536
|
+
end
|
537
|
+
payload.merge!(query.symbolize_keys)
|
538
|
+
end
|
539
|
+
if check_exists
|
540
|
+
folder_content = apifid[:api].read("files/#{apifid[:file_id]}/files")
|
541
|
+
link_name = ".#{new_item}.asp-lnk"
|
542
|
+
found = folder_content.find{ |i| i['name'].eql?(new_item) || i['name'].eql?(link_name)}
|
543
|
+
raise "A #{found['type']} already exists with name #{new_item}" if found
|
544
|
+
end
|
545
|
+
case command_repo
|
546
|
+
when :mkdir
|
547
|
+
payload[:type] = :folder
|
548
|
+
when :mklink
|
549
|
+
payload[:type] = :link
|
550
|
+
Aspera.assert(payload[:target_id]){'Missing target_id'}
|
551
|
+
Aspera.assert(payload[:target_node_id]){'Missing target_node_id'}
|
552
|
+
when :mkfile
|
553
|
+
payload[:type] = :file
|
554
|
+
payload[:contents] = Base64.strict_encode64(options.get_next_argument('contents'))
|
555
|
+
end
|
556
|
+
result = apifid[:api].create("files/#{apifid[:file_id]}/files", payload)
|
557
|
+
return Main.result_single_object(result)
|
506
558
|
when :rename
|
507
559
|
file_path = options.get_next_argument('source path')
|
508
560
|
apifid = @api_node.resolve_api_fid(top_file_id, file_path)
|
@@ -562,11 +614,11 @@ module Aspera
|
|
562
614
|
# TODO: add this ? , 'destination'=>file_info['name']
|
563
615
|
source_paths = [{'source' => '.'}]
|
564
616
|
else
|
565
|
-
raise "Unknown source type: #{file_info['type']}"
|
617
|
+
raise BadArgument, "Unknown source type: #{file_info['type']}"
|
566
618
|
end
|
567
619
|
end
|
568
620
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
|
569
|
-
when :
|
621
|
+
when :cat
|
570
622
|
source_paths = transfer.ts_source_paths
|
571
623
|
source_folder = source_paths.shift['source']
|
572
624
|
if source_paths.empty?
|
@@ -577,15 +629,14 @@ module Aspera
|
|
577
629
|
raise Cli::BadArgument, 'one file at a time only in HTTP mode' if source_paths.length > 1
|
578
630
|
file_name = source_paths.first['source']
|
579
631
|
apifid = @api_node.resolve_api_fid(top_file_id, File.join(source_folder, file_name))
|
580
|
-
apifid[:api].call(
|
632
|
+
result = apifid[:api].call(
|
581
633
|
operation: 'GET',
|
582
|
-
subpath: "files/#{apifid[:file_id]}/content"
|
583
|
-
|
584
|
-
return Main.result_status("downloaded: #{file_name}")
|
634
|
+
subpath: "files/#{apifid[:file_id]}/content")
|
635
|
+
return Main.result_text(result[:http].body)
|
585
636
|
when :show
|
586
637
|
apifid = apifid_from_next_arg(top_file_id)
|
587
638
|
items = apifid[:api].read("files/#{apifid[:file_id]}")
|
588
|
-
return
|
639
|
+
return Main.result_single_object(items)
|
589
640
|
when :modify
|
590
641
|
apifid = apifid_from_next_arg(top_file_id)
|
591
642
|
update_param = options.get_next_argument('update data', validation: Hash)
|
@@ -610,7 +661,7 @@ module Aspera
|
|
610
661
|
list_query['inherited'] = false if list_query.key?('file_id') && !list_query.key?('inherited')
|
611
662
|
# NOTE: supports per_page and page and header X-Total-Count
|
612
663
|
items = apifid[:api].read('permissions', list_query)
|
613
|
-
return
|
664
|
+
return Main.result_object_list(items)
|
614
665
|
when :show
|
615
666
|
perm_id = instance_identifier
|
616
667
|
return Main.result_single_object(apifid[:api].read("permissions/#{perm_id}"))
|
@@ -624,7 +675,7 @@ module Aspera
|
|
624
675
|
end
|
625
676
|
when :create
|
626
677
|
create_param = options.get_next_argument('creation data', validation: Hash)
|
627
|
-
raise 'no file_id' if create_param.key?('file_id')
|
678
|
+
raise Cli::BadArgument, 'no file_id' if create_param.key?('file_id')
|
628
679
|
create_param['file_id'] = apifid[:file_id]
|
629
680
|
create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
630
681
|
# add application specific tags (AoC)
|
@@ -634,7 +685,7 @@ module Aspera
|
|
634
685
|
created_data = apifid[:api].create('permissions', create_param)
|
635
686
|
# notify application of creation
|
636
687
|
the_app&.[](:api)&.permissions_send_event(event_data: created_data, app_info: the_app)
|
637
|
-
return
|
688
|
+
return Main.result_single_object(created_data)
|
638
689
|
else Aspera.error_unreachable_line
|
639
690
|
end
|
640
691
|
else Aspera.error_unreachable_line
|
@@ -658,8 +709,8 @@ module Aspera
|
|
658
709
|
else
|
659
710
|
async_ids = @api_node.read('async/list')['sync_ids']
|
660
711
|
summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
|
661
|
-
selected = summaries.find{|s|s['name'].eql?(async_name)}
|
662
|
-
raise "no such sync: #{async_name}" if selected.nil?
|
712
|
+
selected = summaries.find{ |s| s['name'].eql?(async_name)}
|
713
|
+
raise Cli::BadIdentifier, "no such sync: #{async_name}" if selected.nil?
|
663
714
|
async_id = selected['snid']
|
664
715
|
async_ids = [async_id]
|
665
716
|
end
|
@@ -668,22 +719,22 @@ module Aspera
|
|
668
719
|
case command
|
669
720
|
when :list
|
670
721
|
resp = @api_node.read('async/list')['sync_ids']
|
671
|
-
return
|
722
|
+
return Main.result_value_list(resp, name: 'id')
|
672
723
|
when :show
|
673
724
|
resp = @api_node.create('async/summary', post_data)['sync_summaries']
|
674
725
|
return Main.result_empty if resp.empty?
|
675
|
-
return
|
676
|
-
return
|
726
|
+
return Main.result_object_list(resp, fields: %w[snid name local_dir remote_dir]) if async_id.eql?(SpecialValues::ALL)
|
727
|
+
return Main.result_single_object(resp.first)
|
677
728
|
when :delete
|
678
729
|
resp = @api_node.create('async/delete', post_data)
|
679
|
-
return
|
730
|
+
return Main.result_single_object(resp)
|
680
731
|
when :bandwidth
|
681
732
|
post_data['seconds'] = 100 # TODO: as parameter with --value
|
682
733
|
resp = @api_node.create('async/bandwidth', post_data)
|
683
734
|
data = resp['bandwidth_data']
|
684
735
|
return Main.result_empty if data.empty?
|
685
736
|
data = data.first[async_id]['data']
|
686
|
-
return
|
737
|
+
return Main.result_object_list(data)
|
687
738
|
when :files
|
688
739
|
# count int
|
689
740
|
# filename str
|
@@ -706,17 +757,17 @@ module Aspera
|
|
706
757
|
options.get_option(:username, mandatory: true),
|
707
758
|
async_id]))
|
708
759
|
unless iteration_data.first.nil?
|
709
|
-
data.select!{|l| l['fnid'].to_i > iteration_data.first}
|
760
|
+
data.select!{ |l| l['fnid'].to_i > iteration_data.first}
|
710
761
|
end
|
711
762
|
iteration_data[0] = data.last['fnid'].to_i unless data.empty?
|
712
763
|
end
|
713
764
|
return Main.result_empty if data.empty?
|
714
765
|
skip_ids_persistency&.save
|
715
|
-
return
|
766
|
+
return Main.result_object_list(data)
|
716
767
|
when :counters
|
717
768
|
resp = @api_node.create('async/counters', post_data)['sync_counters'].first[async_id].last
|
718
769
|
return Main.result_empty if resp.nil?
|
719
|
-
return
|
770
|
+
return Main.result_single_object(resp)
|
720
771
|
end
|
721
772
|
end
|
722
773
|
|
@@ -731,7 +782,7 @@ module Aspera
|
|
731
782
|
# name is unique, so we can return
|
732
783
|
return id if sync_info[field].eql?(value)
|
733
784
|
end
|
734
|
-
raise Cli::
|
785
|
+
raise Cli::BadIdentifier, "no such sync: #{field}=#{value}"
|
735
786
|
end
|
736
787
|
|
737
788
|
ACTIONS = %i[
|
@@ -745,64 +796,60 @@ module Aspera
|
|
745
796
|
asperabrowser
|
746
797
|
basic_token
|
747
798
|
bearer_token
|
748
|
-
simulator
|
799
|
+
simulator
|
800
|
+
telemetry
|
801
|
+
].concat(COMMON_ACTIONS).freeze
|
749
802
|
|
750
|
-
def execute_action(command=nil
|
803
|
+
def execute_action(command=nil)
|
751
804
|
command ||= options.get_next_command(ACTIONS)
|
752
805
|
case command
|
753
|
-
when *COMMON_ACTIONS then return execute_simple_common(command
|
806
|
+
when *COMMON_ACTIONS then return execute_simple_common(command)
|
754
807
|
when :async then return execute_async # former API
|
755
808
|
when :ssync
|
756
809
|
# newer API
|
757
810
|
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
|
758
811
|
case sync_command
|
759
|
-
when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids'){|field, value|ssync_lookup(field, value)}
|
812
|
+
when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids'){ |field, value| ssync_lookup(field, value)}
|
760
813
|
else
|
761
|
-
asyncs_id = instance_identifier
|
814
|
+
asyncs_id = instance_identifier{ |field, value| ssync_lookup(field, value)}
|
762
815
|
if %i[start stop].include?(sync_command)
|
763
816
|
@api_node.call(
|
764
|
-
operation:
|
765
|
-
subpath:
|
766
|
-
|
767
|
-
|
817
|
+
operation: 'POST',
|
818
|
+
subpath: "asyncs/#{asyncs_id}/#{sync_command}",
|
819
|
+
content_type: Rest::MIME_TEXT,
|
820
|
+
body: ''
|
821
|
+
)[:http].body
|
768
822
|
return Main.result_status('Done')
|
769
823
|
end
|
770
824
|
parameters = nil
|
771
825
|
parameters = options.get_option(:query, default: {}) if %i[bandwidth counters files].include?(sync_command)
|
772
|
-
return
|
826
|
+
return Main.result_single_object(@api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters))
|
773
827
|
end
|
774
828
|
when :stream
|
775
829
|
command = options.get_next_command(%i[list create show modify cancel])
|
776
830
|
case command
|
777
831
|
when :list
|
778
832
|
resp = @api_node.read('ops/transfers', query_read_delete)
|
779
|
-
return
|
833
|
+
return Main.result_object_list(resp, fields: %w[id status]) # TODO: useful?
|
780
834
|
when :create
|
781
835
|
resp = @api_node.create('streams', value_create_modify(command: command))
|
782
|
-
return
|
836
|
+
return Main.result_single_object(resp)
|
783
837
|
when :show
|
784
838
|
resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
|
785
|
-
return
|
839
|
+
return Main.result_single_object(resp)
|
786
840
|
when :modify
|
787
841
|
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command))
|
788
|
-
return
|
842
|
+
return Main.result_single_object(resp)
|
789
843
|
when :cancel
|
790
844
|
resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
|
791
|
-
return
|
792
|
-
else
|
793
|
-
raise 'error'
|
845
|
+
return Main.result_single_object(resp)
|
846
|
+
else Aspera.error_unexpected_value(command)
|
794
847
|
end
|
795
848
|
when :transfer
|
796
849
|
command = options.get_next_command(%i[list cancel show modify bandwidth_average sessions])
|
797
|
-
res_class_path = 'ops/transfers'
|
798
|
-
if %i[cancel show modify].include?(command)
|
799
|
-
one_res_id = instance_identifier
|
800
|
-
one_res_path = "#{res_class_path}/#{one_res_id}"
|
801
|
-
end
|
802
850
|
case command
|
803
851
|
when :list
|
804
852
|
transfer_filter = query_read_delete(default: {})
|
805
|
-
last_iteration_token = nil
|
806
853
|
iteration_persistency = nil
|
807
854
|
if options.get_option(:once_only, mandatory: true)
|
808
855
|
iteration_persistency = PersistencyActionOnce.new(
|
@@ -818,36 +865,10 @@ module Aspera
|
|
818
865
|
iteration_persistency.save
|
819
866
|
return Main.result_status('Persistency reset')
|
820
867
|
end
|
821
|
-
last_iteration_token = iteration_persistency.data.first
|
822
868
|
end
|
823
|
-
raise 'reset only with once_only' if transfer_filter.key?('reset') && iteration_persistency.nil?
|
869
|
+
raise Cli::BadArgument, 'reset only with once_only' if transfer_filter.key?('reset') && iteration_persistency.nil?
|
824
870
|
max_items = transfer_filter.delete(MAX_ITEMS)
|
825
|
-
transfers_data =
|
826
|
-
loop do
|
827
|
-
transfer_filter['iteration_token'] = last_iteration_token unless last_iteration_token.nil?
|
828
|
-
result = @api_node.call(operation: 'GET', subpath: res_class_path, query: transfer_filter)
|
829
|
-
# no data
|
830
|
-
break if result[:data].empty?
|
831
|
-
# get next iteration token from link
|
832
|
-
next_iteration_token = nil
|
833
|
-
link_info = result[:http]['Link']
|
834
|
-
unless link_info.nil?
|
835
|
-
m = link_info.match(/<([^>]+)>/)
|
836
|
-
raise "Cannot parse iteration in Link: #{link_info}" if m.nil?
|
837
|
-
next_iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
|
838
|
-
end
|
839
|
-
# same as last iteration: stop
|
840
|
-
break if next_iteration_token&.eql?(last_iteration_token)
|
841
|
-
last_iteration_token = next_iteration_token
|
842
|
-
transfers_data.concat(result[:data])
|
843
|
-
if max_items&.<=(transfers_data.length)
|
844
|
-
# if !max_items.nil? && (transfers_data.length >= max_items)
|
845
|
-
transfers_data = transfers_data.slice(0, max_items)
|
846
|
-
break
|
847
|
-
end
|
848
|
-
break if last_iteration_token.nil?
|
849
|
-
end
|
850
|
-
iteration_persistency&.data&.[]=(0, last_iteration_token)
|
871
|
+
transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', max: max_items, query: transfer_filter, iteration: iteration_persistency&.data)
|
851
872
|
iteration_persistency&.save
|
852
873
|
return {
|
853
874
|
type: :object_list,
|
@@ -855,8 +876,8 @@ module Aspera
|
|
855
876
|
fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
|
856
877
|
}
|
857
878
|
when :sessions
|
858
|
-
transfers_data = @api_node.read(
|
859
|
-
sessions = transfers_data.map{|t|t['sessions']}.flatten
|
879
|
+
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
880
|
+
sessions = transfers_data.map{ |t| t['sessions']}.flatten
|
860
881
|
sessions.each do |session|
|
861
882
|
session['start_time'] = Time.at(session['start_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
862
883
|
session['end_time'] = Time.at(session['end_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
@@ -867,16 +888,16 @@ module Aspera
|
|
867
888
|
fields: %w[id status start_time end_time target_rate_kbps]
|
868
889
|
}
|
869
890
|
when :cancel
|
870
|
-
resp = @api_node.cancel(
|
871
|
-
return
|
891
|
+
resp = @api_node.cancel("ops/transfers/#{instance_identifier}")
|
892
|
+
return Main.result_single_object(resp)
|
872
893
|
when :show
|
873
|
-
resp = @api_node.read(
|
874
|
-
return
|
894
|
+
resp = @api_node.read("ops/transfers/#{instance_identifier}")
|
895
|
+
return Main.result_single_object(resp)
|
875
896
|
when :modify
|
876
|
-
resp = @api_node.update(
|
877
|
-
return
|
897
|
+
resp = @api_node.update("ops/transfers/#{instance_identifier}", options.get_next_argument('update value', validation: Hash))
|
898
|
+
return Main.result_single_object(resp)
|
878
899
|
when :bandwidth_average
|
879
|
-
transfers_data = @api_node.read(
|
900
|
+
transfers_data = @api_node.read('ops/transfers', query_read_delete)
|
880
901
|
# collect all key dates
|
881
902
|
bandwidth_period = {}
|
882
903
|
dir_info = %i[avg_kbps sessions].freeze
|
@@ -919,9 +940,8 @@ module Aspera
|
|
919
940
|
end
|
920
941
|
result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
|
921
942
|
end
|
922
|
-
return
|
923
|
-
else
|
924
|
-
raise 'error'
|
943
|
+
return Main.result_object_list(result)
|
944
|
+
else Aspera.error_unexpected_value(command)
|
925
945
|
end
|
926
946
|
when :service
|
927
947
|
command = options.get_next_command(%i[list create delete])
|
@@ -931,7 +951,7 @@ module Aspera
|
|
931
951
|
case command
|
932
952
|
when :list
|
933
953
|
resp = @api_node.read('rund/services')
|
934
|
-
return
|
954
|
+
return Main.result_object_list(resp['services'])
|
935
955
|
when :create
|
936
956
|
# @json:'{"type":"WATCHFOLDERD","run_as":{"user":"user1"}}'
|
937
957
|
params = options.get_next_argument('creation data', validation: Hash)
|
@@ -957,9 +977,9 @@ module Aspera
|
|
957
977
|
return Main.result_status("#{resp['id']} created")
|
958
978
|
when :list
|
959
979
|
resp = @api_node.read(res_class_path, query_read_delete)
|
960
|
-
return
|
980
|
+
return Main.result_value_list(resp['ids'], name: 'id')
|
961
981
|
when :show
|
962
|
-
return
|
982
|
+
return Main.result_single_object(@api_node.read(one_res_path))
|
963
983
|
when :modify
|
964
984
|
@api_node.update(one_res_path, options.get_option(:query, mandatory: true))
|
965
985
|
return Main.result_status("#{one_res_id} updated")
|
@@ -967,7 +987,7 @@ module Aspera
|
|
967
987
|
@api_node.delete(one_res_path)
|
968
988
|
return Main.result_status("#{one_res_id} deleted")
|
969
989
|
when :state
|
970
|
-
return
|
990
|
+
return Main.result_single_object(@api_node.read("#{one_res_path}/state"))
|
971
991
|
end
|
972
992
|
when :central
|
973
993
|
command = options.get_next_command(%i[session file])
|
@@ -997,7 +1017,7 @@ module Aspera
|
|
997
1017
|
resp = @api_node.create('services/rest/transfers/v1/files', request_data)
|
998
1018
|
resp = JSON.parse(resp) if resp.is_a?(String)
|
999
1019
|
Log.log.debug{Log.dump(:resp, resp)}
|
1000
|
-
return
|
1020
|
+
return Main.result_object_list(resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path])
|
1001
1021
|
when :modify
|
1002
1022
|
request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
|
1003
1023
|
request_data.deep_merge!(validation) unless validation.nil?
|
@@ -1024,32 +1044,143 @@ module Aspera
|
|
1024
1044
|
return Main.result_status(Api::Node.bearer_token(payload: token_info, access_key: access_key, private_key: private_key))
|
1025
1045
|
when :simulator
|
1026
1046
|
require 'aspera/node_simulator'
|
1027
|
-
parameters = value_create_modify(command: command)
|
1028
|
-
|
1029
|
-
|
1030
|
-
uri
|
1031
|
-
server = WebServerSimple.new(uri, certificate: parameters[:certificate])
|
1032
|
-
server.mount(uri.path, NodeSimulatorServlet, parameters[:credentials], NodeSimulator.new)
|
1047
|
+
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
1048
|
+
uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
|
1049
|
+
server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
|
1050
|
+
server.mount(uri.path, NodeSimulatorServlet, parameters.except(*WebServerSimple::PARAMS), NodeSimulator.new)
|
1033
1051
|
server.start
|
1034
1052
|
return Main.result_status('Simulator terminated')
|
1053
|
+
when :telemetry
|
1054
|
+
parameters = value_create_modify(command: command, default: {}).symbolize_keys
|
1055
|
+
%i[url key].each do |psym|
|
1056
|
+
raise Cli::BadArgument, "Missing parameter: #{psym}" unless parameters.key?(psym)
|
1057
|
+
end
|
1058
|
+
require 'socket'
|
1059
|
+
parameters[:interval] = 10 unless parameters.key?(:interval)
|
1060
|
+
parameters[:hostname] = Socket.gethostname unless parameters.key?(:hostname)
|
1061
|
+
interval = parameters[:interval].to_f
|
1062
|
+
raise Cli::BadArgument, 'Interval must be a positive number in seconds' if interval <= 0
|
1063
|
+
otel_api = Rest.new(
|
1064
|
+
base_url: "#{parameters[:url]}/v1",
|
1065
|
+
headers: {
|
1066
|
+
# 'Authorization' => "apiToken #{parameters[:key]}",
|
1067
|
+
'x-instana-key' => parameters[:key],
|
1068
|
+
'x-instana-host' => parameters[:hostname]
|
1069
|
+
}
|
1070
|
+
)
|
1071
|
+
datapoint = {
|
1072
|
+
attributes: [
|
1073
|
+
{
|
1074
|
+
key: 'server.name',
|
1075
|
+
value: {
|
1076
|
+
stringValue: 'HSTS1'
|
1077
|
+
}
|
1078
|
+
}
|
1079
|
+
],
|
1080
|
+
asInt: nil,
|
1081
|
+
timeUnixNano: nil
|
1082
|
+
}
|
1083
|
+
# https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge
|
1084
|
+
metrics = {
|
1085
|
+
resourceMetrics: [
|
1086
|
+
{
|
1087
|
+
resource: {
|
1088
|
+
attributes: [
|
1089
|
+
{
|
1090
|
+
key: 'service.name',
|
1091
|
+
value: {
|
1092
|
+
stringValue: 'IBMAspera'
|
1093
|
+
}
|
1094
|
+
}
|
1095
|
+
]
|
1096
|
+
},
|
1097
|
+
scopeMetrics: [
|
1098
|
+
{
|
1099
|
+
metrics: [
|
1100
|
+
{
|
1101
|
+
name: 'active.transfers',
|
1102
|
+
description: 'Number of active transfers',
|
1103
|
+
unit: '1',
|
1104
|
+
gauge: {
|
1105
|
+
dataPoints: [
|
1106
|
+
datapoint
|
1107
|
+
]
|
1108
|
+
}
|
1109
|
+
}
|
1110
|
+
]
|
1111
|
+
}
|
1112
|
+
]
|
1113
|
+
}
|
1114
|
+
]
|
1115
|
+
}
|
1116
|
+
loop do
|
1117
|
+
timestamp = Time.now
|
1118
|
+
transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', query: {active_only: true})
|
1119
|
+
datapoint[:asInt] = transfers_data.length
|
1120
|
+
datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
|
1121
|
+
Log.log.info("#{datapoint[:asInt]} active transfers")
|
1122
|
+
# https://www.ibm.com/docs/en/instana-observability/current?topic=instana-backend
|
1123
|
+
otel_api.create('metrics', metrics)
|
1124
|
+
break if interval.eql?(0.0)
|
1125
|
+
sleep([0.0, interval - (Time.now - timestamp)].max)
|
1126
|
+
end
|
1035
1127
|
end
|
1036
|
-
|
1128
|
+
Aspera.error_unreachable_line
|
1037
1129
|
end
|
1038
1130
|
|
1039
1131
|
private
|
1040
1132
|
|
1041
1133
|
# get remaining path arguments from command line, and add prefix
|
1042
|
-
def get_all_arguments_with_prefix(
|
1134
|
+
def get_all_arguments_with_prefix(name)
|
1043
1135
|
path_args = options.get_next_argument(name, multiple: true)
|
1044
|
-
return path_args if
|
1045
|
-
return path_args.map
|
1136
|
+
return path_args if @prefix_path.nil?
|
1137
|
+
return path_args.map{ |p| File.join(@prefix_path, p)}
|
1046
1138
|
end
|
1047
1139
|
|
1048
1140
|
# get next path argument from command line, and add prefix
|
1049
|
-
def get_one_argument_with_prefix(
|
1141
|
+
def get_one_argument_with_prefix(name)
|
1050
1142
|
path_arg = options.get_next_argument(name, validation: String)
|
1051
|
-
return path_arg if
|
1052
|
-
return File.join(
|
1143
|
+
return path_arg if @prefix_path.nil?
|
1144
|
+
return File.join(@prefix_path, path_arg)
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
# Executes the provided API call in loop
|
1148
|
+
# @param api [Rest] the API to call
|
1149
|
+
# @param iteration [Array] a single element array with the iteration token or nil
|
1150
|
+
# @param max [Integer] maximum number of items to return, or nil for no limit
|
1151
|
+
# @param query [Hash] query parameters to use for the API call
|
1152
|
+
# @param call_args [Hash] additional arguments to pass to the API call
|
1153
|
+
# @return [Array] list of items returned by the API call
|
1154
|
+
def call_with_iteration(api:, iteration: nil, max: nil, query: nil, **call_args)
|
1155
|
+
query_token = query.clone || {}
|
1156
|
+
item_list = []
|
1157
|
+
query_token[:iteration_token] = iteration.first if iteration.is_a?(Array)
|
1158
|
+
loop do
|
1159
|
+
result = api.call(**call_args, query: query_token)
|
1160
|
+
Aspera.assert_type(result[:data], Array){"Expected data to be an Array, got: #{result[:data].class}"}
|
1161
|
+
# no data
|
1162
|
+
break if result[:data].empty?
|
1163
|
+
# get next iteration token from link
|
1164
|
+
next_iteration_token = nil
|
1165
|
+
link_info = result[:http]['Link']
|
1166
|
+
unless link_info.nil?
|
1167
|
+
m = link_info.match(/<([^>]+)>/)
|
1168
|
+
Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
|
1169
|
+
next_iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
|
1170
|
+
end
|
1171
|
+
# same as last iteration: stop
|
1172
|
+
break if next_iteration_token&.eql?(query_token[:iteration_token])
|
1173
|
+
query_token[:iteration_token] = next_iteration_token
|
1174
|
+
item_list.concat(result[:data])
|
1175
|
+
if max&.<=(item_list.length)
|
1176
|
+
item_list = item_list.slice(0, max)
|
1177
|
+
break
|
1178
|
+
end
|
1179
|
+
break if next_iteration_token.nil?
|
1180
|
+
end
|
1181
|
+
# save iteration token if needed
|
1182
|
+
iteration[0] = query_token[:iteration_token] unless iteration.nil?
|
1183
|
+
item_list
|
1053
1184
|
end
|
1054
1185
|
end
|
1055
1186
|
end
|