Class: Aspera::Api::Node

Inherits:
Rest
  • Object
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_ACCEPT_VERSION =
'Accept-Version'
PATH_SEPARATOR =

/ in cloud

'/'
HEADER_X_TOTAL_COUNT =
'X-Total-Count'
OPTIONS =
@api_options.keys.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from Rest

#auth_params, #base_url, #headers

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Rest

basic_authorization, build_uri, #call, #cancel, #create, #delete, h_to_query_array, io_http_session, #oauth, #params, parse_header, php_style, query_to_h, #read, remote_certificate_chain, start_http_session, #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



206
207
208
209
210
211
212
213
214
# File 'lib/aspera/api/node.rb', line 206

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_optionsObject

Returns the value of attribute api_options.



76
77
78
# File 'lib/aspera/api/node.rb', line 76

def api_options
  @api_options
end

.use_dynamic_keyObject

Returns the value of attribute use_dynamic_key.



77
78
79
# File 'lib/aspera/api/node.rb', line 77

def use_dynamic_key
  @use_dynamic_key
end

Instance Attribute Details

#app_infoApi::AoC::AppInfo? (readonly)

Returns set for AoC.

Returns:



199
200
201
# File 'lib/aspera/api/node.rb', line 199

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



93
94
95
96
# File 'lib/aspera/api/node.rb', line 93

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



122
123
124
125
# File 'lib/aspera/api/node.rb', line 122

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



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/aspera/api/node.rb', line 108

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



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/aspera/api/node.rb', line 185

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.decode_scope(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



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/aspera/api/node.rb', line 156

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['_scope'] || Scope::USER
  payload.delete('_scope')
  payload['scope'] ||= token_scope(access_key, 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



180
181
182
# File 'lib/aspera/api/node.rb', line 180

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

.decode_scope(scope) ⇒ Hash

Decode node scope into access key and scope

Parameters:

  • scope (String)

    scope string to decode

Returns:

  • (Hash)

    decoded scope with access key and scope parts



145
146
147
148
149
150
# File 'lib/aspera/api/node.rb', line 145

def decode_scope(scope)
  items = scope.split(Scope::SEPARATOR, 2)
  Aspera.assert(items.length.eql?(2)) { "invalid scope: #{scope}" }
  Aspera.assert(items[0].start_with?(Scope::NODE_PREFIX)) { "invalid scope: #{scope}" }
  return {access_key: items[0].delete_prefix(Scope::NODE_PREFIX), scope: items[1]}
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



130
131
132
133
134
# File 'lib/aspera/api/node.rb', line 130

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

.token_scope(access_key, scope) ⇒ String

Node API scopes

Returns:



138
139
140
# File 'lib/aspera/api/node.rb', line 138

def token_scope(access_key, scope)
  return [Scope::NODE_PREFIX, access_key, Scope::SEPARATOR, scope].join('')
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



219
220
221
222
# File 'lib/aspera/api/node.rb', line 219

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

#base_specHash

Get a base download transfer spec (gen3)

Returns:

  • (Hash)

    Base transfer spec



451
452
453
454
455
456
# File 'lib/aspera/api/node.rb', line 451

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



244
245
246
247
248
249
250
251
252
253
254
# File 'lib/aspera/api/node.rb', line 244

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



430
431
432
433
434
435
# File 'lib/aspera/api/node.rb', line 430

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



438
439
440
441
442
# File 'lib/aspera/api/node.rb', line 438

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



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/aspera/api/node.rb', line 225

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 RestCallError => 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 = /)



320
321
322
323
324
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
# File 'lib/aspera/api/node.rb', line 320

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)



276
277
278
279
280
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
# File 'lib/aspera/api/node.rb', line 276

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
      RestParameters.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
    RestParameters.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.read

Returns:

  • (Array<Hash>)

    List of folder entries (files, folders, links)



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# File 'lib/aspera/api/node.rb', line 588

def read_with_pages(subpath, query = nil, **kwargs)
  items = []
  query ||= {}
  query['per_page'] ||= 500
  query['page'] ||= 1
  suffix = nil
  loop do
    RestParameters.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
  RestParameters.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.call

Returns:

  • (Array)

    list of items returned by the API call



532
533
534
535
536
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
# File 'lib/aspera/api/node.rb', line 532

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(RestList::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
    RestParameters.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
  RestParameters.instance.spinner_cb.call(action: :success)
  # save iteration token if needed
  iteration[0] = query[:iteration_token] unless iteration.nil?
  item_list
end

#refreshed_transfer_tokenObject

Generate a refreshed auth token



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

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:



366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/aspera/api/node.rb', line 366

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)


387
388
389
390
391
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
# File 'lib/aspera/api/node.rb', line 387

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



468
469
470
471
472
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
# File 'lib/aspera/api/node.rb', line 468

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_paramsHash

Get generic part of transfer spec with transport parameters only

Returns:

  • (Hash)

    Base transfer spec



460
461
462
# File 'lib/aspera/api/node.rb', line 460

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