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