aspera-cli 4.24.1 → 4.25.0.pre

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 (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1064 -745
  4. data/CONTRIBUTING.md +43 -100
  5. data/README.md +1281 -720
  6. data/bin/ascli +20 -1
  7. data/bin/asession +23 -27
  8. data/lib/aspera/agent/base.rb +10 -21
  9. data/lib/aspera/agent/connect.rb +2 -3
  10. data/lib/aspera/agent/desktop.rb +2 -2
  11. data/lib/aspera/agent/direct.rb +49 -32
  12. data/lib/aspera/agent/factory.rb +31 -0
  13. data/lib/aspera/api/aoc.rb +134 -76
  14. data/lib/aspera/api/cos_node.rb +3 -2
  15. data/lib/aspera/api/faspex.rb +213 -0
  16. data/lib/aspera/api/node.rb +107 -94
  17. data/lib/aspera/ascmd.rb +1 -2
  18. data/lib/aspera/ascp/installation.rb +73 -58
  19. data/lib/aspera/ascp/management.rb +119 -23
  20. data/lib/aspera/assert.rb +39 -11
  21. data/lib/aspera/cli/error.rb +4 -2
  22. data/lib/aspera/cli/extended_value.rb +91 -67
  23. data/lib/aspera/cli/formatter.rb +62 -27
  24. data/lib/aspera/cli/hints.rb +8 -0
  25. data/lib/aspera/cli/info.rb +4 -4
  26. data/lib/aspera/cli/main.rb +76 -84
  27. data/lib/aspera/cli/manager.rb +352 -248
  28. data/lib/aspera/cli/plugins/alee.rb +5 -4
  29. data/lib/aspera/cli/plugins/aoc.rb +175 -195
  30. data/lib/aspera/cli/plugins/ats.rb +4 -4
  31. data/lib/aspera/cli/plugins/base.rb +343 -0
  32. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  33. data/lib/aspera/cli/plugins/config.rb +283 -269
  34. data/lib/aspera/cli/plugins/console.rb +27 -22
  35. data/lib/aspera/cli/plugins/cos.rb +3 -3
  36. data/lib/aspera/cli/plugins/factory.rb +78 -0
  37. data/lib/aspera/cli/plugins/faspex.rb +49 -46
  38. data/lib/aspera/cli/plugins/faspex5.rb +113 -225
  39. data/lib/aspera/cli/plugins/faspio.rb +19 -18
  40. data/lib/aspera/cli/plugins/httpgw.rb +14 -13
  41. data/lib/aspera/cli/plugins/node.rb +162 -149
  42. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  43. data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
  44. data/lib/aspera/cli/plugins/preview.rb +30 -50
  45. data/lib/aspera/cli/plugins/server.rb +21 -21
  46. data/lib/aspera/cli/plugins/shares.rb +45 -47
  47. data/lib/aspera/cli/sync_actions.rb +50 -39
  48. data/lib/aspera/cli/transfer_agent.rb +35 -49
  49. data/lib/aspera/cli/transfer_progress.rb +6 -6
  50. data/lib/aspera/cli/version.rb +3 -3
  51. data/lib/aspera/cli/wizard.rb +70 -55
  52. data/lib/aspera/colors.rb +6 -0
  53. data/lib/aspera/command_line_builder.rb +59 -61
  54. data/lib/aspera/command_line_converter.rb +2 -1
  55. data/lib/aspera/coverage.rb +2 -2
  56. data/lib/aspera/data_repository.rb +1 -1
  57. data/lib/aspera/environment.rb +51 -41
  58. data/lib/aspera/faspex_gw.rb +7 -5
  59. data/lib/aspera/faspex_postproc.rb +1 -1
  60. data/lib/aspera/keychain/factory.rb +1 -2
  61. data/lib/aspera/keychain/macos_security.rb +1 -1
  62. data/lib/aspera/log.rb +37 -9
  63. data/lib/aspera/markdown.rb +31 -0
  64. data/lib/aspera/nagios.rb +7 -6
  65. data/lib/aspera/oauth/base.rb +25 -28
  66. data/lib/aspera/oauth/factory.rb +9 -9
  67. data/lib/aspera/oauth/url_json.rb +2 -1
  68. data/lib/aspera/oauth/web.rb +2 -2
  69. data/lib/aspera/preview/file_types.rb +23 -37
  70. data/lib/aspera/products/connect.rb +7 -6
  71. data/lib/aspera/products/desktop.rb +1 -4
  72. data/lib/aspera/products/other.rb +9 -1
  73. data/lib/aspera/products/transferd.rb +0 -1
  74. data/lib/aspera/rest.rb +168 -113
  75. data/lib/aspera/rest_error_analyzer.rb +4 -4
  76. data/lib/aspera/ssh.rb +7 -4
  77. data/lib/aspera/ssl.rb +41 -0
  78. data/lib/aspera/sync/args.schema.yaml +46 -3
  79. data/lib/aspera/sync/conf.schema.yaml +307 -123
  80. data/lib/aspera/sync/database.rb +2 -1
  81. data/lib/aspera/sync/operations.rb +135 -79
  82. data/lib/aspera/temp_file_manager.rb +17 -5
  83. data/lib/aspera/transfer/error.rb +16 -7
  84. data/lib/aspera/transfer/parameters.rb +35 -22
  85. data/lib/aspera/transfer/resumer.rb +74 -0
  86. data/lib/aspera/transfer/spec.rb +5 -5
  87. data/lib/aspera/transfer/spec.schema.yaml +170 -59
  88. data/lib/aspera/transfer/spec_doc.rb +49 -43
  89. data/lib/aspera/uri_reader.rb +2 -2
  90. data/lib/aspera/web_auth.rb +6 -6
  91. data/lib/transferd_pb.rb +2 -2
  92. data.tar.gz.sig +0 -0
  93. metadata +26 -11
  94. metadata.gz.sig +0 -0
  95. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  96. data/lib/aspera/cli/plugin.rb +0 -333
  97. data/lib/aspera/cli/plugin_factory.rb +0 -81
  98. data/lib/aspera/resumer.rb +0 -77
  99. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/cli/error'
4
3
  require 'aspera/transfer/spec'
5
4
  require 'aspera/rest'
6
5
  require 'aspera/oauth'
@@ -17,14 +16,18 @@ module Aspera
17
16
  module Api
18
17
  # Provides additional functions using node API with gen4 extensions (access keys)
19
18
  class Node < Aspera::Rest
19
+ # Separator between node.AK and user:all
20
20
  SCOPE_SEPARATOR = ':'
21
21
  SCOPE_NODE_PREFIX = 'node.'
22
+ # Accepted types in `file_matcher`
22
23
  MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
24
+ # Delimiter in decoded node token
23
25
  SIGNATURE_DELIMITER = '==SIGNATURE=='
26
+ # Default validity when generating a bearer token "manually"
24
27
  BEARER_TOKEN_VALIDITY_DEFAULT = 86400
25
- # fields in @app_info
28
+ # Fields in @app_info
26
29
  REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
27
- # methods of @app_info[:api]
30
+ # Methods of @app_info[:api]
28
31
  REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
29
32
  private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
30
33
  :SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
@@ -32,30 +35,40 @@ module Aspera
32
35
 
33
36
  # Node API permissions
34
37
  ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
38
+ # Special HTTP Headers
35
39
  HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
36
40
  HEADER_X_TOTAL_COUNT = 'X-Total-Count'
37
41
  HEADER_X_CACHE_CONTROL = 'X-Aspera-Cache-Control'
38
42
  HEADER_X_NEXT_ITER_TOKEN = 'X-Aspera-Next-Iteration-Token'
43
+ # Node sub-scopes
39
44
  SCOPE_USER = 'user:all'
40
45
  SCOPE_ADMIN = 'admin:all'
41
46
  # / in cloud
42
47
  PATH_SEPARATOR = '/'
43
48
 
44
- # register node special token decoder
49
+ # Register node special token decoder
45
50
  OAuth::Factory.instance.register_decoder(lambda{ |token| Node.decode_bearer_token(token)})
46
51
 
47
- # class instance variable, access with accessors on class
52
+ # Class instance variable, access with accessors on class
48
53
  @use_standard_ports = true
49
54
  @use_node_cache = true
50
55
 
51
56
  class << self
52
- # set to false to read transfer parameters from download_setup
57
+ # Set to false to read transfer parameters from download_setup
53
58
  attr_accessor :use_standard_ports
54
- # set to false to bypass cache in redis
59
+ # Set to false to bypass cache in redis
55
60
  attr_accessor :use_node_cache
56
61
  attr_reader :use_dynamic_key
57
62
 
58
- # set private key to be used
63
+ # Adds cache control header, as globally specified to read request
64
+ # Use like this: read(...,**cache_control)
65
+ def cache_control
66
+ headers = {'Accept' => Rest::MIME_JSON}
67
+ headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless use_node_cache
68
+ {headers: headers}
69
+ end
70
+
71
+ # Set private key to be used
59
72
  # @param pem_content [String] PEM encoded private key
60
73
  def use_dynamic_key=(pem_content)
61
74
  Aspera.assert_type(pem_content, String)
@@ -67,7 +80,7 @@ module Aspera
67
80
  def add_public_key(h)
68
81
  if @dynamic_key
69
82
  ssh_key = Net::SSH::Buffer.from(:key, @dynamic_key)
70
- # get pub key in OpenSSH public key format (authorized_keys)
83
+ # Get pub key in OpenSSH public key format (authorized_keys)
71
84
  h['public_keys'] = [
72
85
  ssh_key.read_string,
73
86
  Base64.strict_encode64(ssh_key.to_s)
@@ -93,14 +106,16 @@ module Aspera
93
106
  when String
94
107
  return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
95
108
  when NilClass then return ->(_){true}
96
- else Aspera.error_unexpected_value(match_expression.class.name, type: Cli::BadArgument)
109
+ else Aspera.error_unexpected_value(match_expression.class.name, type: ParameterError)
97
110
  end
98
111
  end
99
112
 
113
+ # @return [Proc] lambda from provided CLI options
100
114
  def file_matcher_from_argument(options)
101
115
  return file_matcher(options.get_next_argument('filter', validation: MATCH_TYPES, mandatory: false))
102
116
  end
103
117
 
118
+ # Split path into folder + filename
104
119
  # @return [Array] containing folder + inside folder/file
105
120
  def split_folder(path)
106
121
  folder = path.split(PATH_SEPARATOR)
@@ -108,11 +123,14 @@ module Aspera
108
123
  [folder.join(PATH_SEPARATOR), inside]
109
124
  end
110
125
 
111
- # node API scopes
126
+ # Node API scopes
127
+ # @return [String] node scope
112
128
  def token_scope(access_key, scope)
113
129
  return [SCOPE_NODE_PREFIX, access_key, SCOPE_SEPARATOR, scope].join('')
114
130
  end
115
131
 
132
+ # Decode node scope into access key and scope
133
+ # @return [Hash]
116
134
  def decode_scope(scope)
117
135
  items = scope.split(SCOPE_SEPARATOR, 2)
118
136
  Aspera.assert(items.length.eql?(2)){"invalid scope: #{scope}"}
@@ -130,7 +148,7 @@ module Aspera
130
148
  Aspera.assert_type(payload['user_id'], String)
131
149
  Aspera.assert(!payload['user_id'].empty?)
132
150
  Aspera.assert_type(private_key, OpenSSL::PKey::RSA)
133
- # manage convenience parameters
151
+ # Manage convenience parameters
134
152
  expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
135
153
  payload.delete('_validity')
136
154
  scope = payload['_scope'] || SCOPE_USER
@@ -152,8 +170,9 @@ module Aspera
152
170
  return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
153
171
  end
154
172
 
173
+ # @return [Hash] Headers to call node API with access key and auth
155
174
  def bearer_headers(bearer_auth, access_key: nil)
156
- # if username is not provided, use the access key from the token
175
+ # If username is not provided, use the access key from the token
157
176
  if access_key.nil?
158
177
  access_key = Node.decode_scope(Node.decode_bearer_token(OAuth::Factory.bearer_token(bearer_auth))['scope'])[:access_key]
159
178
  Aspera.assert(!access_key.nil?)
@@ -167,17 +186,17 @@ module Aspera
167
186
 
168
187
  attr_reader :app_info
169
188
 
170
- # @param app_info [Hash,NilClass] Special processing for AoC
189
+ # @param app_info [Hash,NilClass] App information, typically AoC
171
190
  # @param add_tspec [Hash,NilClass] Additional transfer spec
172
191
  # @param base_url [String] Rest parameters
173
192
  # @param auth [String,NilClass] Rest parameters
174
193
  # @param headers [String,NilClass] Rest parameters
175
194
  def initialize(app_info: nil, add_tspec: nil, **rest_args)
176
- # init Rest
195
+ # Init Rest
177
196
  super(**rest_args)
178
197
  @dynamic_key = nil
179
198
  @app_info = app_info
180
- # this is added to transfer spec, for instance to add tags (COS)
199
+ # This is added to transfer spec, for instance to add tags (COS)
181
200
  @add_tspec = add_tspec
182
201
  @std_t_spec_cache = nil
183
202
  if !@app_info.nil?
@@ -190,25 +209,15 @@ module Aspera
190
209
  end
191
210
  end
192
211
 
193
- # Call node API, possibly adding cache control header, as globally specified
194
- def read_with_cache(subpath, query = nil)
195
- headers = {'Accept' => Rest::MIME_JSON}
196
- headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
197
- return call(
198
- operation: 'GET',
199
- subpath: subpath,
200
- headers: headers,
201
- query: query
202
- )[:data]
203
- end
204
-
205
- # update transfer spec with special additional tags
212
+ # Update transfer spec with special additional tags
213
+ # @param tspec [Hash] Transfer spec to be modified
214
+ # @return [Hash] initial modified tspec
206
215
  def add_tspec_info(tspec)
207
216
  tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
208
217
  return tspec
209
218
  end
210
219
 
211
- # @returns [Node] a Node or nil
220
+ # @returns [Node] a Node Api object or nil if no App defined
212
221
  def node_id_to_node(node_id)
213
222
  if !@app_info.nil?
214
223
  return self if node_id.eql?(@app_info[:node_info]['id'])
@@ -226,7 +235,7 @@ module Aspera
226
235
  # @param entry [Hash] entry in folder
227
236
  # @return [Boolean] true if target information is available
228
237
  def entry_has_link_information(entry)
229
- # if target information is missing in folder, try to get it on entry
238
+ # If target information is missing in folder, try to get it on entry
230
239
  if entry['target_node_id'].nil? || entry['target_id'].nil?
231
240
  link_entry = read("files/#{entry['id']}")
232
241
  entry['target_node_id'] = link_entry['target_node_id']
@@ -238,27 +247,28 @@ module Aspera
238
247
  end
239
248
 
240
249
  # Recursively browse in a folder (with non-recursive method)
241
- # sub folders are processed if the processing method returns true
242
- # links are processed on the respective node
250
+ # Entries of folders are processed if the processing method returns true
251
+ # Links are processed on the respective node
243
252
  # @param method_sym [Symbol] processing method, arguments: entry, path, state
244
253
  # @param state [Object] state object sent to processing method
245
254
  # @param top_file_id [String] file id to start at (default = access key root file id)
246
255
  # @param top_file_path [String] path of top folder (default = /)
247
- def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
256
+ # @para query [Hash, nil] optional query for `read`
257
+ def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
248
258
  Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
249
259
  Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
250
- # start at top folder
260
+ # Start at top folder
251
261
  folders_to_explore = [{id: top_file_id, path: top_file_path}]
252
262
  Log.dump(:folders_to_explore, folders_to_explore)
253
263
  until folders_to_explore.empty?
254
- # consume first in job list
264
+ # Consume first in job list
255
265
  current_item = folders_to_explore.shift
256
266
  Log.log.debug{"Exploring #{current_item[:path]}".bg_green}
257
- # get folder content
267
+ # Get folder content
258
268
  folder_contents =
259
269
  begin
260
270
  # TODO: use header
261
- read_with_cache("files/#{current_item[:id]}/files")
271
+ read("files/#{current_item[:id]}/files", query, **self.class.cache_control)
262
272
  rescue StandardError => e
263
273
  Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
264
274
  []
@@ -271,9 +281,9 @@ module Aspera
271
281
  end
272
282
  current_path = File.join(current_item[:path], entry['name'])
273
283
  Log.log.debug{"process_folder_tree: checking #{current_path}"}
274
- # call block, continue only if method returns true
284
+ # Call block, continue only if method returns true
275
285
  next unless send(method_sym, entry, current_path, state)
276
- # entry type is file, folder or link
286
+ # Entry type is file, folder or link
277
287
  case entry['type']
278
288
  when 'folder'
279
289
  folders_to_explore.push({id: entry['id'], path: current_path})
@@ -303,9 +313,9 @@ module Aspera
303
313
  process_last_link ||= path.end_with?(PATH_SEPARATOR)
304
314
  path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
305
315
  return {api: self, file_id: top_file_id} if path_elements.empty?
306
- resolve_state = {path: path_elements, result: nil, process_last_link: process_last_link}
316
+ resolve_state = {path: path_elements, consumed: [], result: nil, process_last_link: process_last_link}
307
317
  process_folder_tree(method_sym: :process_api_fid, state: resolve_state, top_file_id: top_file_id)
308
- raise "entry not found: #{resolve_state[:path]}" if resolve_state[:result].nil?
318
+ raise ParameterError, "Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}" if resolve_state[:result].nil?
309
319
  Log.log.debug{"resolve_api_fid: #{path} -> #{resolve_state[:result][:api].base_url} #{resolve_state[:result][:file_id]}"}
310
320
  return resolve_state[:result]
311
321
  end
@@ -338,14 +348,14 @@ module Aspera
338
348
  source_paths =
339
349
  case file_info['type']
340
350
  when 'file'
341
- # if the single source is a file, we need to split into folder path and filename
351
+ # If the single source is a file, we need to split into folder path and filename
342
352
  src_dir_elements = source_folder.split(Api::Node::PATH_SEPARATOR)
343
353
  filename = src_dir_elements.pop
344
354
  apifid = resolve_api_fid(top_file_id, src_dir_elements.join(Api::Node::PATH_SEPARATOR), true)
345
- # filename is the last one, source folder is what remains
355
+ # Filename is the last one, source folder is what remains
346
356
  [{'source' => filename}]
347
357
  when 'link', 'folder'
348
- # single source is 'folder' or 'link'
358
+ # Single source is 'folder' or 'link'
349
359
  # TODO: add this ? , 'destination'=>file_info['name']
350
360
  [{'source' => '.'}]
351
361
  else Aspera.error_unexpected_value(file_info['type']){'source type'}
@@ -354,6 +364,9 @@ module Aspera
354
364
  [apifid, source_paths]
355
365
  end
356
366
 
367
+ # Recursively find files matching lambda
368
+ # @param top_file_id [String] Search root
369
+ # @param test_lambda [Proc] Test function
357
370
  def find_files(top_file_id, test_lambda)
358
371
  Log.log.debug{"find_files: file id=#{top_file_id}"}
359
372
  find_state = {found: [], test_lambda: test_lambda}
@@ -361,36 +374,37 @@ module Aspera
361
374
  return find_state[:found]
362
375
  end
363
376
 
364
- def list_files(top_file_id)
377
+ # Recursively list all files and folders
378
+ def list_files(top_file_id, query: nil)
365
379
  find_state = {found: []}
366
- process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
380
+ process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id, query: query)
367
381
  return find_state[:found]
368
382
  end
369
383
 
384
+ # Generate a refreshed auth token
370
385
  def refreshed_transfer_token
371
386
  return oauth.authorization(refresh: true)
372
387
  end
373
388
 
374
- # @return part of transfer spec with transport parameters only
389
+ # Get a base download transfer spec (gen3)
390
+ # @return [Hash] Base transfer spec
391
+ def base_spec
392
+ create(
393
+ 'files/download_setup',
394
+ {transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
395
+ )['transfer_specs'].first['transfer_spec']
396
+ end
397
+
398
+ # Get generic part of transfer spec with transport parameters only
399
+ # @return [Hash] Base transfer spec
375
400
  def transport_params
376
- if @std_t_spec_cache.nil?
377
- # retrieve values from API (and keep a copy/cache)
378
- full_spec = create(
379
- 'files/download_setup',
380
- {transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
381
- )['transfer_specs'].first['transfer_spec']
382
- # set available fields
383
- @std_t_spec_cache = Transfer::Spec::TRANSPORT_FIELDS.each_with_object({}) do |i, h|
384
- h[i] = full_spec[i] if full_spec.key?(i)
385
- end
386
- end
387
- return @std_t_spec_cache
401
+ @std_t_spec_cache ||= base_spec.slice(*Transfer::Spec::TRANSPORT_FIELDS).freeze
388
402
  end
389
403
 
390
404
  # Create transfer spec for gen4
391
- # @param file_id destination or source folder (id)
392
- # @param direction one of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
393
- # @param ts_merge additional transfer spec to merge
405
+ # @param file_id [String] Destination or source folder (id)
406
+ # @param direction [Symbol] One of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
407
+ # @param ts_merge [Hash,nil] Additional transfer spec to merge
394
408
  def transfer_spec_gen4(file_id, direction, ts_merge = nil)
395
409
  ak_name = nil
396
410
  ak_token = nil
@@ -402,7 +416,7 @@ module Aspera
402
416
  when :oauth2
403
417
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
404
418
  # TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
405
- # get bearer token, possibly use cache
419
+ # Get bearer token, possibly use cache
406
420
  ak_token = oauth.authorization
407
421
  when :none
408
422
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
@@ -417,92 +431,91 @@ module Aspera
417
431
  'node' => {
418
432
  'access_key' => ak_name,
419
433
  'file_id' => file_id
420
- } # node
421
- } # aspera
422
- } # tags
434
+ }
435
+ }
436
+ }
423
437
  }
424
- # add specials tags (cos)
438
+ # Add specials tags (cos)
425
439
  add_tspec_info(transfer_spec)
426
440
  transfer_spec.deep_merge!(ts_merge) unless ts_merge.nil?
427
- # add application specific tags (AoC)
428
- app_info[:api].add_ts_tags(transfer_spec: transfer_spec, app_info: app_info) unless app_info.nil?
429
- # add remote host info
441
+ # Add application specific tags (AoC)
442
+ @app_info[:api].add_ts_tags(transfer_spec: transfer_spec, app_info: @app_info) unless @app_info.nil?
443
+ # Add remote host info
430
444
  if self.class.use_standard_ports
431
- # get default TCP/UDP ports and transfer user
445
+ # Get default TCP/UDP ports and transfer user
432
446
  transfer_spec.merge!(Transfer::Spec::AK_TSPEC_BASE)
433
- # by default: same address as node API
447
+ # By default: same address as node API
434
448
  transfer_spec['remote_host'] = URI.parse(base_url).host
435
449
  # AoC allows specification of other url
436
450
  transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url'] if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
437
451
  info = read('info')
438
- # get the transfer user from info on access key
452
+ # Get the transfer user from info on access key
439
453
  transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
440
- # get settings from name.value array to hash key.value
454
+ # Get settings from name.value array to hash key.value
441
455
  settings = info['settings']&.each_with_object({}){ |i, h| h[i['name']] = i['value']}
442
- # check WSS ports
456
+ # Check WSS ports
443
457
  Transfer::Spec::WSS_FIELDS.each do |i|
444
458
  transfer_spec[i] = settings[i] if settings.key?(i)
445
459
  end if settings.is_a?(Hash)
446
460
  else
447
461
  transfer_spec.merge!(transport_params)
448
462
  end
449
- Log.log.warn{"Expected transfer user: #{Transfer::Spec::ACCESS_KEY_TRANSFER_USER}, but have #{transfer_spec['remote_user']}"} \
450
- unless transfer_spec['remote_user'].eql?(Transfer::Spec::ACCESS_KEY_TRANSFER_USER)
463
+ Aspera.assert_values(transfer_spec['remote_user'], Transfer::Spec::ACCESS_KEY_TRANSFER_USER, type: :warn){'transfer user'}
451
464
  return transfer_spec
452
465
  end
453
466
 
454
467
  private
455
468
 
456
- # method called in loop for each entry for `resolve_api_fid`
469
+ # Method called in loop for each entry for `resolve_api_fid`
470
+ # @return `true` to continue digging, `false` to stop processing: set state[:result] if found
457
471
  def process_api_fid(entry, path, state)
458
- # stop digging here if not in right path
472
+ # Stop digging here if not in right path
459
473
  return false unless entry['name'].eql?(state[:path].first)
460
- # ok it matches, so we remove the match, and continue digging
461
- state[:path].shift
474
+ # Ok it matches, so we remove the match, and continue digging
475
+ state[:consumed].push(state[:path].shift)
462
476
  path_fully_consumed = state[:path].empty?
463
477
  case entry['type']
464
478
  when 'file'
465
- # file must be terminal
479
+ # File must be terminal
466
480
  raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless path_fully_consumed
467
- # it's terminal, we found it
468
- Log.log.debug{"resolve_api_fid: found #{path} -> #{entry['id']}"}
481
+ # It's terminal, we found it
482
+ Log.log.debug{"process_api_fid: found #{path} -> #{entry['id']}"}
469
483
  state[:result] = {api: self, file_id: entry['id']}
470
484
  return false
471
485
  when 'folder'
472
486
  if path_fully_consumed
473
- # we found it
487
+ # We found it
474
488
  state[:result] = {api: self, file_id: entry['id']}
475
489
  return false
476
490
  end
477
491
  when 'link'
478
492
  if path_fully_consumed
479
493
  if state[:process_last_link]
480
- # we found it
494
+ # We found it
481
495
  other_node = nil
482
496
  other_node = node_id_to_node(entry['target_node_id']) if entry_has_link_information(entry)
483
497
  raise Error, 'Cannot resolve link' if other_node.nil?
484
498
  state[:result] = {api: other_node, file_id: entry['target_id']}
485
499
  else
486
- # we found it but we do not process the link
500
+ # We found it but we do not process the link
487
501
  state[:result] = {api: self, file_id: entry['id']}
488
502
  end
489
503
  return false
490
504
  end
491
- else
492
- Log.log.warn{"Unknown element type: #{entry['type']}"}
505
+ else Aspera.error_unexpected_value(entry['type'], type: :warn){'folder entry type'}
493
506
  end
494
- # continue to dig folder
507
+ # Continue to dig folder
495
508
  return true
496
509
  end
497
510
 
498
- # method called in loop for each entry for `find_files`
511
+ # Method called in loop for each entry for `find_files`
499
512
  def process_find_files(entry, path, state)
500
513
  state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
501
- # test all files deeply
514
+ # Test all files deeply
502
515
  return true
503
516
  end
504
517
 
505
- # method called in loop for each entry for `list_files`
518
+ # Method called in loop for each entry for `list_files`
506
519
  def process_list_files(entry, path, state)
507
520
  state[:found].push(entry.merge({'path' => path}))
508
521
  return false
data/lib/aspera/ascmd.rb CHANGED
@@ -94,8 +94,7 @@ module Aspera
94
94
  arguments = [] if arguments.nil?
95
95
  Log.log.debug{"execute_single:#{action_sym}:#{arguments}"}
96
96
  Aspera.assert_type(action_sym, Symbol)
97
- Aspera.assert_type(arguments, Array)
98
- Aspera.assert(arguments.all?(String), 'arguments must be strings')
97
+ Aspera.assert_array_all(arguments, String){'arguments'}
99
98
  remote_cmd = 'ascmd'
100
99
  # lines of commands (String's)
101
100
  command_lines = []