Class: Aspera::Cli::Plugins::Faspex5

Inherits:
Oauth show all
Defined in:
lib/aspera/cli/plugins/faspex5.rb

Constant Summary collapse

QUERY_SCHEMA_COMMANDS =

Commands that may carry a query_schema annotation

%i[list delete].freeze
WORKGROUP_TYPES =
%w{workgroup shared_inbox}.freeze

Constants inherited from Oauth

Oauth::AUTH_OPTIONS, Oauth::AUTH_TYPES

Constants inherited from Base

Base::FILTER_ARGS

Instance Attribute Summary

Attributes inherited from Base

#context, #help_path

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Oauth

kwargs_from_options

Methods inherited from BasicAuth

#basic_auth_api, #basic_auth_params

Methods inherited from Base

#action_for, #add_manual_header, application_name, #bulk_result, command, command_registry, commands_under, #config, crud_commands, declare_options, define_action_method, #dispatch_child, #dispatch_from_registry, #dispatch_leaf, #entity_create, #entity_delete, entity_display_name, #entity_list, #entity_modify, #entity_res_path, #entity_show, #execute_action, #execute_leaf, file_matcher, #formatter, #generate_help, #http_config, #invoke_action, option, #options, #persistency, #presets, #progress_bar, #query_read_delete, #resolve_argument, root_setup, #transfer, use_options, used_option_sources

Constructor Details

#initialize(**_) ⇒ Faspex5

Returns a new instance of Faspex5.



99
100
101
102
103
104
# File 'lib/aspera/cli/plugins/faspex5.rb', line 99

def initialize(**_)
  super
  options.parse_options!
  # [Aspera::Api::Faspex]
  @api_v5 = nil
end

Class Method Details

.detect(address_or_url) ⇒ Hash, NilClass

Returns:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/aspera/cli/plugins/faspex5.rb', line 29

def detect(address_or_url)
  # add scheme if missing
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
  urls = [address_or_url]
  urls.push("#{address_or_url}#{Api::Faspex::PATH_STANDARD_ROOT}") unless address_or_url.end_with?(Api::Faspex::PATH_STANDARD_ROOT)
  error = nil
  urls.each do |base_url|
    # Faspex is always HTTPS
    next unless base_url.start_with?('https://')
    api = Rest.new(base_url: base_url, redirect_max: 1)
    response = api.read(Api::Faspex::PATH_API_DETECT, ret: :resp)
    next unless response.code.start_with?('2') && response.body.strip.empty?
    # end is at -1, and subtract 1 for "/"
    url_length = -2 - Api::Faspex::PATH_API_DETECT.length
    # take redirect if any
    return {
      version: response[Api::Faspex::HEADER_FASPEX_VERSION] || '5',
      url:     response.uri.to_s[0..url_length]
    }
  rescue StandardError => e
    error = e
    Log.log.debug { "detect error: #{e}" }
  end
  raise error if error
  return
end

Instance Method Details

#action_admin_accounts_reset_password(contact_id:) ⇒ Object

admin > accounts > reset_password



756
757
758
759
# File 'lib/aspera/cli/plugins/faspex5.rb', line 756

def action_admin_accounts_reset_password(contact_id:, **)
  @api_v5.create("accounts/#{contact_id}/reset_password", {})
  Result::Status.new('password reset, user shall check email')
end

#action_admin_file_processing_nextObject

admin > file_processing > next



762
763
764
765
766
# File 'lib/aspera/cli/plugins/faspex5.rb', line 762

def action_admin_file_processing_next
  args = res_exec_args(:file_processing)
  result, count = @api_v5.list_entities_limit_offset_total_count(entity: args[:entity], operation: 'POST', items_key: 'files')
  Result::ObjectList.new(result, total: count)
end

#action_admin_nodes_browse(folder_path:, node_id:) ⇒ Object

admin > nodes > browse



769
770
771
# File 'lib/aspera/cli/plugins/faspex5.rb', line 769

def action_admin_nodes_browse(folder_path:, node_id:, **)
  browse_folder("nodes/#{node_id}/browse", {}, folder_path: folder_path)
end

#action_admin_smtp_test(test_data:) ⇒ Object



743
744
745
746
747
748
749
750
751
752
753
# File 'lib/aspera/cli/plugins/faspex5.rb', line 743

def action_admin_smtp_test(test_data:, **)
  test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
  creation = @api_v5.create('configuration/smtp/test', test_data)
  result = wait_for_job(creation['job_id'])
  begin
    result['serialized_args'] = JSON.parse(result['serialized_args'])
  rescue JSON::ParserError
    # keep as string if not valid JSON
  end
  Result::SingleObject.new(result)
end

#action_gateway(parameters: {}) ⇒ Object



956
957
958
959
960
961
962
963
964
965
# File 'lib/aspera/cli/plugins/faspex5.rb', line 956

def action_gateway(parameters: {}, **)
  require 'aspera/faspex_gw'
  parameters = parameters.symbolize_keys
  uri = URI.parse(parameters.delete(:url) { WebServerSimple::DEFAULT_URL })
  server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
  Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?) { "unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}" }
  server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
  server.start
  Result::Status.new('Gateway terminated')
end

#action_healthObject

--- handlers ---



910
911
912
913
914
915
916
917
918
919
920
921
922
923
# File 'lib/aspera/cli/plugins/faspex5.rb', line 910

def action_health
  nagios = Nagios.new
  begin
    data, http = Rest.new(base_url: options.get_option(:url, mandatory: true))
      .read('health', ret: :both)
    data.each do |k, v|
      nagios.add_ok(k, v.to_s)
    end
    nagios.add_ok('version', http['X-IBM-Aspera']) if http['X-IBM-Aspera']
  rescue StandardError => e
    nagios.add_critical('core', e.to_s)
  end
  Result::ObjectList.new(nagios.status_list)
end

#action_invitations_create(input_data:) ⇒ Object



949
950
951
952
953
954
# File 'lib/aspera/cli/plugins/faspex5.rb', line 949

def action_invitations_create(input_data:, **)
  bulk_result(input_data, command: :create) do |params|
    endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
    @api_v5.create(endpoint, params)
  end
end

#action_invitations_resend(invitation_id:) ⇒ Object

invitations sub-handlers



944
945
946
947
# File 'lib/aspera/cli/plugins/faspex5.rb', line 944

def action_invitations_resend(invitation_id:, **)
  @api_v5.create("invitations/#{invitation_id}/resend", nil)
  Result::Status.new('Invitation resent')
end

#action_packages_delete(package_id:) ⇒ Object



894
895
896
897
898
899
900
901
902
903
904
905
906
# File 'lib/aspera/cli/plugins/faspex5.rb', line 894

def action_packages_delete(package_id:, **)
  ids = package_id.is_a?(Array) ? package_id : [package_id]
  Aspera.assert_array_all(ids, String) { 'Package id(s)' }
  # API returns 204, empty on success
  @api_v5.call(
    operation:    'DELETE',
    subpath:      'packages',
    content_type: Mime::JSON,
    body:         {ids: ids},
    headers:      {'Accept' => Mime::JSON}
  )
  Result::Status.new('Package(s) deleted')
end

#action_packages_list(filter: nil) ⇒ Object



885
886
887
888
889
890
891
892
# File 'lib/aspera/cli/plugins/faspex5.rb', line 885

def action_packages_list(filter: nil, **)
  list, max_items, total = list_packages_with_filter(filter: filter)
  list = list[0, max_items] if max_items
  fields = %w[id title status sender.name recipients.0.name release_date total_bytes total_files]
  fields.delete('recipients.0.name') if %w[inbox inbox_history].include?(options.get_option(:box))
  fields.delete('sender.name') if %w[outbox outbox_history].include?(options.get_option(:box))
  Result::ObjectList.new(list, total: total, fields: fields)
end

#action_postprocessing(parameters: {}) ⇒ Object



967
968
969
970
971
972
973
974
975
976
# File 'lib/aspera/cli/plugins/faspex5.rb', line 967

def action_postprocessing(parameters: {}, **)
  require 'aspera/faspex_postproc' # cspell:disable-line
  parameters = parameters.symbolize_keys
  uri = URI.parse(parameters.delete(:url) { WebServerSimple::DEFAULT_URL })
  parameters[:root] = uri.path
  server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
  server.mount(uri.path, Faspex4PostProcServlet, parameters.except(*WebServerSimple::PARAMS))
  server.start
  Result::Status.new('Gateway terminated')
end

#action_shared_folders_browse(folder_path:, shared_folder_id:) ⇒ Object



935
936
937
938
939
940
# File 'lib/aspera/cli/plugins/faspex5.rb', line 935

def action_shared_folders_browse(folder_path:, shared_folder_id:, **)
  all_shared_folders = @api_v5.read('shared_folders')['shared_folders']
  node = all_shared_folders.find { |i| i['id'].eql?(shared_folder_id) }
  Aspera.assert(!node.nil?) { "No such shared folder id #{shared_folder_id}" }
  browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse", {}, folder_path: folder_path)
end

#browse_folder(browse_endpoint, base_query = {}, folder_path: '/') ⇒ Object

Browse a folder

Parameters:

  • browse_endpoint (String)

    the endpoint to browse

  • folder_path (String) (defaults to: '/')

    starting path (default: '/')



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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/aspera/cli/plugins/faspex5.rb', line 337

def browse_folder(browse_endpoint, base_query = {}, folder_path: '/')
  folders_to_process = [folder_path]
  query = base_query.merge(query_read_delete(default: {}))
  filters = query.delete('filters') { {} }
  Aspera.assert_type(filters, Hash)
  filters['basenames'] ||= []
  Aspera.assert_type(filters, Hash) { 'filters' }
  max_items = query.delete(RestList::MAX_ITEMS)
  recursive = query.delete('recursive')
  use_paging = query.delete('paging') { true }
  if use_paging
    browse_endpoint = "#{browse_endpoint}/page"
    query['per_page'] ||= 500
  else
    query['offset'] ||= 0
    query['limit'] ||= 500
  end
  all_items = []
  total_count = nil
  until folders_to_process.empty?
    path = folders_to_process.shift
    loop do
      data, http = @api_v5.call(
        operation:    'POST',
        subpath:      browse_endpoint,
        query:        query,
        content_type: Mime::JSON,
        body:         {'path' => path, 'filters' => filters},
        headers:      {'Accept' => Mime::JSON},
        ret:          :both
      )
      all_items.concat(data['items'])
      if !max_items.nil? && (all_items.count >= max_items)
        all_items = all_items.slice(0, max_items) if all_items.count > max_items
        break
      end
      folders_to_process.concat(data['items'].select { |i| i['type'].eql?('directory') }.map { |i| i['path'] }) if recursive
      if use_paging
        iteration_token = http[Api::Faspex::HEADER_X_NEXT_ITER_TOKEN]
        break if iteration_token.nil? || iteration_token.empty?
        query['iteration_token'] = iteration_token
      else
        total_count = data['total_count'] if total_count.nil?
        break if data['item_count'].eql?(0)
        query['offset'] += data['item_count']
      end
      RestParameters.instance.spinner_cb.call(all_items.count)
    end
    query.delete('iteration_token')
  end
  RestParameters.instance.spinner_cb.call(action: :success)
  return Result::ObjectList.new(all_items, total: total_count)
end

#list_packages_with_filter(filter: nil, query: {}) ⇒ Array(Array, Integer|nil, Integer|nil)

List all packages with optional filter. The special max key is extracted before the API call and returned separately, so callers that apply a post-API filter (e.g. once_only) can enforce the limit after filtering rather than before.

Parameters:

  • filter (Proc, nil) (defaults to: nil)

    optional filter lambda applied to each package entry

  • query (Hash) (defaults to: {})

    additional query parameters forwarded to the API

Returns:

  • (Array(Array, Integer|nil, Integer|nil))

    [filtered list, max (or nil), total count]



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/aspera/cli/plugins/faspex5.rb', line 178

def list_packages_with_filter(filter: nil, query: {})
  filter ||= ->(_x) { true }
  box = options.get_option(:box)
  # Translate box name to API prefix (with ending slash)
  entity =
    case box
    when SpecialValues::ALL then 'packages' # only admin can list all packages globally
    when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
    else
      group_type = options.get_option(:group_type)
      "#{group_type}/#{@api_v5.lookup_entity_by_field(entity: group_type, value: box)['id']}/packages"
    end
  # Merge default query with user-provided query: user values take precedence, but defaults are preserved
  user_query = query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::FASPEX, 'packages'))
  merged_query = user_query.nil? ? query.dup : query.merge(user_query)
  # Extract `max` before the API call so callers can apply it after post-API filtering
  max_items = merged_query.delete(RestList::MAX_ITEMS)&.to_i
  list, total = @api_v5.list_entities_limit_offset_total_count(entity: entity, query: merged_query)
  return list.select(&filter), max_items, total
end

#lookup_node_id(field, value) ⇒ Object

admin > nodes — lookup node id by field/value



463
464
465
# File 'lib/aspera/cli/plugins/faspex5.rb', line 463

def lookup_node_id(field, value, **)
  @api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
end

#lookup_sf_id(field, value, sf_entity:) ⇒ Object

Lookup shared folder id by field/value within a node's shared_folders entity. sf_entity is in ctx from setup_admin_nodes_shared_folders (Phase A of parent).



780
781
782
# File 'lib/aspera/cli/plugins/faspex5.rb', line 780

def lookup_sf_id(field, value, sf_entity:, **)
  @api_v5.lookup_entity_by_field(entity: sf_entity, items_key: 'shared_folders', field: field, value: value)['id']
end

#lookup_sf_user_id(field, value, user_path:) ⇒ Object

Lookup custom access user id by field/value within a shared folder's users entity. user_path is in ctx from setup_admin_nodes_shared_folders_user (Phase A of parent).



786
787
788
# File 'lib/aspera/cli/plugins/faspex5.rb', line 786

def lookup_sf_user_id(field, value, user_path:, **)
  @api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
end

#lookup_shared_folder_id(field, value) ⇒ Object

Lookup a shared folder id by field/value. Called via lookup: :lookup_shared_folder_id on the shared_folders > browse command.



927
928
929
930
931
932
933
# File 'lib/aspera/cli/plugins/faspex5.rb', line 927

def lookup_shared_folder_id(field, value, **)
  all = @api_v5.read('shared_folders')['shared_folders']
  matches = all.select { |i| i[field].eql?(value) }
  Aspera.assert(!matches.empty?) { "no match for #{field} = #{value}" }
  Aspera.assert(matches.length == 1) { "multiple matches for #{field} = #{value}" }
  matches.first['id']
end

#normalize_recipients(parameters, type) ⇒ Object

if recipient is just an email, then convert to expected API hash : name and type



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/aspera/cli/plugins/faspex5.rb', line 107

def normalize_recipients(parameters, type)
  type = type.to_s
  return unless parameters.key?(type)
  Aspera.assert_type(parameters[type], Array) { type }
  recipient_types = Api::Faspex::RECIPIENT_TYPES
  if parameters.key?('recipient_types')
    recipient_types = parameters['recipient_types']
    parameters.delete('recipient_types')
    recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
  end
  parameters[type].map! do |recipient_data|
    # If just a string, make a general lookup and build expected name/type hash
    if recipient_data.is_a?(String)
      matched = @api_v5.lookup_with_q('contacts', value: recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
      recipient_data = {
        name:           matched['name'],
        recipient_type: matched['type']
      }
    end
    # result for mapping
    recipient_data
  end
end

#package_receive(package_ids) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/aspera/cli/plugins/faspex5.rb', line 209

def package_receive(package_ids)
  # prepare persistency if needed
  skip_ids_persistency = nil
  if options.get_option(:once_only, mandatory: true)
    # read ids from persistency
    skip_ids_persistency = PersistencyActionOnce.new(
      manager: persistency,
      data:    [],
      id:      IdGenerator.from_list(
        'faspex_recv',
        options.get_option(:url, mandatory: true),
        options.get_option(:username, mandatory: true),
        options.get_option(:box, mandatory: true)
      )
    )
  end
  packages = []
  case package_ids
  when SpecialValues::INIT
    Aspera.assert(skip_ids_persistency, 'Only with option once_only')
    skip_ids_persistency.data.clear.concat(list_packages_with_filter.first.map { |p| p['id'] }) # no filter: all packages, max ignored
    skip_ids_persistency.save
    return Result::Status.new("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
  when SpecialValues::ALL
    # TODO: if packages have same name, they will overwrite ?
    packages, max_items = list_packages_with_filter(query: {'status' => 'completed'}) # no filter: all completed packages
    Log.dump(:package_ids, level: :trace1) { packages.map { |p| p['id'] } }
    Log.dump(:skip_ids, skip_ids_persistency.data, level: :trace1)
    packages.reject! { |p| skip_ids_persistency.data.include?(p['id']) } if skip_ids_persistency
    # Apply `max` after once_only filtering so we get the N first not-yet-downloaded packages
    packages = packages[0, max_items] if max_items
    Log.dump(:package_ids, level: :trace1) { packages.map { |p| p['id'] } }
  else
    # a single id was provided, or a list of ids
    package_ids = [package_ids] unless package_ids.is_a?(Array)
    Aspera.assert_array_all(package_ids, String) { 'Package id(s)' }
    # packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")}
    packages = package_ids.map { |pkg_id| {'id'=>pkg_id} }
  end
  result_transfer = []
  param_file_list = {}
  begin
    param_file_list['paths'] = transfer.ts_source_paths
  rescue Cli::MissingArgument
    # paths is optional
  end
  box = options.get_option(:box)
  download_params = {
    type:          Api::Faspex.box_type(box),
    transfer_type: Api::Faspex::TRANSFER_CONNECT
  }
  # download_params[:recipient_workgroup_id] = @api_v5.lookup_entity_by_field(entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
  packages.each do |package|
    pkg_id = package['id']
    formatter.display_status("Receiving package #{pkg_id}")
    # TODO: allow from sent as well ?
    transfer_spec = @api_v5.call(
      operation:    'POST',
      subpath:      "packages/#{pkg_id}/transfer_spec/download",
      query:        download_params.merge(recipient_query(pkg_id)),
      content_type: Mime::JSON,
      body:         param_file_list,
      headers:      {'Accept' => Mime::JSON}
    )
    # delete flag for Connect Client
    transfer_spec.delete('authentication')
    statuses = transfer.start(transfer_spec)
    result_transfer.push({'package' => pkg_id, Runner::STATUS_FIELD => statuses})
    # skip only if all sessions completed
    if statuses.is_a?(Transfer::Result::Success) && skip_ids_persistency
      skip_ids_persistency.data.push(pkg_id)
      skip_ids_persistency.save
    end
  end
  return Runner.result_transfer_multiple(result_transfer)
end

#package_send(parameters) ⇒ Object



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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/aspera/cli/plugins/faspex5.rb', line 286

def package_send(parameters)
  # autofill recipient for public url
  if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
    parameters['recipients'] = [{
      name:           @api_v5.pub_link_context['name'],
      recipient_type: @api_v5.pub_link_context['recipient_type']
    }]
  end
  PACKAGE_RECIPIENT_TYPES.each { |type| normalize_recipients(parameters, type) }
  # User specified content prot in tspec, but faspex requires in package creation
  # `transfer_spec/upload` will set `content_protection`
  if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
    transfer.user_transfer_spec.delete('content_protection')
    parameters['ear_enabled'] = true
  end
  package = @api_v5.create('packages', parameters)
  shared_folder = options.get_option(:shared_folder)
  if shared_folder.nil?
    # send from local files
    transfer_spec = @api_v5.create(
      "packages/#{package['id']}/transfer_spec/upload",
      {paths: transfer.source_list},
      query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
    )
    # well, we asked a TS for connect, but we actually want a generic one
    transfer_spec.delete('authentication')
    return Runner.result_transfer(transfer.start(transfer_spec))
  else
    # send from remote shared folder
    if (m = Parser.percent_selector(shared_folder))
      shared_folder = @api_v5.lookup_entity_by_field(
        entity: 'shared_folders',
        field: m[:field],
        value: m[:value]
      )['id']
    end
    transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
    # start remote transfer and get first status
    result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
    result['id'] = package['id']
    unless result['status'].eql?('completed')
      formatter.display_status("Package #{package['id']}")
      result = wait_package_status(package['id'])
    end
    return Result::SingleObject.new(result)
  end
end

#recipient_query(package_id) ⇒ Object

Build query to get package recipients based on package info in case of shared inbox or workgroup recipient

Parameters:

  • package_id (String)

    the package id to get info from



201
202
203
204
205
206
207
# File 'lib/aspera/cli/plugins/faspex5.rb', line 201

def recipient_query(package_id)
  package_info = @api_v5.read("packages/#{package_id}")
  base_query = {}
  base_query['recipient_workgroup_id'] = package_info['recipients'].first['id'] if WORKGROUP_TYPES.include?(package_info['recipients'].first['recipient_type'])
  base_query['recipient_user_id'] = package_info['recipients'].first['id'] if package_info['recipients'].first['recipient_type'].eql?('user')
  base_query
end

#res_exec_args(res_sym) ⇒ Object

Build args hash from RESOURCE_CONFIG for use with entity_list/show/create/modify/delete.



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/aspera/cli/plugins/faspex5.rb', line 446

def res_exec_args(res_sym)
  cfg = RESOURCE_CONFIG.fetch(res_sym, {})
  {
    api:             resource_config_value(cfg, :api) || @api_v5,
    entity:          resource_config_value(cfg, :entity) || res_sym.to_s,
    items_key:       resource_config_value(cfg, :items_key),
    delete_style:    resource_config_value(cfg, :delete_style),
    id_as_arg:       resource_config_value(cfg, :id_as_arg) || false,
    display_fields:  resource_config_value(cfg, :display_fields),
    list_query:      resource_config_value(cfg, :list_query),
    is_singleton:    resource_config_value(cfg, :is_singleton) || false,
    query_component: resource_config_value(cfg, :query_component),
    body_component:  resource_config_value(cfg, :body_component)
  }.compact
end

#res_lookup_id(res_sym, field, value) ⇒ Object

Lookup id for a RESOURCE_CONFIG resource by field/value.



468
469
470
471
472
473
474
# File 'lib/aspera/cli/plugins/faspex5.rb', line 468

def res_lookup_id(res_sym, field, value)
  cfg = RESOURCE_CONFIG.fetch(res_sym, {})
  entity      = resource_config_value(cfg, :entity) || res_sym.to_s
  items_key   = resource_config_value(cfg, :items_key)
  res_id_query = resource_config_value(cfg, :res_id_query) || :default
  @api_v5.lookup_entity_by_field(entity: entity, value: value, field: field, items_key: items_key, query: res_id_query)['id']
end

#resolve_member_user_ids(users) ⇒ Array<String>

Resolve user ids for shared_inbox/workgroup members/create, handling percent selectors.

Parameters:

  • users (Array)

    raw user ids or percent-selector strings

Returns:

  • (Array<String>)

    resolved user ids



809
810
811
812
813
814
815
816
817
818
# File 'lib/aspera/cli/plugins/faspex5.rb', line 809

def resolve_member_user_ids(users)
  users = [users] unless users.is_a?(Array)
  users.map do |user|
    if (m = Parser.percent_selector(user))
      @api_v5.lookup_entity_by_field(entity: 'accounts', field: m[:field], value: m[:value], query: Rest.php_style({type: ACCOUNT_TYPES}))['id']
    else
      user
    end
  end
end

#resource_config_value(cfg, key) ⇒ Object

Resolve a RESOURCE_CONFIG value that may be a Proc (evaluated in instance context).



440
441
442
443
# File 'lib/aspera/cli/plugins/faspex5.rb', line 440

def resource_config_value(cfg, key)
  v = cfg[key]
  v.is_a?(Proc) ? instance_exec(&v) : v
end

#setup_admin_nodes_shared_folders(node_id:) ⇒ Object

admin > nodes > shared_folders — node_id: already in ctx via arguments: on the :shared_folders command



774
775
776
# File 'lib/aspera/cli/plugins/faspex5.rb', line 774

def setup_admin_nodes_shared_folders(node_id:, **)
  {sf_entity: "nodes/#{node_id}/shared_folders"}
end

#setup_admin_nodes_shared_folders_user(sf_entity:, sf_id:) ⇒ Object

admin > nodes > shared_folders > user — sf_id: already in ctx via arguments: on the :user command



791
792
793
# File 'lib/aspera/cli/plugins/faspex5.rb', line 791

def setup_admin_nodes_shared_folders_user(sf_entity:, sf_id:, **)
  {user_path: "#{sf_entity}/#{sf_id}/custom_access_users"}
end

#setup_api_v5Hash

Build @api_v5 for all commands that need it (all except :health and :postprocessing).

Returns:

  • (Hash)

    empty ctx (state stored in @api_v5)



870
871
872
873
874
875
876
# File 'lib/aspera/cli/plugins/faspex5.rb', line 870

def setup_api_v5(**)
  return {} if @api_v5
  @api_v5 = Api::Faspex.new(**Oauth.kwargs_from_options(options))
  # in case user wants to use HTTPGW tell transfer agent how to get address
  transfer.httpgw_url_cb = lambda { @api_v5.read('account')['gateway_url'] }
  {}
end

#setup_package_idHash

Setup for package sub-commands that need an id: resolves package_id from pub_link or argument.

Returns:

  • (Hash)

    ctx key: package_id



880
881
882
883
# File 'lib/aspera/cli/plugins/faspex5.rb', line 880

def setup_package_id(**)
  package_id = @api_v5.pub_link_context&.key?('package_id') ? @api_v5.pub_link_context['package_id'] : options.instance_identifier
  {package_id: package_id}
end

#wait_for_job(job_id) ⇒ Hash

Returns result of API call for job status.

Parameters:

  • job_id (String)

    job identifier

Returns:

  • (Hash)

    result of API call for job status



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/aspera/cli/plugins/faspex5.rb', line 159

def wait_for_job(job_id)
  result = nil
  loop do
    result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
    break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
    RestParameters.instance.spinner_cb.call(result['status'])
    sleep(0.5)
  end
  RestParameters.instance.spinner_cb.call(action: :success)
  return result
end

#wait_package_status(id, status_list: Api::Faspex::PACKAGE_TERMINATED) ⇒ Object

wait for package status to be in provided list



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/aspera/cli/plugins/faspex5.rb', line 132

def wait_package_status(id, status_list: Api::Faspex::PACKAGE_TERMINATED)
  total_sent = false
  loop do
    status = @api_v5.read("packages/#{id}/upload_details")
    status['id'] = id
    # user asked to not follow
    return status if status_list.nil?
    if status['upload_status'].eql?('submitted')
      progress_bar&.event(:sessions_init, session_id: nil, info: status['upload_status'])
    elsif !total_sent
      progress_bar&.event(:session_start, session_id: id)
      progress_bar&.event(:session_size, session_id: id, info: status['bytes_total'].to_i)
      total_sent = true
    else
      progress_bar&.event(:transfer, session_id: id, info: status['bytes_written'].to_i)
    end
    if status_list.include?(status['upload_status'])
      progress_bar&.event(:session_end, session_id: id)
      progress_bar&.event(:end)
      return status
    end
    sleep(1.0)
  end
end

#wizard(wizard, app_url) ⇒ Hash

Returns :preset_value, :test_args.

Parameters:

  • wizard (Wizard)

    The wizard object

  • app_url (String)

    Tested URL

Returns:

  • (Hash)

    :preset_value, :test_args



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/aspera/cli/plugins/faspex5.rb', line 60

def wizard(wizard, app_url)
  client_id = options.get_option(:client_id)
  client_secret = options.get_option(:client_secret)
  if client_id.nil? || client_secret.nil?
    formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
    formatter.display_status("Log in as an admin user at: #{app_url}")
    Environment.instance.open_uri(app_url)
    formatter.display_status('Navigate to: 𓃑  → Admin → Configurations → API clients')
    formatter.display_status('Create an API client with:')
    formatter.display_status('- name: ascli')
    formatter.display_status('- JWT: enabled')
    formatter.display_status('Upon creation, the admin shall get those parameters:')
    client_id = options.get_option(:client_id, mandatory: wizard.required)
    client_secret = options.get_option(:client_secret, mandatory: wizard.required)
  end
  wiz_username = options.get_option(:username, mandatory: true)
  wizard.check_email(wiz_username)
  private_key_path = wizard.ask_private_key(
    user: wiz_username,
    url: app_url,
    page: '👤 → Account Settings → Preferences → Public Key in PEM'
  )
  return {
    preset_value: {
      url:           app_url,
      username:      wiz_username,
      auth:          :jwt.to_s,
      private_key:   "@file:#{private_key_path}",
      client_id:     client_id,
      client_secret: client_secret
    },
    test_args:    'user profile show'
  }
end