aspera-cli 4.20.0 → 4.21.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 (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +20 -2
  4. data/README.md +281 -156
  5. data/bin/asession +2 -2
  6. data/lib/aspera/agent/alpha.rb +7 -12
  7. data/lib/aspera/agent/connect.rb +19 -1
  8. data/lib/aspera/agent/direct.rb +20 -29
  9. data/lib/aspera/agent/node.rb +1 -11
  10. data/lib/aspera/agent/trsdk.rb +4 -25
  11. data/lib/aspera/api/aoc.rb +5 -0
  12. data/lib/aspera/api/node.rb +45 -28
  13. data/lib/aspera/ascp/installation.rb +69 -38
  14. data/lib/aspera/ascp/management.rb +27 -6
  15. data/lib/aspera/cli/formatter.rb +149 -141
  16. data/lib/aspera/cli/info.rb +1 -1
  17. data/lib/aspera/cli/manager.rb +1 -0
  18. data/lib/aspera/cli/plugin.rb +2 -2
  19. data/lib/aspera/cli/plugins/aoc.rb +27 -17
  20. data/lib/aspera/cli/plugins/config.rb +31 -21
  21. data/lib/aspera/cli/plugins/faspex.rb +1 -1
  22. data/lib/aspera/cli/plugins/faspex5.rb +11 -3
  23. data/lib/aspera/cli/plugins/node.rb +44 -38
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/command_line_builder.rb +1 -1
  26. data/lib/aspera/environment.rb +5 -6
  27. data/lib/aspera/node_simulator.rb +228 -112
  28. data/lib/aspera/oauth/base.rb +31 -42
  29. data/lib/aspera/oauth/factory.rb +41 -2
  30. data/lib/aspera/persistency_folder.rb +20 -2
  31. data/lib/aspera/preview/generator.rb +1 -1
  32. data/lib/aspera/preview/utils.rb +1 -1
  33. data/lib/aspera/products/alpha.rb +30 -0
  34. data/lib/aspera/products/connect.rb +48 -0
  35. data/lib/aspera/products/other.rb +82 -0
  36. data/lib/aspera/products/trsdk.rb +54 -0
  37. data/lib/aspera/rest.rb +18 -13
  38. data/lib/aspera/ssh.rb +28 -24
  39. data/lib/aspera/transfer/spec.yaml +22 -20
  40. data.tar.gz.sig +0 -0
  41. metadata +21 -4
  42. metadata.gz.sig +0 -0
  43. data/lib/aspera/ascp/products.rb +0 -168
@@ -220,12 +220,12 @@ module Aspera
220
220
  query = options.get_option(:query)
221
221
  # dup default, as it could be frozen
222
222
  query = default.dup if query.nil?
223
- Log.log.debug{"Query=#{query}".bg_red}
223
+ Log.log.debug{"query_read_delete=#{query}".bg_red}
224
224
  begin
225
225
  # check it is suitable
226
226
  URI.encode_www_form(query) unless query.nil?
227
227
  rescue StandardError => e
228
- raise Cli::BadArgument, "Query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
228
+ raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
229
229
  end
230
230
  return query
231
231
  end
@@ -66,14 +66,14 @@ module Aspera
66
66
  base_url = "#{base_url}.#{Api::AoC::SAAS_DOMAIN_PROD}" unless base_url.include?('.')
67
67
  # AoC is only https
68
68
  return nil unless base_url.start_with?('https://')
69
- res_http = Rest.new(base_url: base_url, redirect_max: 10).call(operation: 'GET')[:http]
70
- # Any AoC is on this domain
71
- return nil unless res_http.uri.host.end_with?(Api::AoC::SAAS_DOMAIN_PROD)
72
- Log.log.debug{"AoC Main page: #{res_http.body.include?(Api::AoC::PRODUCT_NAME)}"}
73
- base_url = res_http.uri.to_s if res_http.uri.path.include?('/public')
69
+ res_http = Rest.new(base_url: base_url, redirect_max: 0).call(operation: 'GET', subpath: 'auth/ping', return_error: true)[:http]
70
+ return nil if res_http['Location'].nil?
71
+ redirect_uri = URI.parse(res_http['Location'])
72
+ od = Api::AoC.split_org_domain(URI.parse(base_url))
73
+ return nil unless redirect_uri.path.end_with?("oauth2/#{od[:organization]}/login")
74
74
  # either in standard domain, or product name in page
75
75
  return {
76
- version: 'SaaS',
76
+ version: Api::AoC.saas_url?(base_url) ? 'SaaS' : 'Self-managed',
77
77
  url: base_url
78
78
  }
79
79
  end
@@ -92,8 +92,6 @@ module Aspera
92
92
  # set vars to look like object
93
93
  options = object.options
94
94
  formatter = object.formatter
95
- options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: true)
96
- options.parse_options!
97
95
  instance_url = options.get_option(:url, mandatory: true)
98
96
  pub_link_info = Api::AoC.link_info(instance_url)
99
97
  if !pub_link_info[:token].nil?
@@ -108,6 +106,8 @@ module Aspera
108
106
  test_args: 'organization'
109
107
  }
110
108
  end
109
+ options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: Api::AoC.saas_url?(instance_url))
110
+ options.parse_options!
111
111
  # make username mandatory for jwt, this triggers interactive input
112
112
  wiz_username = options.get_option(:username, mandatory: true)
113
113
  raise "Username shall be an email in AoC: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
@@ -133,15 +133,15 @@ module Aspera
133
133
  formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
134
134
  formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
135
135
  formatter.display_status('Check or create in integration:')
136
- formatter.display_status("- name: #{@info[:name]}")
136
+ formatter.display_status('- name: cli')
137
137
  formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
138
138
  formatter.display_status('- origin: localhost')
139
139
  formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
140
140
  end
141
- Environment.instance.open_uri("#{instance_url}/admin/api-clients")
141
+ Environment.instance.open_uri("#{instance_url}/admin/integrations/api-clients")
142
142
  options.get_option(:client_id, mandatory: true)
143
143
  options.get_option(:client_secret, mandatory: true)
144
- use_browser_authentication = true
144
+ # use_browser_authentication = true
145
145
  end
146
146
  if use_browser_authentication
147
147
  formatter.display_status('We will use web authentication to bootstrap.')
@@ -282,13 +282,17 @@ module Aspera
282
282
  end
283
283
 
284
284
  # list all entities, given additional, default and user's queries
285
+ # @param resource_class_path path to query on API
286
+ # @param fields fields to display
287
+ # @param base_query a query applied always
288
+ # @param default_query default query unless overriden by user
285
289
  def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
286
290
  Aspera.assert_type(base_query, Hash)
287
291
  Aspera.assert_type(default_query, Hash)
288
292
  user_query = query_read_delete(default: default_query)
289
293
  # caller may add specific modifications or checks
290
294
  yield(user_query) if block_given?
291
- return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query)))
295
+ return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query).compact))
292
296
  end
293
297
 
294
298
  def resolve_dropbox_name_default_ws_id(query)
@@ -411,8 +415,8 @@ module Aspera
411
415
  when :operation then default_fields = nil
412
416
  when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
413
417
  when :user then default_fields.push('name', 'email')
414
- when :group_membership then default_fields.push(*%w[group_id member_type member_id])
415
- when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
418
+ when :group_membership then default_fields.push('group_id', 'member_type', 'member_id')
419
+ when :workspace_membership then default_fields.push('workspace_id', 'member_type', 'member_id')
416
420
  end
417
421
  return result_list(resource_class_path, fields: default_fields, default_query: default_query)
418
422
  when :show
@@ -801,6 +805,12 @@ module Aspera
801
805
  else
802
806
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
803
807
  end
808
+ file_list =
809
+ begin
810
+ transfer.source_list.map{|i|{'source'=>i}}
811
+ rescue Cli::BadArgument
812
+ [{'source' => '.'}]
813
+ end
804
814
  # list here
805
815
  result_transfer = []
806
816
  formatter.display_status("found #{ids_to_download.length} package(s).")
@@ -816,7 +826,7 @@ module Aspera
816
826
  package_node_api.transfer_spec_gen4(
817
827
  package_info['contents_file_id'],
818
828
  Transfer::Spec::DIRECTION_RECEIVE,
819
- {'paths'=> [{'source' => '.'}]}),
829
+ {'paths'=> file_list}),
820
830
  rest_token: package_node_api)
821
831
  result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
822
832
  # update skip list only if all transfer sessions completed
@@ -837,14 +847,14 @@ module Aspera
837
847
  resolve_dropbox_name_default_ws_id(query)
838
848
  end
839
849
  when :delete
840
- return do_bulk_operation(command: package_command, descr: 'identifier', values: identifier) do |id|
850
+ return do_bulk_operation(command: package_command, descr: 'identifier', values: instance_identifier) do |id|
841
851
  Aspera.assert_values(id.class, [String, Integer]){'identifier'}
842
852
  aoc_api.delete("packages/#{id}")
843
853
  end
844
854
  when *Node::NODE4_READ_ACTIONS
845
855
  package_id = instance_identifier
846
856
  package_info = aoc_api.read("packages/#{package_id}")
847
- return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: Api::Node::SCOPE_USER)
857
+ return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['contents_file_id'], scope: Api::Node::SCOPE_USER)
848
858
  end
849
859
  when :files
850
860
  command_repo = options.get_next_command([:short_link].concat(NODE4_EXT_COMMANDS))
@@ -9,7 +9,7 @@ require 'aspera/cli/formatter'
9
9
  require 'aspera/cli/info'
10
10
  require 'aspera/cli/transfer_progress'
11
11
  require 'aspera/ascp/installation'
12
- require 'aspera/ascp/products'
12
+ require 'aspera/products/trsdk'
13
13
  require 'aspera/transfer/error_info'
14
14
  require 'aspera/transfer/parameters'
15
15
  require 'aspera/transfer/spec'
@@ -54,7 +54,7 @@ module Aspera
54
54
  PERSISTENCY_FOLDER = 'persist_store'
55
55
  ASPERA = 'aspera'
56
56
  SERVER_COMMAND = 'server'
57
- APP_NAME_SDK = 'sdk'
57
+ DIR_SDK = 'sdk'
58
58
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
59
59
  CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
60
60
  DEMO_SERVER = 'demo'
@@ -213,7 +213,7 @@ module Aspera
213
213
  options.declare(:ascp_path, 'Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
214
214
  options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
215
215
  options.declare(:sdk_url, 'URL to get SDK', default: SpecialValues::DEF)
216
- options.declare(:sdk_folder, 'SDK folder path', handler: {o: Ascp::Installation.instance, m: :sdk_folder})
216
+ options.declare(:sdk_folder, 'SDK folder path', handler: {o: Products::Trsdk, m: :sdk_directory})
217
217
  options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
218
218
  # email options
219
219
  options.declare(:smtp, 'SMTP configuration', types: Hash)
@@ -232,22 +232,22 @@ module Aspera
232
232
  options.parse_options!
233
233
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
234
234
  # Check SDK folder is set or not, for compatibility, we check in two places
235
- sdk_folder = Ascp::Installation.instance.sdk_folder rescue nil
236
- if sdk_folder.nil?
235
+ sdk_dir = Products::Trsdk.sdk_directory rescue nil
236
+ if sdk_dir.nil?
237
237
  @sdk_default_location = true
238
238
  Log.log.debug('SDK folder is not set, checking default')
239
239
  # new location
240
- sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK)
241
- Log.log.debug{"checking: #{sdk_folder}"}
242
- if !Dir.exist?(sdk_folder)
243
- Log.log.debug{"not exists: #{sdk_folder}"}
240
+ sdk_dir = self.class.default_app_main_folder(app_name: DIR_SDK)
241
+ Log.log.debug{"checking: #{sdk_dir}"}
242
+ if !Dir.exist?(sdk_dir)
243
+ Log.log.debug{"not exists: #{sdk_dir}"}
244
244
  # former location
245
- former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), APP_NAME_SDK)
245
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), DIR_SDK)
246
246
  Log.log.debug{"checking: #{former_sdk_folder}"}
247
- sdk_folder = former_sdk_folder if Dir.exist?(former_sdk_folder)
247
+ sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
248
248
  end
249
- Log.log.debug{"using: #{sdk_folder}"}
250
- Ascp::Installation.instance.sdk_folder = sdk_folder
249
+ Log.log.debug{"using: #{sdk_dir}"}
250
+ Products::Trsdk.sdk_directory = sdk_dir
251
251
  end
252
252
  pac_script = options.get_option(:fpac)
253
253
  # create PAC executor
@@ -414,7 +414,7 @@ module Aspera
414
414
 
415
415
  def periodic_check_newer_gem_version
416
416
  # get verification period
417
- delay_days = options.get_option(:version_check_days, mandatory: true)
417
+ delay_days = options.get_option(:version_check_days, mandatory: true).to_i
418
418
  # check only if not zero day
419
419
  return if delay_days.eql?(0)
420
420
  # get last date from persistency
@@ -700,6 +700,7 @@ module Aspera
700
700
  return execute_connect_action
701
701
  when :use
702
702
  ascp_path = options.get_next_argument('path to ascp')
703
+ Ascp::Installation.instance.ascp_path = ascp_path
703
704
  formatter.display_status("ascp version: #{Ascp::Installation.instance.get_ascp_version(ascp_path)}")
704
705
  set_global_default(:ascp_path, ascp_path)
705
706
  return Main.result_nothing
@@ -713,13 +714,13 @@ module Aspera
713
714
  # add keys
714
715
  DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
715
716
  # declare those as secrets
716
- SecretHider::ADDITIONAL_KEYS_TO_HIDE.push(*DataRepository::ELEMENTS.map(&:to_s))
717
+ SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
717
718
  return {type: :single_object, data: data}
718
719
  when :products
719
720
  command = options.get_next_command(%i[list use])
720
721
  case command
721
722
  when :list
722
- return {type: :object_list, data: Ascp::Products.installed_products, fields: %w[name app_root]}
723
+ return {type: :object_list, data: Ascp::Installation.instance.installed_products, fields: %w[name app_root]}
723
724
  when :use
724
725
  default_product = options.get_next_argument('product name')
725
726
  Ascp::Installation.instance.use_ascp_from_product(default_product)
@@ -728,7 +729,7 @@ module Aspera
728
729
  end
729
730
  when :install
730
731
  # reset to default location, if older default was used
731
- Ascp::Installation.instance.sdk_folder = self.class.default_app_main_folder(app_name: APP_NAME_SDK) if @sdk_default_location
732
+ Products::Trsdk.sdk_directory = self.class.default_app_main_folder(app_name: DIR_SDK) if @sdk_default_location
732
733
  n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true))
733
734
  return Main.result_status("Installed #{n} version #{v}")
734
735
  when :spec
@@ -856,7 +857,7 @@ module Aspera
856
857
  remote_certificate
857
858
  gem
858
859
  plugins
859
- flush_tokens
860
+ tokens
860
861
  echo
861
862
  wizard
862
863
  detect
@@ -912,9 +913,18 @@ module Aspera
912
913
  end
913
914
  when :echo # display the content of a value given on command line
914
915
  return Formatter.auto_type(options.get_next_argument('value', validation: nil))
915
- when :flush_tokens
916
- deleted_files = OAuth::Factory.instance.flush_tokens
917
- return {type: :value_list, data: deleted_files, name: 'file'}
916
+ when :tokens
917
+ require 'aspera/api/node'
918
+ case options.get_next_command(%i{flush list show})
919
+ when :flush
920
+ return {type: :value_list, data: OAuth::Factory.instance.flush_tokens, name: 'file'}
921
+ when :list
922
+ return {type: :object_list, data: OAuth::Factory.instance.persisted_tokens}
923
+ when :show
924
+ data = OAuth::Factory.instance.get_token_info(instance_identifier)
925
+ raise Cli::Error, 'No such identifier' if data.nil?
926
+ return {type: :single_object, data: data}
927
+ end
918
928
  when :plugins
919
929
  case options.get_next_command(%i[list create])
920
930
  when :list
@@ -305,7 +305,7 @@ module Aspera
305
305
  # authenticated user
306
306
  delivery_info['sources'] ||= [{'paths' => []}]
307
307
  first_source = delivery_info['sources'].first
308
- first_source['paths'].push(*transfer.source_list)
308
+ first_source['paths'].concat(transfer.source_list)
309
309
  source_id = instance_identifier(as_option: :remote_source) do |field, value|
310
310
  Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
311
311
  source_list = api_v3.read('source_shares')['items']
@@ -253,9 +253,10 @@ module Aspera
253
253
  # @param query [Hash,nil] additional query parameters
254
254
  # @param real_path [String] real path if it's n ot just the type
255
255
  # @param item_list_key [String] key in the result to get the list of items
256
- def list_entities(type:, real_path: nil, item_list_key: nil, query: {})
256
+ def list_entities(type:, real_path: nil, item_list_key: nil, query: nil)
257
257
  Log.log.trace1{"list_entities t=#{type} p=#{real_path} k=#{item_list_key} q=#{query}"}
258
258
  type = type.to_s if type.is_a?(Symbol)
259
+ query = {} if query.nil?
259
260
  Aspera.assert_type(type, String)
260
261
  Aspera.assert_type(query, Hash)
261
262
  item_list_key = type if item_list_key.nil?
@@ -555,6 +556,7 @@ module Aspera
555
556
  id_as_arg = 'type'
556
557
  when :accounts
557
558
  display_fields = Formatter.all_but('user_profile_data_attributes')
559
+ available_commands.push(:reset_password)
558
560
  when :oauth_clients
559
561
  display_fields = Formatter.all_but('public_key')
560
562
  adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
@@ -642,7 +644,12 @@ module Aspera
642
644
  value: value,
643
645
  query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})})['id']
644
646
  end
647
+ when :reset_password
648
+ contact_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value, query: res_id_query)['id']}
649
+ adm_api.create("#{res_type}/#{contact_id}/reset_password", {})
650
+ return Main.result_status('password reset, user shall check email')
645
651
  end
652
+ Aspera.error_unreachable_line
646
653
  end
647
654
 
648
655
  def execute_admin
@@ -662,9 +669,10 @@ module Aspera
662
669
  event_type = options.get_next_command(%i[application webhook])
663
670
  case event_type
664
671
  when :application
665
- return {type: :object_list, data: list_entities(type: 'application_events'), fields: %w[event_type created_at application user.name]}
672
+ return {type: :object_list, data: list_entities(type: 'application_events', query: query_read_delete),
673
+ fields: %w[event_type created_at application user.name]}
666
674
  when :webhook
667
- return {type: :object_list, data: list_entities(type: 'all_webhooks_events', item_list_key: 'events')}
675
+ return {type: :object_list, data: list_entities(type: 'all_webhooks_events', query: query_read_delete, item_list_key: 'events')}
668
676
  end
669
677
  when :configuration
670
678
  conf_path = 'configuration'
@@ -131,6 +131,8 @@ module Aspera
131
131
  COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
132
132
  COMMANDS_FASPEX = COMMON_ACTIONS
133
133
 
134
+ GEN4_LS_FIELDS = %w[name type recursive_size size modified_time access_level].freeze
135
+
134
136
  def initialize(api: nil, **env)
135
137
  super(**env, basic_options: api.nil?)
136
138
  Node.declare_options(options) if api.nil?
@@ -484,22 +486,15 @@ module Aspera
484
486
  when :browse
485
487
  apifid = apifid_from_next_arg(top_file_id)
486
488
  file_info = apifid[:api].read_with_cache("files/#{apifid[:file_id]}")
487
- if file_info['type'].eql?('folder')
488
- result = apifid[:api].call(
489
- operation: 'GET',
490
- subpath: "files/#{apifid[:file_id]}/files",
491
- headers: Api::Node.cache_control_headers,
492
- query: query_read_delete)
493
- items = result[:data]
494
- formatter.display_item_count(result[:data].length, result[:http]['X-Total-Count'])
495
- else
496
- items = [file_info]
489
+ unless file_info['type'].eql?('folder')
490
+ # a single file
491
+ return {type: :object_list, data: [file_info], fields: GEN4_LS_FIELDS}
497
492
  end
498
- return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
493
+ return {type: :object_list, data: apifid[:api].list_files(apifid[:file_id]), fields: GEN4_LS_FIELDS}
499
494
  when :find
500
495
  apifid = apifid_from_next_arg(top_file_id)
501
- test_block = Api::Node.file_matcher_from_argument(options)
502
- return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
496
+ find_lambda = Api::Node.file_matcher_from_argument(options)
497
+ return {type: :object_list, data: @api_node.find_files(apifid[:file_id], find_lambda), fields: ['path']}
503
498
  when :mkdir
504
499
  containing_folder_path = options.get_next_argument('path').split(Api::Node::PATH_SEPARATOR)
505
500
  new_folder = containing_folder_path.pop
@@ -608,12 +603,12 @@ module Aspera
608
603
  command_perm = options.get_next_command(%i[list create delete])
609
604
  case command_perm
610
605
  when :list
611
- # generic options : TODO: as arg ? query_read_delete
612
- list_options ||= {'include' => Rest.array_params(%w[access_level permission_count])}
613
- # add which one to get
614
- list_options['file_id'] = apifid[:file_id]
615
- list_options['inherited'] ||= false
616
- items = apifid[:api].read('permissions', list_options)
606
+ list_query = query_read_delete(default: {'include' => Rest.array_params(%w[access_level permission_count])})
607
+ # specify file to get permissions for unless not specified
608
+ list_query['file_id'] = apifid[:file_id] unless apifid[:file_id].to_s.empty?
609
+ list_query['inherited'] = false if list_query.key?('file_id') && !list_query.key?('inherited')
610
+ # NOTE: supports per_page and page and header X-Total-Count
611
+ items = apifid[:api].read('permissions', list_query)
617
612
  return {type: :object_list, data: items}
618
613
  when :delete
619
614
  return do_bulk_operation(command: command_perm, descr: 'identifier', values: :identifier) do |one_id|
@@ -802,42 +797,53 @@ module Aspera
802
797
  end
803
798
  case command
804
799
  when :list
800
+ transfer_filter = query_read_delete(default: {})
801
+ last_iteration_token = nil
805
802
  iteration_persistency = nil
806
- iteration_data = []
807
803
  if options.get_option(:once_only, mandatory: true)
808
804
  iteration_persistency = PersistencyActionOnce.new(
809
805
  manager: persistency,
810
- data: iteration_data,
806
+ data: [],
811
807
  id: IdGenerator.from_list([
812
808
  'node_transfers',
813
809
  options.get_option(:url, mandatory: true),
814
810
  options.get_option(:username, mandatory: true)
815
811
  ]))
812
+ if transfer_filter.delete('reset')
813
+ iteration_persistency.data.clear
814
+ iteration_persistency.save
815
+ return Main.result_status('Persistency reset')
816
+ end
817
+ last_iteration_token = iteration_persistency.data.first
816
818
  end
817
- transfer_filter = query_read_delete(default: {})
818
- if transfer_filter.delete('reset')
819
- iteration_data.clear
820
- iteration_persistency&.save
821
- return Main.result_status('Persistency reset')
822
- end
819
+ raise 'reset only with once_only' if transfer_filter.key?('reset') && iteration_persistency.nil?
823
820
  max_items = transfer_filter.delete(MAX_ITEMS)
824
- transfer_filter['iteration_token'] = iteration_persistency.data[0] unless iteration_data.empty?
825
821
  transfers_data = []
826
822
  loop do
823
+ transfer_filter['iteration_token'] = last_iteration_token unless last_iteration_token.nil?
827
824
  result = @api_node.call(operation: 'GET', subpath: res_class_path, query: transfer_filter)
828
- data = result[:data]
829
- transfers_data.concat(data)
830
- if !max_items.nil? && (transfers_data.length >= max_items)
825
+ # no data
826
+ break if result[:data].empty?
827
+ # get next iteration token from link
828
+ next_iteration_token = nil
829
+ link_info = result[:http]['Link']
830
+ unless link_info.nil?
831
+ m = link_info.match(/<([^>]+)>/)
832
+ raise "Cannot parse iteration in Link: #{link_info}" if m.nil?
833
+ next_iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
834
+ end
835
+ # same as last iteration: stop
836
+ break if next_iteration_token&.eql?(last_iteration_token)
837
+ last_iteration_token = next_iteration_token
838
+ transfers_data.concat(result[:data])
839
+ if max_items&.<=(transfers_data.length)
840
+ # if !max_items.nil? && (transfers_data.length >= max_items)
831
841
  transfers_data = transfers_data.slice(0, max_items)
832
842
  break
833
843
  end
834
- link_info = result[:http]['Link']
835
- break if iteration_persistency.nil? || data.empty? || link_info.nil?
836
- m = link_info.match(/<([^>]+)>/)
837
- raise "Problem with iteration: #{link_info}" if m.nil?
838
- iteration_token = CGI.parse(URI.parse(m[1]).query)['iteration_token']&.first
839
- iteration_data[0] = transfer_filter['iteration_token'] = iteration_token
844
+ break if last_iteration_token.nil?
840
845
  end
846
+ iteration_persistency&.data&.[]=(0, last_iteration_token)
841
847
  iteration_persistency&.save
842
848
  return {
843
849
  type: :object_list,
@@ -1019,7 +1025,7 @@ module Aspera
1019
1025
  raise 'Missing key: url' unless parameters.key?(:url)
1020
1026
  uri = URI.parse(parameters[:url])
1021
1027
  server = WebServerSimple.new(uri, certificate: parameters[:certificate])
1022
- server.mount(uri.path, NodeSimulatorServlet, parameters[:credentials], transfer)
1028
+ server.mount(uri.path, NodeSimulatorServlet, parameters[:credentials], NodeSimulator.new)
1023
1029
  server.start
1024
1030
  return Main.result_status('Simulator terminated')
1025
1031
  end
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # for beta add extension : .beta1
6
6
  # for dev version add extension : .pre
7
- VERSION = '4.20.0'
7
+ VERSION = '4.21.0'
8
8
  end
9
9
  end
@@ -77,7 +77,7 @@ module Aspera
77
77
  @param_hash.each_pair{|key, val|Log.log.warn{"unrecognized parameter: #{key} = \"#{val}\""} if !@used_param_names.include?(key)}
78
78
  # set result
79
79
  env_args[:env].merge!(@result[:env])
80
- env_args[:args].push(*@result[:args])
80
+ env_args[:args].concat(@result[:args])
81
81
  return nil
82
82
  end
83
83
 
@@ -46,8 +46,7 @@ module Aspera
46
46
  return OS_LINUX
47
47
  when /aix/
48
48
  return OS_AIX
49
- else
50
- raise "Unknown OS: #{RbConfig::CONFIG['host_os']}"
49
+ else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']){'host_os'}
51
50
  end
52
51
  end
53
52
 
@@ -62,8 +61,8 @@ module Aspera
62
61
  return CPU_S390
63
62
  when /arm/, /aarch64/
64
63
  return CPU_ARM64
64
+ else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'}
65
65
  end
66
- raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
67
66
  end
68
67
 
69
68
  # normalized architecture name
@@ -73,9 +72,9 @@ module Aspera
73
72
  end
74
73
 
75
74
  # executable file extension for current OS
76
- def exe_extension
77
- return '.exe' if os.eql?(OS_WINDOWS)
78
- return ''
75
+ def exe_file(name='')
76
+ return "#{name}.exe" if os.eql?(OS_WINDOWS)
77
+ return name
79
78
  end
80
79
 
81
80
  # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%