aspera-cli 4.21.2 → 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 (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +34 -16
  5. data/CONTRIBUTING.md +6 -10
  6. data/README.md +805 -574
  7. data/examples/get_proto_file.rb +1 -1
  8. data/lib/aspera/agent/base.rb +9 -5
  9. data/lib/aspera/agent/connect.rb +30 -28
  10. data/lib/aspera/agent/desktop.rb +29 -25
  11. data/lib/aspera/agent/direct.rb +137 -125
  12. data/lib/aspera/agent/httpgw.rb +22 -26
  13. data/lib/aspera/agent/node.rb +14 -11
  14. data/lib/aspera/agent/transferd.rb +6 -2
  15. data/lib/aspera/api/aoc.rb +6 -6
  16. data/lib/aspera/api/cos_node.rb +1 -1
  17. data/lib/aspera/api/httpgw.rb +7 -3
  18. data/lib/aspera/api/node.rb +6 -4
  19. data/lib/aspera/ascmd.rb +3 -3
  20. data/lib/aspera/ascp/installation.rb +15 -16
  21. data/lib/aspera/ascp/management.rb +1 -1
  22. data/lib/aspera/assert.rb +11 -2
  23. data/lib/aspera/cli/error.rb +2 -2
  24. data/lib/aspera/cli/extended_value.rb +38 -19
  25. data/lib/aspera/cli/formatter.rb +48 -48
  26. data/lib/aspera/cli/hints.rb +1 -1
  27. data/lib/aspera/cli/main.rb +190 -168
  28. data/lib/aspera/cli/manager.rb +15 -15
  29. data/lib/aspera/cli/plugin.rb +23 -20
  30. data/lib/aspera/cli/plugin_factory.rb +1 -1
  31. data/lib/aspera/cli/plugins/alee.rb +1 -1
  32. data/lib/aspera/cli/plugins/aoc.rb +144 -107
  33. data/lib/aspera/cli/plugins/ats.rb +19 -17
  34. data/lib/aspera/cli/plugins/config.rb +67 -83
  35. data/lib/aspera/cli/plugins/console.rb +5 -3
  36. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  37. data/lib/aspera/cli/plugins/faspex5.rb +104 -80
  38. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  39. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  40. data/lib/aspera/cli/plugins/node.rb +306 -179
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  42. data/lib/aspera/cli/plugins/preview.rb +3 -3
  43. data/lib/aspera/cli/plugins/server.rb +6 -6
  44. data/lib/aspera/cli/plugins/shares.rb +5 -5
  45. data/lib/aspera/cli/sync_actions.rb +19 -18
  46. data/lib/aspera/cli/transfer_agent.rb +5 -5
  47. data/lib/aspera/cli/transfer_progress.rb +2 -2
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/command_line_builder.rb +116 -95
  50. data/lib/aspera/coverage.rb +4 -3
  51. data/lib/aspera/environment.rb +6 -6
  52. data/lib/aspera/faspex_gw.rb +14 -14
  53. data/lib/aspera/faspex_postproc.rb +7 -6
  54. data/lib/aspera/hash_ext.rb +2 -2
  55. data/lib/aspera/json_rpc.rb +1 -1
  56. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  57. data/lib/aspera/keychain/factory.rb +41 -0
  58. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  59. data/lib/aspera/keychain/macos_security.rb +19 -11
  60. data/lib/aspera/log.rb +28 -34
  61. data/lib/aspera/nagios.rb +6 -6
  62. data/lib/aspera/node_simulator.rb +8 -8
  63. data/lib/aspera/oauth/base.rb +8 -6
  64. data/lib/aspera/oauth/factory.rb +5 -6
  65. data/lib/aspera/oauth/url_json.rb +6 -6
  66. data/lib/aspera/persistency_action_once.rb +6 -4
  67. data/lib/aspera/persistency_folder.rb +2 -2
  68. data/lib/aspera/preview/generator.rb +1 -1
  69. data/lib/aspera/preview/options.rb +16 -16
  70. data/lib/aspera/preview/terminal.rb +3 -3
  71. data/lib/aspera/preview/utils.rb +11 -13
  72. data/lib/aspera/products/connect.rb +1 -1
  73. data/lib/aspera/products/desktop.rb +1 -1
  74. data/lib/aspera/products/transferd.rb +1 -1
  75. data/lib/aspera/proxy_auto_config.rb +2 -2
  76. data/lib/aspera/rest.rb +52 -43
  77. data/lib/aspera/rest_errors_aspera.rb +1 -1
  78. data/lib/aspera/secret_hider.rb +5 -5
  79. data/lib/aspera/ssh.rb +4 -4
  80. data/lib/aspera/transfer/convert.rb +29 -0
  81. data/lib/aspera/transfer/error_info.rb +66 -66
  82. data/lib/aspera/transfer/parameters.rb +13 -68
  83. data/lib/aspera/transfer/spec.rb +5 -6
  84. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  85. data/lib/aspera/transfer/spec_doc.rb +62 -0
  86. data/lib/aspera/transfer/sync.rb +23 -72
  87. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  88. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  89. data/lib/aspera/transfer/uri.rb +6 -6
  90. data/lib/aspera/uri_reader.rb +1 -1
  91. data/lib/aspera/web_auth.rb +1 -1
  92. data/lib/aspera/web_server_simple.rb +53 -44
  93. data.tar.gz.sig +1 -2
  94. metadata +37 -4
  95. metadata.gz.sig +0 -0
  96. data/examples/build_package.sh +0 -28
  97. 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
@@ -480,7 +503,7 @@ module Aspera
480
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)
@@ -610,7 +660,7 @@ 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)
614
664
  when :show
615
665
  perm_id = instance_identifier
616
666
  return Main.result_single_object(apifid[:api].read("permissions/#{perm_id}"))
@@ -624,7 +674,7 @@ module Aspera
624
674
  end
625
675
  when :create
626
676
  create_param = options.get_next_argument('creation data', validation: Hash)
627
- raise 'no file_id' if create_param.key?('file_id')
677
+ raise Cli::BadArgument, 'no file_id' if create_param.key?('file_id')
628
678
  create_param['file_id'] = apifid[:file_id]
629
679
  create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
630
680
  # add application specific tags (AoC)
@@ -634,7 +684,7 @@ module Aspera
634
684
  created_data = apifid[:api].create('permissions', create_param)
635
685
  # notify application of creation
636
686
  the_app&.[](:api)&.permissions_send_event(event_data: created_data, app_info: the_app)
637
- return { type: :single_object, data: created_data}
687
+ return Main.result_single_object(created_data)
638
688
  else Aspera.error_unreachable_line
639
689
  end
640
690
  else Aspera.error_unreachable_line
@@ -658,8 +708,8 @@ module Aspera
658
708
  else
659
709
  async_ids = @api_node.read('async/list')['sync_ids']
660
710
  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?
711
+ selected = summaries.find{ |s| s['name'].eql?(async_name)}
712
+ raise Cli::BadIdentifier, "no such sync: #{async_name}" if selected.nil?
663
713
  async_id = selected['snid']
664
714
  async_ids = [async_id]
665
715
  end
@@ -668,22 +718,22 @@ module Aspera
668
718
  case command
669
719
  when :list
670
720
  resp = @api_node.read('async/list')['sync_ids']
671
- return { type: :value_list, data: resp, name: 'id' }
721
+ return Main.result_value_list(resp, name: 'id')
672
722
  when :show
673
723
  resp = @api_node.create('async/summary', post_data)['sync_summaries']
674
724
  return Main.result_empty if resp.empty?
675
- return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if async_id.eql?(SpecialValues::ALL)
676
- 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)
677
727
  when :delete
678
728
  resp = @api_node.create('async/delete', post_data)
679
- return { type: :single_object, data: resp, name: 'id' }
729
+ return Main.result_single_object(resp)
680
730
  when :bandwidth
681
731
  post_data['seconds'] = 100 # TODO: as parameter with --value
682
732
  resp = @api_node.create('async/bandwidth', post_data)
683
733
  data = resp['bandwidth_data']
684
734
  return Main.result_empty if data.empty?
685
735
  data = data.first[async_id]['data']
686
- return { type: :object_list, data: data, name: 'id' }
736
+ return Main.result_object_list(data)
687
737
  when :files
688
738
  # count int
689
739
  # filename str
@@ -706,17 +756,17 @@ module Aspera
706
756
  options.get_option(:username, mandatory: true),
707
757
  async_id]))
708
758
  unless iteration_data.first.nil?
709
- data.select!{|l| l['fnid'].to_i > iteration_data.first}
759
+ data.select!{ |l| l['fnid'].to_i > iteration_data.first}
710
760
  end
711
761
  iteration_data[0] = data.last['fnid'].to_i unless data.empty?
712
762
  end
713
763
  return Main.result_empty if data.empty?
714
764
  skip_ids_persistency&.save
715
- return { type: :object_list, data: data, name: 'id' }
765
+ return Main.result_object_list(data)
716
766
  when :counters
717
767
  resp = @api_node.create('async/counters', post_data)['sync_counters'].first[async_id].last
718
768
  return Main.result_empty if resp.nil?
719
- return { type: :single_object, data: resp }
769
+ return Main.result_single_object(resp)
720
770
  end
721
771
  end
722
772
 
@@ -731,7 +781,7 @@ module Aspera
731
781
  # name is unique, so we can return
732
782
  return id if sync_info[field].eql?(value)
733
783
  end
734
- raise Cli::BadArgument, "no such sync: #{field}=#{value}"
784
+ raise Cli::BadIdentifier, "no such sync: #{field}=#{value}"
735
785
  end
736
786
 
737
787
  ACTIONS = %i[
@@ -745,60 +795,57 @@ module Aspera
745
795
  asperabrowser
746
796
  basic_token
747
797
  bearer_token
748
- simulator].concat(COMMON_ACTIONS).freeze
798
+ simulator
799
+ telemetry
800
+ ].concat(COMMON_ACTIONS).freeze
749
801
 
750
- def execute_action(command=nil, prefix_path=nil)
802
+ def execute_action(command=nil)
751
803
  command ||= options.get_next_command(ACTIONS)
752
804
  case command
753
- when *COMMON_ACTIONS then return execute_simple_common(command, prefix_path)
805
+ when *COMMON_ACTIONS then return execute_simple_common(command)
754
806
  when :async then return execute_async # former API
755
807
  when :ssync
756
808
  # newer API
757
809
  sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
758
810
  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)}
811
+ when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids'){ |field, value| ssync_lookup(field, value)}
760
812
  else
761
- asyncs_id = instance_identifier {|field, value|ssync_lookup(field, value)}
813
+ asyncs_id = instance_identifier{ |field, value| ssync_lookup(field, value)}
762
814
  if %i[start stop].include?(sync_command)
763
815
  @api_node.call(
764
- operation: 'POST',
765
- subpath: "asyncs/#{asyncs_id}/#{sync_command}",
766
- body: '',
767
- 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
768
821
  return Main.result_status('Done')
769
822
  end
770
823
  parameters = nil
771
824
  parameters = options.get_option(:query, default: {}) if %i[bandwidth counters files].include?(sync_command)
772
- 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))
773
826
  end
774
827
  when :stream
775
828
  command = options.get_next_command(%i[list create show modify cancel])
776
829
  case command
777
830
  when :list
778
831
  resp = @api_node.read('ops/transfers', query_read_delete)
779
- 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?
780
833
  when :create
781
834
  resp = @api_node.create('streams', value_create_modify(command: command))
782
- return { type: :single_object, data: resp }
835
+ return Main.result_single_object(resp)
783
836
  when :show
784
837
  resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
785
- return { type: :other_struct, data: resp }
838
+ return Main.result_single_object(resp)
786
839
  when :modify
787
840
  resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command))
788
- return { type: :other_struct, data: resp }
841
+ return Main.result_single_object(resp)
789
842
  when :cancel
790
843
  resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
791
- return { type: :other_struct, data: resp }
792
- else
793
- raise 'error'
844
+ return Main.result_single_object(resp)
845
+ else Aspera.error_unexpected_value(command)
794
846
  end
795
847
  when :transfer
796
848
  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
849
  case command
803
850
  when :list
804
851
  transfer_filter = query_read_delete(default: {})
@@ -820,12 +867,12 @@ module Aspera
820
867
  end
821
868
  last_iteration_token = iteration_persistency.data.first
822
869
  end
823
- 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?
824
871
  max_items = transfer_filter.delete(MAX_ITEMS)
825
872
  transfers_data = []
826
873
  loop do
827
874
  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)
875
+ result = @api_node.call(operation: 'GET', subpath: 'ops/transfers', query: transfer_filter)
829
876
  # no data
830
877
  break if result[:data].empty?
831
878
  # get next iteration token from link
@@ -841,7 +888,6 @@ module Aspera
841
888
  last_iteration_token = next_iteration_token
842
889
  transfers_data.concat(result[:data])
843
890
  if max_items&.<=(transfers_data.length)
844
- # if !max_items.nil? && (transfers_data.length >= max_items)
845
891
  transfers_data = transfers_data.slice(0, max_items)
846
892
  break
847
893
  end
@@ -855,8 +901,8 @@ module Aspera
855
901
  fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
856
902
  }
857
903
  when :sessions
858
- transfers_data = @api_node.read(res_class_path, query_read_delete)
859
- 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
860
906
  sessions.each do |session|
861
907
  session['start_time'] = Time.at(session['start_time_usec'] / 1_000_000.0).utc.iso8601(0)
862
908
  session['end_time'] = Time.at(session['end_time_usec'] / 1_000_000.0).utc.iso8601(0)
@@ -867,16 +913,16 @@ module Aspera
867
913
  fields: %w[id status start_time end_time target_rate_kbps]
868
914
  }
869
915
  when :cancel
870
- resp = @api_node.cancel(one_res_path)
871
- return { type: :other_struct, data: resp }
916
+ resp = @api_node.cancel("ops/transfers/#{instance_identifier}")
917
+ return Main.result_single_object(resp)
872
918
  when :show
873
- resp = @api_node.read(one_res_path)
874
- return { type: :other_struct, data: resp }
919
+ resp = @api_node.read("ops/transfers/#{instance_identifier}")
920
+ return Main.result_single_object(resp)
875
921
  when :modify
876
- resp = @api_node.update(one_res_path, options.get_next_argument('update value', validation: Hash))
877
- 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)
878
924
  when :bandwidth_average
879
- transfers_data = @api_node.read(res_class_path, query_read_delete)
925
+ transfers_data = @api_node.read('ops/transfers', query_read_delete)
880
926
  # collect all key dates
881
927
  bandwidth_period = {}
882
928
  dir_info = %i[avg_kbps sessions].freeze
@@ -919,9 +965,8 @@ module Aspera
919
965
  end
920
966
  result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
921
967
  end
922
- return { type: :object_list, data: result }
923
- else
924
- raise 'error'
968
+ return Main.result_object_list(result)
969
+ else Aspera.error_unexpected_value(command)
925
970
  end
926
971
  when :service
927
972
  command = options.get_next_command(%i[list create delete])
@@ -931,7 +976,7 @@ module Aspera
931
976
  case command
932
977
  when :list
933
978
  resp = @api_node.read('rund/services')
934
- return { type: :object_list, data: resp['services'] }
979
+ return Main.result_object_list(resp['services'])
935
980
  when :create
936
981
  # @json:'{"type":"WATCHFOLDERD","run_as":{"user":"user1"}}'
937
982
  params = options.get_next_argument('creation data', validation: Hash)
@@ -957,9 +1002,9 @@ module Aspera
957
1002
  return Main.result_status("#{resp['id']} created")
958
1003
  when :list
959
1004
  resp = @api_node.read(res_class_path, query_read_delete)
960
- return { type: :value_list, data: resp['ids'], name: 'id' }
1005
+ return Main.result_value_list(resp['ids'], name: 'id')
961
1006
  when :show
962
- return { type: :single_object, data: @api_node.read(one_res_path)}
1007
+ return Main.result_single_object(@api_node.read(one_res_path))
963
1008
  when :modify
964
1009
  @api_node.update(one_res_path, options.get_option(:query, mandatory: true))
965
1010
  return Main.result_status("#{one_res_id} updated")
@@ -967,7 +1012,7 @@ module Aspera
967
1012
  @api_node.delete(one_res_path)
968
1013
  return Main.result_status("#{one_res_id} deleted")
969
1014
  when :state
970
- 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"))
971
1016
  end
972
1017
  when :central
973
1018
  command = options.get_next_command(%i[session file])
@@ -997,7 +1042,7 @@ module Aspera
997
1042
  resp = @api_node.create('services/rest/transfers/v1/files', request_data)
998
1043
  resp = JSON.parse(resp) if resp.is_a?(String)
999
1044
  Log.log.debug{Log.dump(:resp, resp)}
1000
- 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])
1001
1046
  when :modify
1002
1047
  request_data = options.get_next_argument('request data', mandatory: false, validation: Hash, default: {})
1003
1048
  request_data.deep_merge!(validation) unless validation.nil?
@@ -1024,32 +1069,114 @@ module Aspera
1024
1069
  return Main.result_status(Api::Node.bearer_token(payload: token_info, access_key: access_key, private_key: private_key))
1025
1070
  when :simulator
1026
1071
  require 'aspera/node_simulator'
1027
- parameters = value_create_modify(command: command)
1028
- parameters = parameters.symbolize_keys
1029
- raise 'Missing key: url' unless parameters.key?(:url)
1030
- uri = URI.parse(parameters[:url])
1031
- server = WebServerSimple.new(uri, certificate: parameters[:certificate])
1032
- 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)
1033
1076
  server.start
1034
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
1035
1162
  end
1036
- raise 'ERROR: shall not reach this line'
1163
+ Aspera.error_unreachable_line
1037
1164
  end
1038
1165
 
1039
1166
  private
1040
1167
 
1041
1168
  # get remaining path arguments from command line, and add prefix
1042
- def get_all_arguments_with_prefix(path_prefix, name)
1169
+ def get_all_arguments_with_prefix(name)
1043
1170
  path_args = options.get_next_argument(name, multiple: true)
1044
- return path_args if path_prefix.nil?
1045
- 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)}
1046
1173
  end
1047
1174
 
1048
1175
  # get next path argument from command line, and add prefix
1049
- def get_one_argument_with_prefix(path_prefix, name)
1176
+ def get_one_argument_with_prefix(name)
1050
1177
  path_arg = options.get_next_argument(name, validation: String)
1051
- return path_arg if path_prefix.nil?
1052
- return File.join(path_prefix, path_arg)
1178
+ return path_arg if @prefix_path.nil?
1179
+ return File.join(@prefix_path, path_arg)
1053
1180
  end
1054
1181
  end
1055
1182
  end