Class: Aspera::Api::Node

Inherits:
Rest::Client show all
Defined in:
lib/aspera/api/node.rb

Overview

Aspera Node API client with gen4 extensions (access keys)

Direct Known Subclasses

CosNode

Defined Under Namespace

Modules: Scope

Constant Summary collapse

ACCESS_LEVELS =

Node API permissions: delete list mkdir preview read rename write

%w[delete list mkdir preview read rename write].freeze
HEADER_X_ASPERA_ACCESS_KEY =

Special HTTP Headers

'X-Aspera-AccessKey'
HEADER_X_CACHE_CONTROL =
'X-Aspera-Cache-Control'
HEADER_X_NEXT_ITER_TOKEN =
'X-Aspera-Next-Iteration-Token'
HEADER_X_TOTAL_COUNT =
'X-Total-Count'
HEADER_ACCEPT_VERSION =
'Accept-Version'
PATH_SEPARATOR =

/ in cloud

'/'
OPTIONS =
@api_options.keys.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from Rest::Client

#auth_params, #base_url, #headers

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Rest::Client

#call, #cancel, #create, #delete, #oauth, #params, #read, #update

Constructor Details

#initialize(app_info: nil, add_tspec: nil, **rest_args) ⇒ Node

Returns a new instance of Node.

Parameters:

  • app_info (Api::AoC::AppInfo, nil) (defaults to: nil) —

    App information, typically AoC

  • add_tspec (Hash, nil) (defaults to: nil) —

    Additional transfer spec

  • base_url (String) —

    Rest parameters

  • auth (String, nil) —

    Rest parameters

  • headers (String, nil) —

    Rest parameters



211
212
213
214
215
216
217
218
219
# File 'lib/aspera/api/node.rb', line 211

def initialize(app_info: nil, add_tspec: nil, **rest_args)
  # Init Rest
  super(**rest_args)
  @dynamic_key = nil
  @app_info = app_info
  # This is added to transfer spec, for instance to add tags (COS)
  @add_tspec = add_tspec
  @std_t_spec_cache = nil
end

Class Attribute Details

.api_options ⇒ Object

Returns the value of attribute api_options.



98
99
100
# File 'lib/aspera/api/node.rb', line 98

def api_options
  @api_options
end

.use_dynamic_key ⇒ Object

Returns the value of attribute use_dynamic_key.



99
100
101
# File 'lib/aspera/api/node.rb', line 99

def use_dynamic_key
  @use_dynamic_key
end

Instance Attribute Details

#app_info ⇒ Api::AoC::AppInfo? (readonly)

Returns set for AoC.

Returns:



204
205
206
# File 'lib/aspera/api/node.rb', line 204

def app_info
  @app_info
end

Class Method Details

.add_cache_control(headers = {}) ⇒ Hash

Adds cache control header for node API /files/:id as globally specified to read request Use like this: read(..., headers: add_cache_control)

Parameters:

  • headers (Hash) (defaults to: {}) —

    optional initial headers to add to

Returns:

  • (Hash) —

    headers with cache control header added if needed



115
116
117
118
# File 'lib/aspera/api/node.rb', line 115

def add_cache_control(headers = {})
  headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless api_options[:cache]
  headers
end

.add_private_key(parameters) ⇒ Object

Adds fields ssh_private_key in provided Hash, if dynamic key is set.

Parameters:

  • parameters (Hash) —

    Hash to add private key to



144
145
146
147
# File 'lib/aspera/api/node.rb', line 144

def add_private_key(parameters)
  parameters['ssh_private_key'] = @dynamic_key.to_pem if @dynamic_key
  return parameters
end

.add_public_key(parameters) ⇒ Hash

Adds fields public_keys in provided Hash, if dynamic key is set.

Parameters:

  • parameters (Hash) —

    Hash to add public key to

Returns:

  • (Hash) —

    Hash with public key added



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/aspera/api/node.rb', line 130

def add_public_key(parameters)
  if @dynamic_key
    ssh_key = Net::SSH::Buffer.from(:key, @dynamic_key)
    # Get pub key in OpenSSH public key format (authorized_keys)
    parameters['public_keys'] = [
      ssh_key.read_string,
      Base64.strict_encode64(ssh_key.to_s)
    ].join(' ')
  end
  return parameters
end

.bearer_headers(bearer_auth, access_key: nil) ⇒ Hash

Returns Headers to call node API with access key and auth.

Returns:

  • (Hash) —

    Headers to call node API with access key and auth



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/aspera/api/node.rb', line 190

def bearer_headers(bearer_auth, access_key: nil)
  # If username is not provided, use the access key from the token
  if access_key.nil?
    access_key = Node::Scope.split(Node.decode_bearer_token(OAuth::Factory.bearer_token(bearer_auth))['scope'])[:access_key]
    Aspera.assert(!access_key.nil?, 'access_key could not be determined from token')
  end
  return {
    HEADER_X_ASPERA_ACCESS_KEY => access_key,
    'Authorization'            => bearer_auth
  }
end

.bearer_token(access_key:, payload:, private_key:) ⇒ Object

Create an Aspera Node bearer token

Parameters:

  • access_key (String) —

    Access key identifier

  • payload (String) —

    JSON payload to be included in the token

  • private_key (OpenSSL::PKey::RSA) —

    Private key to sign the token



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/aspera/api/node.rb', line 162

def bearer_token(access_key:, payload:, private_key:)
  Aspera.assert_type(payload, Hash)
  Aspera.assert(payload.key?('user_id'), 'payload must have user_id')
  Aspera.assert_type(payload['user_id'], String)
  Aspera.assert(!payload['user_id'].empty?, 'user_id must not be empty')
  Aspera.assert_type(private_key, OpenSSL::PKey::RSA)
  # Manage convenience parameters
  expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
  payload.delete('_validity')
  scope = payload.delete('_scope') || Scope::USER
  payload['scope'] ||= Scope.join(access_key: access_key, scope: scope)
  payload['auth_type'] ||= 'access_key'
  payload['expires_at'] ||= (Time.now + expiration_sec).utc.strftime('%FT%TZ')
  payload_json = JSON.generate(payload)
  return Base64.strict_encode64(Zlib::Deflate.deflate([
    payload_json,
    SIGNATURE_DELIMITER,
    Base64.strict_encode64(private_key.sign(OpenSSL::Digest.new('sha512'), payload_json)).scan(/.{1,60}/).join("\n"),
    ''
  ].join("\n")))
end

.decode_bearer_token(token) ⇒ Object

Decode an Aspera Node bearer token



185
186
187
# File 'lib/aspera/api/node.rb', line 185

def decode_bearer_token(token)
  return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
end

.split_folder(path) ⇒ Array(String, String)

Split path into folder + filename

Parameters:

  • path (String) —

    file path to split

Returns:

  • (Array(String, String)) —

    containing folder + inside folder/file



152
153
154
155
156
# File 'lib/aspera/api/node.rb', line 152

def split_folder(path)
  folder = path.split(PATH_SEPARATOR)
  inside = folder.pop
  [folder.join(PATH_SEPARATOR), inside]
end

Instance Method Details

#add_tspec_info(tspec) ⇒ Hash

Update transfer spec with special additional tags

Parameters:

  • tspec (Hash) —

    Transfer spec to be modified

Returns:

  • (Hash) —

    initial modified tspec



224
225
226
227
# File 'lib/aspera/api/node.rb', line 224

def add_tspec_info(tspec)
  tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
  return tspec
end

#base_spec ⇒ Hash

Get a base download transfer spec (gen3)

Returns:

  • (Hash) —

    Base transfer spec



456
457
458
459
460
461
# File 'lib/aspera/api/node.rb', line 456

def base_spec
  create(
    'files/download_setup',
    {transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
  )['transfer_specs'].first['transfer_spec']
end

#entry_has_link_information(entry) ⇒ Boolean

Check if a link entry in folder has target information

Parameters:

  • entry (Hash) —

    entry in folder

Returns:

  • (Boolean) —

    true if target information is available



249
250
251
252
253
254
255
256
257
258
259
# File 'lib/aspera/api/node.rb', line 249

def entry_has_link_information(entry)
  # If target information is missing in folder, try to get it on entry
  if entry['target_node_id'].nil? || entry['target_id'].nil?
    link_entry = read("files/#{entry['id']}")
    entry['target_node_id'] = link_entry['target_node_id']
    entry['target_id'] = link_entry['target_id']
  end
  return true unless entry['target_node_id'].nil? || entry['target_id'].nil?
  Log.log.warn { "Missing target information for link: #{entry['name']}" }
  return false
end

#find_files(top_file_id, test_lambda) ⇒ Object

Recursively find files matching lambda

Parameters:

  • top_file_id (String) —

    Search root

  • test_lambda (Proc) —

    Test function



435
436
437
438
439
440
# File 'lib/aspera/api/node.rb', line 435

def find_files(top_file_id, test_lambda)
  Log.log.debug { "find_files: file id=#{top_file_id}" }
  find_state = {found: [], test_lambda: test_lambda}
  process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
  return find_state[:found]
end

#list_files(top_file_id, query: nil) ⇒ Object

Recursively list all files and folders



443
444
445
446
447
# File 'lib/aspera/api/node.rb', line 443

def list_files(top_file_id, query: nil)
  find_state = {found: []}
  process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id, query: query)
  return find_state[:found]
end

#node_id_to_node(node_id) ⇒ Node?

Returns a Node Api object or nil if no App defined.

Returns:

  • (Node, nil) —

    a Node Api object or nil if no App defined



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/aspera/api/node.rb', line 230

def node_id_to_node(node_id)
  if !@app_info.nil?
    return self if node_id.eql?(@app_info.node_info['id'])
    return @app_info.api.node_api_from(
      node_id: node_id,
      workspace_id: @app_info.workspace_id,
      workspace_name: @app_info.workspace_name
    )
  end
  Log.log.warn { "Cannot resolve link with node id #{node_id}, no resolver" }
  return
rescue Rest::CallError => e
  Log.log.warn { "Cannot resolve link with node id #{node_id}: #{e.message}" }
  return
end

#process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil) ⇒ Object

Recursively browse in a folder (with non-recursive method) Entries of folders are processed if the processing method returns true Links are processed on the respective node

Parameters:

  • method_sym (Symbol) —

    processing method, arguments: entry, path, state

  • state (Object) —

    state object sent to processing method

  • top_file_id (String) —

    file id to start at (default = access key root file id)

  • top_file_path (String) (defaults to: '/') —

    path of top folder (default = /)



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/aspera/api/node.rb', line 325

def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
  Aspera.assert(!top_file_path.nil?, 'top_file_path not set')
  Log.log.debug { "process_folder_tree: node=#{@app_info ? @app_info.node_info['id'] : 'nil'}, file id=#{top_file_id},  path=#{top_file_path}" }
  # Start at top folder
  folders_to_explore = [{id: top_file_id, path: top_file_path}]
  Log.dump(:folders_to_explore, folders_to_explore)
  until folders_to_explore.empty?
    # Consume first in job list
    current_item = folders_to_explore.shift
    Log.log.debug { "Exploring #{current_item[:path]}".bg(:green) }
    # Get folder content
    folder_contents = read_folder_content(current_item[:id], query, exception: false, path: current_item[:path])
    Log.dump(:folder_contents, folder_contents)
    folder_contents.each do |entry|
      if entry.key?('error')
        Log.log.error(entry['error']['user_message']) if entry['error'].is_a?(Hash) && entry['error'].key?('user_message')
        next
      end
      current_path = File.join(current_item[:path], entry['name'])
      Log.log.debug { "process_folder_tree: checking #{current_path}" }
      # Call block, continue only if method returns true
      next unless send(method_sym, entry, current_path, state)
      # Entry type is file, folder or link
      case entry['type']
      when 'folder'
        folders_to_explore.push({id: entry['id'], path: current_path})
      when 'link'
        if entry_has_link_information(entry)
          node_id_to_node(entry['target_node_id'])&.process_folder_tree(
            method_sym:    method_sym,
            state:         state,
            top_file_id:   entry['target_id'],
            top_file_path: current_path
          )
        end
      end
    end
  end
end

#read_folder_content(file_id, query = nil, exception: true, path: nil) ⇒ Array<Hash>

Read folder content with pagination management for gen4 (non-recursive)

Behavior WITHOUT Accept-Version: 4.0:

  • Without page and per_page: all entries are returned
  • With page or per_page: both parameters are required, otherwise returns 400 error

Behavior WITH Accept-Version: 4.0:

  • Unavailable query parameters (ignored or return 400 error): page, sort, min_size, max_size, min_modified_time, max_modified_time, target_id, target_node_id, files_prefetch_count, name_iglob
  • Query parameter include: accepted but has no effect (access_levels and recursive_counts already included)
  • Query parameter iteration_token: enables pagination
    • Response header X-Aspera-Next-Iteration-Token: token for next page
    • Response header X-Aspera-Total-Count: total count of entries

Parameters:

  • file_id (String) —

    The folder file identifier

  • query (Hash, nil) (defaults to: nil) —

    Optional query parameters for the API request

  • exception (Boolean) (defaults to: true) —

    If true, raises exceptions on errors; if false, logs warnings

  • path (String, nil) (defaults to: nil) —

    Optional path for logging purposes

Returns:

  • (Array<Hash>) —

    List of folder entries (files, folders, links)



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/aspera/api/node.rb', line 281

def read_folder_content(file_id, query = nil, exception: true, path: nil)
  folder_items = []
  begin
    query ||= {}
    headers = self.class.add_cache_control
    use_v4 = self.class.api_options[:accept_v4]
    return read("files/#{file_id}/files", query, headers: headers) unless use_v4 || query.key?('page') || query.key?('per_page')
    if use_v4
      headers[HEADER_ACCEPT_VERSION] = '4.0'
      query['per_page'] = 1000 unless query.key?('per_page')
    elsif query.key?('per_page') && !query.key?('page')
      query['page'] = 0
    end
    loop do
      Rest::Parameters.instance.spinner_cb.call(folder_items.count)
      data, http = read("files/#{file_id}/files", query, headers: headers, ret: :both)
      folder_items.concat(data)
      if use_v4
        iteration_token = http[HEADER_X_NEXT_ITER_TOKEN]
        break if iteration_token.nil? || iteration_token.empty?
        query['iteration_token'] = iteration_token
      else
        break if data['item_count'].eql?(0)
        query['offset'] += data['item_count']
      end
    end
  rescue StandardError => e
    raise e if exception
    Log.log.warn { "#{path || file_id}: #{e.class} #{e.message}" }
    Log.log.debug { (['Backtrace:'] + e.backtrace).join("\n") }
  ensure
    Rest::Parameters.instance.spinner_cb.call(folder_items.count, action: :success)
  end
  folder_items
end

#read_with_pages(subpath, query = nil, **kwargs) ⇒ Array<Hash>

Read resource content with pagination (page, per_page)

Parameters:

  • subpath (String) —

    API Path

  • query (Hash, nil) (defaults to: nil) —

    Optional query parameters for the API request

  • kwargs (Hash) —

    Other parameters for Rest::Client#read

Returns:

  • (Array<Hash>) —

    List of folder entries (files, folders, links)



593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/aspera/api/node.rb', line 593

def read_with_pages(subpath, query = nil, **kwargs)
  items = []
  query ||= {}
  query['per_page'] ||= 500
  query['page'] ||= 1
  suffix = nil
  loop do
    Rest::Parameters.instance.spinner_cb.call("#{items.count}#{suffix}")
    data, http = read(subpath, query, **kwargs, ret: :both)
    items.concat(data)
    break if data.length < query['per_page']
    suffix ||= "/#{http[HEADER_X_TOTAL_COUNT]}" if http[HEADER_X_TOTAL_COUNT]
    query['page'] += 1
  end
rescue StandardError => e
  Log.log.warn { "#{e.class} #{e.message}" }
  Log.log.debug { (['Backtrace:'] + e.backtrace).join("\n") }
ensure
  Rest::Parameters.instance.spinner_cb.call(items.count, action: :success)
  return items # rubocop:disable Lint/EnsureReturn
end

#read_with_paging(subpath, query = nil, iteration: nil, **call_args) ⇒ Array

Executes GET call in loop using iteration_token (/ops/transfers)

Parameters:

  • iteration (Array) (defaults to: nil) —

    a single element array with the iteration token or nil

  • call_args (Hash) —

    additional arguments to pass to Rest::Client#call

Returns:

  • (Array) —

    list of items returned by the API call



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/aspera/api/node.rb', line 537

def read_with_paging(subpath, query = nil, iteration: nil, **call_args)
  Aspera.assert_type(iteration, Array, NilClass) { 'iteration' }
  Aspera.assert_type(query, Hash, NilClass) { 'query' }
  Aspera.assert(!call_args.key?(:query), ':query must not be in call_args (use query parameter)')
  query = {} if query.nil?
  query[:iteration_token] = iteration[0] unless iteration.nil? || iteration[0].nil?
  max = query.delete(Rest::List::MAX_ITEMS)
  # Return empty list immediately if max is 0
  return [] if max&.zero?
  item_list = []
  loop do
    data, http = read(subpath, query, **call_args, ret: :both)
    Aspera.assert_type(data, Array) { "Expected data to be an Array, got: #{data.class}" }
    # no data
    break if data.empty?
    item_list.concat(data)
    # Check if we reached the max limit
    if max&.<=(item_list.length)
      item_list = item_list.slice(0, max)
      break
    end
    # Update progress spinner
    Rest::Parameters.instance.spinner_cb.call(item_list.length)
    # Parse Link header according to RFC 8288 to extract next iteration token
    next_url = LinkHeader.parse(http['Link']).find_href(rel: 'next')
    next_iteration_token = nil
    if next_url
      begin
        parsed_uri = URI.parse(next_url)
        query_params = Rest.query_to_h(parsed_uri.query) if parsed_uri.query
        next_iteration_token = query_params['iteration_token'] if query_params
      rescue URI::InvalidURIError => e
        Log.log.warn { "Invalid URI in Link header: #{next_url} - #{e.message}" }
      end
    end
    # Stop if no next token
    break if next_iteration_token.nil?
    # Stop if same token as current (infinite loop protection)
    break if next_iteration_token.eql?(query[:iteration_token])
    # Update token for next iteration
    query[:iteration_token] = next_iteration_token
  end
  # Signal completion
  Rest::Parameters.instance.spinner_cb.call(action: :success)
  # save iteration token if needed
  iteration[0] = query[:iteration_token] unless iteration.nil?
  item_list
end

#refreshed_transfer_token ⇒ Object

Generate a refreshed auth token



450
451
452
# File 'lib/aspera/api/node.rb', line 450

def refreshed_transfer_token
  return oauth.authorization(refresh: true)
end

#resolve_api_fid(top_file_id, path, process_last_link = false) ⇒ NodeFileId

Navigate the path from given file id on current node, and return the node and file id of target. If the path ends with a "/" or process_last_link is true then if the last item in path is a link, it is followed.

Parameters:

  • top_file_id (String) —

    id initial file id

  • path (String) —

    file or folder path (end with "/" is like setting process_last_link)

  • process_last_link (Boolean) (defaults to: false) —

    if true, follow the last link

Returns:



371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/aspera/api/node.rb', line 371

def resolve_api_fid(top_file_id, path, process_last_link = false)
  Aspera.assert_type(top_file_id, String)
  Aspera.assert_type(path, String)
  process_last_link ||= path.end_with?(PATH_SEPARATOR)
  path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
  return NodeFileId.new(self, top_file_id) if path_elements.empty?
  resolve_state = {path: path_elements, consumed: [], result: nil, process_last_link: process_last_link}
  process_folder_tree(method_sym: :process_api_fid, state: resolve_state, top_file_id: top_file_id)
  Aspera.assert(!resolve_state[:result].nil?, type: ParameterError) { "Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}" }
  Log.log.debug { "resolve_api_fid: #{path} -> #{resolve_state[:result].node_api.base_url} #{resolve_state[:result].file_id}" }
  return resolve_state[:result]
end

#resolve_api_fid_paths(top_file_id, paths) ⇒ Array<(NodeFileId, Array<Hash>)>

Given a list of paths, finds a common root and list of sub-paths

Parameters:

  • top_file_id (String) —

    Root file id

  • paths (Array(Hash)) —

    List of paths

Returns:

  • (Array<(NodeFileId, Array<Hash>)>) —

    Tuple containing the file identifier and paths

    • [0] NodeFileId: Reference to the file on the node
    • [1] Array: Transfer paths, each Hash having:
      • 'source' [String]: Source path
      • 'destination' [String]: Destination path (optional)


392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/aspera/api/node.rb', line 392

def resolve_api_fid_paths(top_file_id, paths)
  Aspera.assert_type(paths, Array)
  Aspera.assert(paths.size.positive?, 'paths must not be empty')
  split_sources = paths.map { |p| Pathname(p['source']).each_filename.to_a }
  root = []
  split_sources.map(&:size).min.times do |i|
    parts = split_sources.map { |s| s[i] }
    break unless parts.uniq.size == 1
    root << parts.first
  end
  source_folder = File.join(root)
  source_paths = paths.each_with_index.map do |p, i|
    m = {'source' => File.join(split_sources[i][root.size..])}
    m['destination'] = p['destination'] if p.key?('destination')
    m
  end
  apifid = resolve_api_fid(top_file_id, source_folder, true)
  # If a single item
  if source_paths.size.eql?(1)
    # Get precise info in this element
    file_info = apifid.node_api.read("files/#{apifid.file_id}")
    source_paths =
      case file_info['type']
      when 'file'
        # If the single source is a file, we need to split into folder path and filename
        src_dir_elements = source_folder.split(Api::Node::PATH_SEPARATOR)
        filename = src_dir_elements.pop
        apifid = resolve_api_fid(top_file_id, src_dir_elements.join(Api::Node::PATH_SEPARATOR), true)
        # Filename is the last one, source folder is what remains
        [{'source' => filename}]
      when 'link', 'folder'
        # Single source is 'folder' or 'link'
        # TODO: add this ? , 'destination'=>file_info['name']
        [{'source' => '.'}]
      else Aspera.error_unexpected_value(file_info['type']) { 'source type' }
      end
  end
  [apifid, source_paths]
end

#transfer_spec_gen4(file_id, direction, ts_merge = nil) ⇒ Object

Create transfer spec for gen4

Parameters:

  • file_id (String) —

    Destination or source folder (id)

  • direction (Symbol) —

    One of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE

  • ts_merge (Hash, nil) (defaults to: nil) —

    Additional transfer spec to merge



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/aspera/api/node.rb', line 473

def transfer_spec_gen4(file_id, direction, ts_merge = nil)
  ak_name = nil
  ak_token = nil
  case auth_params[:type]
  when :basic
    ak_name = auth_params[:username]
    Aspera.assert(auth_params[:password], 'no secret in node object')
    ak_token = Rest.basic_authorization(auth_params[:username], auth_params[:password])
  when :oauth2
    ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
    # TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
    # Get bearer token, possibly use cache
    ak_token = oauth.authorization
  when :none
    ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
    ak_token = params[:headers]['Authorization']
  else Aspera.error_unexpected_value(auth_params[:type])
  end
  transfer_spec = {
    'direction' => direction,
    'token'     => ak_token,
    'tags'      => {
      Transfer::Spec::TAG_RESERVED => {
        'node' => {
          'access_key' => ak_name,
          'file_id'    => file_id
        }
      }
    }
  }
  # Add specials tags (cos)
  add_tspec_info(transfer_spec)
  transfer_spec.deep_merge!(ts_merge) unless ts_merge.nil?
  # Add application specific tags (AoC)
  @app_info&.api&.add_ts_tags(transfer_spec: transfer_spec, app_info: @app_info)
  # Add remote host info
  if self.class.api_options[:standard_ports]
    # Get default TCP/UDP ports and transfer user
    transfer_spec.merge!(Transfer::Spec::AK_TSPEC_BASE)
    # By default: same address as node API
    transfer_spec['remote_host'] = URI.parse(base_url).host
    # AoC allows specification of other url (in UI: `Transfer endpoint (optional)`)
    transfer_url = @app_info&.node_info&.[]('transfer_url').to_s
    transfer_spec['remote_host'] = transfer_url unless transfer_url.empty?
    info = read('info')
    # Get the transfer user from info on access key
    transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
    # Get settings from name.value array to hash key.value
    settings = info['settings']&.to_h { |i| [i['name'], i['value']] }
    # Check WSS ports
    Transfer::Spec::WSS_FIELDS.each do |i|
      transfer_spec[i] = settings[i] if settings.key?(i)
    end if settings.is_a?(Hash)
  else
    transfer_spec.merge!(transport_params)
  end
  Aspera.assert_values(transfer_spec['remote_user'], [Transfer::Spec::ACCESS_KEY_TRANSFER_USER], type: :warn) { 'transfer user' }
  return transfer_spec
end

#transport_params ⇒ Hash

Get generic part of transfer spec with transport parameters only

Returns:

  • (Hash) —

    Base transfer spec



465
466
467
# File 'lib/aspera/api/node.rb', line 465

def transport_params
  @std_t_spec_cache ||= base_spec.slice(*Transfer::Spec::TRANSPORT_FIELDS).freeze
end