aspera-cli 4.26.2 → 4.27.1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +310 -21
  4. data/CONTRIBUTING.md +99 -14
  5. data/TODO.md +50 -0
  6. data/bin/ascli +3 -3
  7. data/docs/README.md +10977 -0
  8. data/docs/test-mcp-with-ai.md +202 -0
  9. data/lib/aspera/agent/base.rb +29 -7
  10. data/lib/aspera/agent/connect.rb +58 -21
  11. data/lib/aspera/agent/desktop.rb +53 -18
  12. data/lib/aspera/agent/direct.rb +71 -40
  13. data/lib/aspera/agent/factory.rb +3 -3
  14. data/lib/aspera/agent/httpgw.rb +55 -14
  15. data/lib/aspera/agent/node.rb +34 -2
  16. data/lib/aspera/agent/transferd.rb +48 -12
  17. data/lib/aspera/api/alee.rb +1 -1
  18. data/lib/aspera/api/aoc.rb +42 -44
  19. data/lib/aspera/api/cos_node.rb +10 -9
  20. data/lib/aspera/api/faspex.rb +16 -15
  21. data/lib/aspera/api/httpgw.rb +23 -23
  22. data/lib/aspera/api/node.rb +51 -70
  23. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  24. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  25. data/lib/aspera/ascmd.rb +32 -30
  26. data/lib/aspera/ascp/installation.rb +75 -69
  27. data/lib/aspera/ascp/management.rb +7 -8
  28. data/lib/aspera/assert.rb +12 -13
  29. data/lib/aspera/cli/ascp_actions.rb +155 -0
  30. data/lib/aspera/cli/async_transfer_store.rb +125 -0
  31. data/lib/aspera/cli/bootstrapper.rb +197 -0
  32. data/lib/aspera/cli/command_registry.rb +145 -0
  33. data/lib/aspera/cli/command_spec.rb +141 -0
  34. data/lib/aspera/cli/context.rb +19 -6
  35. data/lib/aspera/cli/error.rb +16 -0
  36. data/lib/aspera/cli/extended_value.rb +27 -26
  37. data/lib/aspera/cli/formatter.rb +95 -59
  38. data/lib/aspera/cli/gem_checker.rb +65 -0
  39. data/lib/aspera/cli/hints.rb +3 -3
  40. data/lib/aspera/cli/http.rb +74 -30
  41. data/lib/aspera/cli/info.rb +2 -0
  42. data/lib/aspera/cli/mailer.rb +97 -0
  43. data/lib/aspera/cli/mcp_tool.rb +225 -0
  44. data/lib/aspera/cli/option_declarator.rb +77 -0
  45. data/lib/aspera/cli/options.schema.yaml +634 -7
  46. data/lib/aspera/cli/parser.rb +1217 -0
  47. data/lib/aspera/cli/plugins/alee.rb +20 -22
  48. data/lib/aspera/cli/plugins/aoc.rb +1178 -881
  49. data/lib/aspera/cli/plugins/ats.rb +205 -162
  50. data/lib/aspera/cli/plugins/base.rb +703 -175
  51. data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
  52. data/lib/aspera/cli/plugins/config.rb +434 -757
  53. data/lib/aspera/cli/plugins/console.rb +107 -65
  54. data/lib/aspera/cli/plugins/cos.rb +46 -33
  55. data/lib/aspera/cli/plugins/factory.rb +6 -6
  56. data/lib/aspera/cli/plugins/faspex5.rb +627 -388
  57. data/lib/aspera/cli/plugins/faspio.rb +54 -51
  58. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  59. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  60. data/lib/aspera/cli/plugins/node.rb +988 -840
  61. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  62. data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
  63. data/lib/aspera/cli/plugins/preview.rb +195 -157
  64. data/lib/aspera/cli/plugins/server.rb +141 -91
  65. data/lib/aspera/cli/plugins/shares.rb +343 -110
  66. data/lib/aspera/cli/preset_actions.rb +159 -0
  67. data/lib/aspera/cli/preset_manager.rb +82 -39
  68. data/lib/aspera/cli/result.rb +78 -26
  69. data/lib/aspera/cli/runner.rb +323 -153
  70. data/lib/aspera/cli/secret_finder.rb +40 -0
  71. data/lib/aspera/cli/special_values.rb +1 -0
  72. data/lib/aspera/cli/sync_actions.rb +89 -64
  73. data/lib/aspera/cli/terminal_formatter.rb +3 -3
  74. data/lib/aspera/cli/transfer_actions.rb +90 -0
  75. data/lib/aspera/cli/transfer_agent.rb +129 -64
  76. data/lib/aspera/cli/transfer_progress.rb +9 -9
  77. data/lib/aspera/cli/vault_manager.rb +79 -0
  78. data/lib/aspera/cli/version.rb +1 -1
  79. data/lib/aspera/cli/wizard.rb +28 -30
  80. data/lib/aspera/colors.rb +3 -3
  81. data/lib/aspera/command_line_builder.rb +34 -34
  82. data/lib/aspera/command_line_converter.rb +1 -1
  83. data/lib/aspera/coverage.rb +1 -2
  84. data/lib/aspera/data_repository.rb +2 -1
  85. data/lib/aspera/dot_container.rb +12 -12
  86. data/lib/aspera/environment.rb +30 -23
  87. data/lib/aspera/exec_spec.rb +13 -0
  88. data/lib/aspera/faspex_gw.rb +5 -5
  89. data/lib/aspera/faspex_postproc.rb +16 -10
  90. data/lib/aspera/graphql.rb +37 -0
  91. data/lib/aspera/hash_ext.rb +8 -2
  92. data/lib/aspera/json_rpc/client.rb +62 -0
  93. data/lib/aspera/json_rpc/version.rb +7 -0
  94. data/lib/aspera/keychain/base.rb +10 -3
  95. data/lib/aspera/keychain/encrypted_hash.rb +92 -17
  96. data/lib/aspera/keychain/factory.rb +20 -8
  97. data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
  98. data/lib/aspera/keychain/macos_security.rb +23 -25
  99. data/lib/aspera/keychain/one_password_api.rb +86 -0
  100. data/lib/aspera/keychain/one_password_base.rb +34 -0
  101. data/lib/aspera/keychain/one_password_cli.rb +98 -0
  102. data/lib/aspera/link_header.rb +82 -0
  103. data/lib/aspera/log.rb +29 -15
  104. data/lib/aspera/markdown.rb +90 -7
  105. data/lib/aspera/nagios.rb +8 -8
  106. data/lib/aspera/node_simulator.rb +35 -27
  107. data/lib/aspera/oauth/base.rb +9 -10
  108. data/lib/aspera/oauth/boot.rb +5 -5
  109. data/lib/aspera/oauth/factory.rb +12 -10
  110. data/lib/aspera/oauth/jwt.rb +9 -11
  111. data/lib/aspera/oauth/web.rb +6 -6
  112. data/lib/aspera/persistency_action_once.rb +12 -12
  113. data/lib/aspera/persistency_folder.rb +18 -11
  114. data/lib/aspera/preview/file_types.rb +5 -5
  115. data/lib/aspera/preview/generator.rb +52 -49
  116. data/lib/aspera/preview/options.rb +3 -2
  117. data/lib/aspera/preview/terminal.rb +5 -5
  118. data/lib/aspera/preview/utils.rb +66 -19
  119. data/lib/aspera/products/connect.rb +2 -2
  120. data/lib/aspera/products/desktop.rb +1 -1
  121. data/lib/aspera/products/other.rb +3 -3
  122. data/lib/aspera/products/transferd.rb +4 -4
  123. data/lib/aspera/proxy_auto_config.rb +15 -14
  124. data/lib/aspera/rest.rb +159 -151
  125. data/lib/aspera/rest_error_analyzer.rb +6 -6
  126. data/lib/aspera/rest_errors_aspera.rb +0 -10
  127. data/lib/aspera/rest_list.rb +11 -10
  128. data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
  129. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
  130. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
  131. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
  132. data/lib/aspera/schema/documentation.rb +65 -28
  133. data/lib/aspera/schema/reader.rb +136 -13
  134. data/lib/aspera/schema/registry.rb +60 -8
  135. data/lib/aspera/secret_hider.rb +12 -7
  136. data/lib/aspera/ssh.rb +64 -31
  137. data/lib/aspera/ssl.rb +5 -5
  138. data/lib/aspera/sync/conf.schema.yaml +2 -2
  139. data/lib/aspera/sync/database.rb +2 -2
  140. data/lib/aspera/sync/operations.rb +22 -25
  141. data/lib/aspera/temp_file_manager.rb +25 -5
  142. data/lib/aspera/timer_limiter.rb +1 -1
  143. data/lib/aspera/transfer/faux_file.rb +24 -11
  144. data/lib/aspera/transfer/parameters.rb +36 -34
  145. data/lib/aspera/transfer/result.rb +74 -0
  146. data/lib/aspera/transfer/resumer.rb +10 -10
  147. data/lib/aspera/transfer/spec.rb +18 -1
  148. data/lib/aspera/transfer/spec.schema.yaml +12 -3
  149. data/lib/aspera/uri_reader.rb +52 -15
  150. data/lib/aspera/web_auth.rb +8 -7
  151. data/lib/aspera/web_server_simple.rb +15 -12
  152. data/lib/aspera/yaml.rb +5 -4
  153. data.tar.gz.sig +0 -0
  154. metadata +34 -6
  155. metadata.gz.sig +0 -0
  156. data/lib/aspera/cli/manager.rb +0 -766
  157. data/lib/aspera/cli/plugins/faspex.rb +0 -539
  158. data/lib/aspera/json_rpc.rb +0 -52
  159. data/lib/aspera/transfer/uri.rb +0 -56
@@ -25,23 +25,23 @@ module Aspera
25
25
 
26
26
  class AppInfo
27
27
  # @return [Aspera::Api::AoC] Rest Api object
28
- attr_reader :api
28
+ attr_reader :api
29
29
  # @return [Hash, nil] Package information, `"files"` or `"packages"`
30
- attr_reader :package
30
+ attr_reader :package
31
31
  # @return [Hash] Result of GET /nodes/:id
32
- attr_reader :node_info
32
+ attr_reader :node_info
33
33
  # @return [String] Workspace id
34
- attr_reader :workspace_id
34
+ attr_reader :workspace_id
35
35
  # @return [String] Workspace name
36
- attr_reader :workspace_name
36
+ attr_reader :workspace_name
37
37
  # @return [String, nil] "Share as" link name
38
38
  attr_accessor :opt_link_name
39
39
 
40
40
  # @param api [Api::Aoc] Rest Api object
41
41
  # @param package [Hash,nil] package information or nil
42
42
  # @param node_info [Hash] Result of GET /nodes/:id
43
- # @param workspace_id [String]
44
- # @param workspace_name [String]
43
+ # @param workspace_id [String] Workspace identifier
44
+ # @param workspace_name [String] Workspace name
45
45
  def initialize(api, package, node_info, workspace_id, workspace_name)
46
46
  @api = api
47
47
  @package = package
@@ -65,11 +65,11 @@ module Aspera
65
65
  MAX_AOC_URL_REDIRECT = 10
66
66
  CLIENT_ID_PREFIX = 'aspera.'
67
67
  # Well-known AoC global client apps
68
- GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{ |i| i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
68
+ GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select { |i| i.to_s.start_with?(CLIENT_ID_PREFIX) }.freeze
69
69
  # cookie prefix so that console can decode identity
70
70
  COOKIE_PREFIX_CONSOLE_AOC = 'aspera.aoc'
71
- # path in URL of public links
72
- PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public /public/files /public/package /public/send].freeze
71
+ # path in URL of public links (warning if not in list)
72
+ PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public /public/files /public/files/ /public/package /public/send /public/send/package].freeze
73
73
  JWT_AUDIENCE = 'https://api.asperafiles.com/api/v1/oauth2/token'
74
74
  OAUTH_API_SUBPATH = 'api/v1/oauth2'
75
75
  # minimum fields for user info if retrieval fails
@@ -123,9 +123,9 @@ module Aspera
123
123
  # split host of URL into organization and domain
124
124
  def split_org_domain(uri)
125
125
  Aspera.assert_type(uri, URI)
126
- Aspera.assert(!uri.host.nil?){"No host found in URL. Please check URL format: https://myorg.#{SAAS_DOMAIN_PROD}"}
126
+ Aspera.assert(!uri.host.nil?) { "No host found in URL. Please check URL format: https://myorg.#{SAAS_DOMAIN_PROD}" }
127
127
  parts = uri.host.split('.', 2)
128
- Aspera.assert(parts.length == 2){"expecting a public FQDN for #{PRODUCT_NAME}"}
128
+ Aspera.assert(parts.length == 2) { "expecting a public FQDN for #{PRODUCT_NAME}" }
129
129
  parts[0] = nil if parts[0].eql?('api')
130
130
  return %i{organization domain}.zip(parts).to_h
131
131
  end
@@ -146,14 +146,14 @@ module Aspera
146
146
  if (m = final_uri.path.match(%r{/oauth2/([^/]+)/login$}))
147
147
  org_domain[:organization] = m[1] if org_domain[:organization].nil?
148
148
  else
149
- Log.log.debug{"path=#{final_uri.path} does not end with /login"}
149
+ Log.log.debug { "path=#{final_uri.path} does not end with /login" }
150
150
  end
151
151
  Aspera.assert(!final_uri.query.nil?, 'AoC shall redirect to login page with a query', type: Error)
152
152
  query = Rest.query_to_h(final_uri.query)
153
153
  Log.dump(:query, query, level: :trace1)
154
154
  # is that a public link ?
155
155
  if query.key?('token')
156
- Log.log.warn{"Unknown pub link path: #{final_uri.path}"} unless PUBLIC_LINK_PATHS.include?(final_uri.path)
156
+ Log.log.warn { "Unknown pub link path: #{final_uri.path}" } unless PUBLIC_LINK_PATHS.include?(final_uri.path)
157
157
  # ok we get it !
158
158
  return {
159
159
  instance_domain: org_domain[:domain],
@@ -198,7 +198,7 @@ module Aspera
198
198
  # @option return [Array<Hash>] :items The list of items
199
199
  # @option return [Integer] :total The total number of items
200
200
  def call_paging(query: {})
201
- Aspera.assert_type(query, Hash){'query'}
201
+ Aspera.assert_type(query, Hash) { 'query' }
202
202
  Aspera.assert(block_given?, 'block required for call_paging')
203
203
  # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
204
204
  query['per_page'] = 1000 unless query.key?('per_page')
@@ -257,10 +257,10 @@ module Aspera
257
257
  when 'download' then %w[list preview read]
258
258
  when 'upload' then %w[mkdir write]
259
259
  when Array
260
- Aspera.assert_array_all(levels, String){'access_levels'}
261
- levels.each{ |level| Aspera.assert_values(level, Node::ACCESS_LEVELS){'access_level'}}
260
+ Aspera.assert_array_all(levels, String) { 'access_levels' }
261
+ levels.each { |level| Aspera.assert_values(level, Node::ACCESS_LEVELS) { 'access_level' } }
262
262
  levels
263
- else Aspera.error_unexpected_value(levels){"access_levels must be a list of #{Node::ACCESS_LEVELS.join(', ')} or one of edit, preview, download, upload"}
263
+ else Aspera.error_unexpected_value(levels) { "access_levels must be a list of #{Node::ACCESS_LEVELS.join(', ')} or one of edit, preview, download, upload" }
264
264
  end
265
265
  end
266
266
  end
@@ -269,7 +269,6 @@ module Aspera
269
269
  attr_accessor :ws_ids
270
270
 
271
271
  # By default: no workspace
272
- # @param
273
272
  def initialize(
274
273
  scope: nil,
275
274
  subpath: API_V1,
@@ -298,8 +297,7 @@ module Aspera
298
297
  # default values for client id
299
298
  client_id, client_secret = self.class.get_client_info if client_id.nil?
300
299
  # access key secrets are provided out of band to get node api access
301
- # key: access key
302
- # value: associated secret
300
+ # maps access key (String) to its associated secret (String)
303
301
  @secret_finder = secret_finder
304
302
  @cache_user_info = nil
305
303
  @cache_url_token_info = nil
@@ -354,7 +352,7 @@ module Aspera
354
352
  when :none
355
353
  auth_params.clear
356
354
  auth_params[:type] = :none
357
- else Aspera.error_unexpected_value(auth_params[:grant_method]){'auth, use one of: web, jwt, boot'}
355
+ else Aspera.error_unexpected_value(auth_params[:grant_method]) { 'auth, use one of: web, jwt, boot' }
358
356
  end
359
357
  super(**base_args)
360
358
  end
@@ -371,7 +369,7 @@ module Aspera
371
369
 
372
370
  # @param expected [Array<String>] Link types
373
371
  def assert_public_link_types(expected)
374
- Aspera.assert_values(public_link['purpose'], expected){'public link type'}
372
+ Aspera.assert_values(public_link['purpose'], expected) { 'public link type' }
375
373
  end
376
374
 
377
375
  def additional_persistence_ids
@@ -398,10 +396,10 @@ module Aspera
398
396
  read('self')
399
397
  rescue Aspera::RestCallError => e
400
398
  raise if exception || e.message.include?('invalid_grant')
401
- Log.log.debug{"Ignoring error: (#{e.class}) #{e}"}
399
+ Log.log.debug { "Ignoring error: (#{e.class}) #{e}" }
402
400
  {}
403
401
  end
404
- USER_INFO_FIELDS_MIN.each{ |f| @cache_user_info[f] = nil if @cache_user_info[f].nil?}
402
+ USER_INFO_FIELDS_MIN.each { |f| @cache_user_info[f] = nil if @cache_user_info[f].nil? }
405
403
  return @cache_user_info
406
404
  end
407
405
 
@@ -480,17 +478,17 @@ module Aspera
480
478
  # @param workspace_name [String,nil] workspace name
481
479
  # @param scope [String,nil] e.g. Node::Scope::USER, or Node::Scope::ADMIN, or nil (requires secret)
482
480
  # @param package_info [Hash,nil] created package information
483
- # @returns [Node] a node API for access key
481
+ # @return [Node] a node API for access key
484
482
  def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Node::Scope::USER, package_info: nil)
485
483
  Aspera.assert_type(node_id, String)
486
484
  node_info = read("nodes/#{node_id}")
487
485
  workspace_name = read("workspaces/#{workspace_id}")['name'] if workspace_name.nil? && !workspace_id.nil?
488
486
  app_info = AppInfo.new(self, package_info, node_info, workspace_id, workspace_name)
489
487
  node_params = {base_url: node_info['url']}
490
- ak_secret = @secret_finder&.lookup_secret(url: node_info['url'], username: node_info['access_key'])
488
+ ak_secret = @secret_finder&.lookup(url: node_info['url'], username: node_info['access_key'])
491
489
  # If secret is available, or no scope, use basic auth
492
490
  if scope.nil? || ak_secret
493
- Aspera.assert(ak_secret, type: Error){"Secret not found for access key #{node_info['access_key']}@#{node_info['url']}"}
491
+ Aspera.assert(ak_secret, type: Error) { "Secret not found for access key #{node_info['access_key']}@#{node_info['url']}" }
494
492
  node_params[:auth] = {
495
493
  type: :basic,
496
494
  username: node_info['access_key'],
@@ -521,21 +519,21 @@ module Aspera
521
519
  Log.log.debug('no metadata in shared inbox')
522
520
  return
523
521
  end
524
- Aspera.assert(pkg_data.key?('metadata')){"package requires metadata: #{meta_schema}"}
522
+ Aspera.assert(pkg_data.key?('metadata')) { "package requires metadata: #{meta_schema}" }
525
523
  pkg_meta = pkg_data['metadata']
526
- Aspera.assert_type(pkg_meta, Array){'metadata'}
524
+ Aspera.assert_type(pkg_meta, Array) { 'metadata' }
527
525
  Log.dump(:metadata, pkg_meta)
528
526
  pkg_meta.each do |field|
529
- Aspera.assert_type(field, Hash){'metadata field'}
527
+ Aspera.assert_type(field, Hash) { 'metadata field' }
530
528
  Aspera.assert(field.key?('name'), 'metadata field must have name')
531
529
  Aspera.assert(field.key?('values'), 'metadata field must have values')
532
- Aspera.assert_type(field['values'], Array){'metadata field values'}
533
- Aspera.assert(meta_schema.any?{ |i| i['name'].eql?(field['name'])}){"unknown metadata field: #{field['name']}"}
530
+ Aspera.assert_type(field['values'], Array) { 'metadata field values' }
531
+ Aspera.assert(meta_schema.any? { |i| i['name'].eql?(field['name']) }) { "unknown metadata field: #{field['name']}" }
534
532
  end
535
533
  meta_schema.each do |field|
536
- provided = pkg_meta.select{ |i| i['name'].eql?(field['name'])}
537
- Aspera.assert(provided.count <= 1, type: Error){"only one field with name #{field['name']} allowed"}
538
- Aspera.assert(!provided.empty?, type: Error){"missing mandatory field: #{field['name']}"} if field['required']
534
+ provided = pkg_meta.select { |i| i['name'].eql?(field['name']) }
535
+ Aspera.assert(provided.count <= 1, type: Error) { "only one field with name #{field['name']} allowed" }
536
+ Aspera.assert(!provided.empty?, type: Error) { "missing mandatory field: #{field['name']}" } if field['required']
539
537
  end
540
538
  end
541
539
 
@@ -545,19 +543,19 @@ module Aspera
545
543
  # @param package_data [Hash] The whole package creation payload
546
544
  # @param rcpt_lst_field [String] The field in structure, i.e. recipients or bcc_recipients
547
545
  # @param new_user_option [Hash] Additional fields for contact creation
548
- # @return nil, `package_data` is modified
546
+ # @return [nil] `package_data` is modified in place
549
547
  def resolve_package_recipients(package_data, rcpt_lst_field, new_user_option)
550
548
  return unless package_data.key?(rcpt_lst_field)
551
- Aspera.assert_type(package_data[rcpt_lst_field], Array){rcpt_lst_field}
549
+ Aspera.assert_type(package_data[rcpt_lst_field], Array) { rcpt_lst_field }
552
550
  new_user_option = {'package_contact' => true} if new_user_option.nil?
553
- Aspera.assert_type(new_user_option, Hash){'new_user_option'}
551
+ Aspera.assert_type(new_user_option, Hash) { 'new_user_option' }
554
552
  ws_id = package_data['workspace_id']
555
553
  # list with resolved elements
556
554
  resolved_list = []
557
555
  package_data[rcpt_lst_field].each do |short_recipient_info|
558
556
  case short_recipient_info
559
557
  when Hash # native API information, check keys
560
- Aspera.assert(short_recipient_info.keys.sort.eql?(%w[id type])){"#{rcpt_lst_field} element shall have fields: id and type"}
558
+ Aspera.assert(short_recipient_info.keys.sort.eql?(%w[id type])) { "#{rcpt_lst_field} element shall have fields: id and type" }
561
559
  when String # CLI helper: need to resolve provided name to type/id
562
560
  # email: user, else dropbox
563
561
  entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
@@ -565,7 +563,7 @@ module Aspera
565
563
  full_recipient_info = lookup_with_q(entity_type, value: short_recipient_info, query: {'workspace_id' => ws_id})
566
564
  rescue EntityNotFound
567
565
  # dropboxes cannot be created on the fly
568
- Aspera.assert_values(entity_type, %w[contacts], type: Error){"No such shared inbox in workspace #{ws_id}"}
566
+ Aspera.assert_values(entity_type, %w[contacts], type: Error) { "No such shared inbox in workspace #{ws_id}" }
569
567
  # unknown user: create it as external user
570
568
  full_recipient_info = create('contacts', {
571
569
  'current_workspace_id' => ws_id,
@@ -577,7 +575,7 @@ module Aspera
577
575
  else
578
576
  {'id' => full_recipient_info['source_id'], 'type' => full_recipient_info['source_type']}
579
577
  end
580
- else Aspera.error_unexpected_value(short_recipient_info.class.name){"#{rcpt_lst_field} item must be a String (email, shared inbox) or Hash (id,type)"}
578
+ else Aspera.error_unexpected_value(short_recipient_info.class.name) { "#{rcpt_lst_field} item must be a String (email, shared inbox) or Hash (id,type)" }
581
579
  end
582
580
  # add original or resolved recipient info
583
581
  resolved_list.push(short_recipient_info)
@@ -609,7 +607,7 @@ module Aspera
609
607
  # @param package_data [Hash] package creation (with extensions...)
610
608
  # @param validate_meta [TrueClass,FalseClass] true to validate parameters locally
611
609
  # @param new_user_option [Hash,NilClass] options if an unknown user is specified
612
- # @return transfer spec, node api and package information
610
+ # @return [Array] transfer spec, node api and package information
613
611
  def create_package_simple(package_data, validate_meta, new_user_option)
614
612
  update_package_metadata_for_api(package_data)
615
613
  # list of files to include in package, optional
@@ -671,7 +669,7 @@ module Aspera
671
669
  # Console cookie
672
670
  ################
673
671
  # we are sure that fields are not nil
674
- cookie_elements = [app_info.app, current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{ |e| Base64.strict_encode64(e)}
672
+ cookie_elements = [app_info.app, current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map { |e| Base64.strict_encode64(e) }
675
673
  cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
676
674
  transfer_spec['cookie'] = cookie_elements.join(':')
677
675
  # Application tags
@@ -12,19 +12,20 @@ module Aspera
12
12
  IBM_CLOUD_TOKEN_URL = 'https://iam.cloud.ibm.com/identity'
13
13
  TOKEN_FIELD = 'delegated_refresh_token'
14
14
  FASP_INFO_KEYS = %w[ATSEndpoint AccessKey].freeze
15
+ SERVICE_CREDS_PARAMS = %w[apikey resource_instance_id endpoints]
15
16
  class << self
16
17
  def parameters_from_svc_credentials(service_credentials, bucket_region)
17
18
  # check necessary contents
18
- Aspera.assert_type(service_credentials, Hash){'service_credentials'}
19
+ Aspera.assert_type(service_credentials, Hash) { 'service_credentials' }
19
20
  Log.dump(:service_credentials, service_credentials)
20
- %w[apikey resource_instance_id endpoints].each do |field|
21
- Aspera.assert(service_credentials.key?(field)){"service_credentials must have a field: #{field}"}
21
+ SERVICE_CREDS_PARAMS.each do |field|
22
+ Aspera.assert(service_credentials.key?(field)) { "service_credentials must have a field: #{field}" }
22
23
  end
23
24
  # read endpoints from service provided in service credentials
24
25
  endpoints = Aspera::Rest.new(base_url: service_credentials['endpoints']).read('')
25
26
  Log.dump(:endpoints, endpoints)
26
27
  endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
27
- raise "no such region: #{bucket_region}" if endpoint.nil?
28
+ Aspera.assert(!endpoint.nil?) { "no such region: #{bucket_region}" }
28
29
  return {
29
30
  instance_id: service_credentials['resource_instance_id'],
30
31
  api_key: service_credentials['apikey'],
@@ -34,8 +35,8 @@ module Aspera
34
35
  end
35
36
 
36
37
  def initialize(instance_id:, api_key:, endpoint:, bucket:, auth_url: IBM_CLOUD_TOKEN_URL)
37
- Aspera.assert_type(instance_id, String){'resource instance id (crn)'}
38
- Aspera.assert_type(endpoint, String){'endpoint'}
38
+ Aspera.assert_type(instance_id, String) { 'resource instance id (crn)' }
39
+ Aspera.assert_type(endpoint, String) { 'endpoint' }
39
40
  endpoint = "https://#{endpoint}" unless endpoint.start_with?('http')
40
41
  @auth_url = auth_url
41
42
  @api_key = api_key
@@ -61,10 +62,10 @@ module Aspera
61
62
  ret: :resp
62
63
  ).body
63
64
  ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
64
- Log.dump(:ats_info, ats_info)
65
- Aspera.assert_hash_all(ats_info, String, nil){'ats_info'}
65
+ Log.dump(:ats_info, ats_info.reject { |k, _| k == 'AccessKey' })
66
+ Aspera.assert_hash_all(ats_info, String, nil) { 'ats_info' }
66
67
  Aspera.assert((FASP_INFO_KEYS - ats_info.keys).empty?, 'ats_info missing required keys')
67
- Aspera.assert_hash_all(ats_info['AccessKey'], String, String){'ats_info'}
68
+ Aspera.assert_hash_all(ats_info['AccessKey'], String, String) { 'ats_info' }
68
69
  @storage_credentials = {
69
70
  'type' => 'token',
70
71
  'token' => {TOKEN_FIELD => nil}
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/assert'
3
4
  require 'aspera/rest'
4
5
  require 'aspera/oauth/base'
5
6
  require 'aspera/rest_list'
@@ -11,9 +12,9 @@ module Aspera
11
12
  class << self
12
13
  attr_accessor :additional_info
13
14
  end
14
- # @param context The `context` query parameter in public link
15
- # @param redirect_uri URI of web UI login
16
- # @param path_authorize Path to provide passcode
15
+ # @param context [String] The `context` query parameter in public link
16
+ # @param redirect_uri [String] URI of web UI login
17
+ # @param path_authorize [String] Path to provide passcode
17
18
  def initialize(
18
19
  context:,
19
20
  redirect_uri:,
@@ -125,7 +126,7 @@ module Aspera
125
126
  mfa_code
126
127
  ]
127
128
  class << self
128
- # @return true if the URL is a public link
129
+ # @return [Boolean] true if the URL is a public link
129
130
  def public_link?(url)
130
131
  url.include?('?context=')
131
132
  end
@@ -138,15 +139,15 @@ module Aspera
138
139
  end
139
140
  attr_reader :pub_link_context
140
141
 
141
- # @param url Faspex URL, can be a public link
142
- # @param auth Authentication method: :boot (token in header), :web (open browser), :jwt (client_id + private key), :public_link (context in URL)
143
- # @param password For :boot auth, the token copied directly from browser in developer mode
144
- # @param client_id For :web and :jwt auth, the client_id of web UI application
145
- # @param client_secret For :web auth, the client_secret of web UI application (not needed for :jwt)
146
- # @param redirect_uri For :web auth, the redirect_uri of web UI application (must be the same as in application configuration)
147
- # @param username For :jwt auth, the username of the user to impersonate
148
- # @param private_key For :jwt auth, the private key to sign JWT token
149
- # @param passphrase For :jwt auth, the passphrase of the private key
142
+ # @param url [String] Faspex URL, can be a public link
143
+ # @param auth [Symbol] Authentication method: :boot (token in header), :web (open browser), :jwt (client_id + private key), :public_link (context in URL)
144
+ # @param password [String, nil] For :boot auth, the token copied directly from browser in developer mode
145
+ # @param client_id [String, nil] For :web and :jwt auth, the client_id of web UI application
146
+ # @param client_secret [String, nil] For :web auth, the client_secret of web UI application (not needed for :jwt)
147
+ # @param redirect_uri [String, nil] For :web auth, the redirect_uri of web UI application (must be the same as in application configuration)
148
+ # @param username [String, nil] For :jwt auth, the username of the user to impersonate
149
+ # @param private_key [String, nil] For :jwt auth, the private key to sign JWT token
150
+ # @param passphrase [String, nil] For :jwt auth, the passphrase of the private key
150
151
  def initialize(
151
152
  url:,
152
153
  auth:,
@@ -169,7 +170,7 @@ module Aspera
169
170
  Log.dump(:final_url, final_url, level: :trace1)
170
171
  # Get context from query
171
172
  encoded_context = Rest.query_to_h(URI.parse(final_url).query)['context']
172
- raise ParameterError, 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
173
+ Aspera.assert(!encoded_context.nil?, type: ParameterError) { 'Bad faspex5 public link, missing context in query' }
173
174
  # public link information (contains passcode and allowed usage)
174
175
  @pub_link_context = JSON.parse(Base64.decode64(encoded_context))
175
176
  Log.dump(:pub_link_context, @pub_link_context, level: :trace1)
@@ -237,7 +238,7 @@ module Aspera
237
238
  headers: {typ: 'JWT'}
238
239
  }
239
240
  }
240
- else Aspera.error_unexpected_value(auth, type: ParameterError){'auth'}
241
+ else Aspera.error_unexpected_value(auth, type: ParameterError) { 'auth' }
241
242
  end
242
243
  )
243
244
  end
@@ -60,7 +60,7 @@ module Aspera
60
60
 
61
61
  # send data on http gw web socket
62
62
  def ws_send(ws_type:, data:)
63
- Log.log.trace1{"#{LOG_WS_SEND}sending: #{ws_type} (#{data&.length || 0} bytes)"}
63
+ Log.log.trace1 { "#{LOG_WS_SEND}sending: #{ws_type} (#{data&.length || 0} bytes)" }
64
64
  @shared_info[:count][:sent_general] += 1 if ws_type.eql?(:binary)
65
65
  frame_generator = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: ws_type, version: @ws_handshake.version)
66
66
  @ws_io.write(frame_generator.to_s)
@@ -72,16 +72,16 @@ module Aspera
72
72
  @shared_info[:read_exception].nil? &&
73
73
  (((@shared_info[:count][:sent_general] - @shared_info[:count][:received_general]) > 1) ||
74
74
  ((@shared_info[:count][:received_v2_delimiter] - @shared_info[:count][:sent_v2_delimiter]) > 1))
75
- Log.log.trace1{"#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}"} if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
75
+ Log.log.trace1 { "#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}" } if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
76
76
  end
77
77
  end
78
78
  end
79
79
  raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
80
- Log.log.trace2{"#{LOG_WS_SEND}counts: #{@shared_info[:count]}"}
80
+ Log.log.trace2 { "#{LOG_WS_SEND}counts: #{@shared_info[:count]}" }
81
81
  end
82
82
 
83
83
  # Check header ourself and give precise error message, as websocket will only throw error without details
84
- # @param [String] Response Header
84
+ # @param header [String] Response Header
85
85
  # @return [String] Response Header
86
86
  def validated_ws_response_header(header)
87
87
  first_line = header.split("\r\n").first
@@ -91,7 +91,7 @@ module Aspera
91
91
 
92
92
  # message processing for read thread
93
93
  def process_received_message(message)
94
- Log.log.debug{"#{LOG_WS_RECV}message: [#{message}] (#{message.class})"}
94
+ Log.log.debug { "#{LOG_WS_RECV}message: [#{message}] (#{message.class})" }
95
95
  if message.eql?(MSG_RECV_DATA_RECEIVED_SIGNAL)
96
96
  @shared_info[:mutex].synchronize do
97
97
  @shared_info[:count][:received_general] += 1
@@ -119,21 +119,21 @@ module Aspera
119
119
 
120
120
  # main function of read thread
121
121
  def process_read_thread
122
- Log.log.debug{"#{LOG_WS_RECV}read thread started"}
122
+ Log.log.debug { "#{LOG_WS_RECV}read thread started" }
123
123
  frame_parser = ::WebSocket::Frame::Incoming::Client.new(version: @ws_handshake.version)
124
124
  until @ws_io.eof?
125
125
  begin
126
126
  # ready byte by byte until frame is ready
127
127
  # blocking read
128
128
  byte = @ws_io.read(1)
129
- Log.log.trace2{"#{LOG_WS_RECV}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}"}
129
+ Log.log.trace2 { "#{LOG_WS_RECV}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}" }
130
130
  frame_parser << byte
131
131
  frame_ok = frame_parser.next
132
132
  next if frame_ok.nil?
133
133
  process_received_message(frame_ok.data.to_s)
134
- Log.log.trace2{"#{LOG_WS_RECV}counts: #{@shared_info[:count]}"}
134
+ Log.log.trace2 { "#{LOG_WS_RECV}counts: #{@shared_info[:count]}" }
135
135
  rescue => e
136
- Log.log.debug{"#{LOG_WS_RECV}Exception: #{e}"}
136
+ Log.log.debug { "#{LOG_WS_RECV}Exception: #{e}" }
137
137
  @shared_info[:mutex].synchronize do
138
138
  @shared_info[:read_exception] = e
139
139
  @shared_info[:cond_var].signal
@@ -144,7 +144,7 @@ module Aspera
144
144
  Log.log.debug do
145
145
  "#{LOG_WS_RECV}exception: #{@shared_info[:read_exception]},cls=#{@shared_info[:read_exception].class})"
146
146
  end unless @shared_info[:read_exception].nil?
147
- Log.log.debug{"#{LOG_WS_RECV}read thread stopped (ws eof=#{@ws_io.eof?})"}
147
+ Log.log.debug { "#{LOG_WS_RECV}read thread stopped (ws eof=#{@ws_io.eof?})" }
148
148
  end
149
149
 
150
150
  def upload(transfer_spec)
@@ -170,7 +170,7 @@ module Aspera
170
170
  # Get whole HTTP response header, Check and process
171
171
  # no need to check `finished?` or result of `<<` (true), as we give the whole header at once
172
172
  @ws_handshake << validated_ws_response_header(@ws_io.readuntil("\r\n\r\n"))
173
- Log.log.debug{"#{LOG_WS_SEND}handshake success"}
173
+ Log.log.debug { "#{LOG_WS_SEND}handshake success" }
174
174
  # data shared between main thread and read thread
175
175
  @shared_info = {
176
176
  read_exception: nil, # error message if any in callback
@@ -184,7 +184,7 @@ module Aspera
184
184
  cond_var: ConditionVariable.new
185
185
  }
186
186
  # start read thread after handshake
187
- @ws_read_thread = Thread.new{process_read_thread}
187
+ @ws_read_thread = Thread.new { process_read_thread }
188
188
  @notify_cb&.call(:session_start, session_id: session_id)
189
189
  @notify_cb&.call(:session_size, session_id: session_id, info: total_bytes_to_transfer)
190
190
  sleep(1)
@@ -210,7 +210,7 @@ module Aspera
210
210
  fileIndex: file_index
211
211
  }
212
212
  file = file_to_send[:file]
213
- file = File.open(file) unless file.is_a?(Transfer::FauxFile)
213
+ file = File.open(file) unless file.is_a?(Transfer::FauxFile) # rubocop:disable Style/FileOpen
214
214
  begin
215
215
  until file.eof?
216
216
  slice_bin_data = file.read(@upload_chunk_size)
@@ -224,7 +224,7 @@ module Aspera
224
224
  # send once, before data, at beginning
225
225
  ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(0)
226
226
  ws_send(ws_type: :binary, data: slice_bin_data)
227
- Log.log.trace1{"#{LOG_WS_SEND}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{last_slice}"}
227
+ Log.log.trace1 { "#{LOG_WS_SEND}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{last_slice}" }
228
228
  # send once, after data, at end
229
229
  ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(last_slice)
230
230
  end
@@ -232,7 +232,7 @@ module Aspera
232
232
  raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
233
233
  raise e
234
234
  rescue Net::ReadTimeout => e
235
- Log.log.warn{'A timeout condition using HTTPGW may signal a permission problem on destination. Check ascp logs on httpgw.'}
235
+ Log.log.warn { 'A timeout condition using HTTPGW may signal a permission problem on destination. Check ascp logs on httpgw.' }
236
236
  raise e
237
237
  end
238
238
  session_sent_bytes += slice_bin_data.length
@@ -250,7 +250,7 @@ module Aspera
250
250
  ws_send(ws_type: :close, data: nil)
251
251
  Log.log.debug("Finished upload, waiting for end of #{THR_RECV} thread.")
252
252
  @ws_read_thread.join
253
- Log.log.debug{'Read thread joined'}
253
+ Log.log.debug { 'Read thread joined' }
254
254
  ensure
255
255
  # session no more used
256
256
  @ws_io = nil
@@ -284,14 +284,14 @@ module Aspera
284
284
  File.basename(default_file_name)
285
285
  end
286
286
  file_path = File.join(transfer_spec['destination_root'], file_name)
287
- call(operation: 'GET', subpath: "download/#{transfer_uuid}", save_to_file: file_path)
287
+ call(operation: 'GET', subpath: "download/#{transfer_uuid}", save_to: file_path)
288
288
  end
289
289
 
290
290
  def info
291
291
  return @api_info
292
292
  end
293
293
 
294
- # @return the base url of the gateway
294
+ # @return [String] the base url of the gateway
295
295
  def base_url
296
296
  return @gw_root_url
297
297
  end
@@ -308,7 +308,7 @@ module Aspera
308
308
  Log.dump(:gw_url, url)
309
309
  # add scheme if missing
310
310
  url = "https://#{url}" unless url.match?(%r{^[a-z]{1,6}://})
311
- raise Error, 'GW URL shall be with scheme https' unless url.start_with?('https://')
311
+ Aspera.assert(url.start_with?('https://'), type: Error) { 'GW URL shall be with scheme https' }
312
312
  # remove trailing slash and version (o=only once) if present
313
313
  # TODO: issue warning ?
314
314
  url = url.chomp('/').gsub(%r{/#{API_V1}$}o, '')
@@ -326,8 +326,8 @@ module Aspera
326
326
  # web socket endpoint: by default use v2 (newer gateways), without base64 encoding
327
327
  # is the latest supported? else revert to old api
328
328
  if !@upload_version.eql?(API_V1)
329
- if !@api_info['endpoints'].any?{ |i| i.include?(@upload_version)}
330
- Log.log.warn{"API version #{@upload_version} not supported, reverting to #{API_V1}"}
329
+ if !@api_info['endpoints'].any? { |i| i.include?(@upload_version) }
330
+ Log.log.warn { "API version #{@upload_version} not supported, reverting to #{API_V1}" }
331
331
  @upload_version = API_V1
332
332
  end
333
333
  end
@@ -386,7 +386,7 @@ module Aspera
386
386
  size: file_size
387
387
  })
388
388
  else
389
- Log.log.warn{"Ignoring non file/directory: #{entry_path}"}
389
+ Log.log.warn { "Ignoring non file/directory: #{entry_path}" }
390
390
  end
391
391
  end
392
392
  end
@@ -394,7 +394,7 @@ module Aspera
394
394
  raise "File not found: #{source_path}"
395
395
  end
396
396
  end
397
- transfer_spec['paths'] = files_to_send.map{ |i| {'source' => i[:name]}}
397
+ transfer_spec['paths'] = files_to_send.map { |i| {'source' => i[:name]} }
398
398
  files_to_send.push(total_bytes_to_transfer)
399
399
  return files_to_send
400
400
  end