Class: Aspera::Api::Node

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

Overview

Provides additional functions using node API 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_TOTAL_COUNT =
'X-Total-Count'
HEADER_X_NEXT_ITER_TOKEN =
'X-Aspera-Next-Iteration-Token'
HEADER_ACCEPT_VERSION =
'Accept-Version'
PATH_SEPARATOR =

/ in cloud

'/'
OPTIONS =
@api_options.keys.freeze

Constants inherited from Rest

Rest::MAX_ITEMS, Rest::MAX_PAGES

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, #lookup_by_name, #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 (Hash, NilClass) (defaults to: nil)

    App information, typically AoC

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

    Additional transfer spec

  • base_url (String)

    Rest parameters

  • auth (String, NilClass)

    Rest parameters

  • headers (String, NilClass)

    Rest parameters



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# 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
  if !@app_info.nil?
    REQUIRED_APP_INFO_FIELDS.each do |field|
      Aspera.assert(@app_info.key?(field)){"app_info lacks field #{field}"}
    end
    REQUIRED_APP_API_METHODS.each do |method|
      Aspera.assert(@app_info[:api].respond_to?(method)){"#{@app_info[:api].class} lacks method #{method}"}
    end
  end
end

Class Attribute Details

.api_optionsObject

Returns the value of attribute api_options.



66
67
68
# File 'lib/aspera/api/node.rb', line 66

def api_options
  @api_options
end

.use_dynamic_keyObject

Returns the value of attribute use_dynamic_key.



67
68
69
# File 'lib/aspera/api/node.rb', line 67

def use_dynamic_key
  @use_dynamic_key
end

Instance Attribute Details

#app_infoObject (readonly)

Returns the value of attribute app_info.



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



83
84
85
86
# File 'lib/aspera/api/node.rb', line 83

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

.add_private_key(h) ⇒ Object

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

Parameters:

  • h (Hash)

    Hash to add private key to



111
112
113
114
# File 'lib/aspera/api/node.rb', line 111

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

.add_public_key(h) ⇒ Object

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

Parameters:

  • h (Hash)

    Hash to add public key to



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/aspera/api/node.rb', line 97

def add_public_key(h)
  if @dynamic_key
    ssh_key = Net::SSH::Buffer.from(:key, @dynamic_key)
    # Get pub key in OpenSSH public key format (authorized_keys)
    h['public_keys'] = [
      ssh_key.read_string,
      Base64.strict_encode64(ssh_key.to_s)
    ].join(' ')
  end
  return h
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



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

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?)
  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
183
# 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'))
  Aspera.assert_type(payload['user_id'], String)
  Aspera.assert(!payload['user_id'].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



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

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

Returns:



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

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][Scope::NODE_PREFIX.length..-1], scope: items[1]}
end

.file_matcher(match_expression) ⇒ Object

For access keys: provide expression to match entry in folder

Parameters:

  • match_expression

    one of supported types

Returns:

  • lambda function



119
120
121
122
123
124
125
126
127
128
# File 'lib/aspera/api/node.rb', line 119

def file_matcher(match_expression)
  case match_expression
  when Proc then return match_expression
  when Regexp then return ->(f){f['name'].match?(match_expression)}
  when String
    return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
  when NilClass then return ->(_){true}
  else Aspera.error_unexpected_value(match_expression.class.name, type: ParameterError)
  end
end

.file_matcher_from_argument(options) ⇒ Proc

Returns lambda from provided CLI options.

Returns:

  • (Proc)

    lambda from provided CLI options



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

def file_matcher_from_argument(options)
  return file_matcher(options.get_next_argument('filter', validation: MATCH_TYPES, mandatory: false))
end

.split_folder(path) ⇒ Array

Split path into folder + filename

Returns:

  • (Array)

    containing folder + inside folder/file



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

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:



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

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



232
233
234
235
# File 'lib/aspera/api/node.rb', line 232

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



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

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



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/aspera/api/node.rb', line 254

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



426
427
428
429
430
431
# File 'lib/aspera/api/node.rb', line 426

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



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

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) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/aspera/api/node.rb', line 238

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



318
319
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
# File 'lib/aspera/api/node.rb', line 318

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) ⇒ Object

Read folder content, with pagination management for gen4, not recursive if ‘Accept-Version: 4.0` is not specified:

if `page` and `per_page` are not specified, then all entries are returned.
if either `page` or `per_page` is specified, then both are required, else 400

if ‘Accept-Version: 4.0` is specified:

those queries are not available: page (not mentioned), sort, min_size, max_size, min_modified_time, max_modified_time, target_id, target_node_id, files_prefetch_count, page, name_iglob : either ignored or result in API error 400.
query include is accepted, but seems to do nothing as access_levels and recursive_counts are already included in results.
query `iteration_token` is accepted and allows to get paginated results, with `X-Aspera-Next-Iteration-Token` header in response to get next page token. `X-Aspera-Total-Count` header gives total count of entries.


274
275
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
# File 'lib/aspera/api/node.rb', line 274

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}: #{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

#refreshed_transfer_tokenObject

Generate a refreshed auth token



441
442
443
# File 'lib/aspera/api/node.rb', line 441

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

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

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

  • return (Hash)

    a customizable set of options

Returns:

  • (Hash)

    Result data

Raises:



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 {api: self, file_id: 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)
  raise ParameterError, "Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}" if resolve_state[:result].nil?
  Log.log.debug{"resolve_api_fid: #{path} -> #{resolve_state[:result][:api].base_url} #{resolve_state[:result][:file_id]}"}
  return resolve_state[:result]
end

#resolve_api_fid_paths(top_file_id, paths) ⇒ Array

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)

    size=2: apfid, paths (Array(Hash))



383
384
385
386
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
# File 'lib/aspera/api/node.rb', line 383

def resolve_api_fid_paths(top_file_id, paths)
  Aspera.assert_type(paths, Array)
  Aspera.assert(paths.size.positive?)
  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[: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



464
465
466
467
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
# File 'lib/aspera/api/node.rb', line 464

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) unless @app_info.nil?
  # 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
    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?
    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']&.each_with_object({}){ |i, h| h[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



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

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