aspera-cli 4.24.2 → 4.25.0.pre2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1067 -758
  4. data/CONTRIBUTING.md +93 -120
  5. data/README.md +817 -510
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/transferd.rb +4 -4
  8. data/lib/aspera/api/aoc.rb +71 -43
  9. data/lib/aspera/api/cos_node.rb +3 -2
  10. data/lib/aspera/api/faspex.rb +6 -5
  11. data/lib/aspera/api/node.rb +10 -12
  12. data/lib/aspera/ascmd.rb +1 -2
  13. data/lib/aspera/ascp/installation.rb +55 -41
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +28 -6
  16. data/lib/aspera/cli/error.rb +4 -2
  17. data/lib/aspera/cli/extended_value.rb +94 -62
  18. data/lib/aspera/cli/formatter.rb +55 -22
  19. data/lib/aspera/cli/main.rb +21 -14
  20. data/lib/aspera/cli/manager.rb +349 -248
  21. data/lib/aspera/cli/plugins/alee.rb +3 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +94 -51
  23. data/lib/aspera/cli/plugins/base.rb +62 -49
  24. data/lib/aspera/cli/plugins/config.rb +85 -96
  25. data/lib/aspera/cli/plugins/console.rb +15 -9
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +34 -27
  28. data/lib/aspera/cli/plugins/faspex5.rb +47 -44
  29. data/lib/aspera/cli/plugins/faspio.rb +7 -6
  30. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  31. data/lib/aspera/cli/plugins/node.rb +132 -120
  32. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  33. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  34. data/lib/aspera/cli/plugins/preview.rb +26 -46
  35. data/lib/aspera/cli/plugins/server.rb +9 -10
  36. data/lib/aspera/cli/plugins/shares.rb +77 -43
  37. data/lib/aspera/cli/sync_actions.rb +49 -38
  38. data/lib/aspera/cli/transfer_agent.rb +16 -34
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/cli/wizard.rb +8 -5
  41. data/lib/aspera/command_line_builder.rb +20 -17
  42. data/lib/aspera/coverage.rb +6 -2
  43. data/lib/aspera/environment.rb +71 -84
  44. data/lib/aspera/faspex_gw.rb +1 -1
  45. data/lib/aspera/faspex_postproc.rb +1 -1
  46. data/lib/aspera/keychain/factory.rb +1 -2
  47. data/lib/aspera/keychain/macos_security.rb +2 -2
  48. data/lib/aspera/log.rb +2 -1
  49. data/lib/aspera/markdown.rb +31 -0
  50. data/lib/aspera/nagios.rb +6 -5
  51. data/lib/aspera/oauth/base.rb +17 -27
  52. data/lib/aspera/oauth/factory.rb +1 -1
  53. data/lib/aspera/oauth/url_json.rb +2 -1
  54. data/lib/aspera/preview/file_types.rb +23 -37
  55. data/lib/aspera/preview/terminal.rb +95 -29
  56. data/lib/aspera/preview/utils.rb +6 -5
  57. data/lib/aspera/products/connect.rb +3 -3
  58. data/lib/aspera/rest.rb +51 -39
  59. data/lib/aspera/rest_error_analyzer.rb +4 -4
  60. data/lib/aspera/ssh.rb +5 -2
  61. data/lib/aspera/ssl.rb +41 -0
  62. data/lib/aspera/sync/conf.schema.yaml +182 -34
  63. data/lib/aspera/sync/database.rb +2 -1
  64. data/lib/aspera/sync/operations.rb +128 -72
  65. data/lib/aspera/transfer/parameters.rb +3 -4
  66. data/lib/aspera/transfer/spec.rb +2 -3
  67. data/lib/aspera/transfer/spec.schema.yaml +49 -19
  68. data/lib/aspera/transfer/spec_doc.rb +14 -14
  69. data/lib/aspera/uri_reader.rb +1 -1
  70. data/lib/transferd_pb.rb +2 -2
  71. data.tar.gz.sig +0 -0
  72. metadata +33 -6
  73. metadata.gz.sig +0 -0
@@ -21,6 +21,30 @@ module Aspera
21
21
  class Node < BasicAuth
22
22
  include SyncActions
23
23
 
24
+ # Processing of paths in arguments and results
25
+ # Used only by Faspex4 to browse packages
26
+ class NodePathPrefix
27
+ def initialize(path)
28
+ @root = path
29
+ end
30
+
31
+ # get next path argument from command line, and add prefix
32
+ def add_to_path(path_arg)
33
+ File.join(@root, path_arg)
34
+ end
35
+
36
+ # get remaining path arguments from command line, and add prefix
37
+ def add_to_paths!(path_args)
38
+ path_args.map!{ |p| add_to_path(p)}
39
+ end
40
+
41
+ def remove_in_object_list!(obj_list)
42
+ obj_list.each do |item|
43
+ item['path'] = item['path'][@root.length..-1] if item['path'].start_with?(@root)
44
+ end
45
+ end
46
+ end
47
+
24
48
  class << self
25
49
  # directory: node, container: shares
26
50
  FOLDER_TYPES = %w[directory container].freeze
@@ -30,6 +54,7 @@ module Aspera
30
54
  'HSTS Node API'
31
55
  end
32
56
 
57
+ # @return [Hash,NilClass]
33
58
  def detect(address_or_url)
34
59
  urls = if address_or_url.match?(%r{^[a-z]{1,6}://})
35
60
  [address_or_url]
@@ -45,12 +70,12 @@ module Aspera
45
70
  next unless base_url.match?('https?://')
46
71
  api = Rest.new(base_url: base_url)
47
72
  test_endpoint = 'ping'
48
- result = api.call(operation: 'GET', subpath: test_endpoint)
49
- next unless result[:http].body.eql?('')
73
+ http = api.read(test_endpoint, ret: :resp)
74
+ next unless http.body.eql?('')
50
75
  # also remove "/"
51
76
  url_end = -2 - test_endpoint.length
52
77
  return {
53
- url: result[:http].uri.to_s[0..url_end],
78
+ url: http.uri.to_s[0..url_end],
54
79
  version: 'requires authentication'
55
80
  }
56
81
  rescue StandardError => e
@@ -68,11 +93,11 @@ module Aspera
68
93
  options.declare(:validator, 'Identifier of validator (optional for central)')
69
94
  options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
70
95
  options.declare(
71
- :default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', values: :bool, default: :yes,
96
+ :default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', allowed: Allowed::TYPES_BOOLEAN, default: true,
72
97
  handler: {o: Api::Node, m: :use_standard_ports}
73
98
  )
74
99
  options.declare(
75
- :node_cache, 'Gen4: Set to no to force actual file system read', values: :bool,
100
+ :node_cache, 'Gen4: Set to no to force actual file system read', allowed: Allowed::TYPES_BOOLEAN,
76
101
  handler: {o: Api::Node, m: :use_node_cache}
77
102
  )
78
103
  options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
@@ -114,7 +139,7 @@ module Aspera
114
139
  SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
115
140
 
116
141
  # Actions in execute_command_gen3
117
- COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport]
142
+ COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download cat sync transport spec]
118
143
 
119
144
  BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
120
145
 
@@ -142,9 +167,9 @@ module Aspera
142
167
  GEN4_LS_FIELDS = %w[name type recursive_size size modified_time access_level].freeze
143
168
 
144
169
  # @param api [Rest] an existing API object for the Node API
145
- # @param prefix_path [String,nil] for Faspex 4, allows browsing a package
170
+ # @param prefix_path [String,nil] for Faspex 4, allows browsing a package without full path in node (removes storage prefix)
146
171
  def initialize(context:, api: nil, prefix_path: nil)
147
- @prefix_path = prefix_path
172
+ @prefixer = prefix_path ? NodePathPrefix.new(prefix_path) : nil
148
173
  super(context: context, basic_options: api.nil?)
149
174
  Node.declare_options(options)
150
175
  return if context.only_manual?
@@ -171,43 +196,12 @@ module Aspera
171
196
  end
172
197
  end
173
198
 
174
- # reduce the path from a result on given named column
175
- def c_result_remove_prefix_path(result, column)
176
- return result if @prefix_path.nil?
177
- case result[:type]
178
- when :object_list
179
- result[:data].each do |item|
180
- item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
181
- end
182
- when :single_object
183
- item = result[:data]
184
- item[column] = item[column][@prefix_path.length..-1] if item[column].start_with?(@prefix_path)
185
- end
186
- return result
187
- end
188
-
189
- # translates paths results into CLI result, and removes prefix
190
- def c_result_translate_rem_prefix(response, type, success_msg)
191
- errors = []
192
- final_result = {type: :object_list, data: [], fields: [type, 'result']}
193
- response['paths'].each do |p|
194
- result = success_msg
195
- if p.key?('error')
196
- Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
197
- result = p['error']['user_message']
198
- errors.push([p['path'], p['error']['user_message']])
199
- end
200
- final_result[:data].push({type => p['path'], 'result' => result})
201
- end
202
- # one error make all fail
203
- raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ') unless errors.empty?
204
- return c_result_remove_prefix_path(final_result, type)
205
- end
206
-
207
199
  # Gen3 API
208
200
  def browse_gen3
209
- folders_to_process = [get_one_argument_with_prefix('path')]
210
- query = options.get_option(:query, default: {})
201
+ folders_to_process = options.get_next_argument('path', validation: String)
202
+ folders_to_process = @prefixer.add_to_path(folders_to_process) unless @prefixer.nil?
203
+ folders_to_process = [folders_to_process]
204
+ query = options.get_option(:query) || {}
211
205
  # special parameter: max number of entries in result
212
206
  max_items = query.delete(MAX_ITEMS)
213
207
  # special parameter: recursive browsing
@@ -225,14 +219,13 @@ module Aspera
225
219
  query['path'] = path
226
220
  offset = 0
227
221
  total_count = nil
228
- result = nil
229
222
  loop do
230
223
  # example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
231
224
  response = @api_node.create('files/browse', query)
232
225
  # 'file','symbolic_link'
233
226
  if !Node.gen3_entry_folder?(response['self']) || only_path
234
- result = {type: :single_object, data: response['self']}
235
- break
227
+ @prefixer&.remove_in_object_list!([response['self']])
228
+ return Main.result_single_object(response['self'])
236
229
  end
237
230
  items = response['items']
238
231
  total_count ||= response['total_count']
@@ -253,9 +246,10 @@ module Aspera
253
246
  end
254
247
  query.delete('skip')
255
248
  end
256
- result ||= {type: :object_list, data: all_items}
249
+ @prefixer&.remove_in_object_list!(all_items)
250
+ return Main.result_object_list(all_items)
251
+ ensure
257
252
  formatter.long_operation_terminated
258
- return c_result_remove_prefix_path(result, 'path')
259
253
  end
260
254
 
261
255
  # Create async transfer spec request from direction and folders
@@ -294,48 +288,57 @@ module Aspera
294
288
  case command
295
289
  when :delete
296
290
  # TODO: add query for recursive
297
- paths_to_delete = get_all_arguments_with_prefix('file list')
291
+ paths_to_delete = options.get_next_argument('file list', multiple: true)
292
+ @prefixer&.add_to_paths!(paths_to_delete)
298
293
  resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
299
- return c_result_translate_rem_prefix(resp, 'file', 'deleted')
294
+ return cli_result_from_paths_response(resp, 'file deleted')
300
295
  when :search
301
- search_root = get_one_argument_with_prefix('search root')
296
+ search_root = options.get_next_argument('search root', validation: String)
297
+ search_root = @prefixer.add_to_path(search_root) unless @prefixer.nil?
302
298
  parameters = {'path' => search_root}
303
299
  other_options = options.get_option(:query)
304
300
  parameters.merge!(other_options) unless other_options.nil?
305
301
  resp = @api_node.create('files/search', parameters)
306
- result = {type: :object_list, data: resp['items']}
307
- return Main.result_empty if result[:data].empty?
308
- result[:fields] = result[:data].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
302
+ return Main.result_empty if resp['items'].empty?
303
+ fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
309
304
  formatter.display_item_count(resp['item_count'], resp['total_count'])
310
305
  formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
311
- return c_result_remove_prefix_path(result, 'path')
306
+ @prefixer&.remove_in_object_list!(resp['items'])
307
+ return Main.result_object_list(resp['items'], fields: fields)
312
308
  when :space
313
- path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
309
+ path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
310
+ @prefixer&.add_to_paths!(path_list)
314
311
  resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
315
- result = {type: :object_list, data: resp['paths']}
316
- # return c_result_translate_rem_prefix(resp,'folder','created',@prefix_path)
317
- return c_result_remove_prefix_path(result, 'path')
312
+ @prefixer&.remove_in_object_list!(resp['paths'])
313
+ return Main.result_object_list(resp['paths'])
318
314
  when :mkdir
319
- path_list = get_all_arguments_with_prefix('folder path or ext.val. list')
315
+ path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
316
+ @prefixer&.add_to_paths!(path_list)
320
317
  resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
321
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
318
+ return cli_result_from_paths_response(resp, 'folder created')
322
319
  when :mklink
323
- target = get_one_argument_with_prefix('target')
324
- one_path = get_one_argument_with_prefix('link path')
320
+ target = options.get_next_argument('target', validation: String)
321
+ target = @prefixer.add_to_path(target) unless @prefixer.nil?
322
+ one_path = options.get_next_argument('link path', validation: String)
323
+ one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
325
324
  resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
326
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
325
+ return cli_result_from_paths_response(resp, 'link created')
327
326
  when :mkfile
328
- one_path = get_one_argument_with_prefix('file path')
327
+ one_path = options.get_next_argument('file path', validation: String)
328
+ one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
329
329
  contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
330
330
  resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
331
- return c_result_translate_rem_prefix(resp, 'folder', 'created')
331
+ return cli_result_from_paths_response(resp, 'file created')
332
332
  when :rename
333
333
  # TODO: multiple ?
334
- path_base = get_one_argument_with_prefix('path_base')
335
- path_src = get_one_argument_with_prefix('path_src')
336
- path_dst = get_one_argument_with_prefix('path_dst')
334
+ path_base = options.get_next_argument('path_base', validation: String)
335
+ path_base = @prefixer.add_to_path(path_base) unless @prefixer.nil?
336
+ path_src = options.get_next_argument('path_src', validation: String)
337
+ path_src = @prefixer.add_to_path(path_src) unless @prefixer.nil?
338
+ path_dst = options.get_next_argument('path_dst', validation: String)
339
+ path_dst = @prefixer.add_to_path(path_dst) unless @prefixer.nil?
337
340
  resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
338
- return c_result_translate_rem_prefix(resp, 'entry', 'moved')
341
+ return cli_result_from_paths_response(resp, 'entry moved')
339
342
  when :browse
340
343
  return browse_gen3
341
344
  when :sync
@@ -377,15 +380,15 @@ module Aspera
377
380
  transfer_spec.delete('paths') if command.eql?(:upload)
378
381
  return Main.result_transfer(transfer.start(transfer_spec))
379
382
  when :cat
380
- remote_path = get_one_argument_with_prefix('remote path')
383
+ remote_path = options.get_next_argument('remote path', validation: String)
384
+ remote_path = @prefixer.add_to_path(remote_path) unless @prefixer.nil?
381
385
  File.basename(remote_path)
382
- result = @api_node.call(
383
- operation: 'GET',
384
- subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents"
385
- )
386
- return Main.result_text(result[:http].body)
386
+ http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
387
+ return Main.result_text(http.body)
387
388
  when :transport
388
389
  return Main.result_single_object(@api_node.transport_params)
390
+ when :spec
391
+ return Main.result_single_object(@api_node.base_spec)
389
392
  end
390
393
  Aspera.error_unreachable_line
391
394
  end
@@ -438,7 +441,7 @@ module Aspera
438
441
  nagios.add_ok('node api', 'accessible')
439
442
  nagios.check_time_offset(info['current_time'], 'node api')
440
443
  nagios.check_product_version('node api', 'entsrv', info['version'])
441
- rescue RestCallError => e
444
+ rescue StandardError => e
442
445
  nagios.add_critical('node api', e.to_s)
443
446
  end
444
447
  begin
@@ -447,13 +450,14 @@ module Aspera
447
450
  subpath: 'services/soap/Transfer-201210',
448
451
  content_type: Rest::MIME_TEXT,
449
452
  body: CENTRAL_SOAP_API_TEST,
450
- headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'}
451
- )[:http].body
453
+ headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
454
+ ret: :resp
455
+ ).body
452
456
  nagios.add_ok('central', 'accessible by node')
453
457
  rescue StandardError => e
454
458
  nagios.add_critical('central', e.to_s)
455
459
  end
456
- return nagios.result
460
+ Main.result_object_list(nagios.status_list)
457
461
  when :events
458
462
  events = @api_node.read('events', query_read_delete)
459
463
  return Main.result_object_list(events, fields: ->(f){!f.start_with?('data')})
@@ -513,8 +517,8 @@ module Aspera
513
517
  else Aspera.error_unreachable_line
514
518
  end
515
519
  return Main.result_single_object(result) if command_repo.eql?(:node_info)
516
- # check format of bearer token
517
- OAuth::Factory.bearer_token(result[:password])
520
+ raise BadArgument, 'Cannot get bearer token if authenticating with secret' unless apifid[:api].auth_params[:type].eql?(:oauth2)
521
+ Aspera.assert(OAuth::Factory.bearer_auth?(result[:password])){'Not using bearer token auth'}
518
522
  return Main.result_text(result[:password])
519
523
  when :browse
520
524
  apifid = apifid_from_next_arg(top_file_id)
@@ -531,7 +535,7 @@ module Aspera
531
535
  when :mkdir, :mklink, :mkfile
532
536
  containing_folder_path, new_item = Api::Node.split_folder(options.get_next_argument('path'))
533
537
  apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
534
- query = options.get_option(:query, mandatory: false)
538
+ query = options.get_option(:query)
535
539
  check_exists = true
536
540
  payload = {name: new_item}
537
541
  if query
@@ -570,10 +574,11 @@ module Aspera
570
574
  return Main.result_status("renamed to #{newname}")
571
575
  when :delete
572
576
  return do_bulk_operation(command: command_repo, descr: 'path', values: String, id_result: 'path') do |l_path|
573
- apifid = if (m = l_path.match(REGEX_LOOKUP_ID_BY_FIELD))
577
+ apifid = if (m = Base.percent_selector(l_path))
578
+ Aspera.assert_values(m[:field], ['id'], type: BadIdentifier)
574
579
  {
575
580
  api: @api_node,
576
- file_id: m[2]
581
+ file_id: m[:value]
577
582
  }
578
583
  else
579
584
  @api_node.resolve_api_fid(top_file_id, l_path)
@@ -602,11 +607,8 @@ module Aspera
602
607
  return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
603
608
  when :cat
604
609
  apifid = apifid_from_next_arg(top_file_id)
605
- result = apifid[:api].call(
606
- operation: 'GET',
607
- subpath: "files/#{apifid[:file_id]}/content"
608
- )
609
- return Main.result_text(result[:http].body)
610
+ http = apifid[:api].read("files/#{apifid[:file_id]}/content", ret: :resp)
611
+ return Main.result_text(http.body)
610
612
  when :show
611
613
  apifid = apifid_from_next_arg(top_file_id)
612
614
  items = apifid[:api].read("files/#{apifid[:file_id]}")
@@ -618,12 +620,8 @@ module Aspera
618
620
  return Main.result_status('Done')
619
621
  when :thumbnail
620
622
  apifid = apifid_from_next_arg(top_file_id)
621
- result = apifid[:api].call(
622
- operation: 'GET',
623
- subpath: "files/#{apifid[:file_id]}/preview",
624
- headers: {'Accept' => 'image/png'}
625
- )
626
- return Main.result_image(result[:http].body)
623
+ http = apifid[:api].read("files/#{apifid[:file_id]}/preview", headers: {'Accept' => 'image/png'}, ret: :resp)
624
+ return Main.result_image(http.body)
627
625
  when :permission
628
626
  apifid = apifid_from_next_arg(top_file_id)
629
627
  command_perm = options.get_next_command(%i[list show create delete])
@@ -677,7 +675,7 @@ module Aspera
677
675
  async_ids = @api_node.read('async/list')['sync_ids']
678
676
  summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
679
677
  selected = summaries.find{ |s| s['name'].eql?(value)}
680
- raise Cli::BadIdentifier.new('sync', "#{field}=#{value}") if selected.nil?
678
+ raise Cli::BadIdentifier.new('sync', value, field: field) if selected.nil?
681
679
  return selected['snid']
682
680
  end
683
681
 
@@ -762,7 +760,7 @@ module Aspera
762
760
  # name is unique, so we can return
763
761
  return id if sync_info[field].eql?(value)
764
762
  end
765
- raise Cli::BadIdentifier.new('ssync', "#{field}=#{value}")
763
+ raise Cli::BadIdentifier.new('ssync', value, field: field)
766
764
  end
767
765
 
768
766
  WATCH_FOLDER_MUL = %i[create list].freeze
@@ -789,7 +787,7 @@ module Aspera
789
787
  when :show
790
788
  return Main.result_single_object(@api_node.read(one_res_path))
791
789
  when :modify
792
- @api_node.update(one_res_path, options.get_option(:query, mandatory: true))
790
+ @api_node.update(one_res_path, value_create_modify(command: 'watch_folder'))
793
791
  return Main.result_status("#{one_res_id} updated")
794
792
  when :delete
795
793
  @api_node.delete(one_res_path)
@@ -837,12 +835,12 @@ module Aspera
837
835
  operation: 'POST',
838
836
  subpath: "asyncs/#{asyncs_id}/#{sync_command}",
839
837
  content_type: Rest::MIME_TEXT,
840
- body: ''
841
- )[:http].body
838
+ body: '',
839
+ ret: :resp
840
+ ).body
842
841
  return Main.result_status('Done')
843
842
  end
844
- parameters = nil
845
- parameters = options.get_option(:query, default: {}) if %i[bandwidth counters files].include?(sync_command)
843
+ parameters = options.get_option(:query) || {} if %i[bandwidth counters files].include?(sync_command)
846
844
  return Main.result_single_object(@api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters))
847
845
  end
848
846
  when :stream
@@ -895,8 +893,9 @@ module Aspera
895
893
  when :sessions
896
894
  transfers_data = @api_node.read('ops/transfers', query_read_delete)
897
895
  sessions = transfers_data.flat_map{ |t| t['sessions']}
896
+ start_end = %i[start end].freeze
898
897
  sessions.each do |session|
899
- %i[start end].each do |what|
898
+ start_end.each do |what|
900
899
  session["#{what}_time"] = session["#{what}_time_usec"] ? Time.at(session["#{what}_time_usec"] / 1_000_000.0).utc.iso8601(0) : nil
901
900
  end
902
901
  end
@@ -979,7 +978,7 @@ module Aspera
979
978
  command = options.get_next_command(%i[session file])
980
979
  validator_id = options.get_option(:validator)
981
980
  validation = {'validator_id' => validator_id} unless validator_id.nil?
982
- request_data = options.get_option(:query, default: {})
981
+ request_data = options.get_option(:query) || {}
983
982
  case command
984
983
  when :session
985
984
  command = options.get_next_command([:list])
@@ -1112,18 +1111,31 @@ module Aspera
1112
1111
 
1113
1112
  private
1114
1113
 
1115
- # get remaining path arguments from command line, and add prefix
1116
- def get_all_arguments_with_prefix(name)
1117
- path_args = options.get_next_argument(name, multiple: true)
1118
- return path_args if @prefix_path.nil?
1119
- return path_args.map{ |p| File.join(@prefix_path, p)}
1114
+ # Response has key `paths`.
1115
+ # From those, check if there is an error
1116
+ # @return [Array] of Hash with 2 keys: `path` and `result`
1117
+ def response_to_result(response, success_msg)
1118
+ errors = []
1119
+ obj_list = []
1120
+ response['paths'].each do |p|
1121
+ result = success_msg
1122
+ if p.key?('error')
1123
+ Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
1124
+ result = p['error']['user_message']
1125
+ errors.push([p['path'], p['error']['user_message']])
1126
+ end
1127
+ obj_list.push({'path' => p['path'], 'result' => result})
1128
+ end
1129
+ # one error make all fail
1130
+ raise errors.map{ |i| "#{i.first}: #{i.last}"}.join(', ') unless errors.empty?
1131
+ obj_list
1120
1132
  end
1121
1133
 
1122
- # get next path argument from command line, and add prefix
1123
- def get_one_argument_with_prefix(name)
1124
- path_arg = options.get_next_argument(name, validation: String)
1125
- return path_arg if @prefix_path.nil?
1126
- return File.join(@prefix_path, path_arg)
1134
+ # Translates paths results into CLI result, and removes prefix
1135
+ def cli_result_from_paths_response(response, success_msg)
1136
+ obj_list = response_to_result(response, success_msg)
1137
+ @prefixer&.remove_in_object_list!(obj_list)
1138
+ return Main.result_object_list(obj_list, fields: %w[path result])
1127
1139
  end
1128
1140
 
1129
1141
  # Executes the provided API call in loop
@@ -1140,13 +1152,13 @@ module Aspera
1140
1152
  item_list = []
1141
1153
  query_token[:iteration_token] = iteration[0] unless iteration.nil?
1142
1154
  loop do
1143
- result = api.call(**call_args, query: query_token)
1144
- Aspera.assert_type(result[:data], Array){"Expected data to be an Array, got: #{result[:data].class}"}
1155
+ data, http = api.call(**call_args, query: query_token, ret: :both)
1156
+ Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
1145
1157
  # no data
1146
- break if result[:data].empty?
1158
+ break if data.empty?
1147
1159
  # get next iteration token from link
1148
1160
  next_iteration_token = nil
1149
- link_info = result[:http]['Link']
1161
+ link_info = http['Link']
1150
1162
  unless link_info.nil?
1151
1163
  m = link_info.match(/<([^>]+)>/)
1152
1164
  Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
@@ -1155,7 +1167,7 @@ module Aspera
1155
1167
  # same as last iteration: stop
1156
1168
  break if next_iteration_token&.eql?(query_token[:iteration_token])
1157
1169
  query_token[:iteration_token] = next_iteration_token
1158
- item_list.concat(result[:data])
1170
+ item_list.concat(data)
1159
1171
  if max&.<=(item_list.length)
1160
1172
  item_list = item_list.slice(0, max)
1161
1173
  break
@@ -13,7 +13,7 @@ module Aspera
13
13
  AUTH_OPTIONS = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password].freeze
14
14
  def initialize(**_)
15
15
  super
16
- options.declare(:auth, 'OAuth type of authentication', values: AUTH_TYPES, default: :jwt)
16
+ options.declare(:auth, 'OAuth type of authentication', allowed: AUTH_TYPES, default: :jwt)
17
17
  options.declare(:client_id, 'OAuth client identifier')
18
18
  options.declare(:client_secret, 'OAuth client secret')
19
19
  options.declare(:redirect_uri, 'OAuth (Web) redirect URI for web authentication')