aspera-cli 4.18.1 → 4.20.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 (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +33 -0
  4. data/CONTRIBUTING.md +17 -12
  5. data/README.md +396 -185
  6. data/bin/asession +26 -19
  7. data/examples/build_exec +74 -0
  8. data/examples/{rubyc → build_exec_rubyc} +18 -2
  9. data/examples/get_proto_file.rb +7 -0
  10. data/lib/aspera/agent/alpha.rb +8 -8
  11. data/lib/aspera/agent/base.rb +4 -18
  12. data/lib/aspera/agent/connect.rb +14 -13
  13. data/lib/aspera/agent/direct.rb +123 -120
  14. data/lib/aspera/agent/httpgw.rb +2 -3
  15. data/lib/aspera/agent/node.rb +10 -10
  16. data/lib/aspera/agent/trsdk.rb +17 -20
  17. data/lib/aspera/api/alee.rb +15 -0
  18. data/lib/aspera/api/aoc.rb +128 -99
  19. data/lib/aspera/api/ats.rb +1 -1
  20. data/lib/aspera/api/cos_node.rb +1 -1
  21. data/lib/aspera/api/httpgw.rb +104 -64
  22. data/lib/aspera/api/node.rb +33 -12
  23. data/lib/aspera/ascmd.rb +56 -48
  24. data/lib/aspera/ascp/installation.rb +142 -70
  25. data/lib/aspera/ascp/management.rb +7 -3
  26. data/lib/aspera/ascp/products.rb +13 -7
  27. data/lib/aspera/assert.rb +10 -5
  28. data/lib/aspera/cli/formatter.rb +42 -26
  29. data/lib/aspera/cli/hints.rb +2 -1
  30. data/lib/aspera/cli/info.rb +12 -10
  31. data/lib/aspera/cli/main.rb +16 -13
  32. data/lib/aspera/cli/manager.rb +15 -10
  33. data/lib/aspera/cli/plugin.rb +17 -31
  34. data/lib/aspera/cli/plugin_factory.rb +10 -1
  35. data/lib/aspera/cli/plugins/alee.rb +3 -3
  36. data/lib/aspera/cli/plugins/aoc.rb +222 -194
  37. data/lib/aspera/cli/plugins/ats.rb +16 -14
  38. data/lib/aspera/cli/plugins/config.rb +66 -53
  39. data/lib/aspera/cli/plugins/console.rb +3 -3
  40. data/lib/aspera/cli/plugins/faspex.rb +11 -21
  41. data/lib/aspera/cli/plugins/faspex5.rb +44 -42
  42. data/lib/aspera/cli/plugins/faspio.rb +2 -2
  43. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  44. data/lib/aspera/cli/plugins/node.rb +155 -96
  45. data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
  46. data/lib/aspera/cli/plugins/preview.rb +8 -9
  47. data/lib/aspera/cli/plugins/server.rb +6 -10
  48. data/lib/aspera/cli/plugins/shares.rb +13 -9
  49. data/lib/aspera/cli/sync_actions.rb +72 -31
  50. data/lib/aspera/cli/transfer_agent.rb +13 -14
  51. data/lib/aspera/cli/transfer_progress.rb +36 -18
  52. data/lib/aspera/cli/version.rb +1 -1
  53. data/lib/aspera/command_line_builder.rb +3 -4
  54. data/lib/aspera/coverage.rb +13 -1
  55. data/lib/aspera/environment.rb +59 -10
  56. data/lib/aspera/faspex_gw.rb +3 -3
  57. data/lib/aspera/json_rpc.rb +1 -1
  58. data/lib/aspera/keychain/encrypted_hash.rb +2 -0
  59. data/lib/aspera/keychain/macos_security.rb +7 -12
  60. data/lib/aspera/log.rb +4 -4
  61. data/lib/aspera/node_simulator.rb +1 -1
  62. data/lib/aspera/oauth/base.rb +39 -45
  63. data/lib/aspera/oauth/factory.rb +11 -4
  64. data/lib/aspera/oauth/generic.rb +4 -8
  65. data/lib/aspera/oauth/jwt.rb +4 -4
  66. data/lib/aspera/oauth/url_json.rb +3 -2
  67. data/lib/aspera/oauth/web.rb +10 -6
  68. data/lib/aspera/persistency_action_once.rb +16 -8
  69. data/lib/aspera/preview/utils.rb +5 -16
  70. data/lib/aspera/rest.rb +100 -76
  71. data/lib/aspera/secret_hider.rb +3 -2
  72. data/lib/aspera/ssh.rb +1 -1
  73. data/lib/aspera/transfer/faux_file.rb +7 -5
  74. data/lib/aspera/transfer/parameters.rb +41 -35
  75. data/lib/aspera/transfer/spec.rb +16 -18
  76. data/lib/aspera/transfer/sync.rb +51 -50
  77. data/lib/aspera/transfer/uri.rb +1 -1
  78. data/lib/aspera/uri_reader.rb +1 -1
  79. data/lib/aspera/web_auth.rb +166 -18
  80. data/lib/aspera/web_server_simple.rb +27 -15
  81. data/lib/transfer_pb.rb +84 -0
  82. data/lib/transfer_services_pb.rb +82 -0
  83. data.tar.gz.sig +0 -0
  84. metadata +25 -6
  85. metadata.gz.sig +0 -0
@@ -34,12 +34,14 @@ module Aspera
34
34
  # endpoint for authentication API
35
35
  PATH_AUTH = 'auth'
36
36
  PATH_HEALTH = 'configuration/ping'
37
+ PATH_API_DETECT = "#{PATH_API_V5}/#{PATH_HEALTH}"
37
38
  PER_PAGE_DEFAULT = 100
38
39
  # OAuth methods supported
39
40
  STD_AUTH_TYPES = %i[web jwt boot].freeze
40
41
  HEADER_ITERATION_TOKEN = 'X-Aspera-Next-Iteration-Token'
42
+ HEADER_FASPEX_VERSION = 'X-IBM-Aspera'
41
43
  private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED PATH_HEALTH API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE PER_PAGE_DEFAULT
42
- STD_AUTH_TYPES])
44
+ STD_AUTH_TYPES HEADER_ITERATION_TOKEN HEADER_FASPEX_VERSION])
43
45
  class << self
44
46
  def application_name
45
47
  'Faspex'
@@ -55,15 +57,14 @@ module Aspera
55
57
  # Faspex is always HTTPS
56
58
  next unless base_url.start_with?('https://')
57
59
  api = Rest.new(base_url: base_url, redirect_max: 1)
58
- path_api_detect = "#{PATH_API_V5}/#{PATH_HEALTH}"
59
- result = api.read(path_api_detect)
60
- next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
60
+ response = api.call(operation: 'GET', subpath: PATH_API_DETECT)[:http]
61
+ next unless response.code.start_with?('2') && response.body.strip.empty?
61
62
  # end is at -1, and subtract 1 for "/"
62
- url_length = -2 - path_api_detect.length
63
+ url_length = -2 - PATH_API_DETECT.length
63
64
  # take redirect if any
64
65
  return {
65
- version: result[:http]['x-ibm-aspera'] || '5',
66
- url: result[:http].uri.to_s[0..url_length]
66
+ version: response[HEADER_FASPEX_VERSION] || '5',
67
+ url: response.uri.to_s[0..url_length]
67
68
  }
68
69
  rescue StandardError => e
69
70
  error = e
@@ -133,8 +134,8 @@ module Aspera
133
134
  case auth_type
134
135
  when :public_link
135
136
  # resolve any redirect
136
- @faspex5_api_base_url = Rest.new(base_url: @faspex5_api_base_url, redirect_max: 3).read('')[:http].uri.to_s
137
- encoded_context = Rest.decode_query(URI.parse(@faspex5_api_base_url).query)['context']
137
+ @faspex5_api_base_url = Rest.new(base_url: @faspex5_api_base_url, redirect_max: 3).call(operation: 'GET')[:http].uri.to_s
138
+ encoded_context = Rest.query_to_h(URI.parse(@faspex5_api_base_url).query)['context']
138
139
  raise 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
139
140
  # public link information (allowed usage)
140
141
  @pub_link_context = JSON.parse(Base64.decode64(encoded_context))
@@ -182,7 +183,7 @@ module Aspera
182
183
  else Aspera.error_unexpected_value(auth_type)
183
184
  end
184
185
  # in case user wants to use HTTPGW tell transfer agent how to get address
185
- transfer.httpgw_url_cb = lambda { @api_v5.read('account')[:data]['gateway_url'] }
186
+ transfer.httpgw_url_cb = lambda { @api_v5.read('account')['gateway_url'] }
186
187
  end
187
188
 
188
189
  # if recipient is just an email, then convert to expected API hash : name and type
@@ -198,7 +199,7 @@ module Aspera
198
199
  parameters['recipients'].map! do |recipient_data|
199
200
  # if just a string, make a general lookup and build expected name/type hash
200
201
  if recipient_data.is_a?(String)
201
- matched = @api_v5.lookup_by_name('contacts', recipient_data, {context: 'packages', type: Rest.array_params(recipient_types)})
202
+ matched = @api_v5.lookup_by_name('contacts', recipient_data, query: {context: 'packages', type: Rest.array_params(recipient_types)})
202
203
  recipient_data = {
203
204
  name: matched['name'],
204
205
  recipient_type: matched['type']
@@ -213,22 +214,22 @@ module Aspera
213
214
  def wait_package_status(id, status_list: PACKAGE_TERMINATED)
214
215
  total_sent = false
215
216
  loop do
216
- status = @api_v5.read("packages/#{id}/upload_details")[:data]
217
+ status = @api_v5.read("packages/#{id}/upload_details")
217
218
  status['id'] = id
218
219
  # user asked to not follow
219
220
  return status if status_list.nil?
220
221
  if status['upload_status'].eql?('submitted')
221
- config.progress_bar&.event(session_id: nil, type: :pre_start, info: status['upload_status'])
222
+ config.progress_bar&.event(:pre_start, session_id: nil, info: status['upload_status'])
222
223
  elsif !total_sent
223
- config.progress_bar&.event(session_id: id, type: :session_start)
224
- config.progress_bar&.event(session_id: id, type: :session_size, info: status['bytes_total'].to_i)
224
+ config.progress_bar&.event(:session_start, session_id: id)
225
+ config.progress_bar&.event(:session_size, session_id: id, info: status['bytes_total'].to_i)
225
226
  total_sent = true
226
227
  else
227
- config.progress_bar&.event(session_id: id, type: :transfer, info: status['bytes_written'].to_i)
228
+ config.progress_bar&.event(:transfer, session_id: id, info: status['bytes_written'].to_i)
228
229
  end
229
230
  if status_list.include?(status['upload_status'])
230
231
  # if status['upload_status'].eql?('completed')
231
- config.progress_bar&.event(session_id: id, type: :end)
232
+ config.progress_bar&.event(:end, session_id: id)
232
233
  return status
233
234
  # end
234
235
  end
@@ -238,11 +239,12 @@ module Aspera
238
239
 
239
240
  def wait_for_job(job_id)
240
241
  loop do
241
- status = @api_v5.read("jobs/#{job_id}", {type: :formatted})[:data]
242
+ status = @api_v5.read("jobs/#{job_id}", {type: :formatted})
242
243
  return status unless JOB_RUNNING.include?(status['status'])
243
244
  formatter.long_operation_running(status['status'])
244
245
  sleep(0.5)
245
246
  end
247
+ formatter.long_operation_terminated
246
248
  Aspera.error_unreachable_line
247
249
  end
248
250
 
@@ -266,7 +268,7 @@ module Aspera
266
268
  query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
267
269
  loop do
268
270
  query['offset'] = offset
269
- page_result = @api_v5.read(real_path, query)[:data]
271
+ page_result = @api_v5.read(real_path, query)
270
272
  Aspera.assert_type(page_result[item_list_key], Array)
271
273
  result.concat(page_result[item_list_key])
272
274
  # reach the limit set by user ?
@@ -280,6 +282,7 @@ module Aspera
280
282
  offset += page_result[item_list_key].length
281
283
  formatter.long_operation_running
282
284
  end
285
+ formatter.long_operation_terminated
283
286
  return result
284
287
  end
285
288
 
@@ -356,7 +359,7 @@ module Aspera
356
359
  package_ids = [package_ids] unless package_ids.is_a?(Array)
357
360
  Aspera.assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
358
361
  Aspera.assert(package_ids.all?(String)){'Package id shall be String'}
359
- # packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")[:data]}
362
+ # packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")}
360
363
  packages = package_ids.map{|pkg_id|{'id'=>pkg_id}}
361
364
  end
362
365
  result_transfer = []
@@ -439,6 +442,7 @@ module Aspera
439
442
  end
440
443
  query.delete('iteration_token')
441
444
  end
445
+ formatter.long_operation_terminated
442
446
  return {type: :object_list, data: all_items}
443
447
  end
444
448
 
@@ -450,7 +454,7 @@ module Aspera
450
454
  end
451
455
  case command
452
456
  when :show
453
- return {type: :single_object, data: @api_v5.read("packages/#{package_id}")[:data]}
457
+ return {type: :single_object, data: @api_v5.read("packages/#{package_id}")}
454
458
  when :browse
455
459
  location = case options.get_option(:box)
456
460
  when 'inbox' then 'received'
@@ -486,7 +490,7 @@ module Aspera
486
490
  }]
487
491
  end
488
492
  normalize_recipients(parameters)
489
- package = @api_v5.create('packages', parameters)[:data]
493
+ package = @api_v5.create('packages', parameters)
490
494
  shared_folder = options.get_option(:shared_folder)
491
495
  if shared_folder.nil?
492
496
  # send from local files
@@ -511,7 +515,7 @@ module Aspera
511
515
  end
512
516
  transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
513
517
  # start remote transfer and get first status
514
- result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)[:data]
518
+ result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
515
519
  result['id'] = package['id']
516
520
  unless result['status'].eql?('completed')
517
521
  formatter.display_status("Package #{package['id']}")
@@ -597,7 +601,7 @@ module Aspera
597
601
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
598
602
  creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
599
603
  res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
600
- result = adm_api.create(res_path, creation_payload)[:data]
604
+ result = adm_api.create(res_path, creation_payload)
601
605
  formatter.display_status(result['message'])
602
606
  result = lookup_entity_by_field(
603
607
  type: 'members',
@@ -612,8 +616,7 @@ module Aspera
612
616
  list_key = res_command.to_s
613
617
  list_key = 'groups' if res_command.eql?(:saml_groups)
614
618
  sub_command = options.get_next_command(%i[create list modify delete])
615
- if sub_command.eql?(:create) && options.get_option(:value).nil?
616
- raise "use option 'value' to provide saml group_id and access (refer to API)" unless res_command.eql?(:members)
619
+ if sub_command.eql?(:create) && res_command.eql?(:members)
617
620
  # first arg is one user name or list of users
618
621
  users = options.get_next_argument('user id, %name:, or Array')
619
622
  users = [users] unless users.is_a?(Array)
@@ -630,8 +633,7 @@ module Aspera
630
633
  end
631
634
  end
632
635
  access = options.get_next_argument('level', mandatory: false, accept_list: %i[submit_only standard shared_inbox_admin], default: :standard)
633
- # TODO: unshift to command line parameters instead of using deprecated option "value"
634
- options.set_option(:value, {user: users.map{|u|{id: u, access: access}}})
636
+ options.unshift_next_argument({user: users.map{|u|{id: u, access: access}}})
635
637
  end
636
638
  return entity_command(sub_command, adm_api, res_path, item_list_key: list_key) do |field, value|
637
639
  lookup_entity_by_field(
@@ -655,7 +657,7 @@ module Aspera
655
657
  when :clean_deleted
656
658
  delete_data = value_create_modify(command: command, default: {days_before_deleting_package_records: 365})
657
659
  res = @api_v5.create('internal/packages/clean_deleted', delete_data)
658
- return {type: :single_object, data: res[:data]}
660
+ return {type: :single_object, data: res}
659
661
  when :events
660
662
  event_type = options.get_next_command(%i[application webhook])
661
663
  case event_type
@@ -669,27 +671,27 @@ module Aspera
669
671
  conf_cmd = options.get_next_command(%i[show modify])
670
672
  case conf_cmd
671
673
  when :show
672
- return { type: :single_object, data: @api_v5.read(conf_path)[:data] }
674
+ return { type: :single_object, data: @api_v5.read(conf_path) }
673
675
  when :modify
674
- return { type: :single_object, data: @api_v5.update(conf_path, value_create_modify(command: conf_cmd))[:data] }
676
+ return { type: :single_object, data: @api_v5.update(conf_path, value_create_modify(command: conf_cmd)) }
675
677
  end
676
678
  when :smtp
677
679
  smtp_path = 'configuration/smtp'
678
680
  smtp_cmd = options.get_next_command(%i[show create modify delete test])
679
681
  case smtp_cmd
680
682
  when :show
681
- return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
683
+ return { type: :single_object, data: @api_v5.read(smtp_path) }
682
684
  when :create
683
- return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
685
+ return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd)) }
684
686
  when :modify
685
- return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
687
+ return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd)) }
686
688
  when :delete
687
- @api_v5.delete(smtp_path)[:data]
689
+ @api_v5.delete(smtp_path)
688
690
  return Main.result_status('SMTP configuration deleted')
689
691
  when :test
690
692
  test_data = options.get_next_argument('Email or test data, see API')
691
693
  test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
692
- creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
694
+ creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)
693
695
  result = wait_for_job(creation['job_id'])
694
696
  result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
695
697
  return { type: :single_object, data: result }
@@ -704,11 +706,11 @@ module Aspera
704
706
  set_api unless command.eql?(:postprocessing)
705
707
  case command
706
708
  when :version
707
- return { type: :single_object, data: @api_v5.read('version')[:data] }
709
+ return { type: :single_object, data: @api_v5.read('version') }
708
710
  when :health
709
711
  nagios = Nagios.new
710
712
  begin
711
- result = Rest.new(base_url: @faspex5_api_base_url).read('health')[:data]
713
+ result = Rest.new(base_url: @faspex5_api_base_url).read('health')
712
714
  result.each do |k, v|
713
715
  nagios.add_ok(k, v.to_s)
714
716
  end
@@ -719,11 +721,11 @@ module Aspera
719
721
  when :user
720
722
  case options.get_next_command(%i[account profile])
721
723
  when :account
722
- return { type: :single_object, data: @api_v5.read('account')[:data] }
724
+ return { type: :single_object, data: @api_v5.read('account') }
723
725
  when :profile
724
726
  case options.get_next_command(%i[show modify])
725
727
  when :show
726
- return { type: :single_object, data: @api_v5.read('account/preferences')[:data] }
728
+ return { type: :single_object, data: @api_v5.read('account/preferences') }
727
729
  when :modify
728
730
  @api_v5.update('account/preferences', options.get_next_argument('modified parameters', validation: Hash))
729
731
  return Main.result_status('modified')
@@ -734,7 +736,7 @@ module Aspera
734
736
  when :packages
735
737
  return package_action
736
738
  when :shared_folders
737
- all_shared_folders = @api_v5.read('shared_folders')[:data]['shared_folders']
739
+ all_shared_folders = @api_v5.read('shared_folders')['shared_folders']
738
740
  case options.get_next_command(%i[list browse])
739
741
  when :list
740
742
  return {type: :object_list, data: all_shared_folders}
@@ -758,7 +760,7 @@ module Aspera
758
760
  when :create
759
761
  return do_bulk_operation(command: invitation_command, descr: 'data') do |params|
760
762
  invitation_endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
761
- @api_v5.create(invitation_endpoint, params)[:data]
763
+ @api_v5.create(invitation_endpoint, params)
762
764
  end
763
765
  when :resend
764
766
  @api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend")
@@ -15,7 +15,7 @@ module Aspera
15
15
 
16
16
  def detect(base_url)
17
17
  api = Rest.new(base_url: base_url)
18
- ping_result = api.read('ping')
18
+ ping_result = api.call(operation: 'GET', subpath: 'ping', headers: {'Accept' => 'application/json'})
19
19
  server_type = ping_result[:http]['Server']
20
20
  return nil unless ping_result[:data].is_a?(Hash) && ping_result[:data].empty?
21
21
  return nil unless server_type.is_a?(String) && server_type.include?('faspio')
@@ -65,7 +65,7 @@ module Aspera
65
65
  when :health
66
66
  nagios = Nagios.new
67
67
  begin
68
- result = api.read('ping')[:data]
68
+ result = api.read('ping')
69
69
  if result.is_a?(Hash) && result.empty?
70
70
  nagios.add_ok('api', 'answered ok')
71
71
  else
@@ -17,7 +17,7 @@ module Aspera
17
17
  api = Api::Httpgw.new(url: base_url)
18
18
  api_info = api.info
19
19
  return {
20
- url: base_url,
20
+ url: api.base_url,
21
21
  version: api_info['version']
22
22
  } if api_info.is_a?(Hash) && api_info.key?('download_endpoint')
23
23
  return nil